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