1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/windows/nsImageClipboard.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,496 @@ 1.4 +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +#include "nsImageClipboard.h" 1.10 + 1.11 +#include "gfxUtils.h" 1.12 +#include "mozilla/gfx/2D.h" 1.13 +#include "mozilla/gfx/DataSurfaceHelpers.h" 1.14 +#include "mozilla/RefPtr.h" 1.15 +#include "nsITransferable.h" 1.16 +#include "nsGfxCIID.h" 1.17 +#include "nsMemory.h" 1.18 +#include "prmem.h" 1.19 +#include "imgIEncoder.h" 1.20 +#include "nsLiteralString.h" 1.21 +#include "nsComponentManagerUtils.h" 1.22 + 1.23 +#define BFH_LENGTH 14 1.24 + 1.25 +using namespace mozilla; 1.26 +using namespace mozilla::gfx; 1.27 + 1.28 +/* Things To Do 11/8/00 1.29 + 1.30 +Check image metrics, can we support them? Do we need to? 1.31 +Any other render format? HTML? 1.32 + 1.33 +*/ 1.34 + 1.35 + 1.36 +// 1.37 +// nsImageToClipboard ctor 1.38 +// 1.39 +// Given an imgIContainer, convert it to a DIB that is ready to go on the win32 clipboard 1.40 +// 1.41 +nsImageToClipboard::nsImageToClipboard(imgIContainer* aInImage, bool aWantDIBV5) 1.42 + : mImage(aInImage) 1.43 + , mWantDIBV5(aWantDIBV5) 1.44 +{ 1.45 + // nothing to do here 1.46 +} 1.47 + 1.48 + 1.49 +// 1.50 +// nsImageToClipboard dtor 1.51 +// 1.52 +// Clean up after ourselves. We know that we have created the bitmap 1.53 +// successfully if we still have a pointer to the header. 1.54 +// 1.55 +nsImageToClipboard::~nsImageToClipboard() 1.56 +{ 1.57 +} 1.58 + 1.59 + 1.60 +// 1.61 +// GetPicture 1.62 +// 1.63 +// Call to get the actual bits that go on the clipboard. If an error 1.64 +// ocurred during conversion, |outBits| will be null. 1.65 +// 1.66 +// NOTE: The caller owns the handle and must delete it with ::GlobalRelease() 1.67 +// 1.68 +nsresult 1.69 +nsImageToClipboard :: GetPicture ( HANDLE* outBits ) 1.70 +{ 1.71 + NS_ASSERTION ( outBits, "Bad parameter" ); 1.72 + 1.73 + return CreateFromImage ( mImage, outBits ); 1.74 + 1.75 +} // GetPicture 1.76 + 1.77 + 1.78 +// 1.79 +// CalcSize 1.80 +// 1.81 +// Computes # of bytes needed by a bitmap with the specified attributes. 1.82 +// 1.83 +int32_t 1.84 +nsImageToClipboard :: CalcSize ( int32_t aHeight, int32_t aColors, WORD aBitsPerPixel, int32_t aSpanBytes ) 1.85 +{ 1.86 + int32_t HeaderMem = sizeof(BITMAPINFOHEADER); 1.87 + 1.88 + // add size of pallette to header size 1.89 + if (aBitsPerPixel < 16) 1.90 + HeaderMem += aColors * sizeof(RGBQUAD); 1.91 + 1.92 + if (aHeight < 0) 1.93 + aHeight = -aHeight; 1.94 + 1.95 + return (HeaderMem + (aHeight * aSpanBytes)); 1.96 +} 1.97 + 1.98 + 1.99 +// 1.100 +// CalcSpanLength 1.101 +// 1.102 +// Computes the span bytes for determining the overall size of the image 1.103 +// 1.104 +int32_t 1.105 +nsImageToClipboard::CalcSpanLength(uint32_t aWidth, uint32_t aBitCount) 1.106 +{ 1.107 + int32_t spanBytes = (aWidth * aBitCount) >> 5; 1.108 + 1.109 + if ((aWidth * aBitCount) & 0x1F) 1.110 + spanBytes++; 1.111 + spanBytes <<= 2; 1.112 + 1.113 + return spanBytes; 1.114 +} 1.115 + 1.116 + 1.117 +// 1.118 +// CreateFromImage 1.119 +// 1.120 +// Do the work to setup the bitmap header and copy the bits out of the 1.121 +// image. 1.122 +// 1.123 +nsresult 1.124 +nsImageToClipboard::CreateFromImage ( imgIContainer* inImage, HANDLE* outBitmap ) 1.125 +{ 1.126 + nsresult rv; 1.127 + *outBitmap = nullptr; 1.128 + 1.129 + RefPtr<SourceSurface> surface = 1.130 + inImage->GetFrame(imgIContainer::FRAME_CURRENT, 1.131 + imgIContainer::FLAG_SYNC_DECODE); 1.132 + NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); 1.133 + 1.134 + MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 || 1.135 + surface->GetFormat() == SurfaceFormat::B8G8R8X8); 1.136 + 1.137 + RefPtr<DataSourceSurface> dataSurface; 1.138 + if (surface->GetFormat() == SurfaceFormat::B8G8R8A8) { 1.139 + dataSurface = surface->GetDataSurface(); 1.140 + } else { 1.141 + // XXXjwatt Bug 995923 - get rid of this copy and handle B8G8R8X8 1.142 + // directly below once bug 995807 is fixed. 1.143 + dataSurface = gfxUtils:: 1.144 + CopySurfaceToDataSourceSurfaceWithFormat(surface, 1.145 + SurfaceFormat::B8G8R8A8); 1.146 + } 1.147 + NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); 1.148 + 1.149 + nsCOMPtr<imgIEncoder> encoder = do_CreateInstance("@mozilla.org/image/encoder;2?type=image/bmp", &rv); 1.150 + NS_ENSURE_SUCCESS(rv, rv); 1.151 + 1.152 + uint32_t format; 1.153 + nsAutoString options; 1.154 + if (mWantDIBV5) { 1.155 + options.AppendLiteral("version=5;bpp="); 1.156 + } else { 1.157 + options.AppendLiteral("version=3;bpp="); 1.158 + } 1.159 + switch (dataSurface->GetFormat()) { 1.160 + case SurfaceFormat::B8G8R8A8: 1.161 + format = imgIEncoder::INPUT_FORMAT_HOSTARGB; 1.162 + options.AppendInt(32); 1.163 + break; 1.164 +#if 0 1.165 + // XXXjwatt Bug 995923 - fix |format| and reenable once bug 995807 is fixed. 1.166 + case SurfaceFormat::B8G8R8X8: 1.167 + format = imgIEncoder::INPUT_FORMAT_RGB; 1.168 + options.AppendInt(24); 1.169 + break; 1.170 +#endif 1.171 + default: 1.172 + NS_NOTREACHED("Unexpected surface format"); 1.173 + return NS_ERROR_INVALID_ARG; 1.174 + } 1.175 + 1.176 + DataSourceSurface::MappedSurface map; 1.177 + bool mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map); 1.178 + NS_ENSURE_TRUE(mappedOK, NS_ERROR_FAILURE); 1.179 + 1.180 + rv = encoder->InitFromData(map.mData, 0, 1.181 + dataSurface->GetSize().width, 1.182 + dataSurface->GetSize().height, 1.183 + map.mStride, 1.184 + format, options); 1.185 + dataSurface->Unmap(); 1.186 + NS_ENSURE_SUCCESS(rv, rv); 1.187 + 1.188 + uint32_t size; 1.189 + encoder->GetImageBufferUsed(&size); 1.190 + NS_ENSURE_TRUE(size > BFH_LENGTH, NS_ERROR_FAILURE); 1.191 + HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, 1.192 + size - BFH_LENGTH); 1.193 + if (!glob) 1.194 + return NS_ERROR_OUT_OF_MEMORY; 1.195 + 1.196 + char *dst = (char*) ::GlobalLock(glob); 1.197 + char *src; 1.198 + rv = encoder->GetImageBuffer(&src); 1.199 + NS_ENSURE_SUCCESS(rv, rv); 1.200 + 1.201 + ::CopyMemory(dst, src + BFH_LENGTH, size - BFH_LENGTH); 1.202 + ::GlobalUnlock(glob); 1.203 + 1.204 + *outBitmap = (HANDLE)glob; 1.205 + return NS_OK; 1.206 +} 1.207 + 1.208 +nsImageFromClipboard :: nsImageFromClipboard () 1.209 +{ 1.210 + // nothing to do here 1.211 +} 1.212 + 1.213 +nsImageFromClipboard :: ~nsImageFromClipboard ( ) 1.214 +{ 1.215 +} 1.216 + 1.217 +// 1.218 +// GetEncodedImageStream 1.219 +// 1.220 +// Take the raw clipboard image data and convert it to aMIMEFormat in the form of a nsIInputStream 1.221 +// 1.222 +nsresult 1.223 +nsImageFromClipboard ::GetEncodedImageStream (unsigned char * aClipboardData, const char * aMIMEFormat, nsIInputStream** aInputStream ) 1.224 +{ 1.225 + NS_ENSURE_ARG_POINTER (aInputStream); 1.226 + NS_ENSURE_ARG_POINTER (aMIMEFormat); 1.227 + nsresult rv; 1.228 + *aInputStream = nullptr; 1.229 + 1.230 + // pull the size information out of the BITMAPINFO header and 1.231 + // initialize the image 1.232 + BITMAPINFO* header = (BITMAPINFO *) aClipboardData; 1.233 + int32_t width = header->bmiHeader.biWidth; 1.234 + int32_t height = header->bmiHeader.biHeight; 1.235 + // neg. heights mean the Y axis is inverted and we don't handle that case 1.236 + NS_ENSURE_TRUE(height > 0, NS_ERROR_FAILURE); 1.237 + 1.238 + unsigned char * rgbData = new unsigned char[width * height * 3 /* RGB */]; 1.239 + 1.240 + if (rgbData) { 1.241 + BYTE * pGlobal = (BYTE *) aClipboardData; 1.242 + // Convert the clipboard image into RGB packed pixel data 1.243 + rv = ConvertColorBitMap((unsigned char *) (pGlobal + header->bmiHeader.biSize), header, rgbData); 1.244 + // if that succeeded, encode the bitmap as aMIMEFormat data. Don't return early or we risk leaking rgbData 1.245 + if (NS_SUCCEEDED(rv)) { 1.246 + nsAutoCString encoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=")); 1.247 + 1.248 + // Map image/jpg to image/jpeg (which is how the encoder is registered). 1.249 + if (strcmp(aMIMEFormat, kJPGImageMime) == 0) 1.250 + encoderCID.Append("image/jpeg"); 1.251 + else 1.252 + encoderCID.Append(aMIMEFormat); 1.253 + nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get(), &rv); 1.254 + if (NS_SUCCEEDED(rv)){ 1.255 + rv = encoder->InitFromData(rgbData, 0, width, height, 3 * width /* RGB * # pixels in a row */, 1.256 + imgIEncoder::INPUT_FORMAT_RGB, EmptyString()); 1.257 + if (NS_SUCCEEDED(rv)) 1.258 + encoder->QueryInterface(NS_GET_IID(nsIInputStream), (void **) aInputStream); 1.259 + } 1.260 + } 1.261 + delete [] rgbData; 1.262 + } 1.263 + else 1.264 + rv = NS_ERROR_OUT_OF_MEMORY; 1.265 + 1.266 + return rv; 1.267 +} // GetImage 1.268 + 1.269 +// 1.270 +// InvertRows 1.271 +// 1.272 +// Take the image data from the clipboard and invert the rows. Modifying aInitialBuffer in place. 1.273 +// 1.274 +void 1.275 +nsImageFromClipboard::InvertRows(unsigned char * aInitialBuffer, uint32_t aSizeOfBuffer, uint32_t aNumBytesPerRow) 1.276 +{ 1.277 + if (!aNumBytesPerRow) 1.278 + return; 1.279 + 1.280 + uint32_t numRows = aSizeOfBuffer / aNumBytesPerRow; 1.281 + unsigned char * row = new unsigned char[aNumBytesPerRow]; 1.282 + 1.283 + uint32_t currentRow = 0; 1.284 + uint32_t lastRow = (numRows - 1) * aNumBytesPerRow; 1.285 + while (currentRow < lastRow) 1.286 + { 1.287 + // store the current row into a temporary buffer 1.288 + memcpy(row, &aInitialBuffer[currentRow], aNumBytesPerRow); 1.289 + memcpy(&aInitialBuffer[currentRow], &aInitialBuffer[lastRow], aNumBytesPerRow); 1.290 + memcpy(&aInitialBuffer[lastRow], row, aNumBytesPerRow); 1.291 + lastRow -= aNumBytesPerRow; 1.292 + currentRow += aNumBytesPerRow; 1.293 + } 1.294 + 1.295 + delete[] row; 1.296 +} 1.297 + 1.298 +// 1.299 +// ConvertColorBitMap 1.300 +// 1.301 +// Takes the clipboard bitmap and converts it into a RGB packed pixel values. 1.302 +// 1.303 +nsresult 1.304 +nsImageFromClipboard::ConvertColorBitMap(unsigned char * aInputBuffer, PBITMAPINFO pBitMapInfo, unsigned char * aOutBuffer) 1.305 +{ 1.306 + uint8_t bitCount = pBitMapInfo->bmiHeader.biBitCount; 1.307 + uint32_t imageSize = pBitMapInfo->bmiHeader.biSizeImage; // may be zero for BI_RGB bitmaps which means we need to calculate by hand 1.308 + uint32_t bytesPerPixel = bitCount / 8; 1.309 + 1.310 + if (bitCount <= 4) 1.311 + bytesPerPixel = 1; 1.312 + 1.313 + // rows are DWORD aligned. Calculate how many real bytes are in each row in the bitmap. This number won't 1.314 + // correspond to biWidth. 1.315 + uint32_t rowSize = (bitCount * pBitMapInfo->bmiHeader.biWidth + 7) / 8; // +7 to round up 1.316 + if (rowSize % 4) 1.317 + rowSize += (4 - (rowSize % 4)); // Pad to DWORD Boundary 1.318 + 1.319 + // if our buffer includes a color map, skip over it 1.320 + if (bitCount <= 8) 1.321 + { 1.322 + int32_t bytesToSkip = (pBitMapInfo->bmiHeader.biClrUsed ? pBitMapInfo->bmiHeader.biClrUsed : (1 << bitCount) ) * sizeof(RGBQUAD); 1.323 + aInputBuffer += bytesToSkip; 1.324 + } 1.325 + 1.326 + bitFields colorMasks; // only used if biCompression == BI_BITFIELDS 1.327 + 1.328 + if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS) 1.329 + { 1.330 + // color table consists of 3 DWORDS containing the color masks... 1.331 + colorMasks.red = (*((uint32_t*)&(pBitMapInfo->bmiColors[0]))); 1.332 + colorMasks.green = (*((uint32_t*)&(pBitMapInfo->bmiColors[1]))); 1.333 + colorMasks.blue = (*((uint32_t*)&(pBitMapInfo->bmiColors[2]))); 1.334 + CalcBitShift(&colorMasks); 1.335 + aInputBuffer += 3 * sizeof(DWORD); 1.336 + } 1.337 + else if (pBitMapInfo->bmiHeader.biCompression == BI_RGB && !imageSize) // BI_RGB can have a size of zero which means we figure it out 1.338 + { 1.339 + // XXX: note use rowSize here and not biWidth. rowSize accounts for the DWORD padding for each row 1.340 + imageSize = rowSize * pBitMapInfo->bmiHeader.biHeight; 1.341 + } 1.342 + 1.343 + // The windows clipboard image format inverts the rows 1.344 + InvertRows(aInputBuffer, imageSize, rowSize); 1.345 + 1.346 + if (!pBitMapInfo->bmiHeader.biCompression || pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS) 1.347 + { 1.348 + uint32_t index = 0; 1.349 + uint32_t writeIndex = 0; 1.350 + 1.351 + unsigned char redValue, greenValue, blueValue; 1.352 + uint8_t colorTableEntry = 0; 1.353 + int8_t bit; // used for grayscale bitmaps where each bit is a pixel 1.354 + uint32_t numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth; // how many more pixels do we still need to read for the current row 1.355 + uint32_t pos = 0; 1.356 + 1.357 + while (index < imageSize) 1.358 + { 1.359 + switch (bitCount) 1.360 + { 1.361 + case 1: 1.362 + for (bit = 7; bit >= 0 && numPixelsLeftInRow; bit--) 1.363 + { 1.364 + colorTableEntry = (aInputBuffer[index] >> bit) & 1; 1.365 + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed; 1.366 + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen; 1.367 + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue; 1.368 + numPixelsLeftInRow--; 1.369 + } 1.370 + pos += 1; 1.371 + break; 1.372 + case 4: 1.373 + { 1.374 + // each aInputBuffer[index] entry contains data for two pixels. 1.375 + // read the first pixel 1.376 + colorTableEntry = aInputBuffer[index] >> 4; 1.377 + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed; 1.378 + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen; 1.379 + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue; 1.380 + numPixelsLeftInRow--; 1.381 + 1.382 + if (numPixelsLeftInRow) // now read the second pixel 1.383 + { 1.384 + colorTableEntry = aInputBuffer[index] & 0xF; 1.385 + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed; 1.386 + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen; 1.387 + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue; 1.388 + numPixelsLeftInRow--; 1.389 + } 1.390 + pos += 1; 1.391 + } 1.392 + break; 1.393 + case 8: 1.394 + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbRed; 1.395 + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbGreen; 1.396 + aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbBlue; 1.397 + numPixelsLeftInRow--; 1.398 + pos += 1; 1.399 + break; 1.400 + case 16: 1.401 + { 1.402 + uint16_t num = 0; 1.403 + num = (uint8_t) aInputBuffer[index+1]; 1.404 + num <<= 8; 1.405 + num |= (uint8_t) aInputBuffer[index]; 1.406 + 1.407 + redValue = ((uint32_t) (((float)(num & 0xf800) / 0xf800) * 0xFF0000) & 0xFF0000)>> 16; 1.408 + greenValue = ((uint32_t)(((float)(num & 0x07E0) / 0x07E0) * 0x00FF00) & 0x00FF00)>> 8; 1.409 + blueValue = ((uint32_t)(((float)(num & 0x001F) / 0x001F) * 0x0000FF) & 0x0000FF); 1.410 + 1.411 + // now we have the right RGB values... 1.412 + aOutBuffer[writeIndex++] = redValue; 1.413 + aOutBuffer[writeIndex++] = greenValue; 1.414 + aOutBuffer[writeIndex++] = blueValue; 1.415 + numPixelsLeftInRow--; 1.416 + pos += 2; 1.417 + } 1.418 + break; 1.419 + case 32: 1.420 + case 24: 1.421 + if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS) 1.422 + { 1.423 + uint32_t val = *((uint32_t*) (aInputBuffer + index) ); 1.424 + aOutBuffer[writeIndex++] = (val & colorMasks.red) >> colorMasks.redRightShift << colorMasks.redLeftShift; 1.425 + aOutBuffer[writeIndex++] = (val & colorMasks.green) >> colorMasks.greenRightShift << colorMasks.greenLeftShift; 1.426 + aOutBuffer[writeIndex++] = (val & colorMasks.blue) >> colorMasks.blueRightShift << colorMasks.blueLeftShift; 1.427 + numPixelsLeftInRow--; 1.428 + pos += 4; // we read in 4 bytes of data in order to process this pixel 1.429 + } 1.430 + else 1.431 + { 1.432 + aOutBuffer[writeIndex++] = aInputBuffer[index+2]; 1.433 + aOutBuffer[writeIndex++] = aInputBuffer[index+1]; 1.434 + aOutBuffer[writeIndex++] = aInputBuffer[index]; 1.435 + numPixelsLeftInRow--; 1.436 + pos += bytesPerPixel; // 3 bytes for 24 bit data, 4 bytes for 32 bit data (we skip over the 4th byte)... 1.437 + } 1.438 + break; 1.439 + default: 1.440 + // This is probably the wrong place to check this... 1.441 + return NS_ERROR_FAILURE; 1.442 + } 1.443 + 1.444 + index += bytesPerPixel; // increment our loop counter 1.445 + 1.446 + if (!numPixelsLeftInRow) 1.447 + { 1.448 + if (rowSize != pos) 1.449 + { 1.450 + // advance index to skip over remaining padding bytes 1.451 + index += (rowSize - pos); 1.452 + } 1.453 + numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth; 1.454 + pos = 0; 1.455 + } 1.456 + 1.457 + } // while we still have bytes to process 1.458 + } 1.459 + 1.460 + return NS_OK; 1.461 +} 1.462 + 1.463 +void nsImageFromClipboard::CalcBitmask(uint32_t aMask, uint8_t& aBegin, uint8_t& aLength) 1.464 +{ 1.465 + // find the rightmost 1 1.466 + uint8_t pos; 1.467 + bool started = false; 1.468 + aBegin = aLength = 0; 1.469 + for (pos = 0; pos <= 31; pos++) 1.470 + { 1.471 + if (!started && (aMask & (1 << pos))) 1.472 + { 1.473 + aBegin = pos; 1.474 + started = true; 1.475 + } 1.476 + else if (started && !(aMask & (1 << pos))) 1.477 + { 1.478 + aLength = pos - aBegin; 1.479 + break; 1.480 + } 1.481 + } 1.482 +} 1.483 + 1.484 +void nsImageFromClipboard::CalcBitShift(bitFields * aColorMask) 1.485 +{ 1.486 + uint8_t begin, length; 1.487 + // red 1.488 + CalcBitmask(aColorMask->red, begin, length); 1.489 + aColorMask->redRightShift = begin; 1.490 + aColorMask->redLeftShift = 8 - length; 1.491 + // green 1.492 + CalcBitmask(aColorMask->green, begin, length); 1.493 + aColorMask->greenRightShift = begin; 1.494 + aColorMask->greenLeftShift = 8 - length; 1.495 + // blue 1.496 + CalcBitmask(aColorMask->blue, begin, length); 1.497 + aColorMask->blueRightShift = begin; 1.498 + aColorMask->blueLeftShift = 8 - length; 1.499 +}