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 "nsCRT.h" michael@0: #include "mozilla/Endian.h" michael@0: #include "nsBMPEncoder.h" michael@0: #include "nsPNGEncoder.h" michael@0: #include "nsICOEncoder.h" michael@0: #include "prprf.h" michael@0: #include "nsString.h" michael@0: #include "nsStreamUtils.h" michael@0: #include "nsTArray.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::image; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsICOEncoder, imgIEncoder, nsIInputStream, nsIAsyncInputStream) michael@0: michael@0: nsICOEncoder::nsICOEncoder() : mImageBufferStart(nullptr), michael@0: mImageBufferCurr(0), michael@0: mImageBufferSize(0), michael@0: mImageBufferReadPoint(0), michael@0: mFinished(false), michael@0: mUsePNG(true), michael@0: mNotifyThreshold(0) michael@0: { michael@0: } michael@0: michael@0: nsICOEncoder::~nsICOEncoder() michael@0: { michael@0: if (mImageBufferStart) { michael@0: moz_free(mImageBufferStart); michael@0: mImageBufferStart = nullptr; michael@0: mImageBufferCurr = nullptr; michael@0: } michael@0: } michael@0: michael@0: // nsICOEncoder::InitFromData michael@0: // Two output options are supported: format=;bpp= michael@0: // format specifies whether to use png or bitmap format michael@0: // bpp specifies the bits per pixel to use where bpp_value can be 24 or 32 michael@0: NS_IMETHODIMP nsICOEncoder::InitFromData(const uint8_t* aData, michael@0: uint32_t aLength, michael@0: uint32_t aWidth, michael@0: uint32_t aHeight, michael@0: uint32_t aStride, michael@0: uint32_t aInputFormat, michael@0: const nsAString& aOutputOptions) michael@0: { michael@0: // validate input format michael@0: if (aInputFormat != INPUT_FORMAT_RGB && michael@0: aInputFormat != INPUT_FORMAT_RGBA && michael@0: aInputFormat != INPUT_FORMAT_HOSTARGB) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: // Stride is the padded width of each row, so it better be longer michael@0: if ((aInputFormat == INPUT_FORMAT_RGB && michael@0: aStride < aWidth * 3) || michael@0: ((aInputFormat == INPUT_FORMAT_RGBA || aInputFormat == INPUT_FORMAT_HOSTARGB) && michael@0: aStride < aWidth * 4)) { michael@0: NS_WARNING("Invalid stride for InitFromData"); michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: nsresult rv; michael@0: rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride, michael@0: aInputFormat, aOutputOptions); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = EndImageEncode(); michael@0: return rv; michael@0: } michael@0: michael@0: // Returns the number of bytes in the image buffer used michael@0: // For an ICO file, this is all bytes in the buffer. michael@0: NS_IMETHODIMP michael@0: nsICOEncoder::GetImageBufferUsed(uint32_t *aOutputSize) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aOutputSize); michael@0: *aOutputSize = mImageBufferSize; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Returns a pointer to the start of the image buffer michael@0: NS_IMETHODIMP michael@0: nsICOEncoder::GetImageBuffer(char **aOutputBuffer) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aOutputBuffer); michael@0: *aOutputBuffer = reinterpret_cast(mImageBufferStart); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsICOEncoder::AddImageFrame(const uint8_t* aData, michael@0: uint32_t aLength, michael@0: uint32_t aWidth, michael@0: uint32_t aHeight, michael@0: uint32_t aStride, michael@0: uint32_t aInputFormat, michael@0: const nsAString& aFrameOptions) michael@0: { michael@0: if (mUsePNG) { michael@0: michael@0: mContainedEncoder = new nsPNGEncoder(); michael@0: nsresult rv; michael@0: nsAutoString noParams; michael@0: rv = mContainedEncoder->InitFromData(aData, aLength, aWidth, aHeight, michael@0: aStride, aInputFormat, noParams); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t PNGImageBufferSize; michael@0: mContainedEncoder->GetImageBufferUsed(&PNGImageBufferSize); michael@0: mImageBufferSize = ICONFILEHEADERSIZE + ICODIRENTRYSIZE + michael@0: PNGImageBufferSize; michael@0: mImageBufferStart = static_cast(moz_malloc(mImageBufferSize)); michael@0: if (!mImageBufferStart) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: mImageBufferCurr = mImageBufferStart; michael@0: mICODirEntry.mBytesInRes = PNGImageBufferSize; michael@0: michael@0: EncodeFileHeader(); michael@0: EncodeInfoHeader(); michael@0: michael@0: char *imageBuffer; michael@0: rv = mContainedEncoder->GetImageBuffer(&imageBuffer); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: memcpy(mImageBufferCurr, imageBuffer, PNGImageBufferSize); michael@0: mImageBufferCurr += PNGImageBufferSize; michael@0: } else { michael@0: mContainedEncoder = new nsBMPEncoder(); michael@0: nsresult rv; michael@0: michael@0: nsAutoString params; michael@0: params.AppendLiteral("bpp="); michael@0: params.AppendInt(mICODirEntry.mBitCount); michael@0: michael@0: rv = mContainedEncoder->InitFromData(aData, aLength, aWidth, aHeight, michael@0: aStride, aInputFormat, params); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: uint32_t andMaskSize = ((GetRealWidth() + 31) / 32) * 4 * // row AND mask michael@0: GetRealHeight(); // num rows michael@0: michael@0: uint32_t BMPImageBufferSize; michael@0: mContainedEncoder->GetImageBufferUsed(&BMPImageBufferSize); michael@0: mImageBufferSize = ICONFILEHEADERSIZE + ICODIRENTRYSIZE + michael@0: BMPImageBufferSize + andMaskSize; michael@0: mImageBufferStart = static_cast(moz_malloc(mImageBufferSize)); michael@0: if (!mImageBufferStart) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: mImageBufferCurr = mImageBufferStart; michael@0: michael@0: // The icon buffer does not include the BFH at all. michael@0: mICODirEntry.mBytesInRes = BMPImageBufferSize - BFH_LENGTH + andMaskSize; michael@0: michael@0: // Encode the icon headers michael@0: EncodeFileHeader(); michael@0: EncodeInfoHeader(); michael@0: michael@0: char *imageBuffer; michael@0: rv = mContainedEncoder->GetImageBuffer(&imageBuffer); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: memcpy(mImageBufferCurr, imageBuffer + BFH_LENGTH, michael@0: BMPImageBufferSize - BFH_LENGTH); michael@0: // We need to fix the BMP height to be *2 for the AND mask michael@0: uint32_t fixedHeight = GetRealHeight() * 2; michael@0: NativeEndian::swapToLittleEndianInPlace(&fixedHeight, 1); michael@0: // The height is stored at an offset of 8 from the DIB header michael@0: memcpy(mImageBufferCurr + 8, &fixedHeight, sizeof(fixedHeight)); michael@0: mImageBufferCurr += BMPImageBufferSize - BFH_LENGTH; michael@0: michael@0: // Calculate rowsize in DWORD's michael@0: uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up michael@0: int32_t currentLine = GetRealHeight(); michael@0: michael@0: // Write out the AND mask michael@0: while (currentLine > 0) { michael@0: currentLine--; michael@0: uint8_t* encoded = mImageBufferCurr + currentLine * rowSize; michael@0: uint8_t* encodedEnd = encoded + rowSize; michael@0: while (encoded != encodedEnd) { michael@0: *encoded = 0; // make everything visible michael@0: encoded++; michael@0: } michael@0: } michael@0: michael@0: mImageBufferCurr += andMaskSize; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // See ::InitFromData for other info. michael@0: NS_IMETHODIMP nsICOEncoder::StartImageEncode(uint32_t aWidth, michael@0: uint32_t aHeight, michael@0: uint32_t aInputFormat, michael@0: const nsAString& aOutputOptions) michael@0: { michael@0: // can't initialize more than once michael@0: if (mImageBufferStart || mImageBufferCurr) { michael@0: return NS_ERROR_ALREADY_INITIALIZED; michael@0: } michael@0: michael@0: // validate input format michael@0: if (aInputFormat != INPUT_FORMAT_RGB && michael@0: aInputFormat != INPUT_FORMAT_RGBA && michael@0: aInputFormat != INPUT_FORMAT_HOSTARGB) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: // Icons are only 1 byte, so make sure our bitmap is in range michael@0: if (aWidth > 256 || aHeight > 256) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: // parse and check any provided output options michael@0: uint32_t bpp = 24; michael@0: bool usePNG = true; michael@0: nsresult rv = ParseOptions(aOutputOptions, &bpp, &usePNG); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mUsePNG = usePNG; michael@0: michael@0: InitFileHeader(); michael@0: // The width and height are stored as 0 when we have a value of 256 michael@0: InitInfoHeader(bpp, aWidth == 256 ? 0 : (uint8_t)aWidth, michael@0: aHeight == 256 ? 0 : (uint8_t)aHeight); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsICOEncoder::EndImageEncode() michael@0: { michael@0: // must be initialized michael@0: if (!mImageBufferStart || !mImageBufferCurr) { michael@0: return NS_ERROR_NOT_INITIALIZED; michael@0: } michael@0: michael@0: mFinished = true; michael@0: NotifyListener(); michael@0: michael@0: // if output callback can't get enough memory, it will free our buffer michael@0: if (!mImageBufferStart || !mImageBufferCurr) { michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Parses the encoder options and sets the bits per pixel to use and PNG or BMP michael@0: // See InitFromData for a description of the parse options michael@0: nsresult michael@0: nsICOEncoder::ParseOptions(const nsAString& aOptions, uint32_t* bpp, michael@0: bool *usePNG) michael@0: { michael@0: // If no parsing options just use the default of 24BPP and PNG yes michael@0: if (aOptions.Length() == 0) { michael@0: if (usePNG) { michael@0: *usePNG = true; michael@0: } michael@0: if (bpp) { michael@0: *bpp = 24; michael@0: } michael@0: } michael@0: michael@0: // Parse the input string into a set of name/value pairs. michael@0: // From format: format=;bpp= michael@0: // to format: [0] = format=, [1] = bpp= michael@0: nsTArray nameValuePairs; michael@0: if (!ParseString(NS_ConvertUTF16toUTF8(aOptions), ';', nameValuePairs)) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: // For each name/value pair in the set michael@0: for (unsigned i = 0; i < nameValuePairs.Length(); ++i) { michael@0: michael@0: // Split the name value pair [0] = name, [1] = value michael@0: nsTArray nameValuePair; michael@0: if (!ParseString(nameValuePairs[i], '=', nameValuePair)) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: if (nameValuePair.Length() != 2) { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: // Parse the format portion of the string format=;bpp= michael@0: if (nameValuePair[0].Equals("format", nsCaseInsensitiveCStringComparator())) { michael@0: if (nameValuePair[1].Equals("png", nsCaseInsensitiveCStringComparator())) { michael@0: *usePNG = true; michael@0: } michael@0: else if (nameValuePair[1].Equals("bmp", nsCaseInsensitiveCStringComparator())) { michael@0: *usePNG = false; michael@0: } michael@0: else { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: } michael@0: michael@0: // Parse the bpp portion of the string format=;bpp= michael@0: if (nameValuePair[0].Equals("bpp", nsCaseInsensitiveCStringComparator())) { michael@0: if (nameValuePair[1].Equals("24")) { michael@0: *bpp = 24; michael@0: } michael@0: else if (nameValuePair[1].Equals("32")) { michael@0: *bpp = 32; michael@0: } michael@0: else { michael@0: return NS_ERROR_INVALID_ARG; michael@0: } michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsICOEncoder::Close() michael@0: { michael@0: if (mImageBufferStart) { michael@0: moz_free(mImageBufferStart); michael@0: mImageBufferStart = nullptr; michael@0: mImageBufferSize = 0; michael@0: mImageBufferReadPoint = 0; michael@0: mImageBufferCurr = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Obtains the available bytes to read michael@0: NS_IMETHODIMP nsICOEncoder::Available(uint64_t *_retval) michael@0: { michael@0: if (!mImageBufferStart || !mImageBufferCurr) { michael@0: return NS_BASE_STREAM_CLOSED; michael@0: } michael@0: michael@0: *_retval = GetCurrentImageBufferOffset() - mImageBufferReadPoint; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // [noscript] Reads bytes which are available michael@0: NS_IMETHODIMP nsICOEncoder::Read(char *aBuf, uint32_t aCount, michael@0: uint32_t *_retval) michael@0: { michael@0: return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); michael@0: } michael@0: michael@0: // [noscript] Reads segments michael@0: NS_IMETHODIMP nsICOEncoder::ReadSegments(nsWriteSegmentFun aWriter, michael@0: void *aClosure, uint32_t aCount, michael@0: uint32_t *_retval) michael@0: { michael@0: uint32_t maxCount = GetCurrentImageBufferOffset() - mImageBufferReadPoint; michael@0: if (maxCount == 0) { michael@0: *_retval = 0; michael@0: return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; michael@0: } michael@0: michael@0: if (aCount > maxCount) { michael@0: aCount = maxCount; michael@0: } michael@0: michael@0: nsresult rv = aWriter(this, aClosure, michael@0: reinterpret_cast(mImageBufferStart + michael@0: mImageBufferReadPoint), michael@0: 0, aCount, _retval); michael@0: if (NS_SUCCEEDED(rv)) { michael@0: NS_ASSERTION(*_retval <= aCount, "bad write count"); michael@0: mImageBufferReadPoint += *_retval; michael@0: } michael@0: // errors returned from the writer end here! michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsICOEncoder::IsNonBlocking(bool *_retval) michael@0: { michael@0: *_retval = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsICOEncoder::AsyncWait(nsIInputStreamCallback *aCallback, michael@0: uint32_t aFlags, michael@0: uint32_t aRequestedCount, michael@0: nsIEventTarget *aTarget) michael@0: { michael@0: if (aFlags != 0) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: if (mCallback || mCallbackTarget) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: mCallbackTarget = aTarget; michael@0: // 0 means "any number of bytes except 0" michael@0: mNotifyThreshold = aRequestedCount; michael@0: if (!aRequestedCount) { michael@0: mNotifyThreshold = 1024; // We don't want to notify incessantly michael@0: } michael@0: michael@0: // We set the callback absolutely last, because NotifyListener uses it to michael@0: // determine if someone needs to be notified. If we don't set it last, michael@0: // NotifyListener might try to fire off a notification to a null target michael@0: // which will generally cause non-threadsafe objects to be used off the main thread michael@0: mCallback = aCallback; michael@0: michael@0: // What we are being asked for may be present already michael@0: NotifyListener(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsICOEncoder::CloseWithStatus(nsresult aStatus) michael@0: { michael@0: return Close(); michael@0: } michael@0: michael@0: void michael@0: nsICOEncoder::NotifyListener() michael@0: { michael@0: if (mCallback && michael@0: (GetCurrentImageBufferOffset() - mImageBufferReadPoint >= mNotifyThreshold || michael@0: mFinished)) { michael@0: nsCOMPtr callback; michael@0: if (mCallbackTarget) { michael@0: callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); michael@0: } else { michael@0: callback = mCallback; michael@0: } michael@0: michael@0: NS_ASSERTION(callback, "Shouldn't fail to make the callback"); michael@0: // Null the callback first because OnInputStreamReady could reenter michael@0: // AsyncWait michael@0: mCallback = nullptr; michael@0: mCallbackTarget = nullptr; michael@0: mNotifyThreshold = 0; michael@0: michael@0: callback->OnInputStreamReady(this); michael@0: } michael@0: } michael@0: michael@0: // Initializes the icon file header mICOFileHeader michael@0: void michael@0: nsICOEncoder::InitFileHeader() michael@0: { michael@0: memset(&mICOFileHeader, 0, sizeof(mICOFileHeader)); michael@0: mICOFileHeader.mReserved = 0; michael@0: mICOFileHeader.mType = 1; michael@0: mICOFileHeader.mCount = 1; michael@0: } michael@0: michael@0: // Initializes the icon directory info header mICODirEntry michael@0: void michael@0: nsICOEncoder::InitInfoHeader(uint32_t aBPP, uint8_t aWidth, uint8_t aHeight) michael@0: { michael@0: memset(&mICODirEntry, 0, sizeof(mICODirEntry)); michael@0: mICODirEntry.mBitCount = aBPP; michael@0: mICODirEntry.mBytesInRes = 0; michael@0: mICODirEntry.mColorCount = 0; michael@0: mICODirEntry.mWidth = aWidth; michael@0: mICODirEntry.mHeight = aHeight; michael@0: mICODirEntry.mImageOffset = ICONFILEHEADERSIZE + ICODIRENTRYSIZE; michael@0: mICODirEntry.mPlanes = 1; michael@0: mICODirEntry.mReserved = 0; michael@0: } michael@0: michael@0: // Encodes the icon file header mICOFileHeader michael@0: void michael@0: nsICOEncoder::EncodeFileHeader() michael@0: { michael@0: IconFileHeader littleEndianIFH = mICOFileHeader; michael@0: NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mReserved, 1); michael@0: NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mType, 1); michael@0: NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mCount, 1); michael@0: michael@0: memcpy(mImageBufferCurr, &littleEndianIFH.mReserved, michael@0: sizeof(littleEndianIFH.mReserved)); michael@0: mImageBufferCurr += sizeof(littleEndianIFH.mReserved); michael@0: memcpy(mImageBufferCurr, &littleEndianIFH.mType, michael@0: sizeof(littleEndianIFH.mType)); michael@0: mImageBufferCurr += sizeof(littleEndianIFH.mType); michael@0: memcpy(mImageBufferCurr, &littleEndianIFH.mCount, michael@0: sizeof(littleEndianIFH.mCount)); michael@0: mImageBufferCurr += sizeof(littleEndianIFH.mCount); michael@0: } michael@0: michael@0: // Encodes the icon directory info header mICODirEntry michael@0: void michael@0: nsICOEncoder::EncodeInfoHeader() michael@0: { michael@0: IconDirEntry littleEndianmIDE = mICODirEntry; michael@0: michael@0: NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mPlanes, 1); michael@0: NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mBitCount, 1); michael@0: NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mBytesInRes, 1); michael@0: NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mImageOffset, 1); michael@0: michael@0: memcpy(mImageBufferCurr, &littleEndianmIDE.mWidth, michael@0: sizeof(littleEndianmIDE.mWidth)); michael@0: mImageBufferCurr += sizeof(littleEndianmIDE.mWidth); michael@0: memcpy(mImageBufferCurr, &littleEndianmIDE.mHeight, michael@0: sizeof(littleEndianmIDE.mHeight)); michael@0: mImageBufferCurr += sizeof(littleEndianmIDE.mHeight); michael@0: memcpy(mImageBufferCurr, &littleEndianmIDE.mColorCount, michael@0: sizeof(littleEndianmIDE.mColorCount)); michael@0: mImageBufferCurr += sizeof(littleEndianmIDE.mColorCount); michael@0: memcpy(mImageBufferCurr, &littleEndianmIDE.mReserved, michael@0: sizeof(littleEndianmIDE.mReserved)); michael@0: mImageBufferCurr += sizeof(littleEndianmIDE.mReserved); michael@0: memcpy(mImageBufferCurr, &littleEndianmIDE.mPlanes, michael@0: sizeof(littleEndianmIDE.mPlanes)); michael@0: mImageBufferCurr += sizeof(littleEndianmIDE.mPlanes); michael@0: memcpy(mImageBufferCurr, &littleEndianmIDE.mBitCount, michael@0: sizeof(littleEndianmIDE.mBitCount)); michael@0: mImageBufferCurr += sizeof(littleEndianmIDE.mBitCount); michael@0: memcpy(mImageBufferCurr, &littleEndianmIDE.mBytesInRes, michael@0: sizeof(littleEndianmIDE.mBytesInRes)); michael@0: mImageBufferCurr += sizeof(littleEndianmIDE.mBytesInRes); michael@0: memcpy(mImageBufferCurr, &littleEndianmIDE.mImageOffset, michael@0: sizeof(littleEndianmIDE.mImageOffset)); michael@0: mImageBufferCurr += sizeof(littleEndianmIDE.mImageOffset); michael@0: }