widget/windows/nsImageClipboard.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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/. */
     6 #include "nsImageClipboard.h"
     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"
    20 #define BFH_LENGTH 14
    22 using namespace mozilla;
    23 using namespace mozilla::gfx;
    25 /* Things To Do 11/8/00
    27 Check image metrics, can we support them? Do we need to?
    28 Any other render format? HTML?
    30 */
    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 }
    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 }
    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" );
    70   return CreateFromImage ( mImage, outBits );
    72 } // GetPicture
    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);
    85   // add size of pallette to header size
    86   if (aBitsPerPixel < 16)
    87     HeaderMem += aColors * sizeof(RGBQUAD);
    89   if (aHeight < 0)
    90     aHeight = -aHeight;
    92   return (HeaderMem + (aHeight * aSpanBytes));
    93 }
    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;
   106   if ((aWidth * aBitCount) & 0x1F)
   107     spanBytes++;
   108   spanBytes <<= 2;
   110   return spanBytes;
   111 }
   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;
   126     RefPtr<SourceSurface> surface =
   127       inImage->GetFrame(imgIContainer::FRAME_CURRENT,
   128                         imgIContainer::FLAG_SYNC_DECODE);
   129     NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
   131     MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
   132                surface->GetFormat() == SurfaceFormat::B8G8R8X8);
   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);
   146     nsCOMPtr<imgIEncoder> encoder = do_CreateInstance("@mozilla.org/image/encoder;2?type=image/bmp", &rv);
   147     NS_ENSURE_SUCCESS(rv, rv);
   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     }
   173     DataSourceSurface::MappedSurface map;
   174     bool mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map);
   175     NS_ENSURE_TRUE(mappedOK, NS_ERROR_FAILURE);
   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);
   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;
   193     char *dst = (char*) ::GlobalLock(glob);
   194     char *src;
   195     rv = encoder->GetImageBuffer(&src);
   196     NS_ENSURE_SUCCESS(rv, rv);
   198     ::CopyMemory(dst, src + BFH_LENGTH, size - BFH_LENGTH);
   199     ::GlobalUnlock(glob);
   201     *outBitmap = (HANDLE)glob;
   202     return NS_OK;
   203 }
   205 nsImageFromClipboard :: nsImageFromClipboard ()
   206 {
   207   // nothing to do here
   208 }
   210 nsImageFromClipboard :: ~nsImageFromClipboard ( )
   211 {
   212 }
   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;
   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); 
   235   unsigned char * rgbData = new unsigned char[width * height * 3 /* RGB */];
   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="));
   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;
   263   return rv;
   264 } // GetImage
   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; 
   277   uint32_t numRows = aSizeOfBuffer / aNumBytesPerRow;
   278   unsigned char * row = new unsigned char[aNumBytesPerRow];
   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   }
   292   delete[] row;
   293 }
   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;
   307   if (bitCount <= 4)
   308     bytesPerPixel = 1;
   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
   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   }
   323   bitFields colorMasks; // only used if biCompression == BI_BITFIELDS
   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   }
   340   // The windows clipboard image format inverts the rows 
   341   InvertRows(aInputBuffer, imageSize, rowSize);
   343   if (!pBitMapInfo->bmiHeader.biCompression || pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS) 
   344   {  
   345     uint32_t index = 0;
   346     uint32_t writeIndex = 0;
   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;
   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--;
   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];
   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);
   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       }
   441       index += bytesPerPixel; // increment our loop counter
   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       }
   454     } // while we still have bytes to process
   455   }
   457   return NS_OK;
   458 }
   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 }
   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 }

mercurial