diff options
Diffstat (limited to 'image/encoders')
-rw-r--r-- | image/encoders/bmp/moz.build | 15 | ||||
-rw-r--r-- | image/encoders/bmp/nsBMPEncoder.cpp | 764 | ||||
-rw-r--r-- | image/encoders/bmp/nsBMPEncoder.h | 157 | ||||
-rw-r--r-- | image/encoders/ico/moz.build | 18 | ||||
-rw-r--r-- | image/encoders/ico/nsICOEncoder.cpp | 542 | ||||
-rw-r--r-- | image/encoders/ico/nsICOEncoder.h | 99 | ||||
-rw-r--r-- | image/encoders/jpeg/moz.build | 11 | ||||
-rw-r--r-- | image/encoders/jpeg/nsJPEGEncoder.cpp | 532 | ||||
-rw-r--r-- | image/encoders/jpeg/nsJPEGEncoder.h | 84 | ||||
-rw-r--r-- | image/encoders/moz.build | 12 | ||||
-rw-r--r-- | image/encoders/png/moz.build | 15 | ||||
-rw-r--r-- | image/encoders/png/nsPNGEncoder.cpp | 758 | ||||
-rw-r--r-- | image/encoders/png/nsPNGEncoder.h | 83 |
13 files changed, 3090 insertions, 0 deletions
diff --git a/image/encoders/bmp/moz.build b/image/encoders/bmp/moz.build new file mode 100644 index 000000000..f061d067a --- /dev/null +++ b/image/encoders/bmp/moz.build @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + 'nsBMPEncoder.cpp', +] + +LOCAL_INCLUDES += [ + '/image', +] + +FINAL_LIBRARY = 'xul' diff --git a/image/encoders/bmp/nsBMPEncoder.cpp b/image/encoders/bmp/nsBMPEncoder.cpp new file mode 100644 index 000000000..af959a46e --- /dev/null +++ b/image/encoders/bmp/nsBMPEncoder.cpp @@ -0,0 +1,764 @@ +/* -*- Mode: C++; tab-width: 2; 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 "nsCRT.h" +#include "mozilla/EndianUtils.h" +#include "mozilla/UniquePtrExtensions.h" +#include "nsBMPEncoder.h" +#include "prprf.h" +#include "nsString.h" +#include "nsStreamUtils.h" +#include "nsTArray.h" +#include "mozilla/CheckedInt.h" + +using namespace mozilla; +using namespace mozilla::image; +using namespace mozilla::image::bmp; + +NS_IMPL_ISUPPORTS(nsBMPEncoder, imgIEncoder, nsIInputStream, + nsIAsyncInputStream) + +nsBMPEncoder::nsBMPEncoder() : mImageBufferStart(nullptr), + mImageBufferCurr(0), + mImageBufferSize(0), + mImageBufferReadPoint(0), + mFinished(false), + mCallback(nullptr), + mCallbackTarget(nullptr), + mNotifyThreshold(0) +{ +} + +nsBMPEncoder::~nsBMPEncoder() +{ + if (mImageBufferStart) { + free(mImageBufferStart); + mImageBufferStart = nullptr; + mImageBufferCurr = nullptr; + } +} + +// nsBMPEncoder::InitFromData +// +// One output option is supported: bpp=<bpp_value> +// bpp specifies the bits per pixel to use where bpp_value can be 24 or 32 +NS_IMETHODIMP +nsBMPEncoder::InitFromData(const uint8_t* aData, + uint32_t aLength, // (unused, req'd by JS) + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) { + return NS_ERROR_INVALID_ARG; + } + + CheckedInt32 check = CheckedInt32(aWidth) * 4; + if (MOZ_UNLIKELY(!check.isValid())) { + return NS_ERROR_INVALID_ARG; + } + + // Stride is the padded width of each row, so it better be longer + if ((aInputFormat == INPUT_FORMAT_RGB && + aStride < aWidth * 3) || + ((aInputFormat == INPUT_FORMAT_RGBA || + aInputFormat == INPUT_FORMAT_HOSTARGB) && + aStride < aWidth * 4)) { + NS_WARNING("Invalid stride for InitFromData"); + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions); + if (NS_FAILED(rv)) { + return rv; + } + + rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride, + aInputFormat, aOutputOptions); + if (NS_FAILED(rv)) { + return rv; + } + + rv = EndImageEncode(); + return rv; +} + +// Just a helper method to make it explicit in calculations that we are dealing +// with bytes and not bits +static inline uint16_t +BytesPerPixel(uint16_t aBPP) +{ + return aBPP / 8; +} + +// Calculates the number of padding bytes that are needed per row of image data +static inline uint32_t +PaddingBytes(uint16_t aBPP, uint32_t aWidth) +{ + uint32_t rowSize = aWidth * BytesPerPixel(aBPP); + uint8_t paddingSize = 0; + if (rowSize % 4) { + paddingSize = (4 - (rowSize % 4)); + } + return paddingSize; +} + +// See ::InitFromData for other info. +NS_IMETHODIMP +nsBMPEncoder::StartImageEncode(uint32_t aWidth, + uint32_t aHeight, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + // can't initialize more than once + if (mImageBufferStart || mImageBufferCurr) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) { + return NS_ERROR_INVALID_ARG; + } + + // parse and check any provided output options + Version version; + uint16_t bpp; + nsresult rv = ParseOptions(aOutputOptions, version, bpp); + if (NS_FAILED(rv)) { + return rv; + } + MOZ_ASSERT(bpp <= 32); + + rv = InitFileHeader(version, bpp, aWidth, aHeight); + if (NS_FAILED(rv)) { + return rv; + } + rv = InitInfoHeader(version, bpp, aWidth, aHeight); + if (NS_FAILED(rv)) { + return rv; + } + + mImageBufferSize = mBMPFileHeader.filesize; + mImageBufferStart = static_cast<uint8_t*>(malloc(mImageBufferSize)); + if (!mImageBufferStart) { + return NS_ERROR_OUT_OF_MEMORY; + } + mImageBufferCurr = mImageBufferStart; + + EncodeFileHeader(); + EncodeInfoHeader(); + + return NS_OK; +} + +// Returns the number of bytes in the image buffer used. +// For a BMP file, this is all bytes in the buffer. +NS_IMETHODIMP +nsBMPEncoder::GetImageBufferUsed(uint32_t* aOutputSize) +{ + NS_ENSURE_ARG_POINTER(aOutputSize); + *aOutputSize = mImageBufferSize; + return NS_OK; +} + +// Returns a pointer to the start of the image buffer +NS_IMETHODIMP +nsBMPEncoder::GetImageBuffer(char** aOutputBuffer) +{ + NS_ENSURE_ARG_POINTER(aOutputBuffer); + *aOutputBuffer = reinterpret_cast<char*>(mImageBufferStart); + return NS_OK; +} + +NS_IMETHODIMP +nsBMPEncoder::AddImageFrame(const uint8_t* aData, + uint32_t aLength, // (unused, req'd by JS) + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aFrameOptions) +{ + // must be initialized + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_ERROR_NOT_INITIALIZED; + } + + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) { + return NS_ERROR_INVALID_ARG; + } + + if (mBMPInfoHeader.width < 0) { + return NS_ERROR_ILLEGAL_VALUE; + } + + CheckedUint32 size = + CheckedUint32(mBMPInfoHeader.width) * CheckedUint32(BytesPerPixel(mBMPInfoHeader.bpp)); + if (MOZ_UNLIKELY(!size.isValid())) { + return NS_ERROR_FAILURE; + } + + auto row = MakeUniqueFallible<uint8_t[]>(size.value()); + if (!row) { + return NS_ERROR_OUT_OF_MEMORY; + } + + CheckedUint32 check = CheckedUint32(mBMPInfoHeader.height) * aStride; + if (MOZ_UNLIKELY(!check.isValid())) { + return NS_ERROR_FAILURE; + } + + // write each row: if we add more input formats, we may want to + // generalize the conversions + if (aInputFormat == INPUT_FORMAT_HOSTARGB) { + // BMP requires RGBA with post-multiplied alpha, so we need to convert + for (int32_t y = mBMPInfoHeader.height - 1; y >= 0 ; y --) { + ConvertHostARGBRow(&aData[y * aStride], row, mBMPInfoHeader.width); + if(mBMPInfoHeader.bpp == 24) { + EncodeImageDataRow24(row.get()); + } else { + EncodeImageDataRow32(row.get()); + } + } + } else if (aInputFormat == INPUT_FORMAT_RGBA) { + // simple RGBA, no conversion needed + for (int32_t y = 0; y < mBMPInfoHeader.height; y++) { + if (mBMPInfoHeader.bpp == 24) { + EncodeImageDataRow24(row.get()); + } else { + EncodeImageDataRow32(row.get()); + } + } + } else if (aInputFormat == INPUT_FORMAT_RGB) { + // simple RGB, no conversion needed + for (int32_t y = 0; y < mBMPInfoHeader.height; y++) { + if (mBMPInfoHeader.bpp == 24) { + EncodeImageDataRow24(&aData[y * aStride]); + } else { + EncodeImageDataRow32(&aData[y * aStride]); + } + } + } else { + NS_NOTREACHED("Bad format type"); + return NS_ERROR_INVALID_ARG; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsBMPEncoder::EndImageEncode() +{ + // must be initialized + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_ERROR_NOT_INITIALIZED; + } + + mFinished = true; + NotifyListener(); + + // if output callback can't get enough memory, it will free our buffer + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + + +// Parses the encoder options and sets the bits per pixel to use +// See InitFromData for a description of the parse options +nsresult +nsBMPEncoder::ParseOptions(const nsAString& aOptions, Version& aVersionOut, + uint16_t& aBppOut) +{ + aVersionOut = VERSION_3; + aBppOut = 24; + + // Parse the input string into a set of name/value pairs. + // From a format like: name=value;bpp=<bpp_value>;name=value + // to format: [0] = name=value, [1] = bpp=<bpp_value>, [2] = name=value + nsTArray<nsCString> nameValuePairs; + if (!ParseString(NS_ConvertUTF16toUTF8(aOptions), ';', nameValuePairs)) { + return NS_ERROR_INVALID_ARG; + } + + // For each name/value pair in the set + for (uint32_t i = 0; i < nameValuePairs.Length(); ++i) { + + // Split the name value pair [0] = name, [1] = value + nsTArray<nsCString> nameValuePair; + if (!ParseString(nameValuePairs[i], '=', nameValuePair)) { + return NS_ERROR_INVALID_ARG; + } + if (nameValuePair.Length() != 2) { + return NS_ERROR_INVALID_ARG; + } + + // Parse the bpp portion of the string name=value;version=<version_value>; + // name=value + if (nameValuePair[0].Equals("version", + nsCaseInsensitiveCStringComparator())) { + if (nameValuePair[1].EqualsLiteral("3")) { + aVersionOut = VERSION_3; + } else if (nameValuePair[1].EqualsLiteral("5")) { + aVersionOut = VERSION_5; + } else { + return NS_ERROR_INVALID_ARG; + } + } + + // Parse the bpp portion of the string name=value;bpp=<bpp_value>;name=value + if (nameValuePair[0].Equals("bpp", nsCaseInsensitiveCStringComparator())) { + if (nameValuePair[1].EqualsLiteral("24")) { + aBppOut = 24; + } else if (nameValuePair[1].EqualsLiteral("32")) { + aBppOut = 32; + } else { + return NS_ERROR_INVALID_ARG; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsBMPEncoder::Close() +{ + if (mImageBufferStart) { + free(mImageBufferStart); + mImageBufferStart = nullptr; + mImageBufferSize = 0; + mImageBufferReadPoint = 0; + mImageBufferCurr = nullptr; + } + + return NS_OK; +} + +// Obtains the available bytes to read +NS_IMETHODIMP +nsBMPEncoder::Available(uint64_t* _retval) +{ + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_BASE_STREAM_CLOSED; + } + + *_retval = GetCurrentImageBufferOffset() - mImageBufferReadPoint; + return NS_OK; +} + +// [noscript] Reads bytes which are available +NS_IMETHODIMP +nsBMPEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); +} + +// [noscript] Reads segments +NS_IMETHODIMP +nsBMPEncoder::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* _retval) +{ + uint32_t maxCount = GetCurrentImageBufferOffset() - mImageBufferReadPoint; + if (maxCount == 0) { + *_retval = 0; + return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + nsresult rv = aWriter(this, aClosure, + reinterpret_cast<const char*>(mImageBufferStart + + mImageBufferReadPoint), + 0, aCount, _retval); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*_retval <= aCount, "bad write count"); + mImageBufferReadPoint += *_retval; + } + // errors returned from the writer end here! + return NS_OK; +} + +NS_IMETHODIMP +nsBMPEncoder::IsNonBlocking(bool* _retval) +{ + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +nsBMPEncoder::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aTarget) +{ + if (aFlags != 0) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (mCallback || mCallbackTarget) { + return NS_ERROR_UNEXPECTED; + } + + mCallbackTarget = aTarget; + // 0 means "any number of bytes except 0" + mNotifyThreshold = aRequestedCount; + if (!aRequestedCount) { + mNotifyThreshold = 1024; // We don't want to notify incessantly + } + + // We set the callback absolutely last, because NotifyListener uses it to + // determine if someone needs to be notified. If we don't set it last, + // NotifyListener might try to fire off a notification to a null target + // which will generally cause non-threadsafe objects to be used off the + // main thread + mCallback = aCallback; + + // What we are being asked for may be present already + NotifyListener(); + return NS_OK; +} + +NS_IMETHODIMP +nsBMPEncoder::CloseWithStatus(nsresult aStatus) +{ + return Close(); +} + +// nsBMPEncoder::ConvertHostARGBRow +// +// Our colors are stored with premultiplied alphas, but we need +// an output with no alpha in machine-independent byte order. +// +void +nsBMPEncoder::ConvertHostARGBRow(const uint8_t* aSrc, + const UniquePtr<uint8_t[]>& aDest, + uint32_t aPixelWidth) +{ + uint16_t bytes = BytesPerPixel(mBMPInfoHeader.bpp); + + if (mBMPInfoHeader.bpp == 32) { + for (uint32_t x = 0; x < aPixelWidth; x++) { + const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; + uint8_t* pixelOut = &aDest[x * bytes]; + + pixelOut[0] = (pixelIn & 0x00ff0000) >> 16; + pixelOut[1] = (pixelIn & 0x0000ff00) >> 8; + pixelOut[2] = (pixelIn & 0x000000ff) >> 0; + pixelOut[3] = (pixelIn & 0xff000000) >> 24; + } + } else { + for (uint32_t x = 0; x < aPixelWidth; x++) { + const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; + uint8_t* pixelOut = &aDest[x * bytes]; + + pixelOut[0] = (pixelIn & 0xff0000) >> 16; + pixelOut[1] = (pixelIn & 0x00ff00) >> 8; + pixelOut[2] = (pixelIn & 0x0000ff) >> 0; + } + } +} + +void +nsBMPEncoder::NotifyListener() +{ + if (mCallback && + (GetCurrentImageBufferOffset() - mImageBufferReadPoint >= + mNotifyThreshold || mFinished)) { + nsCOMPtr<nsIInputStreamCallback> callback; + if (mCallbackTarget) { + callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); + } else { + callback = mCallback; + } + + NS_ASSERTION(callback, "Shouldn't fail to make the callback"); + // Null the callback first because OnInputStreamReady could + // reenter AsyncWait + mCallback = nullptr; + mCallbackTarget = nullptr; + mNotifyThreshold = 0; + + callback->OnInputStreamReady(this); + } +} + +// Initializes the BMP file header mBMPFileHeader to the passed in values +nsresult +nsBMPEncoder::InitFileHeader(Version aVersion, uint16_t aBPP, uint32_t aWidth, + uint32_t aHeight) +{ + memset(&mBMPFileHeader, 0, sizeof(mBMPFileHeader)); + mBMPFileHeader.signature[0] = 'B'; + mBMPFileHeader.signature[1] = 'M'; + + if (aVersion == VERSION_3) { + mBMPFileHeader.dataoffset = FILE_HEADER_LENGTH + InfoHeaderLength::WIN_V3; + } else { // aVersion == 5 + mBMPFileHeader.dataoffset = FILE_HEADER_LENGTH + InfoHeaderLength::WIN_V5; + } + + // The color table is present only if BPP is <= 8 + if (aBPP <= 8) { + uint32_t numColors = 1 << aBPP; + mBMPFileHeader.dataoffset += 4 * numColors; + CheckedUint32 filesize = + CheckedUint32(mBMPFileHeader.dataoffset) + CheckedUint32(aWidth) * aHeight; + if (MOZ_UNLIKELY(!filesize.isValid())) { + return NS_ERROR_INVALID_ARG; + } + mBMPFileHeader.filesize = filesize.value(); + } else { + CheckedUint32 filesize = + CheckedUint32(mBMPFileHeader.dataoffset) + + (CheckedUint32(aWidth) * BytesPerPixel(aBPP) + PaddingBytes(aBPP, aWidth)) * aHeight; + if (MOZ_UNLIKELY(!filesize.isValid())) { + return NS_ERROR_INVALID_ARG; + } + mBMPFileHeader.filesize = filesize.value(); + } + + mBMPFileHeader.reserved = 0; + + return NS_OK; +} + +#define ENCODE(pImageBufferCurr, value) \ + memcpy(*pImageBufferCurr, &value, sizeof value); \ + *pImageBufferCurr += sizeof value; + +// Initializes the bitmap info header mBMPInfoHeader to the passed in values +nsresult +nsBMPEncoder::InitInfoHeader(Version aVersion, uint16_t aBPP, uint32_t aWidth, + uint32_t aHeight) +{ + memset(&mBMPInfoHeader, 0, sizeof(mBMPInfoHeader)); + if (aVersion == VERSION_3) { + mBMPInfoHeader.bihsize = InfoHeaderLength::WIN_V3; + } else { + MOZ_ASSERT(aVersion == VERSION_5); + mBMPInfoHeader.bihsize = InfoHeaderLength::WIN_V5; + } + + CheckedInt32 width(aWidth); + CheckedInt32 height(aHeight); + if (MOZ_UNLIKELY(!width.isValid() || !height.isValid())) { + return NS_ERROR_INVALID_ARG; + } + mBMPInfoHeader.width = width.value(); + mBMPInfoHeader.height = height.value(); + + mBMPInfoHeader.planes = 1; + mBMPInfoHeader.bpp = aBPP; + mBMPInfoHeader.compression = 0; + mBMPInfoHeader.colors = 0; + mBMPInfoHeader.important_colors = 0; + + CheckedUint32 check = CheckedUint32(aWidth) * BytesPerPixel(aBPP); + if (MOZ_UNLIKELY(!check.isValid())) { + return NS_ERROR_INVALID_ARG; + } + + if (aBPP <= 8) { + CheckedUint32 imagesize = CheckedUint32(aWidth) * aHeight; + if (MOZ_UNLIKELY(!imagesize.isValid())) { + return NS_ERROR_INVALID_ARG; + } + mBMPInfoHeader.image_size = imagesize.value(); + } else { + CheckedUint32 imagesize = + (CheckedUint32(aWidth) * BytesPerPixel(aBPP) + PaddingBytes(aBPP, aWidth)) * CheckedUint32(aHeight); + if (MOZ_UNLIKELY(!imagesize.isValid())) { + return NS_ERROR_INVALID_ARG; + } + mBMPInfoHeader.image_size = imagesize.value(); + } + mBMPInfoHeader.xppm = 0; + mBMPInfoHeader.yppm = 0; + if (aVersion >= VERSION_5) { + mBMPInfoHeader.red_mask = 0x000000FF; + mBMPInfoHeader.green_mask = 0x0000FF00; + mBMPInfoHeader.blue_mask = 0x00FF0000; + mBMPInfoHeader.alpha_mask = 0xFF000000; + mBMPInfoHeader.color_space = V5InfoHeader::COLOR_SPACE_LCS_SRGB; + mBMPInfoHeader.white_point.r.x = 0; + mBMPInfoHeader.white_point.r.y = 0; + mBMPInfoHeader.white_point.r.z = 0; + mBMPInfoHeader.white_point.g.x = 0; + mBMPInfoHeader.white_point.g.y = 0; + mBMPInfoHeader.white_point.g.z = 0; + mBMPInfoHeader.white_point.b.x = 0; + mBMPInfoHeader.white_point.b.y = 0; + mBMPInfoHeader.white_point.b.z = 0; + mBMPInfoHeader.gamma_red = 0; + mBMPInfoHeader.gamma_green = 0; + mBMPInfoHeader.gamma_blue = 0; + mBMPInfoHeader.intent = 0; + mBMPInfoHeader.profile_offset = 0; + mBMPInfoHeader.profile_size = 0; + mBMPInfoHeader.reserved = 0; + } + + return NS_OK; +} + +// Encodes the BMP file header mBMPFileHeader +void +nsBMPEncoder::EncodeFileHeader() +{ + FileHeader littleEndianBFH = mBMPFileHeader; + NativeEndian::swapToLittleEndianInPlace(&littleEndianBFH.filesize, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianBFH.reserved, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianBFH.dataoffset, 1); + + ENCODE(&mImageBufferCurr, littleEndianBFH.signature); + ENCODE(&mImageBufferCurr, littleEndianBFH.filesize); + ENCODE(&mImageBufferCurr, littleEndianBFH.reserved); + ENCODE(&mImageBufferCurr, littleEndianBFH.dataoffset); +} + +// Encodes the BMP infor header mBMPInfoHeader +void +nsBMPEncoder::EncodeInfoHeader() +{ + V5InfoHeader littleEndianmBIH = mBMPInfoHeader; + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.bihsize, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.width, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.height, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.planes, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.bpp, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.compression, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.image_size, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.xppm, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.yppm, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.colors, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.important_colors, + 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.red_mask, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.green_mask, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.blue_mask, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.alpha_mask, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.color_space, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.r.x, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.r.y, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.r.z, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.g.x, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.g.y, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.g.z, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.b.x, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.b.y, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.white_point.b.z, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.gamma_red, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.gamma_green, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.gamma_blue, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.intent, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.profile_offset, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmBIH.profile_size, 1); + + ENCODE(&mImageBufferCurr, littleEndianmBIH.bihsize); + ENCODE(&mImageBufferCurr, littleEndianmBIH.width); + ENCODE(&mImageBufferCurr, littleEndianmBIH.height); + ENCODE(&mImageBufferCurr, littleEndianmBIH.planes); + ENCODE(&mImageBufferCurr, littleEndianmBIH.bpp); + ENCODE(&mImageBufferCurr, littleEndianmBIH.compression); + ENCODE(&mImageBufferCurr, littleEndianmBIH.image_size); + ENCODE(&mImageBufferCurr, littleEndianmBIH.xppm); + ENCODE(&mImageBufferCurr, littleEndianmBIH.yppm); + ENCODE(&mImageBufferCurr, littleEndianmBIH.colors); + ENCODE(&mImageBufferCurr, littleEndianmBIH.important_colors); + + if (mBMPInfoHeader.bihsize > InfoHeaderLength::WIN_V3) { + ENCODE(&mImageBufferCurr, littleEndianmBIH.red_mask); + ENCODE(&mImageBufferCurr, littleEndianmBIH.green_mask); + ENCODE(&mImageBufferCurr, littleEndianmBIH.blue_mask); + ENCODE(&mImageBufferCurr, littleEndianmBIH.alpha_mask); + ENCODE(&mImageBufferCurr, littleEndianmBIH.color_space); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.r.x); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.r.y); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.r.z); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.g.x); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.g.y); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.g.z); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.b.x); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.b.y); + ENCODE(&mImageBufferCurr, littleEndianmBIH.white_point.b.z); + ENCODE(&mImageBufferCurr, littleEndianmBIH.gamma_red); + ENCODE(&mImageBufferCurr, littleEndianmBIH.gamma_green); + ENCODE(&mImageBufferCurr, littleEndianmBIH.gamma_blue); + ENCODE(&mImageBufferCurr, littleEndianmBIH.intent); + ENCODE(&mImageBufferCurr, littleEndianmBIH.profile_offset); + ENCODE(&mImageBufferCurr, littleEndianmBIH.profile_size); + ENCODE(&mImageBufferCurr, littleEndianmBIH.reserved); + } +} + +// Sets a pixel in the image buffer that doesn't have alpha data +static inline void +SetPixel24(uint8_t*& imageBufferCurr, uint8_t aRed, uint8_t aGreen, + uint8_t aBlue) +{ + *imageBufferCurr = aBlue; + *(imageBufferCurr + 1) = aGreen; + *(imageBufferCurr + 2) = aRed; +} + +// Sets a pixel in the image buffer with alpha data +static inline void +SetPixel32(uint8_t*& imageBufferCurr, uint8_t aRed, uint8_t aGreen, + uint8_t aBlue, uint8_t aAlpha = 0xFF) +{ + *imageBufferCurr = aBlue; + *(imageBufferCurr + 1) = aGreen; + *(imageBufferCurr + 2) = aRed; + *(imageBufferCurr + 3) = aAlpha; +} + +// Encodes a row of image data which does not have alpha data +void +nsBMPEncoder::EncodeImageDataRow24(const uint8_t* aData) +{ + for (int32_t x = 0; x < mBMPInfoHeader.width; x++) { + uint32_t pos = x * BytesPerPixel(mBMPInfoHeader.bpp); + SetPixel24(mImageBufferCurr, aData[pos], aData[pos + 1], aData[pos + 2]); + mImageBufferCurr += BytesPerPixel(mBMPInfoHeader.bpp); + } + + for (uint32_t x = 0; x < PaddingBytes(mBMPInfoHeader.bpp, + mBMPInfoHeader.width); x++) { + *mImageBufferCurr++ = 0; + } +} + +// Encodes a row of image data which does have alpha data +void +nsBMPEncoder::EncodeImageDataRow32(const uint8_t* aData) +{ + for (int32_t x = 0; x < mBMPInfoHeader.width; x++) { + uint32_t pos = x * BytesPerPixel(mBMPInfoHeader.bpp); + SetPixel32(mImageBufferCurr, aData[pos], aData[pos + 1], + aData[pos + 2], aData[pos + 3]); + mImageBufferCurr += 4; + } + + for (uint32_t x = 0; x < PaddingBytes(mBMPInfoHeader.bpp, + mBMPInfoHeader.width); x++) { + *mImageBufferCurr++ = 0; + } +} diff --git a/image/encoders/bmp/nsBMPEncoder.h b/image/encoders/bmp/nsBMPEncoder.h new file mode 100644 index 000000000..b90af4d19 --- /dev/null +++ b/image/encoders/bmp/nsBMPEncoder.h @@ -0,0 +1,157 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifndef mozilla_image_encoders_bmp_nsBMPEncoder_h +#define mozilla_image_encoders_bmp_nsBMPEncoder_h + +#include "mozilla/Attributes.h" +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/UniquePtr.h" + +#include "imgIEncoder.h" +#include "BMPHeaders.h" + +#include "nsCOMPtr.h" + +#define NS_BMPENCODER_CID \ +{ /* 13a5320c-4c91-4FA4-bd16-b081a3ba8c0b */ \ + 0x13a5320c, \ + 0x4c91, \ + 0x4fa4, \ + {0xbd, 0x16, 0xb0, 0x81, 0xa3, 0Xba, 0x8c, 0x0b} \ +} + +namespace mozilla { +namespace image { +namespace bmp { + +struct FileHeader { + char signature[2]; // String "BM". + uint32_t filesize; // File size. + int32_t reserved; // Zero. + uint32_t dataoffset; // Offset to raster data. +}; + +struct XYZ { + int32_t x, y, z; +}; + +struct XYZTriple { + XYZ r, g, b; +}; + +struct V5InfoHeader { + uint32_t bihsize; // Header size + int32_t width; // Uint16 in OS/2 BMPs + int32_t height; // Uint16 in OS/2 BMPs + uint16_t planes; // =1 + uint16_t bpp; // Bits per pixel. + uint32_t compression; // See Compression for valid values + uint32_t image_size; // (compressed) image size. Can be 0 if + // compression==0 + uint32_t xppm; // Pixels per meter, horizontal + uint32_t yppm; // Pixels per meter, vertical + uint32_t colors; // Used Colors + uint32_t important_colors; // Number of important colors. 0=all + // The rest of the header is not available in WIN_V3 BMP Files + uint32_t red_mask; // Bits used for red component + uint32_t green_mask; // Bits used for green component + uint32_t blue_mask; // Bits used for blue component + uint32_t alpha_mask; // Bits used for alpha component + uint32_t color_space; // 0x73524742=LCS_sRGB ... + // These members are unused unless color_space == LCS_CALIBRATED_RGB + XYZTriple white_point; // Logical white point + uint32_t gamma_red; // Red gamma component + uint32_t gamma_green; // Green gamma component + uint32_t gamma_blue; // Blue gamma component + uint32_t intent; // Rendering intent + // These members are unused unless color_space == LCS_PROFILE_* + uint32_t profile_offset; // Offset to profile data in bytes + uint32_t profile_size; // Size of profile data in bytes + uint32_t reserved; // =0 + + static const uint32_t COLOR_SPACE_LCS_SRGB = 0x73524742; +}; + +} // namespace bmp +} // namespace image +} // namespace mozilla + +// Provides BMP encoding functionality. Use InitFromData() to do the +// encoding. See that function definition for encoding options. + +class nsBMPEncoder final : public imgIEncoder +{ + typedef mozilla::ReentrantMonitor ReentrantMonitor; +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_IMGIENCODER + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + nsBMPEncoder(); + +protected: + ~nsBMPEncoder(); + + enum Version + { + VERSION_3 = 3, + VERSION_5 = 5 + }; + + // See InitData in the cpp for valid parse options + nsresult ParseOptions(const nsAString& aOptions, Version& aVersionOut, + uint16_t& aBppOut); + // Obtains data with no alpha in machine-independent byte order + void ConvertHostARGBRow(const uint8_t* aSrc, + const mozilla::UniquePtr<uint8_t[]>& aDest, + uint32_t aPixelWidth); + // Thread safe notify listener + void NotifyListener(); + + // Initializes the bitmap file header member mBMPFileHeader + nsresult InitFileHeader(Version aVersion, uint16_t aBPP, uint32_t aWidth, + uint32_t aHeight); + // Initializes the bitmap info header member mBMPInfoHeader + nsresult InitInfoHeader(Version aVersion, uint16_t aBPP, uint32_t aWidth, + uint32_t aHeight); + + // Encodes the bitmap file header member mBMPFileHeader + void EncodeFileHeader(); + // Encodes the bitmap info header member mBMPInfoHeader + void EncodeInfoHeader(); + // Encodes a row of image data which does not have alpha data + void EncodeImageDataRow24(const uint8_t* aData); + // Encodes a row of image data which does have alpha data + void EncodeImageDataRow32(const uint8_t* aData); + // Obtains the current offset filled up to for the image buffer + inline int32_t GetCurrentImageBufferOffset() + { + return static_cast<int32_t>(mImageBufferCurr - mImageBufferStart); + } + + // These headers will always contain endian independent stuff + // They store the BMP headers which will be encoded + mozilla::image::bmp::FileHeader mBMPFileHeader; + mozilla::image::bmp::V5InfoHeader mBMPInfoHeader; + + // Keeps track of the start of the image buffer + uint8_t* mImageBufferStart; + // Keeps track of the current position in the image buffer + uint8_t* mImageBufferCurr; + // Keeps track of the image buffer size + uint32_t mImageBufferSize; + // Keeps track of the number of bytes in the image buffer which are read + uint32_t mImageBufferReadPoint; + // Stores true if the image is done being encoded + bool mFinished; + + nsCOMPtr<nsIInputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mCallbackTarget; + uint32_t mNotifyThreshold; +}; + +#endif // mozilla_image_encoders_bmp_nsBMPEncoder_h diff --git a/image/encoders/ico/moz.build b/image/encoders/ico/moz.build new file mode 100644 index 000000000..c45d49aaf --- /dev/null +++ b/image/encoders/ico/moz.build @@ -0,0 +1,18 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + 'nsICOEncoder.cpp', +] + +# Decoders need RasterImage.h +LOCAL_INCLUDES += [ + '/image', + '/image/encoders/bmp', + '/image/encoders/png', +] + +FINAL_LIBRARY = 'xul' diff --git a/image/encoders/ico/nsICOEncoder.cpp b/image/encoders/ico/nsICOEncoder.cpp new file mode 100644 index 000000000..5b26f5ca2 --- /dev/null +++ b/image/encoders/ico/nsICOEncoder.cpp @@ -0,0 +1,542 @@ +/* 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 "nsCRT.h" +#include "mozilla/EndianUtils.h" +#include "nsBMPEncoder.h" +#include "nsPNGEncoder.h" +#include "nsICOEncoder.h" +#include "prprf.h" +#include "nsString.h" +#include "nsStreamUtils.h" +#include "nsTArray.h" + +using namespace mozilla; +using namespace mozilla::image; + +NS_IMPL_ISUPPORTS(nsICOEncoder, imgIEncoder, nsIInputStream, + nsIAsyncInputStream) + +nsICOEncoder::nsICOEncoder() : mImageBufferStart(nullptr), + mImageBufferCurr(0), + mImageBufferSize(0), + mImageBufferReadPoint(0), + mFinished(false), + mUsePNG(true), + mNotifyThreshold(0) +{ +} + +nsICOEncoder::~nsICOEncoder() +{ + if (mImageBufferStart) { + free(mImageBufferStart); + mImageBufferStart = nullptr; + mImageBufferCurr = nullptr; + } +} + +// nsICOEncoder::InitFromData +// Two output options are supported: format=<png|bmp>;bpp=<bpp_value> +// format specifies whether to use png or bitmap format +// bpp specifies the bits per pixel to use where bpp_value can be 24 or 32 +NS_IMETHODIMP +nsICOEncoder::InitFromData(const uint8_t* aData, + uint32_t aLength, + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) { + return NS_ERROR_INVALID_ARG; + } + + // Stride is the padded width of each row, so it better be longer + if ((aInputFormat == INPUT_FORMAT_RGB && + aStride < aWidth * 3) || + ((aInputFormat == INPUT_FORMAT_RGBA || + aInputFormat == INPUT_FORMAT_HOSTARGB) && + aStride < aWidth * 4)) { + NS_WARNING("Invalid stride for InitFromData"); + return NS_ERROR_INVALID_ARG; + } + + nsresult rv; + rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride, + aInputFormat, aOutputOptions); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EndImageEncode(); + return rv; +} + +// Returns the number of bytes in the image buffer used +// For an ICO file, this is all bytes in the buffer. +NS_IMETHODIMP +nsICOEncoder::GetImageBufferUsed(uint32_t* aOutputSize) +{ + NS_ENSURE_ARG_POINTER(aOutputSize); + *aOutputSize = mImageBufferSize; + return NS_OK; +} + +// Returns a pointer to the start of the image buffer +NS_IMETHODIMP +nsICOEncoder::GetImageBuffer(char** aOutputBuffer) +{ + NS_ENSURE_ARG_POINTER(aOutputBuffer); + *aOutputBuffer = reinterpret_cast<char*>(mImageBufferStart); + return NS_OK; +} + +NS_IMETHODIMP +nsICOEncoder::AddImageFrame(const uint8_t* aData, + uint32_t aLength, + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aFrameOptions) +{ + if (mUsePNG) { + + mContainedEncoder = new nsPNGEncoder(); + nsresult rv; + nsAutoString noParams; + rv = mContainedEncoder->InitFromData(aData, aLength, aWidth, aHeight, + aStride, aInputFormat, noParams); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t PNGImageBufferSize; + mContainedEncoder->GetImageBufferUsed(&PNGImageBufferSize); + mImageBufferSize = ICONFILEHEADERSIZE + ICODIRENTRYSIZE + + PNGImageBufferSize; + mImageBufferStart = static_cast<uint8_t*>(malloc(mImageBufferSize)); + if (!mImageBufferStart) { + return NS_ERROR_OUT_OF_MEMORY; + } + mImageBufferCurr = mImageBufferStart; + mICODirEntry.mBytesInRes = PNGImageBufferSize; + + EncodeFileHeader(); + EncodeInfoHeader(); + + char* imageBuffer; + rv = mContainedEncoder->GetImageBuffer(&imageBuffer); + NS_ENSURE_SUCCESS(rv, rv); + memcpy(mImageBufferCurr, imageBuffer, PNGImageBufferSize); + mImageBufferCurr += PNGImageBufferSize; + } else { + mContainedEncoder = new nsBMPEncoder(); + nsresult rv; + + nsAutoString params; + params.AppendLiteral("bpp="); + params.AppendInt(mICODirEntry.mBitCount); + + rv = mContainedEncoder->InitFromData(aData, aLength, aWidth, aHeight, + aStride, aInputFormat, params); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t andMaskSize = ((GetRealWidth() + 31) / 32) * 4 * // row AND mask + GetRealHeight(); // num rows + + uint32_t BMPImageBufferSize; + mContainedEncoder->GetImageBufferUsed(&BMPImageBufferSize); + mImageBufferSize = ICONFILEHEADERSIZE + ICODIRENTRYSIZE + + BMPImageBufferSize + andMaskSize; + mImageBufferStart = static_cast<uint8_t*>(malloc(mImageBufferSize)); + if (!mImageBufferStart) { + return NS_ERROR_OUT_OF_MEMORY; + } + mImageBufferCurr = mImageBufferStart; + + // Icon files that wrap a BMP file must not include the BITMAPFILEHEADER + // section at the beginning of the encoded BMP data, so we must skip over + // bmp::FILE_HEADER_LENGTH bytes when adding the BMP content to the icon + // file. + mICODirEntry.mBytesInRes = + BMPImageBufferSize - bmp::FILE_HEADER_LENGTH + andMaskSize; + + // Encode the icon headers + EncodeFileHeader(); + EncodeInfoHeader(); + + char* imageBuffer; + rv = mContainedEncoder->GetImageBuffer(&imageBuffer); + NS_ENSURE_SUCCESS(rv, rv); + memcpy(mImageBufferCurr, imageBuffer + bmp::FILE_HEADER_LENGTH, + BMPImageBufferSize - bmp::FILE_HEADER_LENGTH); + // We need to fix the BMP height to be *2 for the AND mask + uint32_t fixedHeight = GetRealHeight() * 2; + NativeEndian::swapToLittleEndianInPlace(&fixedHeight, 1); + // The height is stored at an offset of 8 from the DIB header + memcpy(mImageBufferCurr + 8, &fixedHeight, sizeof(fixedHeight)); + mImageBufferCurr += BMPImageBufferSize - bmp::FILE_HEADER_LENGTH; + + // Calculate rowsize in DWORD's + uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up + int32_t currentLine = GetRealHeight(); + + // Write out the AND mask + while (currentLine > 0) { + currentLine--; + uint8_t* encoded = mImageBufferCurr + currentLine * rowSize; + uint8_t* encodedEnd = encoded + rowSize; + while (encoded != encodedEnd) { + *encoded = 0; // make everything visible + encoded++; + } + } + + mImageBufferCurr += andMaskSize; + } + + return NS_OK; +} + +// See ::InitFromData for other info. +NS_IMETHODIMP +nsICOEncoder::StartImageEncode(uint32_t aWidth, + uint32_t aHeight, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + // can't initialize more than once + if (mImageBufferStart || mImageBufferCurr) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) { + return NS_ERROR_INVALID_ARG; + } + + // Icons are only 1 byte, so make sure our bitmap is in range + if (aWidth > 256 || aHeight > 256) { + return NS_ERROR_INVALID_ARG; + } + + // parse and check any provided output options + uint16_t bpp = 24; + bool usePNG = true; + nsresult rv = ParseOptions(aOutputOptions, bpp, usePNG); + NS_ENSURE_SUCCESS(rv, rv); + MOZ_ASSERT(bpp <= 32); + + mUsePNG = usePNG; + + InitFileHeader(); + // The width and height are stored as 0 when we have a value of 256 + InitInfoHeader(bpp, aWidth == 256 ? 0 : (uint8_t)aWidth, + aHeight == 256 ? 0 : (uint8_t)aHeight); + + return NS_OK; +} + +NS_IMETHODIMP +nsICOEncoder::EndImageEncode() +{ + // must be initialized + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_ERROR_NOT_INITIALIZED; + } + + mFinished = true; + NotifyListener(); + + // if output callback can't get enough memory, it will free our buffer + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +// Parses the encoder options and sets the bits per pixel to use and PNG or BMP +// See InitFromData for a description of the parse options +nsresult +nsICOEncoder::ParseOptions(const nsAString& aOptions, uint16_t& aBppOut, + bool& aUsePNGOut) +{ + // If no parsing options just use the default of 24BPP and PNG yes + if (aOptions.Length() == 0) { + aUsePNGOut = true; + aBppOut = 24; + } + + // Parse the input string into a set of name/value pairs. + // From format: format=<png|bmp>;bpp=<bpp_value> + // to format: [0] = format=<png|bmp>, [1] = bpp=<bpp_value> + nsTArray<nsCString> nameValuePairs; + if (!ParseString(NS_ConvertUTF16toUTF8(aOptions), ';', nameValuePairs)) { + return NS_ERROR_INVALID_ARG; + } + + // For each name/value pair in the set + for (unsigned i = 0; i < nameValuePairs.Length(); ++i) { + + // Split the name value pair [0] = name, [1] = value + nsTArray<nsCString> nameValuePair; + if (!ParseString(nameValuePairs[i], '=', nameValuePair)) { + return NS_ERROR_INVALID_ARG; + } + if (nameValuePair.Length() != 2) { + return NS_ERROR_INVALID_ARG; + } + + // Parse the format portion of the string format=<png|bmp>;bpp=<bpp_value> + if (nameValuePair[0].Equals("format", + nsCaseInsensitiveCStringComparator())) { + if (nameValuePair[1].Equals("png", + nsCaseInsensitiveCStringComparator())) { + aUsePNGOut = true; + } + else if (nameValuePair[1].Equals("bmp", + nsCaseInsensitiveCStringComparator())) { + aUsePNGOut = false; + } + else { + return NS_ERROR_INVALID_ARG; + } + } + + // Parse the bpp portion of the string format=<png|bmp>;bpp=<bpp_value> + if (nameValuePair[0].Equals("bpp", nsCaseInsensitiveCStringComparator())) { + if (nameValuePair[1].EqualsLiteral("24")) { + aBppOut = 24; + } + else if (nameValuePair[1].EqualsLiteral("32")) { + aBppOut = 32; + } + else { + return NS_ERROR_INVALID_ARG; + } + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsICOEncoder::Close() +{ + if (mImageBufferStart) { + free(mImageBufferStart); + mImageBufferStart = nullptr; + mImageBufferSize = 0; + mImageBufferReadPoint = 0; + mImageBufferCurr = nullptr; + } + + return NS_OK; +} + +// Obtains the available bytes to read +NS_IMETHODIMP +nsICOEncoder::Available(uint64_t *_retval) +{ + if (!mImageBufferStart || !mImageBufferCurr) { + return NS_BASE_STREAM_CLOSED; + } + + *_retval = GetCurrentImageBufferOffset() - mImageBufferReadPoint; + return NS_OK; +} + +// [noscript] Reads bytes which are available +NS_IMETHODIMP +nsICOEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); +} + +// [noscript] Reads segments +NS_IMETHODIMP +nsICOEncoder::ReadSegments(nsWriteSegmentFun aWriter, void* aClosure, + uint32_t aCount, uint32_t* _retval) +{ + uint32_t maxCount = GetCurrentImageBufferOffset() - mImageBufferReadPoint; + if (maxCount == 0) { + *_retval = 0; + return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + + nsresult rv = aWriter(this, aClosure, + reinterpret_cast<const char*>(mImageBufferStart + + mImageBufferReadPoint), + 0, aCount, _retval); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*_retval <= aCount, "bad write count"); + mImageBufferReadPoint += *_retval; + } + // errors returned from the writer end here! + return NS_OK; +} + +NS_IMETHODIMP +nsICOEncoder::IsNonBlocking(bool* _retval) +{ + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +nsICOEncoder::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aTarget) +{ + if (aFlags != 0) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (mCallback || mCallbackTarget) { + return NS_ERROR_UNEXPECTED; + } + + mCallbackTarget = aTarget; + // 0 means "any number of bytes except 0" + mNotifyThreshold = aRequestedCount; + if (!aRequestedCount) { + mNotifyThreshold = 1024; // We don't want to notify incessantly + } + + // We set the callback absolutely last, because NotifyListener uses it to + // determine if someone needs to be notified. If we don't set it last, + // NotifyListener might try to fire off a notification to a null target + // which will generally cause non-threadsafe objects to be used off the + // main thread + mCallback = aCallback; + + // What we are being asked for may be present already + NotifyListener(); + return NS_OK; +} + +NS_IMETHODIMP +nsICOEncoder::CloseWithStatus(nsresult aStatus) +{ + return Close(); +} + +void +nsICOEncoder::NotifyListener() +{ + if (mCallback && + (GetCurrentImageBufferOffset() - + mImageBufferReadPoint >= mNotifyThreshold || mFinished)) { + nsCOMPtr<nsIInputStreamCallback> callback; + if (mCallbackTarget) { + callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); + } else { + callback = mCallback; + } + + NS_ASSERTION(callback, "Shouldn't fail to make the callback"); + // Null the callback first because OnInputStreamReady could reenter + // AsyncWait + mCallback = nullptr; + mCallbackTarget = nullptr; + mNotifyThreshold = 0; + + callback->OnInputStreamReady(this); + } +} + +// Initializes the icon file header mICOFileHeader +void +nsICOEncoder::InitFileHeader() +{ + memset(&mICOFileHeader, 0, sizeof(mICOFileHeader)); + mICOFileHeader.mReserved = 0; + mICOFileHeader.mType = 1; + mICOFileHeader.mCount = 1; +} + +// Initializes the icon directory info header mICODirEntry +void +nsICOEncoder::InitInfoHeader(uint16_t aBPP, uint8_t aWidth, uint8_t aHeight) +{ + memset(&mICODirEntry, 0, sizeof(mICODirEntry)); + mICODirEntry.mBitCount = aBPP; + mICODirEntry.mBytesInRes = 0; + mICODirEntry.mColorCount = 0; + mICODirEntry.mWidth = aWidth; + mICODirEntry.mHeight = aHeight; + mICODirEntry.mImageOffset = ICONFILEHEADERSIZE + ICODIRENTRYSIZE; + mICODirEntry.mPlanes = 1; + mICODirEntry.mReserved = 0; +} + +// Encodes the icon file header mICOFileHeader +void +nsICOEncoder::EncodeFileHeader() +{ + IconFileHeader littleEndianIFH = mICOFileHeader; + NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mReserved, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mType, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianIFH.mCount, 1); + + memcpy(mImageBufferCurr, &littleEndianIFH.mReserved, + sizeof(littleEndianIFH.mReserved)); + mImageBufferCurr += sizeof(littleEndianIFH.mReserved); + memcpy(mImageBufferCurr, &littleEndianIFH.mType, + sizeof(littleEndianIFH.mType)); + mImageBufferCurr += sizeof(littleEndianIFH.mType); + memcpy(mImageBufferCurr, &littleEndianIFH.mCount, + sizeof(littleEndianIFH.mCount)); + mImageBufferCurr += sizeof(littleEndianIFH.mCount); +} + +// Encodes the icon directory info header mICODirEntry +void +nsICOEncoder::EncodeInfoHeader() +{ + IconDirEntry littleEndianmIDE = mICODirEntry; + + NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mPlanes, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mBitCount, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mBytesInRes, 1); + NativeEndian::swapToLittleEndianInPlace(&littleEndianmIDE.mImageOffset, 1); + + memcpy(mImageBufferCurr, &littleEndianmIDE.mWidth, + sizeof(littleEndianmIDE.mWidth)); + mImageBufferCurr += sizeof(littleEndianmIDE.mWidth); + memcpy(mImageBufferCurr, &littleEndianmIDE.mHeight, + sizeof(littleEndianmIDE.mHeight)); + mImageBufferCurr += sizeof(littleEndianmIDE.mHeight); + memcpy(mImageBufferCurr, &littleEndianmIDE.mColorCount, + sizeof(littleEndianmIDE.mColorCount)); + mImageBufferCurr += sizeof(littleEndianmIDE.mColorCount); + memcpy(mImageBufferCurr, &littleEndianmIDE.mReserved, + sizeof(littleEndianmIDE.mReserved)); + mImageBufferCurr += sizeof(littleEndianmIDE.mReserved); + memcpy(mImageBufferCurr, &littleEndianmIDE.mPlanes, + sizeof(littleEndianmIDE.mPlanes)); + mImageBufferCurr += sizeof(littleEndianmIDE.mPlanes); + memcpy(mImageBufferCurr, &littleEndianmIDE.mBitCount, + sizeof(littleEndianmIDE.mBitCount)); + mImageBufferCurr += sizeof(littleEndianmIDE.mBitCount); + memcpy(mImageBufferCurr, &littleEndianmIDE.mBytesInRes, + sizeof(littleEndianmIDE.mBytesInRes)); + mImageBufferCurr += sizeof(littleEndianmIDE.mBytesInRes); + memcpy(mImageBufferCurr, &littleEndianmIDE.mImageOffset, + sizeof(littleEndianmIDE.mImageOffset)); + mImageBufferCurr += sizeof(littleEndianmIDE.mImageOffset); +} diff --git a/image/encoders/ico/nsICOEncoder.h b/image/encoders/ico/nsICOEncoder.h new file mode 100644 index 000000000..a8a9a5049 --- /dev/null +++ b/image/encoders/ico/nsICOEncoder.h @@ -0,0 +1,99 @@ +/* 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/. */ + +#ifndef mozilla_image_encoders_ico_nsICOEncoder_h +#define mozilla_image_encoders_ico_nsICOEncoder_h + +#include "mozilla/Attributes.h" +#include "mozilla/ReentrantMonitor.h" + +#include "imgIEncoder.h" + +#include "nsCOMPtr.h" +#include "ICOFileHeaders.h" + +#define NS_ICOENCODER_CID \ +{ /*92AE3AB2-8968-41B1-8709-B6123BCEAF21 */ \ + 0x92ae3ab2, \ + 0x8968, \ + 0x41b1, \ + {0x87, 0x09, 0xb6, 0x12, 0x3b, 0Xce, 0xaf, 0x21} \ +} + +// Provides ICO encoding functionality. Use InitFromData() to do the +// encoding. See that function definition for encoding options. + +class nsICOEncoder final : public imgIEncoder +{ + typedef mozilla::ReentrantMonitor ReentrantMonitor; +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_IMGIENCODER + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + nsICOEncoder(); + + // Obtains the width of the icon directory entry + uint32_t GetRealWidth() const + { + return mICODirEntry.mWidth == 0 ? 256 : mICODirEntry.mWidth; + } + + // Obtains the height of the icon directory entry + uint32_t GetRealHeight() const + { + return mICODirEntry.mHeight == 0 ? 256 : mICODirEntry.mHeight; + } + +protected: + ~nsICOEncoder(); + + nsresult ParseOptions(const nsAString& aOptions, uint16_t& aBppOut, + bool& aUsePNGOut); + void NotifyListener(); + + // Initializes the icon file header mICOFileHeader + void InitFileHeader(); + // Initializes the icon directory info header mICODirEntry + void InitInfoHeader(uint16_t aBPP, uint8_t aWidth, uint8_t aHeight); + // Encodes the icon file header mICOFileHeader + void EncodeFileHeader(); + // Encodes the icon directory info header mICODirEntry + void EncodeInfoHeader(); + // Obtains the current offset filled up to for the image buffer + inline int32_t GetCurrentImageBufferOffset() + { + return static_cast<int32_t>(mImageBufferCurr - mImageBufferStart); + } + + // Holds either a PNG or a BMP depending on the encoding options specified + // or if no encoding options specified will use the default (PNG) + nsCOMPtr<imgIEncoder> mContainedEncoder; + + // These headers will always contain endian independent stuff. + // Don't trust the width and height of mICODirEntry directly, + // instead use the accessors GetRealWidth() and GetRealHeight(). + mozilla::image::IconFileHeader mICOFileHeader; + mozilla::image::IconDirEntry mICODirEntry; + + // Keeps track of the start of the image buffer + uint8_t* mImageBufferStart; + // Keeps track of the current position in the image buffer + uint8_t* mImageBufferCurr; + // Keeps track of the image buffer size + uint32_t mImageBufferSize; + // Keeps track of the number of bytes in the image buffer which are read + uint32_t mImageBufferReadPoint; + // Stores true if the image is done being encoded + bool mFinished; + // Stores true if the contained image is a PNG + bool mUsePNG; + + nsCOMPtr<nsIInputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mCallbackTarget; + uint32_t mNotifyThreshold; +}; + +#endif // mozilla_image_encoders_ico_nsICOEncoder_h diff --git a/image/encoders/jpeg/moz.build b/image/encoders/jpeg/moz.build new file mode 100644 index 000000000..9e5551ce6 --- /dev/null +++ b/image/encoders/jpeg/moz.build @@ -0,0 +1,11 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + 'nsJPEGEncoder.cpp', +] + +FINAL_LIBRARY = 'xul' diff --git a/image/encoders/jpeg/nsJPEGEncoder.cpp b/image/encoders/jpeg/nsJPEGEncoder.cpp new file mode 100644 index 000000000..04cfef07b --- /dev/null +++ b/image/encoders/jpeg/nsJPEGEncoder.cpp @@ -0,0 +1,532 @@ +/* -*- Mode: C++; tab-width: 2; 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 "nsJPEGEncoder.h" +#include "prprf.h" +#include "nsString.h" +#include "nsStreamUtils.h" +#include "gfxColor.h" + +#include <setjmp.h> +#include "jerror.h" + +using namespace mozilla; + +NS_IMPL_ISUPPORTS(nsJPEGEncoder, imgIEncoder, nsIInputStream, + nsIAsyncInputStream) + +// used to pass error info through the JPEG library +struct encoder_error_mgr { + jpeg_error_mgr pub; + jmp_buf setjmp_buffer; +}; + +nsJPEGEncoder::nsJPEGEncoder() + : mFinished(false), + mImageBuffer(nullptr), + mImageBufferSize(0), + mImageBufferUsed(0), + mImageBufferReadPoint(0), + mCallback(nullptr), + mCallbackTarget(nullptr), + mNotifyThreshold(0), + mReentrantMonitor("nsJPEGEncoder.mReentrantMonitor") +{ +} + +nsJPEGEncoder::~nsJPEGEncoder() +{ + if (mImageBuffer) { + free(mImageBuffer); + mImageBuffer = nullptr; + } +} + + +// nsJPEGEncoder::InitFromData +// +// One output option is supported: "quality=X" where X is an integer in the +// range 0-100. Higher values for X give better quality. +// +// Transparency is always discarded. + +NS_IMETHODIMP +nsJPEGEncoder::InitFromData(const uint8_t* aData, + uint32_t aLength, // (unused, req'd by JS) + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + NS_ENSURE_ARG(aData); + + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) + return NS_ERROR_INVALID_ARG; + + // Stride is the padded width of each row, so it better be longer (I'm afraid + // people will not understand what stride means, so check it well) + if ((aInputFormat == INPUT_FORMAT_RGB && + aStride < aWidth * 3) || + ((aInputFormat == INPUT_FORMAT_RGBA || + aInputFormat == INPUT_FORMAT_HOSTARGB) && + aStride < aWidth * 4)) { + NS_WARNING("Invalid stride for InitFromData"); + return NS_ERROR_INVALID_ARG; + } + + // can't initialize more than once + if (mImageBuffer != nullptr) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + // options: we only have one option so this is easy + int quality = 92; + if (aOutputOptions.Length() > 0) { + // have options string + const nsString qualityPrefix(NS_LITERAL_STRING("quality=")); + if (aOutputOptions.Length() > qualityPrefix.Length() && + StringBeginsWith(aOutputOptions, qualityPrefix)) { + // have quality string + nsCString value = + NS_ConvertUTF16toUTF8(Substring(aOutputOptions, + qualityPrefix.Length())); + int newquality = -1; + if (PR_sscanf(value.get(), "%d", &newquality) == 1) { + if (newquality >= 0 && newquality <= 100) { + quality = newquality; + } else { + NS_WARNING("Quality value out of range, should be 0-100," + " using default"); + } + } else { + NS_WARNING("Quality value invalid, should be integer 0-100," + " using default"); + } + } + else { + return NS_ERROR_INVALID_ARG; + } + } + + jpeg_compress_struct cinfo; + + // We set up the normal JPEG error routines, then override error_exit. + // This must be done before the call to create_compress + encoder_error_mgr errmgr; + cinfo.err = jpeg_std_error(&errmgr.pub); + errmgr.pub.error_exit = errorExit; + // Establish the setjmp return context for my_error_exit to use. + if (setjmp(errmgr.setjmp_buffer)) { + // If we get here, the JPEG code has signaled an error. + // We need to clean up the JPEG object, close the input file, and return. + return NS_ERROR_FAILURE; + } + + jpeg_create_compress(&cinfo); + cinfo.image_width = aWidth; + cinfo.image_height = aHeight; + cinfo.input_components = 3; + cinfo.in_color_space = JCS_RGB; + cinfo.data_precision = 8; + + jpeg_set_defaults(&cinfo); + jpeg_set_quality(&cinfo, quality, 1); // quality here is 0-100 + if (quality >= 90) { + int i; + for (i=0; i < MAX_COMPONENTS; i++) { + cinfo.comp_info[i].h_samp_factor=1; + cinfo.comp_info[i].v_samp_factor=1; + } + } + + // set up the destination manager + jpeg_destination_mgr destmgr; + destmgr.init_destination = initDestination; + destmgr.empty_output_buffer = emptyOutputBuffer; + destmgr.term_destination = termDestination; + cinfo.dest = &destmgr; + cinfo.client_data = this; + + jpeg_start_compress(&cinfo, 1); + + // feed it the rows + if (aInputFormat == INPUT_FORMAT_RGB) { + while (cinfo.next_scanline < cinfo.image_height) { + const uint8_t* row = &aData[cinfo.next_scanline * aStride]; + jpeg_write_scanlines(&cinfo, const_cast<uint8_t**>(&row), 1); + } + } else if (aInputFormat == INPUT_FORMAT_RGBA) { + UniquePtr<uint8_t[]> rowptr = MakeUnique<uint8_t[]>(aWidth * 3); + uint8_t* row = rowptr.get(); + while (cinfo.next_scanline < cinfo.image_height) { + ConvertRGBARow(&aData[cinfo.next_scanline * aStride], row, aWidth); + jpeg_write_scanlines(&cinfo, &row, 1); + } + } else if (aInputFormat == INPUT_FORMAT_HOSTARGB) { + UniquePtr<uint8_t[]> rowptr = MakeUnique<uint8_t[]>(aWidth * 3); + uint8_t* row = rowptr.get(); + while (cinfo.next_scanline < cinfo.image_height) { + ConvertHostARGBRow(&aData[cinfo.next_scanline * aStride], row, aWidth); + jpeg_write_scanlines(&cinfo, &row, 1); + } + } + + jpeg_finish_compress(&cinfo); + jpeg_destroy_compress(&cinfo); + + mFinished = true; + NotifyListener(); + + // if output callback can't get enough memory, it will free our buffer + if (!mImageBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + + +NS_IMETHODIMP +nsJPEGEncoder::StartImageEncode(uint32_t aWidth, + uint32_t aHeight, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +// Returns the number of bytes in the image buffer used. +NS_IMETHODIMP +nsJPEGEncoder::GetImageBufferUsed(uint32_t* aOutputSize) +{ + NS_ENSURE_ARG_POINTER(aOutputSize); + *aOutputSize = mImageBufferUsed; + return NS_OK; +} + +// Returns a pointer to the start of the image buffer +NS_IMETHODIMP +nsJPEGEncoder::GetImageBuffer(char** aOutputBuffer) +{ + NS_ENSURE_ARG_POINTER(aOutputBuffer); + *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer); + return NS_OK; +} + +NS_IMETHODIMP +nsJPEGEncoder::AddImageFrame(const uint8_t* aData, + uint32_t aLength, + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aFrameFormat, + const nsAString& aFrameOptions) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsJPEGEncoder::EndImageEncode() +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + + +NS_IMETHODIMP +nsJPEGEncoder::Close() +{ + if (mImageBuffer != nullptr) { + free(mImageBuffer); + mImageBuffer = nullptr; + mImageBufferSize = 0; + mImageBufferUsed = 0; + mImageBufferReadPoint = 0; + } + return NS_OK; +} + +NS_IMETHODIMP +nsJPEGEncoder::Available(uint64_t* _retval) +{ + if (!mImageBuffer) { + return NS_BASE_STREAM_CLOSED; + } + + *_retval = mImageBufferUsed - mImageBufferReadPoint; + return NS_OK; +} + +NS_IMETHODIMP +nsJPEGEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); +} + +NS_IMETHODIMP +nsJPEGEncoder::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, uint32_t* _retval) +{ + // Avoid another thread reallocing the buffer underneath us + ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); + + uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint; + if (maxCount == 0) { + *_retval = 0; + return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + nsresult rv = aWriter(this, aClosure, + reinterpret_cast<const char*> + (mImageBuffer+mImageBufferReadPoint), + 0, aCount, _retval); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*_retval <= aCount, "bad write count"); + mImageBufferReadPoint += *_retval; + } + + // errors returned from the writer end here! + return NS_OK; +} + +NS_IMETHODIMP +nsJPEGEncoder::IsNonBlocking(bool* _retval) +{ + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +nsJPEGEncoder::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, uint32_t aRequestedCount, + nsIEventTarget* aTarget) +{ + if (aFlags != 0) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (mCallback || mCallbackTarget) { + return NS_ERROR_UNEXPECTED; + } + + mCallbackTarget = aTarget; + // 0 means "any number of bytes except 0" + mNotifyThreshold = aRequestedCount; + if (!aRequestedCount) { + mNotifyThreshold = 1024; // 1 KB seems good. We don't want to + // notify incessantly + } + + // We set the callback absolutely last, because NotifyListener uses it to + // determine if someone needs to be notified. If we don't set it last, + // NotifyListener might try to fire off a notification to a null target + // which will generally cause non-threadsafe objects to be used off the + // main thread + mCallback = aCallback; + + // What we are being asked for may be present already + NotifyListener(); + return NS_OK; +} + +NS_IMETHODIMP +nsJPEGEncoder::CloseWithStatus(nsresult aStatus) +{ + return Close(); +} + + + +// nsJPEGEncoder::ConvertHostARGBRow +// +// Our colors are stored with premultiplied alphas, but we need +// an output with no alpha in machine-independent byte order. +// +// See gfx/cairo/cairo/src/cairo-png.c +void +nsJPEGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth) +{ + for (uint32_t x = 0; x < aPixelWidth; x++) { + const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; + uint8_t* pixelOut = &aDest[x * 3]; + + pixelOut[0] = (pixelIn & 0xff0000) >> 16; + pixelOut[1] = (pixelIn & 0x00ff00) >> 8; + pixelOut[2] = (pixelIn & 0x0000ff) >> 0; + } +} + +/** + * nsJPEGEncoder::ConvertRGBARow + * + * Input is RGBA, output is RGB, so we should alpha-premultiply. + */ +void +nsJPEGEncoder::ConvertRGBARow(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth) +{ + for (uint32_t x = 0; x < aPixelWidth; x++) { + const uint8_t* pixelIn = &aSrc[x * 4]; + uint8_t* pixelOut = &aDest[x * 3]; + + uint8_t alpha = pixelIn[3]; + pixelOut[0] = gfxPreMultiply(pixelIn[0], alpha); + pixelOut[1] = gfxPreMultiply(pixelIn[1], alpha); + pixelOut[2] = gfxPreMultiply(pixelIn[2], alpha); + } +} + +// nsJPEGEncoder::initDestination +// +// Initialize destination. This is called by jpeg_start_compress() before +// any data is actually written. It must initialize next_output_byte and +// free_in_buffer. free_in_buffer must be initialized to a positive value. + +void // static +nsJPEGEncoder::initDestination(jpeg_compress_struct* cinfo) +{ + nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data); + NS_ASSERTION(!that->mImageBuffer, "Image buffer already initialized"); + + that->mImageBufferSize = 8192; + that->mImageBuffer = (uint8_t*)malloc(that->mImageBufferSize); + that->mImageBufferUsed = 0; + + cinfo->dest->next_output_byte = that->mImageBuffer; + cinfo->dest->free_in_buffer = that->mImageBufferSize; +} + + +// nsJPEGEncoder::emptyOutputBuffer +// +// This is called whenever the buffer has filled (free_in_buffer reaches +// zero). In typical applications, it should write out the *entire* buffer +// (use the saved start address and buffer length; ignore the current state +// of next_output_byte and free_in_buffer). Then reset the pointer & count +// to the start of the buffer, and return TRUE indicating that the buffer +// has been dumped. free_in_buffer must be set to a positive value when +// TRUE is returned. A FALSE return should only be used when I/O suspension +// is desired (this operating mode is discussed in the next section). + +boolean // static +nsJPEGEncoder::emptyOutputBuffer(jpeg_compress_struct* cinfo) +{ + nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data); + NS_ASSERTION(that->mImageBuffer, "No buffer to empty!"); + + // When we're reallocing the buffer we need to take the lock to ensure + // that nobody is trying to read from the buffer we are destroying + ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor); + + that->mImageBufferUsed = that->mImageBufferSize; + + // expand buffer, just double size each time + that->mImageBufferSize *= 2; + + uint8_t* newBuf = (uint8_t*)realloc(that->mImageBuffer, + that->mImageBufferSize); + if (!newBuf) { + // can't resize, just zero (this will keep us from writing more) + free(that->mImageBuffer); + that->mImageBuffer = nullptr; + that->mImageBufferSize = 0; + that->mImageBufferUsed = 0; + + // This seems to be the only way to do errors through the JPEG library. We + // pass an nsresult masquerading as an int, which works because the + // setjmp() caller casts it back. + longjmp(((encoder_error_mgr*)(cinfo->err))->setjmp_buffer, + static_cast<int>(NS_ERROR_OUT_OF_MEMORY)); + } + that->mImageBuffer = newBuf; + + cinfo->dest->next_output_byte = &that->mImageBuffer[that->mImageBufferUsed]; + cinfo->dest->free_in_buffer = that->mImageBufferSize - that->mImageBufferUsed; + return 1; +} + + +// nsJPEGEncoder::termDestination +// +// Terminate destination --- called by jpeg_finish_compress() after all data +// has been written. In most applications, this must flush any data +// remaining in the buffer. Use either next_output_byte or free_in_buffer +// to determine how much data is in the buffer. + +void // static +nsJPEGEncoder::termDestination(jpeg_compress_struct* cinfo) +{ + nsJPEGEncoder* that = static_cast<nsJPEGEncoder*>(cinfo->client_data); + if (!that->mImageBuffer) { + return; + } + that->mImageBufferUsed = cinfo->dest->next_output_byte - that->mImageBuffer; + NS_ASSERTION(that->mImageBufferUsed < that->mImageBufferSize, + "JPEG library busted, got a bad image buffer size"); + that->NotifyListener(); +} + + +// nsJPEGEncoder::errorExit +// +// Override the standard error method in the IJG JPEG decoder code. This +// was mostly copied from nsJPEGDecoder.cpp + +void // static +nsJPEGEncoder::errorExit(jpeg_common_struct* cinfo) +{ + nsresult error_code; + encoder_error_mgr* err = (encoder_error_mgr*) cinfo->err; + + // Convert error to a browser error code + switch (cinfo->err->msg_code) { + case JERR_OUT_OF_MEMORY: + error_code = NS_ERROR_OUT_OF_MEMORY; + break; + default: + error_code = NS_ERROR_FAILURE; + } + + // Return control to the setjmp point. We pass an nsresult masquerading as + // an int, which works because the setjmp() caller casts it back. + longjmp(err->setjmp_buffer, static_cast<int>(error_code)); +} + +void +nsJPEGEncoder::NotifyListener() +{ + // We might call this function on multiple threads (any threads that call + // AsyncWait and any that do encoding) so we lock to avoid notifying the + // listener twice about the same data (which generally leads to a truncated + // image). + ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); + + if (mCallback && + (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold || + mFinished)) { + nsCOMPtr<nsIInputStreamCallback> callback; + if (mCallbackTarget) { + callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); + } else { + callback = mCallback; + } + + NS_ASSERTION(callback, "Shouldn't fail to make the callback"); + // Null the callback first because OnInputStreamReady could reenter + // AsyncWait + mCallback = nullptr; + mCallbackTarget = nullptr; + mNotifyThreshold = 0; + + callback->OnInputStreamReady(this); + } +} diff --git a/image/encoders/jpeg/nsJPEGEncoder.h b/image/encoders/jpeg/nsJPEGEncoder.h new file mode 100644 index 000000000..2bcf5c2f3 --- /dev/null +++ b/image/encoders/jpeg/nsJPEGEncoder.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifndef mozilla_image_encoders_jpeg_nsJPEGEncoder_h +#define mozilla_image_encoders_jpeg_nsJPEGEncoder_h + +#include "imgIEncoder.h" + +#include "mozilla/ReentrantMonitor.h" +#include "mozilla/Attributes.h" + +#include "nsCOMPtr.h" + +// needed for JPEG library +#include <stdio.h> + +extern "C" { +#include "jpeglib.h" +} + +#define NS_JPEGENCODER_CID \ +{ \ + /* ac2bb8fe-eeeb-4572-b40f-be03932b56e0 */ \ + 0xac2bb8fe, \ + 0xeeeb, \ + 0x4572, \ + {0xb4, 0x0f, 0xbe, 0x03, 0x93, 0x2b, 0x56, 0xe0} \ +} + +// Provides JPEG encoding functionality. Use InitFromData() to do the +// encoding. See that function definition for encoding options. + +class nsJPEGEncoder final : public imgIEncoder +{ + typedef mozilla::ReentrantMonitor ReentrantMonitor; +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_IMGIENCODER + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + nsJPEGEncoder(); + +private: + ~nsJPEGEncoder(); + +protected: + + void ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth); + void ConvertRGBARow(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth); + + static void initDestination(jpeg_compress_struct* cinfo); + static boolean emptyOutputBuffer(jpeg_compress_struct* cinfo); + static void termDestination(jpeg_compress_struct* cinfo); + + static void errorExit(jpeg_common_struct* cinfo); + + void NotifyListener(); + + bool mFinished; + + // image buffer + uint8_t* mImageBuffer; + uint32_t mImageBufferSize; + uint32_t mImageBufferUsed; + + uint32_t mImageBufferReadPoint; + + nsCOMPtr<nsIInputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mCallbackTarget; + uint32_t mNotifyThreshold; + + // nsJPEGEncoder is designed to allow one thread to pump data into it while + // another reads from it. We lock to ensure that the buffer remains + // append-only while we read from it (that it is not realloced) and to ensure + // that only one thread dispatches a callback for each call to AsyncWait. + ReentrantMonitor mReentrantMonitor; +}; + +#endif // mozilla_image_encoders_jpeg_nsJPEGEncoder_h diff --git a/image/encoders/moz.build b/image/encoders/moz.build new file mode 100644 index 000000000..1a76d773a --- /dev/null +++ b/image/encoders/moz.build @@ -0,0 +1,12 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +DIRS += [ + 'ico', + 'png', + 'jpeg', + 'bmp', +] diff --git a/image/encoders/png/moz.build b/image/encoders/png/moz.build new file mode 100644 index 000000000..e6665e424 --- /dev/null +++ b/image/encoders/png/moz.build @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +SOURCES += [ + 'nsPNGEncoder.cpp', +] + +LOCAL_INCLUDES += [ + '/image', +] + +FINAL_LIBRARY = 'xul' diff --git a/image/encoders/png/nsPNGEncoder.cpp b/image/encoders/png/nsPNGEncoder.cpp new file mode 100644 index 000000000..66294146d --- /dev/null +++ b/image/encoders/png/nsPNGEncoder.cpp @@ -0,0 +1,758 @@ +/* -*- Mode: C++; tab-width: 2; 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 "ImageLogging.h" +#include "nsCRT.h" +#include "nsPNGEncoder.h" +#include "nsStreamUtils.h" +#include "nsString.h" +#include "prprf.h" + +using namespace mozilla; + +static LazyLogModule sPNGEncoderLog("PNGEncoder"); + +NS_IMPL_ISUPPORTS(nsPNGEncoder, imgIEncoder, nsIInputStream, + nsIAsyncInputStream) + +nsPNGEncoder::nsPNGEncoder() : mPNG(nullptr), mPNGinfo(nullptr), + mIsAnimation(false), + mFinished(false), + mImageBuffer(nullptr), mImageBufferSize(0), + mImageBufferUsed(0), mImageBufferReadPoint(0), + mCallback(nullptr), + mCallbackTarget(nullptr), mNotifyThreshold(0), + mReentrantMonitor( + "nsPNGEncoder.mReentrantMonitor") +{ } + +nsPNGEncoder::~nsPNGEncoder() +{ + if (mImageBuffer) { + free(mImageBuffer); + mImageBuffer = nullptr; + } + // don't leak if EndImageEncode wasn't called + if (mPNG) { + png_destroy_write_struct(&mPNG, &mPNGinfo); + } +} + +// nsPNGEncoder::InitFromData +// +// One output option is supported: "transparency=none" means that the +// output PNG will not have an alpha channel, even if the input does. +// +// Based partially on gfx/cairo/cairo/src/cairo-png.c +// See also media/libpng/libpng-manual.txt + +NS_IMETHODIMP +nsPNGEncoder::InitFromData(const uint8_t* aData, + uint32_t aLength, // (unused, req'd by JS) + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + NS_ENSURE_ARG(aData); + nsresult rv; + + rv = StartImageEncode(aWidth, aHeight, aInputFormat, aOutputOptions); + if (!NS_SUCCEEDED(rv)) { + return rv; + } + + rv = AddImageFrame(aData, aLength, aWidth, aHeight, aStride, + aInputFormat, aOutputOptions); + if (!NS_SUCCEEDED(rv)) { + return rv; + } + + rv = EndImageEncode(); + + return rv; +} + + +// nsPNGEncoder::StartImageEncode +// +// +// See ::InitFromData for other info. +NS_IMETHODIMP +nsPNGEncoder::StartImageEncode(uint32_t aWidth, + uint32_t aHeight, + uint32_t aInputFormat, + const nsAString& aOutputOptions) +{ + bool useTransparency = true, skipFirstFrame = false; + uint32_t numFrames = 1; + uint32_t numPlays = 0; // For animations, 0 == forever + + // can't initialize more than once + if (mImageBuffer != nullptr) { + return NS_ERROR_ALREADY_INITIALIZED; + } + + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) + return NS_ERROR_INVALID_ARG; + + // parse and check any provided output options + nsresult rv = ParseOptions(aOutputOptions, &useTransparency, &skipFirstFrame, + &numFrames, &numPlays, nullptr, nullptr, + nullptr, nullptr, nullptr); + if (rv != NS_OK) { + return rv; + } + +#ifdef PNG_APNG_SUPPORTED + if (numFrames > 1) { + mIsAnimation = true; + } + +#endif + + // initialize + mPNG = png_create_write_struct(PNG_LIBPNG_VER_STRING, + nullptr, + ErrorCallback, + WarningCallback); + if (!mPNG) { + return NS_ERROR_OUT_OF_MEMORY; + } + + mPNGinfo = png_create_info_struct(mPNG); + if (!mPNGinfo) { + png_destroy_write_struct(&mPNG, nullptr); + return NS_ERROR_FAILURE; + } + + // libpng's error handler jumps back here upon an error. + // Note: It's important that all png_* callers do this, or errors + // will result in a corrupt time-warped stack. + if (setjmp(png_jmpbuf(mPNG))) { + png_destroy_write_struct(&mPNG, &mPNGinfo); + return NS_ERROR_FAILURE; + } + + // Set up to read the data into our image buffer, start out with an 8K + // estimated size. Note: we don't have to worry about freeing this data + // in this function. It will be freed on object destruction. + mImageBufferSize = 8192; + mImageBuffer = (uint8_t*)malloc(mImageBufferSize); + if (!mImageBuffer) { + png_destroy_write_struct(&mPNG, &mPNGinfo); + return NS_ERROR_OUT_OF_MEMORY; + } + mImageBufferUsed = 0; + + // set our callback for libpng to give us the data + png_set_write_fn(mPNG, this, WriteCallback, nullptr); + + // include alpha? + int colorType; + if ((aInputFormat == INPUT_FORMAT_HOSTARGB || + aInputFormat == INPUT_FORMAT_RGBA) && + useTransparency) + colorType = PNG_COLOR_TYPE_RGB_ALPHA; + else + colorType = PNG_COLOR_TYPE_RGB; + + png_set_IHDR(mPNG, mPNGinfo, aWidth, aHeight, 8, colorType, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, + PNG_FILTER_TYPE_DEFAULT); + +#ifdef PNG_APNG_SUPPORTED + if (mIsAnimation) { + png_set_first_frame_is_hidden(mPNG, mPNGinfo, skipFirstFrame); + png_set_acTL(mPNG, mPNGinfo, numFrames, numPlays); + } +#endif + + // XXX: support PLTE, gAMA, tRNS, bKGD? + + png_write_info(mPNG, mPNGinfo); + + return NS_OK; +} + +// Returns the number of bytes in the image buffer used. +NS_IMETHODIMP +nsPNGEncoder::GetImageBufferUsed(uint32_t* aOutputSize) +{ + NS_ENSURE_ARG_POINTER(aOutputSize); + *aOutputSize = mImageBufferUsed; + return NS_OK; +} + +// Returns a pointer to the start of the image buffer +NS_IMETHODIMP +nsPNGEncoder::GetImageBuffer(char** aOutputBuffer) +{ + NS_ENSURE_ARG_POINTER(aOutputBuffer); + *aOutputBuffer = reinterpret_cast<char*>(mImageBuffer); + return NS_OK; +} + +NS_IMETHODIMP +nsPNGEncoder::AddImageFrame(const uint8_t* aData, + uint32_t aLength, // (unused, req'd by JS) + uint32_t aWidth, + uint32_t aHeight, + uint32_t aStride, + uint32_t aInputFormat, + const nsAString& aFrameOptions) +{ + bool useTransparency= true; + uint32_t delay_ms = 500; +#ifdef PNG_APNG_SUPPORTED + uint32_t dispose_op = PNG_DISPOSE_OP_NONE; + uint32_t blend_op = PNG_BLEND_OP_SOURCE; +#else + uint32_t dispose_op; + uint32_t blend_op; +#endif + uint32_t x_offset = 0, y_offset = 0; + + // must be initialized + if (mImageBuffer == nullptr) { + return NS_ERROR_NOT_INITIALIZED; + } + + // EndImageEncode was done, or some error occurred earlier + if (!mPNG) { + return NS_BASE_STREAM_CLOSED; + } + + // validate input format + if (aInputFormat != INPUT_FORMAT_RGB && + aInputFormat != INPUT_FORMAT_RGBA && + aInputFormat != INPUT_FORMAT_HOSTARGB) + return NS_ERROR_INVALID_ARG; + + // libpng's error handler jumps back here upon an error. + if (setjmp(png_jmpbuf(mPNG))) { + png_destroy_write_struct(&mPNG, &mPNGinfo); + return NS_ERROR_FAILURE; + } + + // parse and check any provided output options + nsresult rv = ParseOptions(aFrameOptions, &useTransparency, nullptr, + nullptr, nullptr, &dispose_op, &blend_op, + &delay_ms, &x_offset, &y_offset); + if (rv != NS_OK) { + return rv; + } + +#ifdef PNG_APNG_SUPPORTED + if (mIsAnimation) { + // XXX the row pointers arg (#3) is unused, can it be removed? + png_write_frame_head(mPNG, mPNGinfo, nullptr, + aWidth, aHeight, x_offset, y_offset, + delay_ms, 1000, dispose_op, blend_op); + } +#endif + + // Stride is the padded width of each row, so it better be longer + // (I'm afraid people will not understand what stride means, so + // check it well) + if ((aInputFormat == INPUT_FORMAT_RGB && + aStride < aWidth * 3) || + ((aInputFormat == INPUT_FORMAT_RGBA || + aInputFormat == INPUT_FORMAT_HOSTARGB) && + aStride < aWidth * 4)) { + NS_WARNING("Invalid stride for InitFromData/AddImageFrame"); + return NS_ERROR_INVALID_ARG; + } + +#ifdef PNG_WRITE_FILTER_SUPPORTED + png_set_filter(mPNG, PNG_FILTER_TYPE_BASE, PNG_FILTER_VALUE_NONE); +#endif + + // write each row: if we add more input formats, we may want to + // generalize the conversions + if (aInputFormat == INPUT_FORMAT_HOSTARGB) { + // PNG requires RGBA with post-multiplied alpha, so we need to + // convert + UniquePtr<uint8_t[]> row = MakeUnique<uint8_t[]>(aWidth * 4); + for (uint32_t y = 0; y < aHeight; y++) { + ConvertHostARGBRow(&aData[y * aStride], row.get(), aWidth, useTransparency); + png_write_row(mPNG, row.get()); + } + } else if (aInputFormat == INPUT_FORMAT_RGBA && !useTransparency) { + // RBGA, but we need to strip the alpha + UniquePtr<uint8_t[]> row = MakeUnique<uint8_t[]>(aWidth * 4); + for (uint32_t y = 0; y < aHeight; y++) { + StripAlpha(&aData[y * aStride], row.get(), aWidth); + png_write_row(mPNG, row.get()); + } + } else if (aInputFormat == INPUT_FORMAT_RGB || + aInputFormat == INPUT_FORMAT_RGBA) { + // simple RBG(A), no conversion needed + for (uint32_t y = 0; y < aHeight; y++) { + png_write_row(mPNG, (uint8_t*)&aData[y * aStride]); + } + + } else { + NS_NOTREACHED("Bad format type"); + return NS_ERROR_INVALID_ARG; + } + +#ifdef PNG_APNG_SUPPORTED + if (mIsAnimation) { + png_write_frame_tail(mPNG, mPNGinfo); + } +#endif + + return NS_OK; +} + + +NS_IMETHODIMP +nsPNGEncoder::EndImageEncode() +{ + // must be initialized + if (mImageBuffer == nullptr) { + return NS_ERROR_NOT_INITIALIZED; + } + + // EndImageEncode has already been called, or some error + // occurred earlier + if (!mPNG) { + return NS_BASE_STREAM_CLOSED; + } + + // libpng's error handler jumps back here upon an error. + if (setjmp(png_jmpbuf(mPNG))) { + png_destroy_write_struct(&mPNG, &mPNGinfo); + return NS_ERROR_FAILURE; + } + + png_write_end(mPNG, mPNGinfo); + png_destroy_write_struct(&mPNG, &mPNGinfo); + + mFinished = true; + NotifyListener(); + + // if output callback can't get enough memory, it will free our buffer + if (!mImageBuffer) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + + +nsresult +nsPNGEncoder::ParseOptions(const nsAString& aOptions, + bool* useTransparency, + bool* skipFirstFrame, + uint32_t* numFrames, + uint32_t* numPlays, + uint32_t* frameDispose, + uint32_t* frameBlend, + uint32_t* frameDelay, + uint32_t* offsetX, + uint32_t* offsetY) +{ +#ifdef PNG_APNG_SUPPORTED + // Make a copy of aOptions, because strtok() will modify it. + nsAutoCString optionsCopy; + optionsCopy.Assign(NS_ConvertUTF16toUTF8(aOptions)); + char* options = optionsCopy.BeginWriting(); + + while (char* token = nsCRT::strtok(options, ";", &options)) { + // If there's an '=' character, split the token around it. + char* equals = token; + char* value = nullptr; + while(*equals != '=' && *equals) { + ++equals; + } + if (*equals == '=') { + value = equals + 1; + } + + if (value) { + *equals = '\0'; // temporary null + } + + // transparency=[yes|no|none] + if (nsCRT::strcmp(token, "transparency") == 0 && useTransparency) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (nsCRT::strcmp(value, "none") == 0 || + nsCRT::strcmp(value, "no") == 0) { + *useTransparency = false; + } else if (nsCRT::strcmp(value, "yes") == 0) { + *useTransparency = true; + } else { + return NS_ERROR_INVALID_ARG; + } + + // skipfirstframe=[yes|no] + } else if (nsCRT::strcmp(token, "skipfirstframe") == 0 && + skipFirstFrame) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (nsCRT::strcmp(value, "no") == 0) { + *skipFirstFrame = false; + } else if (nsCRT::strcmp(value, "yes") == 0) { + *skipFirstFrame = true; + } else { + return NS_ERROR_INVALID_ARG; + } + + // frames=# + } else if (nsCRT::strcmp(token, "frames") == 0 && numFrames) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (PR_sscanf(value, "%u", numFrames) != 1) { + return NS_ERROR_INVALID_ARG; + } + + // frames=0 is nonsense. + if (*numFrames == 0) { + return NS_ERROR_INVALID_ARG; + } + + // plays=# + } else if (nsCRT::strcmp(token, "plays") == 0 && numPlays) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + // plays=0 to loop forever, otherwise play sequence specified + // number of times + if (PR_sscanf(value, "%u", numPlays) != 1) { + return NS_ERROR_INVALID_ARG; + } + + // dispose=[none|background|previous] + } else if (nsCRT::strcmp(token, "dispose") == 0 && frameDispose) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (nsCRT::strcmp(value, "none") == 0) { + *frameDispose = PNG_DISPOSE_OP_NONE; + } else if (nsCRT::strcmp(value, "background") == 0) { + *frameDispose = PNG_DISPOSE_OP_BACKGROUND; + } else if (nsCRT::strcmp(value, "previous") == 0) { + *frameDispose = PNG_DISPOSE_OP_PREVIOUS; + } else { + return NS_ERROR_INVALID_ARG; + } + + // blend=[source|over] + } else if (nsCRT::strcmp(token, "blend") == 0 && frameBlend) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (nsCRT::strcmp(value, "source") == 0) { + *frameBlend = PNG_BLEND_OP_SOURCE; + } else if (nsCRT::strcmp(value, "over") == 0) { + *frameBlend = PNG_BLEND_OP_OVER; + } else { + return NS_ERROR_INVALID_ARG; + } + + // delay=# (in ms) + } else if (nsCRT::strcmp(token, "delay") == 0 && frameDelay) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (PR_sscanf(value, "%u", frameDelay) != 1) { + return NS_ERROR_INVALID_ARG; + } + + // xoffset=# + } else if (nsCRT::strcmp(token, "xoffset") == 0 && offsetX) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (PR_sscanf(value, "%u", offsetX) != 1) { + return NS_ERROR_INVALID_ARG; + } + + // yoffset=# + } else if (nsCRT::strcmp(token, "yoffset") == 0 && offsetY) { + if (!value) { + return NS_ERROR_INVALID_ARG; + } + + if (PR_sscanf(value, "%u", offsetY) != 1) { + return NS_ERROR_INVALID_ARG; + } + + // unknown token name + } else + return NS_ERROR_INVALID_ARG; + + if (value) { + *equals = '='; // restore '=' so strtok doesn't get lost + } + } + +#endif + return NS_OK; +} + + +NS_IMETHODIMP +nsPNGEncoder::Close() +{ + if (mImageBuffer != nullptr) { + free(mImageBuffer); + mImageBuffer = nullptr; + mImageBufferSize = 0; + mImageBufferUsed = 0; + mImageBufferReadPoint = 0; + } + return NS_OK; +} + +NS_IMETHODIMP +nsPNGEncoder::Available(uint64_t* _retval) +{ + if (!mImageBuffer) { + return NS_BASE_STREAM_CLOSED; + } + + *_retval = mImageBufferUsed - mImageBufferReadPoint; + return NS_OK; +} + +NS_IMETHODIMP +nsPNGEncoder::Read(char* aBuf, uint32_t aCount, uint32_t* _retval) +{ + return ReadSegments(NS_CopySegmentToBuffer, aBuf, aCount, _retval); +} + +NS_IMETHODIMP +nsPNGEncoder::ReadSegments(nsWriteSegmentFun aWriter, + void* aClosure, uint32_t aCount, + uint32_t* _retval) +{ + // Avoid another thread reallocing the buffer underneath us + ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); + + uint32_t maxCount = mImageBufferUsed - mImageBufferReadPoint; + if (maxCount == 0) { + *_retval = 0; + return mFinished ? NS_OK : NS_BASE_STREAM_WOULD_BLOCK; + } + + if (aCount > maxCount) { + aCount = maxCount; + } + + nsresult rv = + aWriter(this, aClosure, + reinterpret_cast<const char*>(mImageBuffer+mImageBufferReadPoint), + 0, aCount, _retval); + if (NS_SUCCEEDED(rv)) { + NS_ASSERTION(*_retval <= aCount, "bad write count"); + mImageBufferReadPoint += *_retval; + } + + // errors returned from the writer end here! + return NS_OK; +} + +NS_IMETHODIMP +nsPNGEncoder::IsNonBlocking(bool* _retval) +{ + *_retval = true; + return NS_OK; +} + +NS_IMETHODIMP +nsPNGEncoder::AsyncWait(nsIInputStreamCallback* aCallback, + uint32_t aFlags, + uint32_t aRequestedCount, + nsIEventTarget* aTarget) +{ + if (aFlags != 0) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + if (mCallback || mCallbackTarget) { + return NS_ERROR_UNEXPECTED; + } + + mCallbackTarget = aTarget; + // 0 means "any number of bytes except 0" + mNotifyThreshold = aRequestedCount; + if (!aRequestedCount) { + mNotifyThreshold = 1024; // We don't want to notify incessantly + } + + // We set the callback absolutely last, because NotifyListener uses it to + // determine if someone needs to be notified. If we don't set it last, + // NotifyListener might try to fire off a notification to a null target + // which will generally cause non-threadsafe objects to be used off the main + // thread + mCallback = aCallback; + + // What we are being asked for may be present already + NotifyListener(); + return NS_OK; +} + +NS_IMETHODIMP +nsPNGEncoder::CloseWithStatus(nsresult aStatus) +{ + return Close(); +} + +// nsPNGEncoder::ConvertHostARGBRow +// +// Our colors are stored with premultiplied alphas, but PNGs use +// post-multiplied alpha. This swaps to PNG-style alpha. +// +// Copied from gfx/cairo/cairo/src/cairo-png.c + +void +nsPNGEncoder::ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth, + bool aUseTransparency) +{ + uint32_t pixelStride = aUseTransparency ? 4 : 3; + for (uint32_t x = 0; x < aPixelWidth; x++) { + const uint32_t& pixelIn = ((const uint32_t*)(aSrc))[x]; + uint8_t* pixelOut = &aDest[x * pixelStride]; + + uint8_t alpha = (pixelIn & 0xff000000) >> 24; + pixelOut[pixelStride - 1] = alpha; // overwritten below if pixelStride == 3 + if (alpha == 255) { + pixelOut[0] = (pixelIn & 0xff0000) >> 16; + pixelOut[1] = (pixelIn & 0x00ff00) >> 8; + pixelOut[2] = (pixelIn & 0x0000ff) ; + } else if (alpha == 0) { + pixelOut[0] = pixelOut[1] = pixelOut[2] = 0; + } else { + pixelOut[0] = (((pixelIn & 0xff0000) >> 16) * 255 + alpha / 2) / alpha; + pixelOut[1] = (((pixelIn & 0x00ff00) >> 8) * 255 + alpha / 2) / alpha; + pixelOut[2] = (((pixelIn & 0x0000ff) ) * 255 + alpha / 2) / alpha; + } + } +} + + +// nsPNGEncoder::StripAlpha +// +// Input is RGBA, output is RGB + +void +nsPNGEncoder::StripAlpha(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth) +{ + for (uint32_t x = 0; x < aPixelWidth; x++) { + const uint8_t* pixelIn = &aSrc[x * 4]; + uint8_t* pixelOut = &aDest[x * 3]; + pixelOut[0] = pixelIn[0]; + pixelOut[1] = pixelIn[1]; + pixelOut[2] = pixelIn[2]; + } +} + + +// nsPNGEncoder::WarningCallback + +void +nsPNGEncoder::WarningCallback(png_structp png_ptr, + png_const_charp warning_msg) +{ + MOZ_LOG(sPNGEncoderLog, LogLevel::Warning, + ("libpng warning: %s\n", warning_msg)); +} + + +// nsPNGEncoder::ErrorCallback + +void +nsPNGEncoder::ErrorCallback(png_structp png_ptr, + png_const_charp error_msg) +{ + MOZ_LOG(sPNGEncoderLog, LogLevel::Error, ("libpng error: %s\n", error_msg)); + png_longjmp(png_ptr, 1); +} + +// nsPNGEncoder::WriteCallback + +void // static +nsPNGEncoder::WriteCallback(png_structp png, png_bytep data, + png_size_t size) +{ + nsPNGEncoder* that = static_cast<nsPNGEncoder*>(png_get_io_ptr(png)); + if (!that->mImageBuffer) { + return; + } + + if (that->mImageBufferUsed + size > that->mImageBufferSize) { + // When we're reallocing the buffer we need to take the lock to ensure + // that nobody is trying to read from the buffer we are destroying + ReentrantMonitorAutoEnter autoEnter(that->mReentrantMonitor); + + // expand buffer, just double each time + that->mImageBufferSize *= 2; + uint8_t* newBuf = (uint8_t*)realloc(that->mImageBuffer, + that->mImageBufferSize); + if (!newBuf) { + // can't resize, just zero (this will keep us from writing more) + free(that->mImageBuffer); + that->mImageBuffer = nullptr; + that->mImageBufferSize = 0; + that->mImageBufferUsed = 0; + return; + } + that->mImageBuffer = newBuf; + } + memcpy(&that->mImageBuffer[that->mImageBufferUsed], data, size); + that->mImageBufferUsed += size; + that->NotifyListener(); +} + +void +nsPNGEncoder::NotifyListener() +{ + // We might call this function on multiple threads (any threads that call + // AsyncWait and any that do encoding) so we lock to avoid notifying the + // listener twice about the same data (which generally leads to a truncated + // image). + ReentrantMonitorAutoEnter autoEnter(mReentrantMonitor); + + if (mCallback && + (mImageBufferUsed - mImageBufferReadPoint >= mNotifyThreshold || + mFinished)) { + nsCOMPtr<nsIInputStreamCallback> callback; + if (mCallbackTarget) { + callback = NS_NewInputStreamReadyEvent(mCallback, mCallbackTarget); + } else { + callback = mCallback; + } + + NS_ASSERTION(callback, "Shouldn't fail to make the callback"); + // Null the callback first because OnInputStreamReady could reenter + // AsyncWait + mCallback = nullptr; + mCallbackTarget = nullptr; + mNotifyThreshold = 0; + + callback->OnInputStreamReady(this); + } +} diff --git a/image/encoders/png/nsPNGEncoder.h b/image/encoders/png/nsPNGEncoder.h new file mode 100644 index 000000000..95e7d5c19 --- /dev/null +++ b/image/encoders/png/nsPNGEncoder.h @@ -0,0 +1,83 @@ +/* -*- Mode: C++; tab-width: 2; 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/. */ + +#ifndef nsPNGEncoder_h + +#include <png.h> + +#include "imgIEncoder.h" +#include "nsCOMPtr.h" + +#include "mozilla/Attributes.h" +#include "mozilla/ReentrantMonitor.h" + +#define NS_PNGENCODER_CID \ +{ /* 38d1592e-b81e-432b-86f8-471878bbfe07 */ \ + 0x38d1592e, \ + 0xb81e, \ + 0x432b, \ + {0x86, 0xf8, 0x47, 0x18, 0x78, 0xbb, 0xfe, 0x07} \ +} + +// Provides PNG encoding functionality. Use InitFromData() to do the +// encoding. See that function definition for encoding options. + +class nsPNGEncoder final : public imgIEncoder +{ + typedef mozilla::ReentrantMonitor ReentrantMonitor; +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_IMGIENCODER + NS_DECL_NSIINPUTSTREAM + NS_DECL_NSIASYNCINPUTSTREAM + + nsPNGEncoder(); + +protected: + ~nsPNGEncoder(); + nsresult ParseOptions(const nsAString& aOptions, + bool* useTransparency, + bool* skipFirstFrame, + uint32_t* numAnimatedFrames, + uint32_t* numIterations, + uint32_t* frameDispose, + uint32_t* frameBlend, + uint32_t* frameDelay, + uint32_t* offsetX, + uint32_t* offsetY); + void ConvertHostARGBRow(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth, bool aUseTransparency); + void StripAlpha(const uint8_t* aSrc, uint8_t* aDest, + uint32_t aPixelWidth); + static void WarningCallback(png_structp png_ptr, png_const_charp warning_msg); + static void ErrorCallback(png_structp png_ptr, png_const_charp error_msg); + static void WriteCallback(png_structp png, png_bytep data, png_size_t size); + void NotifyListener(); + + png_struct* mPNG; + png_info* mPNGinfo; + + bool mIsAnimation; + bool mFinished; + + // image buffer + uint8_t* mImageBuffer; + uint32_t mImageBufferSize; + uint32_t mImageBufferUsed; + + uint32_t mImageBufferReadPoint; + + nsCOMPtr<nsIInputStreamCallback> mCallback; + nsCOMPtr<nsIEventTarget> mCallbackTarget; + uint32_t mNotifyThreshold; + + // nsPNGEncoder is designed to allow one thread to pump data into it while + // another reads from it. We lock to ensure that the buffer remains + // append-only while we read from it (that it is not realloced) and to + // ensure that only one thread dispatches a callback for each call to + // AsyncWait. + ReentrantMonitor mReentrantMonitor; +}; +#endif // nsPNGEncoder_h |