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"
12 #include "imgIEncoder.h"
13 #include "nsLiteralString.h"
14 #include "nsComponentManagerUtils.h"
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
)
33 , mWantDIBV5(aWantDIBV5
)
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()
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()
59 nsImageToClipboard :: GetPicture ( HANDLE
* outBits
)
61 NS_ASSERTION ( outBits
, "Bad parameter" );
63 return CreateFromImage ( mImage
, outBits
);
71 // Computes # of bytes needed by a bitmap with the specified attributes.
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
);
85 return (HeaderMem
+ (aHeight
* aSpanBytes
));
92 // Computes the span bytes for determining the overall size of the image
95 nsImageToClipboard::CalcSpanLength(uint32_t aWidth
, uint32_t aBitCount
)
97 int32_t spanBytes
= (aWidth
* aBitCount
) >> 5;
99 if ((aWidth
* aBitCount
) & 0x1F)
110 // Do the work to setup the bitmap header and copy the bits out of the
114 nsImageToClipboard::CreateFromImage ( imgIContainer
* inImage
, HANDLE
* outBitmap
)
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
);
132 nsAutoString options
;
134 options
.AppendLiteral("version=5;bpp=");
136 options
.AppendLiteral("version=3;bpp=");
138 switch (frame
->Format()) {
139 case gfxASurface::ImageFormatARGB32
:
140 format
= imgIEncoder::INPUT_FORMAT_HOSTARGB
;
141 options
.AppendInt(32);
143 case gfxASurface::ImageFormatRGB24
:
144 format
= imgIEncoder::INPUT_FORMAT_RGB
;
145 options
.AppendInt(24);
148 return NS_ERROR_INVALID_ARG
;
151 rv
= encoder
->InitFromData(frame
->Data(), 0, frame
->Width(),
152 frame
->Height(), frame
->Stride(),
154 NS_ENSURE_SUCCESS(rv
, rv
);
157 encoder
->GetImageBufferUsed(&size
);
158 NS_ENSURE_TRUE(size
> BFH_LENGTH
, NS_ERROR_FAILURE
);
159 HGLOBAL glob
= ::GlobalAlloc(GMEM_MOVEABLE
| GMEM_DDESHARE
| GMEM_ZEROINIT
,
162 return NS_ERROR_OUT_OF_MEMORY
;
164 char *dst
= (char*) ::GlobalLock(glob
);
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
;
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
191 nsImageFromClipboard ::GetEncodedImageStream (unsigned char * aClipboardData
, const char * aMIMEFormat
, nsIInputStream
** aInputStream
)
193 NS_ENSURE_ARG_POINTER (aInputStream
);
194 NS_ENSURE_ARG_POINTER (aMIMEFormat
);
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 */];
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");
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
);
232 rv
= NS_ERROR_OUT_OF_MEMORY
;
240 // Take the image data from the clipboard and invert the rows. Modifying aInitialBuffer in place.
243 nsImageFromClipboard::InvertRows(unsigned char * aInitialBuffer
, uint32_t aSizeOfBuffer
, uint32_t aNumBytesPerRow
)
245 if (!aNumBytesPerRow
)
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
;
267 // ConvertColorBitMap
269 // Takes the clipboard bitmap and converts it into a RGB packed pixel values.
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;
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
285 rowSize
+= (4 - (rowSize
% 4)); // Pad to DWORD Boundary
287 // if our buffer includes a color map, skip over it
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
)
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
325 while (index
< imageSize
)
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
--;
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
--;
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
--;
371 num
= (uint8_t) aInputBuffer
[index
+1];
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
--;
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
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)...
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
)
418 // advance index to skip over remaining padding bytes
419 index
+= (rowSize
- pos
);
421 numPixelsLeftInRow
= pBitMapInfo
->bmiHeader
.biWidth
;
425 } // while we still have bytes to process
431 void nsImageFromClipboard::CalcBitmask(uint32_t aMask
, uint8_t& aBegin
, uint8_t& aLength
)
433 // find the rightmost 1
435 bool started
= false;
436 aBegin
= aLength
= 0;
437 for (pos
= 0; pos
<= 31; pos
++)
439 if (!started
&& (aMask
& (1 << pos
)))
444 else if (started
&& !(aMask
& (1 << pos
)))
446 aLength
= pos
- aBegin
;
452 void nsImageFromClipboard::CalcBitShift(bitFields
* aColorMask
)
454 uint8_t begin
, length
;
456 CalcBitmask(aColorMask
->red
, begin
, length
);
457 aColorMask
->redRightShift
= begin
;
458 aColorMask
->redLeftShift
= 8 - length
;
460 CalcBitmask(aColorMask
->green
, begin
, length
);
461 aColorMask
->greenRightShift
= begin
;
462 aColorMask
->greenLeftShift
= 8 - length
;
464 CalcBitmask(aColorMask
->blue
, begin
, length
);
465 aColorMask
->blueRightShift
= begin
;
466 aColorMask
->blueLeftShift
= 8 - length
;