Bug 867104 - Add a crashtest. r=ehsan
[gecko.git] / widget / windows / nsImageClipboard.cpp
blobbbde516c14c69b232b045080f5bf7899586293b9
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsITransferable.h"
8 #include "nsImageClipboard.h"
9 #include "nsGfxCIID.h"
10 #include "nsMemory.h"
11 #include "prmem.h"
12 #include "imgIEncoder.h"
13 #include "nsLiteralString.h"
14 #include "nsComponentManagerUtils.h"
16 #define BFH_LENGTH 14
18 /* Things To Do 11/8/00
20 Check image metrics, can we support them? Do we need to?
21 Any other render format? HTML?
27 // nsImageToClipboard ctor
29 // Given an imgIContainer, convert it to a DIB that is ready to go on the win32 clipboard
31 nsImageToClipboard::nsImageToClipboard(imgIContainer* aInImage, bool aWantDIBV5)
32 : mImage(aInImage)
33 , mWantDIBV5(aWantDIBV5)
35 // nothing to do here
40 // nsImageToClipboard dtor
42 // Clean up after ourselves. We know that we have created the bitmap
43 // successfully if we still have a pointer to the header.
45 nsImageToClipboard::~nsImageToClipboard()
51 // GetPicture
53 // Call to get the actual bits that go on the clipboard. If an error
54 // ocurred during conversion, |outBits| will be null.
56 // NOTE: The caller owns the handle and must delete it with ::GlobalRelease()
58 nsresult
59 nsImageToClipboard :: GetPicture ( HANDLE* outBits )
61 NS_ASSERTION ( outBits, "Bad parameter" );
63 return CreateFromImage ( mImage, outBits );
65 } // GetPicture
69 // CalcSize
71 // Computes # of bytes needed by a bitmap with the specified attributes.
73 int32_t
74 nsImageToClipboard :: CalcSize ( int32_t aHeight, int32_t aColors, WORD aBitsPerPixel, int32_t aSpanBytes )
76 int32_t HeaderMem = sizeof(BITMAPINFOHEADER);
78 // add size of pallette to header size
79 if (aBitsPerPixel < 16)
80 HeaderMem += aColors * sizeof(RGBQUAD);
82 if (aHeight < 0)
83 aHeight = -aHeight;
85 return (HeaderMem + (aHeight * aSpanBytes));
90 // CalcSpanLength
92 // Computes the span bytes for determining the overall size of the image
94 int32_t
95 nsImageToClipboard::CalcSpanLength(uint32_t aWidth, uint32_t aBitCount)
97 int32_t spanBytes = (aWidth * aBitCount) >> 5;
99 if ((aWidth * aBitCount) & 0x1F)
100 spanBytes++;
101 spanBytes <<= 2;
103 return spanBytes;
108 // CreateFromImage
110 // Do the work to setup the bitmap header and copy the bits out of the
111 // image.
113 nsresult
114 nsImageToClipboard::CreateFromImage ( imgIContainer* inImage, HANDLE* outBitmap )
116 nsresult rv;
117 *outBitmap = nullptr;
119 nsRefPtr<gfxASurface> surface;
120 inImage->GetFrame(imgIContainer::FRAME_CURRENT,
121 imgIContainer::FLAG_SYNC_DECODE,
122 getter_AddRefs(surface));
123 NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
125 nsRefPtr<gfxImageSurface> frame(surface->GetAsReadableARGB32ImageSurface());
126 NS_ENSURE_TRUE(frame, NS_ERROR_FAILURE);
128 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance("@mozilla.org/image/encoder;2?type=image/bmp", &rv);
129 NS_ENSURE_SUCCESS(rv, rv);
131 uint32_t format;
132 nsAutoString options;
133 if (mWantDIBV5) {
134 options.AppendLiteral("version=5;bpp=");
135 } else {
136 options.AppendLiteral("version=3;bpp=");
138 switch (frame->Format()) {
139 case gfxASurface::ImageFormatARGB32:
140 format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
141 options.AppendInt(32);
142 break;
143 case gfxASurface::ImageFormatRGB24:
144 format = imgIEncoder::INPUT_FORMAT_RGB;
145 options.AppendInt(24);
146 break;
147 default:
148 return NS_ERROR_INVALID_ARG;
151 rv = encoder->InitFromData(frame->Data(), 0, frame->Width(),
152 frame->Height(), frame->Stride(),
153 format, options);
154 NS_ENSURE_SUCCESS(rv, rv);
156 uint32_t size;
157 encoder->GetImageBufferUsed(&size);
158 NS_ENSURE_TRUE(size > BFH_LENGTH, NS_ERROR_FAILURE);
159 HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT,
160 size - BFH_LENGTH);
161 if (!glob)
162 return NS_ERROR_OUT_OF_MEMORY;
164 char *dst = (char*) ::GlobalLock(glob);
165 char *src;
166 rv = encoder->GetImageBuffer(&src);
167 NS_ENSURE_SUCCESS(rv, rv);
169 ::CopyMemory(dst, src + BFH_LENGTH, size - BFH_LENGTH);
170 ::GlobalUnlock(glob);
172 *outBitmap = (HANDLE)glob;
173 return NS_OK;
176 nsImageFromClipboard :: nsImageFromClipboard ()
178 // nothing to do here
181 nsImageFromClipboard :: ~nsImageFromClipboard ( )
186 // GetEncodedImageStream
188 // Take the raw clipboard image data and convert it to aMIMEFormat in the form of a nsIInputStream
190 nsresult
191 nsImageFromClipboard ::GetEncodedImageStream (unsigned char * aClipboardData, const char * aMIMEFormat, nsIInputStream** aInputStream )
193 NS_ENSURE_ARG_POINTER (aInputStream);
194 NS_ENSURE_ARG_POINTER (aMIMEFormat);
195 nsresult rv;
196 *aInputStream = nullptr;
198 // pull the size information out of the BITMAPINFO header and
199 // initialize the image
200 BITMAPINFO* header = (BITMAPINFO *) aClipboardData;
201 int32_t width = header->bmiHeader.biWidth;
202 int32_t height = header->bmiHeader.biHeight;
203 // neg. heights mean the Y axis is inverted and we don't handle that case
204 NS_ENSURE_TRUE(height > 0, NS_ERROR_FAILURE);
206 unsigned char * rgbData = new unsigned char[width * height * 3 /* RGB */];
208 if (rgbData) {
209 BYTE * pGlobal = (BYTE *) aClipboardData;
210 // Convert the clipboard image into RGB packed pixel data
211 rv = ConvertColorBitMap((unsigned char *) (pGlobal + header->bmiHeader.biSize), header, rgbData);
212 // if that succeeded, encode the bitmap as aMIMEFormat data. Don't return early or we risk leaking rgbData
213 if (NS_SUCCEEDED(rv)) {
214 nsAutoCString encoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type="));
216 // Map image/jpg to image/jpeg (which is how the encoder is registered).
217 if (strcmp(aMIMEFormat, kJPGImageMime) == 0)
218 encoderCID.Append("image/jpeg");
219 else
220 encoderCID.Append(aMIMEFormat);
221 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get(), &rv);
222 if (NS_SUCCEEDED(rv)){
223 rv = encoder->InitFromData(rgbData, 0, width, height, 3 * width /* RGB * # pixels in a row */,
224 imgIEncoder::INPUT_FORMAT_RGB, EmptyString());
225 if (NS_SUCCEEDED(rv))
226 encoder->QueryInterface(NS_GET_IID(nsIInputStream), (void **) aInputStream);
229 delete [] rgbData;
231 else
232 rv = NS_ERROR_OUT_OF_MEMORY;
234 return rv;
235 } // GetImage
238 // InvertRows
240 // Take the image data from the clipboard and invert the rows. Modifying aInitialBuffer in place.
242 void
243 nsImageFromClipboard::InvertRows(unsigned char * aInitialBuffer, uint32_t aSizeOfBuffer, uint32_t aNumBytesPerRow)
245 if (!aNumBytesPerRow)
246 return;
248 uint32_t numRows = aSizeOfBuffer / aNumBytesPerRow;
249 unsigned char * row = new unsigned char[aNumBytesPerRow];
251 uint32_t currentRow = 0;
252 uint32_t lastRow = (numRows - 1) * aNumBytesPerRow;
253 while (currentRow < lastRow)
255 // store the current row into a temporary buffer
256 memcpy(row, &aInitialBuffer[currentRow], aNumBytesPerRow);
257 memcpy(&aInitialBuffer[currentRow], &aInitialBuffer[lastRow], aNumBytesPerRow);
258 memcpy(&aInitialBuffer[lastRow], row, aNumBytesPerRow);
259 lastRow -= aNumBytesPerRow;
260 currentRow += aNumBytesPerRow;
263 delete[] row;
267 // ConvertColorBitMap
269 // Takes the clipboard bitmap and converts it into a RGB packed pixel values.
271 nsresult
272 nsImageFromClipboard::ConvertColorBitMap(unsigned char * aInputBuffer, PBITMAPINFO pBitMapInfo, unsigned char * aOutBuffer)
274 uint8_t bitCount = pBitMapInfo->bmiHeader.biBitCount;
275 uint32_t imageSize = pBitMapInfo->bmiHeader.biSizeImage; // may be zero for BI_RGB bitmaps which means we need to calculate by hand
276 uint32_t bytesPerPixel = bitCount / 8;
278 if (bitCount <= 4)
279 bytesPerPixel = 1;
281 // rows are DWORD aligned. Calculate how many real bytes are in each row in the bitmap. This number won't
282 // correspond to biWidth.
283 uint32_t rowSize = (bitCount * pBitMapInfo->bmiHeader.biWidth + 7) / 8; // +7 to round up
284 if (rowSize % 4)
285 rowSize += (4 - (rowSize % 4)); // Pad to DWORD Boundary
287 // if our buffer includes a color map, skip over it
288 if (bitCount <= 8)
290 int32_t bytesToSkip = (pBitMapInfo->bmiHeader.biClrUsed ? pBitMapInfo->bmiHeader.biClrUsed : (1 << bitCount) ) * sizeof(RGBQUAD);
291 aInputBuffer += bytesToSkip;
294 bitFields colorMasks; // only used if biCompression == BI_BITFIELDS
296 if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
298 // color table consists of 3 DWORDS containing the color masks...
299 colorMasks.red = (*((uint32_t*)&(pBitMapInfo->bmiColors[0])));
300 colorMasks.green = (*((uint32_t*)&(pBitMapInfo->bmiColors[1])));
301 colorMasks.blue = (*((uint32_t*)&(pBitMapInfo->bmiColors[2])));
302 CalcBitShift(&colorMasks);
303 aInputBuffer += 3 * sizeof(DWORD);
305 else if (pBitMapInfo->bmiHeader.biCompression == BI_RGB && !imageSize) // BI_RGB can have a size of zero which means we figure it out
307 // XXX: note use rowSize here and not biWidth. rowSize accounts for the DWORD padding for each row
308 imageSize = rowSize * pBitMapInfo->bmiHeader.biHeight;
311 // The windows clipboard image format inverts the rows
312 InvertRows(aInputBuffer, imageSize, rowSize);
314 if (!pBitMapInfo->bmiHeader.biCompression || pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
316 uint32_t index = 0;
317 uint32_t writeIndex = 0;
319 unsigned char redValue, greenValue, blueValue;
320 uint8_t colorTableEntry = 0;
321 int8_t bit; // used for grayscale bitmaps where each bit is a pixel
322 uint32_t numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth; // how many more pixels do we still need to read for the current row
323 uint32_t pos = 0;
325 while (index < imageSize)
327 switch (bitCount)
329 case 1:
330 for (bit = 7; bit >= 0 && numPixelsLeftInRow; bit--)
332 colorTableEntry = (aInputBuffer[index] >> bit) & 1;
333 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
334 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
335 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
336 numPixelsLeftInRow--;
338 pos += 1;
339 break;
340 case 4:
342 // each aInputBuffer[index] entry contains data for two pixels.
343 // read the first pixel
344 colorTableEntry = aInputBuffer[index] >> 4;
345 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
346 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
347 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
348 numPixelsLeftInRow--;
350 if (numPixelsLeftInRow) // now read the second pixel
352 colorTableEntry = aInputBuffer[index] & 0xF;
353 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
354 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
355 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
356 numPixelsLeftInRow--;
358 pos += 1;
360 break;
361 case 8:
362 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbRed;
363 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbGreen;
364 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbBlue;
365 numPixelsLeftInRow--;
366 pos += 1;
367 break;
368 case 16:
370 uint16_t num = 0;
371 num = (uint8_t) aInputBuffer[index+1];
372 num <<= 8;
373 num |= (uint8_t) aInputBuffer[index];
375 redValue = ((uint32_t) (((float)(num & 0xf800) / 0xf800) * 0xFF0000) & 0xFF0000)>> 16;
376 greenValue = ((uint32_t)(((float)(num & 0x07E0) / 0x07E0) * 0x00FF00) & 0x00FF00)>> 8;
377 blueValue = ((uint32_t)(((float)(num & 0x001F) / 0x001F) * 0x0000FF) & 0x0000FF);
379 // now we have the right RGB values...
380 aOutBuffer[writeIndex++] = redValue;
381 aOutBuffer[writeIndex++] = greenValue;
382 aOutBuffer[writeIndex++] = blueValue;
383 numPixelsLeftInRow--;
384 pos += 2;
386 break;
387 case 32:
388 case 24:
389 if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
391 uint32_t val = *((uint32_t*) (aInputBuffer + index) );
392 aOutBuffer[writeIndex++] = (val & colorMasks.red) >> colorMasks.redRightShift << colorMasks.redLeftShift;
393 aOutBuffer[writeIndex++] = (val & colorMasks.green) >> colorMasks.greenRightShift << colorMasks.greenLeftShift;
394 aOutBuffer[writeIndex++] = (val & colorMasks.blue) >> colorMasks.blueRightShift << colorMasks.blueLeftShift;
395 numPixelsLeftInRow--;
396 pos += 4; // we read in 4 bytes of data in order to process this pixel
398 else
400 aOutBuffer[writeIndex++] = aInputBuffer[index+2];
401 aOutBuffer[writeIndex++] = aInputBuffer[index+1];
402 aOutBuffer[writeIndex++] = aInputBuffer[index];
403 numPixelsLeftInRow--;
404 pos += bytesPerPixel; // 3 bytes for 24 bit data, 4 bytes for 32 bit data (we skip over the 4th byte)...
406 break;
407 default:
408 // This is probably the wrong place to check this...
409 return NS_ERROR_FAILURE;
412 index += bytesPerPixel; // increment our loop counter
414 if (!numPixelsLeftInRow)
416 if (rowSize != pos)
418 // advance index to skip over remaining padding bytes
419 index += (rowSize - pos);
421 numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth;
422 pos = 0;
425 } // while we still have bytes to process
428 return NS_OK;
431 void nsImageFromClipboard::CalcBitmask(uint32_t aMask, uint8_t& aBegin, uint8_t& aLength)
433 // find the rightmost 1
434 uint8_t pos;
435 bool started = false;
436 aBegin = aLength = 0;
437 for (pos = 0; pos <= 31; pos++)
439 if (!started && (aMask & (1 << pos)))
441 aBegin = pos;
442 started = true;
444 else if (started && !(aMask & (1 << pos)))
446 aLength = pos - aBegin;
447 break;
452 void nsImageFromClipboard::CalcBitShift(bitFields * aColorMask)
454 uint8_t begin, length;
455 // red
456 CalcBitmask(aColorMask->red, begin, length);
457 aColorMask->redRightShift = begin;
458 aColorMask->redLeftShift = 8 - length;
459 // green
460 CalcBitmask(aColorMask->green, begin, length);
461 aColorMask->greenRightShift = begin;
462 aColorMask->greenLeftShift = 8 - length;
463 // blue
464 CalcBitmask(aColorMask->blue, begin, length);
465 aColorMask->blueRightShift = begin;
466 aColorMask->blueLeftShift = 8 - length;