/* -*- 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 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 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 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 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; }