|
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/. */ |
|
5 |
|
6 #include "nsImageClipboard.h" |
|
7 |
|
8 #include "gfxUtils.h" |
|
9 #include "mozilla/gfx/2D.h" |
|
10 #include "mozilla/gfx/DataSurfaceHelpers.h" |
|
11 #include "mozilla/RefPtr.h" |
|
12 #include "nsITransferable.h" |
|
13 #include "nsGfxCIID.h" |
|
14 #include "nsMemory.h" |
|
15 #include "prmem.h" |
|
16 #include "imgIEncoder.h" |
|
17 #include "nsLiteralString.h" |
|
18 #include "nsComponentManagerUtils.h" |
|
19 |
|
20 #define BFH_LENGTH 14 |
|
21 |
|
22 using namespace mozilla; |
|
23 using namespace mozilla::gfx; |
|
24 |
|
25 /* Things To Do 11/8/00 |
|
26 |
|
27 Check image metrics, can we support them? Do we need to? |
|
28 Any other render format? HTML? |
|
29 |
|
30 */ |
|
31 |
|
32 |
|
33 // |
|
34 // nsImageToClipboard ctor |
|
35 // |
|
36 // Given an imgIContainer, convert it to a DIB that is ready to go on the win32 clipboard |
|
37 // |
|
38 nsImageToClipboard::nsImageToClipboard(imgIContainer* aInImage, bool aWantDIBV5) |
|
39 : mImage(aInImage) |
|
40 , mWantDIBV5(aWantDIBV5) |
|
41 { |
|
42 // nothing to do here |
|
43 } |
|
44 |
|
45 |
|
46 // |
|
47 // nsImageToClipboard dtor |
|
48 // |
|
49 // Clean up after ourselves. We know that we have created the bitmap |
|
50 // successfully if we still have a pointer to the header. |
|
51 // |
|
52 nsImageToClipboard::~nsImageToClipboard() |
|
53 { |
|
54 } |
|
55 |
|
56 |
|
57 // |
|
58 // GetPicture |
|
59 // |
|
60 // Call to get the actual bits that go on the clipboard. If an error |
|
61 // ocurred during conversion, |outBits| will be null. |
|
62 // |
|
63 // NOTE: The caller owns the handle and must delete it with ::GlobalRelease() |
|
64 // |
|
65 nsresult |
|
66 nsImageToClipboard :: GetPicture ( HANDLE* outBits ) |
|
67 { |
|
68 NS_ASSERTION ( outBits, "Bad parameter" ); |
|
69 |
|
70 return CreateFromImage ( mImage, outBits ); |
|
71 |
|
72 } // GetPicture |
|
73 |
|
74 |
|
75 // |
|
76 // CalcSize |
|
77 // |
|
78 // Computes # of bytes needed by a bitmap with the specified attributes. |
|
79 // |
|
80 int32_t |
|
81 nsImageToClipboard :: CalcSize ( int32_t aHeight, int32_t aColors, WORD aBitsPerPixel, int32_t aSpanBytes ) |
|
82 { |
|
83 int32_t HeaderMem = sizeof(BITMAPINFOHEADER); |
|
84 |
|
85 // add size of pallette to header size |
|
86 if (aBitsPerPixel < 16) |
|
87 HeaderMem += aColors * sizeof(RGBQUAD); |
|
88 |
|
89 if (aHeight < 0) |
|
90 aHeight = -aHeight; |
|
91 |
|
92 return (HeaderMem + (aHeight * aSpanBytes)); |
|
93 } |
|
94 |
|
95 |
|
96 // |
|
97 // CalcSpanLength |
|
98 // |
|
99 // Computes the span bytes for determining the overall size of the image |
|
100 // |
|
101 int32_t |
|
102 nsImageToClipboard::CalcSpanLength(uint32_t aWidth, uint32_t aBitCount) |
|
103 { |
|
104 int32_t spanBytes = (aWidth * aBitCount) >> 5; |
|
105 |
|
106 if ((aWidth * aBitCount) & 0x1F) |
|
107 spanBytes++; |
|
108 spanBytes <<= 2; |
|
109 |
|
110 return spanBytes; |
|
111 } |
|
112 |
|
113 |
|
114 // |
|
115 // CreateFromImage |
|
116 // |
|
117 // Do the work to setup the bitmap header and copy the bits out of the |
|
118 // image. |
|
119 // |
|
120 nsresult |
|
121 nsImageToClipboard::CreateFromImage ( imgIContainer* inImage, HANDLE* outBitmap ) |
|
122 { |
|
123 nsresult rv; |
|
124 *outBitmap = nullptr; |
|
125 |
|
126 RefPtr<SourceSurface> surface = |
|
127 inImage->GetFrame(imgIContainer::FRAME_CURRENT, |
|
128 imgIContainer::FLAG_SYNC_DECODE); |
|
129 NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE); |
|
130 |
|
131 MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 || |
|
132 surface->GetFormat() == SurfaceFormat::B8G8R8X8); |
|
133 |
|
134 RefPtr<DataSourceSurface> dataSurface; |
|
135 if (surface->GetFormat() == SurfaceFormat::B8G8R8A8) { |
|
136 dataSurface = surface->GetDataSurface(); |
|
137 } else { |
|
138 // XXXjwatt Bug 995923 - get rid of this copy and handle B8G8R8X8 |
|
139 // directly below once bug 995807 is fixed. |
|
140 dataSurface = gfxUtils:: |
|
141 CopySurfaceToDataSourceSurfaceWithFormat(surface, |
|
142 SurfaceFormat::B8G8R8A8); |
|
143 } |
|
144 NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE); |
|
145 |
|
146 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance("@mozilla.org/image/encoder;2?type=image/bmp", &rv); |
|
147 NS_ENSURE_SUCCESS(rv, rv); |
|
148 |
|
149 uint32_t format; |
|
150 nsAutoString options; |
|
151 if (mWantDIBV5) { |
|
152 options.AppendLiteral("version=5;bpp="); |
|
153 } else { |
|
154 options.AppendLiteral("version=3;bpp="); |
|
155 } |
|
156 switch (dataSurface->GetFormat()) { |
|
157 case SurfaceFormat::B8G8R8A8: |
|
158 format = imgIEncoder::INPUT_FORMAT_HOSTARGB; |
|
159 options.AppendInt(32); |
|
160 break; |
|
161 #if 0 |
|
162 // XXXjwatt Bug 995923 - fix |format| and reenable once bug 995807 is fixed. |
|
163 case SurfaceFormat::B8G8R8X8: |
|
164 format = imgIEncoder::INPUT_FORMAT_RGB; |
|
165 options.AppendInt(24); |
|
166 break; |
|
167 #endif |
|
168 default: |
|
169 NS_NOTREACHED("Unexpected surface format"); |
|
170 return NS_ERROR_INVALID_ARG; |
|
171 } |
|
172 |
|
173 DataSourceSurface::MappedSurface map; |
|
174 bool mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map); |
|
175 NS_ENSURE_TRUE(mappedOK, NS_ERROR_FAILURE); |
|
176 |
|
177 rv = encoder->InitFromData(map.mData, 0, |
|
178 dataSurface->GetSize().width, |
|
179 dataSurface->GetSize().height, |
|
180 map.mStride, |
|
181 format, options); |
|
182 dataSurface->Unmap(); |
|
183 NS_ENSURE_SUCCESS(rv, rv); |
|
184 |
|
185 uint32_t size; |
|
186 encoder->GetImageBufferUsed(&size); |
|
187 NS_ENSURE_TRUE(size > BFH_LENGTH, NS_ERROR_FAILURE); |
|
188 HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT, |
|
189 size - BFH_LENGTH); |
|
190 if (!glob) |
|
191 return NS_ERROR_OUT_OF_MEMORY; |
|
192 |
|
193 char *dst = (char*) ::GlobalLock(glob); |
|
194 char *src; |
|
195 rv = encoder->GetImageBuffer(&src); |
|
196 NS_ENSURE_SUCCESS(rv, rv); |
|
197 |
|
198 ::CopyMemory(dst, src + BFH_LENGTH, size - BFH_LENGTH); |
|
199 ::GlobalUnlock(glob); |
|
200 |
|
201 *outBitmap = (HANDLE)glob; |
|
202 return NS_OK; |
|
203 } |
|
204 |
|
205 nsImageFromClipboard :: nsImageFromClipboard () |
|
206 { |
|
207 // nothing to do here |
|
208 } |
|
209 |
|
210 nsImageFromClipboard :: ~nsImageFromClipboard ( ) |
|
211 { |
|
212 } |
|
213 |
|
214 // |
|
215 // GetEncodedImageStream |
|
216 // |
|
217 // Take the raw clipboard image data and convert it to aMIMEFormat in the form of a nsIInputStream |
|
218 // |
|
219 nsresult |
|
220 nsImageFromClipboard ::GetEncodedImageStream (unsigned char * aClipboardData, const char * aMIMEFormat, nsIInputStream** aInputStream ) |
|
221 { |
|
222 NS_ENSURE_ARG_POINTER (aInputStream); |
|
223 NS_ENSURE_ARG_POINTER (aMIMEFormat); |
|
224 nsresult rv; |
|
225 *aInputStream = nullptr; |
|
226 |
|
227 // pull the size information out of the BITMAPINFO header and |
|
228 // initialize the image |
|
229 BITMAPINFO* header = (BITMAPINFO *) aClipboardData; |
|
230 int32_t width = header->bmiHeader.biWidth; |
|
231 int32_t height = header->bmiHeader.biHeight; |
|
232 // neg. heights mean the Y axis is inverted and we don't handle that case |
|
233 NS_ENSURE_TRUE(height > 0, NS_ERROR_FAILURE); |
|
234 |
|
235 unsigned char * rgbData = new unsigned char[width * height * 3 /* RGB */]; |
|
236 |
|
237 if (rgbData) { |
|
238 BYTE * pGlobal = (BYTE *) aClipboardData; |
|
239 // Convert the clipboard image into RGB packed pixel data |
|
240 rv = ConvertColorBitMap((unsigned char *) (pGlobal + header->bmiHeader.biSize), header, rgbData); |
|
241 // if that succeeded, encode the bitmap as aMIMEFormat data. Don't return early or we risk leaking rgbData |
|
242 if (NS_SUCCEEDED(rv)) { |
|
243 nsAutoCString encoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type=")); |
|
244 |
|
245 // Map image/jpg to image/jpeg (which is how the encoder is registered). |
|
246 if (strcmp(aMIMEFormat, kJPGImageMime) == 0) |
|
247 encoderCID.Append("image/jpeg"); |
|
248 else |
|
249 encoderCID.Append(aMIMEFormat); |
|
250 nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get(), &rv); |
|
251 if (NS_SUCCEEDED(rv)){ |
|
252 rv = encoder->InitFromData(rgbData, 0, width, height, 3 * width /* RGB * # pixels in a row */, |
|
253 imgIEncoder::INPUT_FORMAT_RGB, EmptyString()); |
|
254 if (NS_SUCCEEDED(rv)) |
|
255 encoder->QueryInterface(NS_GET_IID(nsIInputStream), (void **) aInputStream); |
|
256 } |
|
257 } |
|
258 delete [] rgbData; |
|
259 } |
|
260 else |
|
261 rv = NS_ERROR_OUT_OF_MEMORY; |
|
262 |
|
263 return rv; |
|
264 } // GetImage |
|
265 |
|
266 // |
|
267 // InvertRows |
|
268 // |
|
269 // Take the image data from the clipboard and invert the rows. Modifying aInitialBuffer in place. |
|
270 // |
|
271 void |
|
272 nsImageFromClipboard::InvertRows(unsigned char * aInitialBuffer, uint32_t aSizeOfBuffer, uint32_t aNumBytesPerRow) |
|
273 { |
|
274 if (!aNumBytesPerRow) |
|
275 return; |
|
276 |
|
277 uint32_t numRows = aSizeOfBuffer / aNumBytesPerRow; |
|
278 unsigned char * row = new unsigned char[aNumBytesPerRow]; |
|
279 |
|
280 uint32_t currentRow = 0; |
|
281 uint32_t lastRow = (numRows - 1) * aNumBytesPerRow; |
|
282 while (currentRow < lastRow) |
|
283 { |
|
284 // store the current row into a temporary buffer |
|
285 memcpy(row, &aInitialBuffer[currentRow], aNumBytesPerRow); |
|
286 memcpy(&aInitialBuffer[currentRow], &aInitialBuffer[lastRow], aNumBytesPerRow); |
|
287 memcpy(&aInitialBuffer[lastRow], row, aNumBytesPerRow); |
|
288 lastRow -= aNumBytesPerRow; |
|
289 currentRow += aNumBytesPerRow; |
|
290 } |
|
291 |
|
292 delete[] row; |
|
293 } |
|
294 |
|
295 // |
|
296 // ConvertColorBitMap |
|
297 // |
|
298 // Takes the clipboard bitmap and converts it into a RGB packed pixel values. |
|
299 // |
|
300 nsresult |
|
301 nsImageFromClipboard::ConvertColorBitMap(unsigned char * aInputBuffer, PBITMAPINFO pBitMapInfo, unsigned char * aOutBuffer) |
|
302 { |
|
303 uint8_t bitCount = pBitMapInfo->bmiHeader.biBitCount; |
|
304 uint32_t imageSize = pBitMapInfo->bmiHeader.biSizeImage; // may be zero for BI_RGB bitmaps which means we need to calculate by hand |
|
305 uint32_t bytesPerPixel = bitCount / 8; |
|
306 |
|
307 if (bitCount <= 4) |
|
308 bytesPerPixel = 1; |
|
309 |
|
310 // rows are DWORD aligned. Calculate how many real bytes are in each row in the bitmap. This number won't |
|
311 // correspond to biWidth. |
|
312 uint32_t rowSize = (bitCount * pBitMapInfo->bmiHeader.biWidth + 7) / 8; // +7 to round up |
|
313 if (rowSize % 4) |
|
314 rowSize += (4 - (rowSize % 4)); // Pad to DWORD Boundary |
|
315 |
|
316 // if our buffer includes a color map, skip over it |
|
317 if (bitCount <= 8) |
|
318 { |
|
319 int32_t bytesToSkip = (pBitMapInfo->bmiHeader.biClrUsed ? pBitMapInfo->bmiHeader.biClrUsed : (1 << bitCount) ) * sizeof(RGBQUAD); |
|
320 aInputBuffer += bytesToSkip; |
|
321 } |
|
322 |
|
323 bitFields colorMasks; // only used if biCompression == BI_BITFIELDS |
|
324 |
|
325 if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS) |
|
326 { |
|
327 // color table consists of 3 DWORDS containing the color masks... |
|
328 colorMasks.red = (*((uint32_t*)&(pBitMapInfo->bmiColors[0]))); |
|
329 colorMasks.green = (*((uint32_t*)&(pBitMapInfo->bmiColors[1]))); |
|
330 colorMasks.blue = (*((uint32_t*)&(pBitMapInfo->bmiColors[2]))); |
|
331 CalcBitShift(&colorMasks); |
|
332 aInputBuffer += 3 * sizeof(DWORD); |
|
333 } |
|
334 else if (pBitMapInfo->bmiHeader.biCompression == BI_RGB && !imageSize) // BI_RGB can have a size of zero which means we figure it out |
|
335 { |
|
336 // XXX: note use rowSize here and not biWidth. rowSize accounts for the DWORD padding for each row |
|
337 imageSize = rowSize * pBitMapInfo->bmiHeader.biHeight; |
|
338 } |
|
339 |
|
340 // The windows clipboard image format inverts the rows |
|
341 InvertRows(aInputBuffer, imageSize, rowSize); |
|
342 |
|
343 if (!pBitMapInfo->bmiHeader.biCompression || pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS) |
|
344 { |
|
345 uint32_t index = 0; |
|
346 uint32_t writeIndex = 0; |
|
347 |
|
348 unsigned char redValue, greenValue, blueValue; |
|
349 uint8_t colorTableEntry = 0; |
|
350 int8_t bit; // used for grayscale bitmaps where each bit is a pixel |
|
351 uint32_t numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth; // how many more pixels do we still need to read for the current row |
|
352 uint32_t pos = 0; |
|
353 |
|
354 while (index < imageSize) |
|
355 { |
|
356 switch (bitCount) |
|
357 { |
|
358 case 1: |
|
359 for (bit = 7; bit >= 0 && numPixelsLeftInRow; bit--) |
|
360 { |
|
361 colorTableEntry = (aInputBuffer[index] >> bit) & 1; |
|
362 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed; |
|
363 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen; |
|
364 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue; |
|
365 numPixelsLeftInRow--; |
|
366 } |
|
367 pos += 1; |
|
368 break; |
|
369 case 4: |
|
370 { |
|
371 // each aInputBuffer[index] entry contains data for two pixels. |
|
372 // read the first pixel |
|
373 colorTableEntry = aInputBuffer[index] >> 4; |
|
374 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed; |
|
375 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen; |
|
376 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue; |
|
377 numPixelsLeftInRow--; |
|
378 |
|
379 if (numPixelsLeftInRow) // now read the second pixel |
|
380 { |
|
381 colorTableEntry = aInputBuffer[index] & 0xF; |
|
382 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed; |
|
383 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen; |
|
384 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue; |
|
385 numPixelsLeftInRow--; |
|
386 } |
|
387 pos += 1; |
|
388 } |
|
389 break; |
|
390 case 8: |
|
391 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbRed; |
|
392 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbGreen; |
|
393 aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbBlue; |
|
394 numPixelsLeftInRow--; |
|
395 pos += 1; |
|
396 break; |
|
397 case 16: |
|
398 { |
|
399 uint16_t num = 0; |
|
400 num = (uint8_t) aInputBuffer[index+1]; |
|
401 num <<= 8; |
|
402 num |= (uint8_t) aInputBuffer[index]; |
|
403 |
|
404 redValue = ((uint32_t) (((float)(num & 0xf800) / 0xf800) * 0xFF0000) & 0xFF0000)>> 16; |
|
405 greenValue = ((uint32_t)(((float)(num & 0x07E0) / 0x07E0) * 0x00FF00) & 0x00FF00)>> 8; |
|
406 blueValue = ((uint32_t)(((float)(num & 0x001F) / 0x001F) * 0x0000FF) & 0x0000FF); |
|
407 |
|
408 // now we have the right RGB values... |
|
409 aOutBuffer[writeIndex++] = redValue; |
|
410 aOutBuffer[writeIndex++] = greenValue; |
|
411 aOutBuffer[writeIndex++] = blueValue; |
|
412 numPixelsLeftInRow--; |
|
413 pos += 2; |
|
414 } |
|
415 break; |
|
416 case 32: |
|
417 case 24: |
|
418 if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS) |
|
419 { |
|
420 uint32_t val = *((uint32_t*) (aInputBuffer + index) ); |
|
421 aOutBuffer[writeIndex++] = (val & colorMasks.red) >> colorMasks.redRightShift << colorMasks.redLeftShift; |
|
422 aOutBuffer[writeIndex++] = (val & colorMasks.green) >> colorMasks.greenRightShift << colorMasks.greenLeftShift; |
|
423 aOutBuffer[writeIndex++] = (val & colorMasks.blue) >> colorMasks.blueRightShift << colorMasks.blueLeftShift; |
|
424 numPixelsLeftInRow--; |
|
425 pos += 4; // we read in 4 bytes of data in order to process this pixel |
|
426 } |
|
427 else |
|
428 { |
|
429 aOutBuffer[writeIndex++] = aInputBuffer[index+2]; |
|
430 aOutBuffer[writeIndex++] = aInputBuffer[index+1]; |
|
431 aOutBuffer[writeIndex++] = aInputBuffer[index]; |
|
432 numPixelsLeftInRow--; |
|
433 pos += bytesPerPixel; // 3 bytes for 24 bit data, 4 bytes for 32 bit data (we skip over the 4th byte)... |
|
434 } |
|
435 break; |
|
436 default: |
|
437 // This is probably the wrong place to check this... |
|
438 return NS_ERROR_FAILURE; |
|
439 } |
|
440 |
|
441 index += bytesPerPixel; // increment our loop counter |
|
442 |
|
443 if (!numPixelsLeftInRow) |
|
444 { |
|
445 if (rowSize != pos) |
|
446 { |
|
447 // advance index to skip over remaining padding bytes |
|
448 index += (rowSize - pos); |
|
449 } |
|
450 numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth; |
|
451 pos = 0; |
|
452 } |
|
453 |
|
454 } // while we still have bytes to process |
|
455 } |
|
456 |
|
457 return NS_OK; |
|
458 } |
|
459 |
|
460 void nsImageFromClipboard::CalcBitmask(uint32_t aMask, uint8_t& aBegin, uint8_t& aLength) |
|
461 { |
|
462 // find the rightmost 1 |
|
463 uint8_t pos; |
|
464 bool started = false; |
|
465 aBegin = aLength = 0; |
|
466 for (pos = 0; pos <= 31; pos++) |
|
467 { |
|
468 if (!started && (aMask & (1 << pos))) |
|
469 { |
|
470 aBegin = pos; |
|
471 started = true; |
|
472 } |
|
473 else if (started && !(aMask & (1 << pos))) |
|
474 { |
|
475 aLength = pos - aBegin; |
|
476 break; |
|
477 } |
|
478 } |
|
479 } |
|
480 |
|
481 void nsImageFromClipboard::CalcBitShift(bitFields * aColorMask) |
|
482 { |
|
483 uint8_t begin, length; |
|
484 // red |
|
485 CalcBitmask(aColorMask->red, begin, length); |
|
486 aColorMask->redRightShift = begin; |
|
487 aColorMask->redLeftShift = 8 - length; |
|
488 // green |
|
489 CalcBitmask(aColorMask->green, begin, length); |
|
490 aColorMask->greenRightShift = begin; |
|
491 aColorMask->greenLeftShift = 8 - length; |
|
492 // blue |
|
493 CalcBitmask(aColorMask->blue, begin, length); |
|
494 aColorMask->blueRightShift = begin; |
|
495 aColorMask->blueLeftShift = 8 - length; |
|
496 } |