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