summaryrefslogtreecommitdiffstats
path: root/widget/windows/nsImageClipboard.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/windows/nsImageClipboard.cpp')
-rw-r--r--widget/windows/nsImageClipboard.cpp497
1 files changed, 497 insertions, 0 deletions
diff --git a/widget/windows/nsImageClipboard.cpp b/widget/windows/nsImageClipboard.cpp
new file mode 100644
index 000000000..fab62eab5
--- /dev/null
+++ b/widget/windows/nsImageClipboard.cpp
@@ -0,0 +1,497 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsImageClipboard.h"
+
+#include "gfxUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/RefPtr.h"
+#include "nsITransferable.h"
+#include "nsGfxCIID.h"
+#include "nsMemory.h"
+#include "prmem.h"
+#include "imgIEncoder.h"
+#include "nsLiteralString.h"
+#include "nsComponentManagerUtils.h"
+
+#define BFH_LENGTH 14
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+/* Things To Do 11/8/00
+
+Check image metrics, can we support them? Do we need to?
+Any other render format? HTML?
+
+*/
+
+
+//
+// nsImageToClipboard ctor
+//
+// Given an imgIContainer, convert it to a DIB that is ready to go on the win32 clipboard
+//
+nsImageToClipboard::nsImageToClipboard(imgIContainer* aInImage, bool aWantDIBV5)
+ : mImage(aInImage)
+ , mWantDIBV5(aWantDIBV5)
+{
+ // nothing to do here
+}
+
+
+//
+// nsImageToClipboard dtor
+//
+// Clean up after ourselves. We know that we have created the bitmap
+// successfully if we still have a pointer to the header.
+//
+nsImageToClipboard::~nsImageToClipboard()
+{
+}
+
+
+//
+// GetPicture
+//
+// Call to get the actual bits that go on the clipboard. If an error
+// ocurred during conversion, |outBits| will be null.
+//
+// NOTE: The caller owns the handle and must delete it with ::GlobalRelease()
+//
+nsresult
+nsImageToClipboard :: GetPicture ( HANDLE* outBits )
+{
+ NS_ASSERTION ( outBits, "Bad parameter" );
+
+ return CreateFromImage ( mImage, outBits );
+
+} // GetPicture
+
+
+//
+// CalcSize
+//
+// Computes # of bytes needed by a bitmap with the specified attributes.
+//
+int32_t
+nsImageToClipboard :: CalcSize ( int32_t aHeight, int32_t aColors, WORD aBitsPerPixel, int32_t aSpanBytes )
+{
+ int32_t HeaderMem = sizeof(BITMAPINFOHEADER);
+
+ // add size of pallette to header size
+ if (aBitsPerPixel < 16)
+ HeaderMem += aColors * sizeof(RGBQUAD);
+
+ if (aHeight < 0)
+ aHeight = -aHeight;
+
+ return (HeaderMem + (aHeight * aSpanBytes));
+}
+
+
+//
+// CalcSpanLength
+//
+// Computes the span bytes for determining the overall size of the image
+//
+int32_t
+nsImageToClipboard::CalcSpanLength(uint32_t aWidth, uint32_t aBitCount)
+{
+ int32_t spanBytes = (aWidth * aBitCount) >> 5;
+
+ if ((aWidth * aBitCount) & 0x1F)
+ spanBytes++;
+ spanBytes <<= 2;
+
+ return spanBytes;
+}
+
+
+//
+// CreateFromImage
+//
+// Do the work to setup the bitmap header and copy the bits out of the
+// image.
+//
+nsresult
+nsImageToClipboard::CreateFromImage ( imgIContainer* inImage, HANDLE* outBitmap )
+{
+ nsresult rv;
+ *outBitmap = nullptr;
+
+ RefPtr<SourceSurface> surface =
+ inImage->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE);
+ NS_ENSURE_TRUE(surface, NS_ERROR_FAILURE);
+
+ MOZ_ASSERT(surface->GetFormat() == SurfaceFormat::B8G8R8A8 ||
+ surface->GetFormat() == SurfaceFormat::B8G8R8X8);
+
+ RefPtr<DataSourceSurface> dataSurface;
+ if (surface->GetFormat() == SurfaceFormat::B8G8R8A8) {
+ dataSurface = surface->GetDataSurface();
+ } else {
+ // XXXjwatt Bug 995923 - get rid of this copy and handle B8G8R8X8
+ // directly below once bug 995807 is fixed.
+ dataSurface = gfxUtils::
+ CopySurfaceToDataSourceSurfaceWithFormat(surface,
+ SurfaceFormat::B8G8R8A8);
+ }
+ NS_ENSURE_TRUE(dataSurface, NS_ERROR_FAILURE);
+
+ nsCOMPtr<imgIEncoder> encoder = do_CreateInstance("@mozilla.org/image/encoder;2?type=image/bmp", &rv);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t format;
+ nsAutoString options;
+ if (mWantDIBV5) {
+ options.AppendLiteral("version=5;bpp=");
+ } else {
+ options.AppendLiteral("version=3;bpp=");
+ }
+ switch (dataSurface->GetFormat()) {
+ case SurfaceFormat::B8G8R8A8:
+ format = imgIEncoder::INPUT_FORMAT_HOSTARGB;
+ options.AppendInt(32);
+ break;
+#if 0
+ // XXXjwatt Bug 995923 - fix |format| and reenable once bug 995807 is fixed.
+ case SurfaceFormat::B8G8R8X8:
+ format = imgIEncoder::INPUT_FORMAT_RGB;
+ options.AppendInt(24);
+ break;
+#endif
+ default:
+ NS_NOTREACHED("Unexpected surface format");
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ DataSourceSurface::MappedSurface map;
+ bool mappedOK = dataSurface->Map(DataSourceSurface::MapType::READ, &map);
+ NS_ENSURE_TRUE(mappedOK, NS_ERROR_FAILURE);
+
+ rv = encoder->InitFromData(map.mData, 0,
+ dataSurface->GetSize().width,
+ dataSurface->GetSize().height,
+ map.mStride,
+ format, options);
+ dataSurface->Unmap();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ uint32_t size;
+ encoder->GetImageBufferUsed(&size);
+ NS_ENSURE_TRUE(size > BFH_LENGTH, NS_ERROR_FAILURE);
+ HGLOBAL glob = ::GlobalAlloc(GMEM_MOVEABLE | GMEM_DDESHARE | GMEM_ZEROINIT,
+ size - BFH_LENGTH);
+ if (!glob)
+ return NS_ERROR_OUT_OF_MEMORY;
+
+ char *dst = (char*) ::GlobalLock(glob);
+ char *src;
+ rv = encoder->GetImageBuffer(&src);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ ::CopyMemory(dst, src + BFH_LENGTH, size - BFH_LENGTH);
+ ::GlobalUnlock(glob);
+
+ *outBitmap = (HANDLE)glob;
+ return NS_OK;
+}
+
+nsImageFromClipboard :: nsImageFromClipboard ()
+{
+ // nothing to do here
+}
+
+nsImageFromClipboard :: ~nsImageFromClipboard ( )
+{
+}
+
+//
+// GetEncodedImageStream
+//
+// Take the raw clipboard image data and convert it to aMIMEFormat in the form of a nsIInputStream
+//
+nsresult
+nsImageFromClipboard ::GetEncodedImageStream (unsigned char * aClipboardData, const char * aMIMEFormat, nsIInputStream** aInputStream )
+{
+ NS_ENSURE_ARG_POINTER (aInputStream);
+ NS_ENSURE_ARG_POINTER (aMIMEFormat);
+ nsresult rv;
+ *aInputStream = nullptr;
+
+ // pull the size information out of the BITMAPINFO header and
+ // initialize the image
+ BITMAPINFO* header = (BITMAPINFO *) aClipboardData;
+ int32_t width = header->bmiHeader.biWidth;
+ int32_t height = header->bmiHeader.biHeight;
+ // neg. heights mean the Y axis is inverted and we don't handle that case
+ NS_ENSURE_TRUE(height > 0, NS_ERROR_FAILURE);
+
+ unsigned char * rgbData = new unsigned char[width * height * 3 /* RGB */];
+
+ if (rgbData) {
+ BYTE * pGlobal = (BYTE *) aClipboardData;
+ // Convert the clipboard image into RGB packed pixel data
+ rv = ConvertColorBitMap((unsigned char *) (pGlobal + header->bmiHeader.biSize), header, rgbData);
+ // if that succeeded, encode the bitmap as aMIMEFormat data. Don't return early or we risk leaking rgbData
+ if (NS_SUCCEEDED(rv)) {
+ nsAutoCString encoderCID(NS_LITERAL_CSTRING("@mozilla.org/image/encoder;2?type="));
+
+ // Map image/jpg to image/jpeg (which is how the encoder is registered).
+ if (strcmp(aMIMEFormat, kJPGImageMime) == 0)
+ encoderCID.AppendLiteral("image/jpeg");
+ else
+ encoderCID.Append(aMIMEFormat);
+ nsCOMPtr<imgIEncoder> encoder = do_CreateInstance(encoderCID.get(), &rv);
+ if (NS_SUCCEEDED(rv)){
+ rv = encoder->InitFromData(rgbData, 0, width, height, 3 * width /* RGB * # pixels in a row */,
+ imgIEncoder::INPUT_FORMAT_RGB, EmptyString());
+ if (NS_SUCCEEDED(rv)) {
+ encoder.forget(aInputStream);
+ }
+ }
+ }
+ delete [] rgbData;
+ }
+ else
+ rv = NS_ERROR_OUT_OF_MEMORY;
+
+ return rv;
+} // GetImage
+
+//
+// InvertRows
+//
+// Take the image data from the clipboard and invert the rows. Modifying aInitialBuffer in place.
+//
+void
+nsImageFromClipboard::InvertRows(unsigned char * aInitialBuffer, uint32_t aSizeOfBuffer, uint32_t aNumBytesPerRow)
+{
+ if (!aNumBytesPerRow)
+ return;
+
+ uint32_t numRows = aSizeOfBuffer / aNumBytesPerRow;
+ unsigned char * row = new unsigned char[aNumBytesPerRow];
+
+ uint32_t currentRow = 0;
+ uint32_t lastRow = (numRows - 1) * aNumBytesPerRow;
+ while (currentRow < lastRow)
+ {
+ // store the current row into a temporary buffer
+ memcpy(row, &aInitialBuffer[currentRow], aNumBytesPerRow);
+ memcpy(&aInitialBuffer[currentRow], &aInitialBuffer[lastRow], aNumBytesPerRow);
+ memcpy(&aInitialBuffer[lastRow], row, aNumBytesPerRow);
+ lastRow -= aNumBytesPerRow;
+ currentRow += aNumBytesPerRow;
+ }
+
+ delete[] row;
+}
+
+//
+// ConvertColorBitMap
+//
+// Takes the clipboard bitmap and converts it into a RGB packed pixel values.
+//
+nsresult
+nsImageFromClipboard::ConvertColorBitMap(unsigned char * aInputBuffer, PBITMAPINFO pBitMapInfo, unsigned char * aOutBuffer)
+{
+ uint8_t bitCount = pBitMapInfo->bmiHeader.biBitCount;
+ uint32_t imageSize = pBitMapInfo->bmiHeader.biSizeImage; // may be zero for BI_RGB bitmaps which means we need to calculate by hand
+ uint32_t bytesPerPixel = bitCount / 8;
+
+ if (bitCount <= 4)
+ bytesPerPixel = 1;
+
+ // rows are DWORD aligned. Calculate how many real bytes are in each row in the bitmap. This number won't
+ // correspond to biWidth.
+ uint32_t rowSize = (bitCount * pBitMapInfo->bmiHeader.biWidth + 7) / 8; // +7 to round up
+ if (rowSize % 4)
+ rowSize += (4 - (rowSize % 4)); // Pad to DWORD Boundary
+
+ // if our buffer includes a color map, skip over it
+ if (bitCount <= 8)
+ {
+ int32_t bytesToSkip = (pBitMapInfo->bmiHeader.biClrUsed ? pBitMapInfo->bmiHeader.biClrUsed : (1 << bitCount) ) * sizeof(RGBQUAD);
+ aInputBuffer += bytesToSkip;
+ }
+
+ bitFields colorMasks; // only used if biCompression == BI_BITFIELDS
+
+ if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
+ {
+ // color table consists of 3 DWORDS containing the color masks...
+ colorMasks.red = (*((uint32_t*)&(pBitMapInfo->bmiColors[0])));
+ colorMasks.green = (*((uint32_t*)&(pBitMapInfo->bmiColors[1])));
+ colorMasks.blue = (*((uint32_t*)&(pBitMapInfo->bmiColors[2])));
+ CalcBitShift(&colorMasks);
+ aInputBuffer += 3 * sizeof(DWORD);
+ }
+ else if (pBitMapInfo->bmiHeader.biCompression == BI_RGB && !imageSize) // BI_RGB can have a size of zero which means we figure it out
+ {
+ // XXX: note use rowSize here and not biWidth. rowSize accounts for the DWORD padding for each row
+ imageSize = rowSize * pBitMapInfo->bmiHeader.biHeight;
+ }
+
+ // The windows clipboard image format inverts the rows
+ InvertRows(aInputBuffer, imageSize, rowSize);
+
+ if (!pBitMapInfo->bmiHeader.biCompression || pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
+ {
+ uint32_t index = 0;
+ uint32_t writeIndex = 0;
+
+ unsigned char redValue, greenValue, blueValue;
+ uint8_t colorTableEntry = 0;
+ int8_t bit; // used for grayscale bitmaps where each bit is a pixel
+ uint32_t numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth; // how many more pixels do we still need to read for the current row
+ uint32_t pos = 0;
+
+ while (index < imageSize)
+ {
+ switch (bitCount)
+ {
+ case 1:
+ for (bit = 7; bit >= 0 && numPixelsLeftInRow; bit--)
+ {
+ colorTableEntry = (aInputBuffer[index] >> bit) & 1;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
+ numPixelsLeftInRow--;
+ }
+ pos += 1;
+ break;
+ case 4:
+ {
+ // each aInputBuffer[index] entry contains data for two pixels.
+ // read the first pixel
+ colorTableEntry = aInputBuffer[index] >> 4;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
+ numPixelsLeftInRow--;
+
+ if (numPixelsLeftInRow) // now read the second pixel
+ {
+ colorTableEntry = aInputBuffer[index] & 0xF;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbRed;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbGreen;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[colorTableEntry].rgbBlue;
+ numPixelsLeftInRow--;
+ }
+ pos += 1;
+ }
+ break;
+ case 8:
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbRed;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbGreen;
+ aOutBuffer[writeIndex++] = pBitMapInfo->bmiColors[aInputBuffer[index]].rgbBlue;
+ numPixelsLeftInRow--;
+ pos += 1;
+ break;
+ case 16:
+ {
+ uint16_t num = 0;
+ num = (uint8_t) aInputBuffer[index+1];
+ num <<= 8;
+ num |= (uint8_t) aInputBuffer[index];
+
+ redValue = ((uint32_t) (((float)(num & 0xf800) / 0xf800) * 0xFF0000) & 0xFF0000)>> 16;
+ greenValue = ((uint32_t)(((float)(num & 0x07E0) / 0x07E0) * 0x00FF00) & 0x00FF00)>> 8;
+ blueValue = ((uint32_t)(((float)(num & 0x001F) / 0x001F) * 0x0000FF) & 0x0000FF);
+
+ // now we have the right RGB values...
+ aOutBuffer[writeIndex++] = redValue;
+ aOutBuffer[writeIndex++] = greenValue;
+ aOutBuffer[writeIndex++] = blueValue;
+ numPixelsLeftInRow--;
+ pos += 2;
+ }
+ break;
+ case 32:
+ case 24:
+ if (pBitMapInfo->bmiHeader.biCompression == BI_BITFIELDS)
+ {
+ uint32_t val = *((uint32_t*) (aInputBuffer + index) );
+ aOutBuffer[writeIndex++] = (val & colorMasks.red) >> colorMasks.redRightShift << colorMasks.redLeftShift;
+ aOutBuffer[writeIndex++] = (val & colorMasks.green) >> colorMasks.greenRightShift << colorMasks.greenLeftShift;
+ aOutBuffer[writeIndex++] = (val & colorMasks.blue) >> colorMasks.blueRightShift << colorMasks.blueLeftShift;
+ numPixelsLeftInRow--;
+ pos += 4; // we read in 4 bytes of data in order to process this pixel
+ }
+ else
+ {
+ aOutBuffer[writeIndex++] = aInputBuffer[index+2];
+ aOutBuffer[writeIndex++] = aInputBuffer[index+1];
+ aOutBuffer[writeIndex++] = aInputBuffer[index];
+ numPixelsLeftInRow--;
+ pos += bytesPerPixel; // 3 bytes for 24 bit data, 4 bytes for 32 bit data (we skip over the 4th byte)...
+ }
+ break;
+ default:
+ // This is probably the wrong place to check this...
+ return NS_ERROR_FAILURE;
+ }
+
+ index += bytesPerPixel; // increment our loop counter
+
+ if (!numPixelsLeftInRow)
+ {
+ if (rowSize != pos)
+ {
+ // advance index to skip over remaining padding bytes
+ index += (rowSize - pos);
+ }
+ numPixelsLeftInRow = pBitMapInfo->bmiHeader.biWidth;
+ pos = 0;
+ }
+
+ } // while we still have bytes to process
+ }
+
+ return NS_OK;
+}
+
+void nsImageFromClipboard::CalcBitmask(uint32_t aMask, uint8_t& aBegin, uint8_t& aLength)
+{
+ // find the rightmost 1
+ uint8_t pos;
+ bool started = false;
+ aBegin = aLength = 0;
+ for (pos = 0; pos <= 31; pos++)
+ {
+ if (!started && (aMask & (1 << pos)))
+ {
+ aBegin = pos;
+ started = true;
+ }
+ else if (started && !(aMask & (1 << pos)))
+ {
+ aLength = pos - aBegin;
+ break;
+ }
+ }
+}
+
+void nsImageFromClipboard::CalcBitShift(bitFields * aColorMask)
+{
+ uint8_t begin, length;
+ // red
+ CalcBitmask(aColorMask->red, begin, length);
+ aColorMask->redRightShift = begin;
+ aColorMask->redLeftShift = 8 - length;
+ // green
+ CalcBitmask(aColorMask->green, begin, length);
+ aColorMask->greenRightShift = begin;
+ aColorMask->greenLeftShift = 8 - length;
+ // blue
+ CalcBitmask(aColorMask->blue, begin, length);
+ aColorMask->blueRightShift = begin;
+ aColorMask->blueLeftShift = 8 - length;
+}