summaryrefslogtreecommitdiffstats
path: root/image/decoders/nsICODecoder.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 00:47:55 +0000
commit26a029d407be480d791972afb5975cf62c9360a6 (patch)
treef435a8308119effd964b339f76abb83a57c29483 /image/decoders/nsICODecoder.cpp
parentInitial commit. (diff)
downloadfirefox-upstream/124.0.1.tar.xz
firefox-upstream/124.0.1.zip
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'image/decoders/nsICODecoder.cpp')
-rw-r--r--image/decoders/nsICODecoder.cpp709
1 files changed, 709 insertions, 0 deletions
diff --git a/image/decoders/nsICODecoder.cpp b/image/decoders/nsICODecoder.cpp
new file mode 100644
index 0000000000..ff37355429
--- /dev/null
+++ b/image/decoders/nsICODecoder.cpp
@@ -0,0 +1,709 @@
+/* vim:set tw=80 expandtab softtabstop=2 ts=2 sw=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/. */
+
+/* This is a Cross-Platform ICO Decoder, which should work everywhere, including
+ * Big-Endian machines like the PowerPC. */
+
+#include "nsICODecoder.h"
+
+#include <stdlib.h>
+
+#include <utility>
+
+#include "RasterImage.h"
+#include "mozilla/EndianUtils.h"
+#include "mozilla/gfx/Swizzle.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace image {
+
+// Constants.
+static const uint32_t ICOHEADERSIZE = 6;
+static const uint32_t BITMAPINFOSIZE = bmp::InfoHeaderLength::WIN_ICO;
+
+// ----------------------------------------
+// Actual Data Processing
+// ----------------------------------------
+
+// Obtains the number of colors from the bits per pixel
+uint16_t nsICODecoder::GetNumColors() {
+ uint16_t numColors = 0;
+ if (mBPP <= 8) {
+ switch (mBPP) {
+ case 1:
+ numColors = 2;
+ break;
+ case 4:
+ numColors = 16;
+ break;
+ case 8:
+ numColors = 256;
+ break;
+ default:
+ numColors = (uint16_t)-1;
+ }
+ }
+ return numColors;
+}
+
+nsICODecoder::nsICODecoder(RasterImage* aImage)
+ : Decoder(aImage),
+ mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE),
+ Transition::TerminateSuccess()),
+ mDirEntry(nullptr),
+ mNumIcons(0),
+ mCurrIcon(0),
+ mBPP(0),
+ mMaskRowSize(0),
+ mCurrMaskLine(0),
+ mIsCursor(false),
+ mHasMaskAlpha(false) {}
+
+nsresult nsICODecoder::FinishInternal() {
+ // We shouldn't be called in error cases
+ MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
+
+ return GetFinalStateFromContainedDecoder();
+}
+
+nsresult nsICODecoder::FinishWithErrorInternal() {
+ // No need to assert !mInFrame here because this condition is enforced by
+ // mContainedDecoder.
+ return GetFinalStateFromContainedDecoder();
+}
+
+nsresult nsICODecoder::GetFinalStateFromContainedDecoder() {
+ if (!mContainedDecoder) {
+ return NS_OK;
+ }
+
+ // Let the contained decoder finish up if necessary.
+ FlushContainedDecoder();
+
+ // Make our state the same as the state of the contained decoder.
+ mDecodeDone = mContainedDecoder->GetDecodeDone();
+ mProgress |= mContainedDecoder->TakeProgress();
+ mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
+ mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
+
+ // Finalize the frame which we deferred to ensure we could modify the final
+ // result (e.g. to apply the BMP mask).
+ MOZ_ASSERT(!mContainedDecoder->GetFinalizeFrames());
+ if (mCurrentFrame) {
+ mCurrentFrame->FinalizeSurface();
+ }
+
+ // Propagate errors.
+ nsresult rv =
+ HasError() || mContainedDecoder->HasError() ? NS_ERROR_FAILURE : NS_OK;
+
+ MOZ_ASSERT(NS_FAILED(rv) || !mCurrentFrame || mCurrentFrame->IsFinished());
+ return rv;
+}
+
+LexerTransition<ICOState> nsICODecoder::ReadHeader(const char* aData) {
+ // If the third byte is 1, this is an icon. If 2, a cursor.
+ if ((aData[2] != 1) && (aData[2] != 2)) {
+ return Transition::TerminateFailure();
+ }
+ mIsCursor = (aData[2] == 2);
+
+ // The fifth and sixth bytes specify the number of resources in the file.
+ mNumIcons = LittleEndian::readUint16(aData + 4);
+ if (mNumIcons == 0) {
+ return Transition::TerminateSuccess(); // Nothing to do.
+ }
+
+ // Downscale-during-decode can end up decoding different resources in the ICO
+ // file depending on the target size. Since the resources are not necessarily
+ // scaled versions of the same image, some may be transparent and some may not
+ // be. We could be precise about transparency if we decoded the metadata of
+ // every resource, but for now we don't and it's safest to assume that
+ // transparency could be present.
+ PostHasTransparency();
+
+ return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
+}
+
+size_t nsICODecoder::FirstResourceOffset() const {
+ MOZ_ASSERT(mNumIcons > 0,
+ "Calling FirstResourceOffset before processing header");
+
+ // The first resource starts right after the directory, which starts right
+ // after the ICO header.
+ return ICOHEADERSIZE + mNumIcons * ICODIRENTRYSIZE;
+}
+
+LexerTransition<ICOState> nsICODecoder::ReadDirEntry(const char* aData) {
+ mCurrIcon++;
+
+ // Ensure the resource has an offset past the ICO headers.
+ uint32_t offset = LittleEndian::readUint32(aData + 12);
+ if (offset >= FirstResourceOffset()) {
+ // Read the directory entry.
+ IconDirEntryEx e;
+ e.mWidth = aData[0];
+ e.mHeight = aData[1];
+ e.mColorCount = aData[2];
+ e.mReserved = aData[3];
+ e.mPlanes = LittleEndian::readUint16(aData + 4);
+ e.mBitCount = LittleEndian::readUint16(aData + 6);
+ e.mBytesInRes = LittleEndian::readUint32(aData + 8);
+ e.mImageOffset = offset;
+ e.mSize = OrientedIntSize(e.mWidth, e.mHeight);
+
+ // Only accept entries with sufficient resource data to actually contain
+ // some image data.
+ if (e.mBytesInRes > BITMAPINFOSIZE) {
+ if (e.mWidth == 0 || e.mHeight == 0) {
+ mUnsizedDirEntries.AppendElement(e);
+ } else {
+ mDirEntries.AppendElement(e);
+ }
+ }
+ }
+
+ if (mCurrIcon == mNumIcons) {
+ if (mUnsizedDirEntries.IsEmpty()) {
+ return Transition::To(ICOState::FINISHED_DIR_ENTRY, 0);
+ }
+ return Transition::To(ICOState::ITERATE_UNSIZED_DIR_ENTRY, 0);
+ }
+
+ return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
+}
+
+LexerTransition<ICOState> nsICODecoder::IterateUnsizedDirEntry() {
+ MOZ_ASSERT(!mUnsizedDirEntries.IsEmpty());
+
+ if (!mDirEntry) {
+ // The first time we are here, there is no entry selected. We must prepare a
+ // new iterator for the contained decoder to advance as it wills. Cloning at
+ // this point ensures it will begin at the end of the dir entries.
+ mReturnIterator = mLexer.Clone(*mIterator, SIZE_MAX);
+ if (mReturnIterator.isNothing()) {
+ // If we cannot read further than this point, then there is no resource
+ // data to read.
+ return Transition::TerminateFailure();
+ }
+ } else {
+ // We have already selected an entry which means a metadata decoder has
+ // finished. Verify the size is valid and if so, add to the discovered
+ // resources.
+ if (mDirEntry->mSize.width > 0 && mDirEntry->mSize.height > 0) {
+ mDirEntries.AppendElement(*mDirEntry);
+ }
+
+ // Remove the entry from the unsized list either way.
+ mDirEntry = nullptr;
+ mUnsizedDirEntries.RemoveElementAt(0);
+
+ // Our iterator is at an unknown point, so reset it to the point that we
+ // saved.
+ mIterator = mLexer.Clone(*mReturnIterator, SIZE_MAX);
+ if (mIterator.isNothing()) {
+ MOZ_ASSERT_UNREACHABLE("Cannot re-clone return iterator");
+ return Transition::TerminateFailure();
+ }
+ }
+
+ // There are no more unsized entries, so we can finally decide which entry to
+ // select for decoding.
+ if (mUnsizedDirEntries.IsEmpty()) {
+ mReturnIterator.reset();
+ return Transition::To(ICOState::FINISHED_DIR_ENTRY, 0);
+ }
+
+ // Move to the resource data to start metadata decoding.
+ mDirEntry = &mUnsizedDirEntries[0];
+ size_t offsetToResource = mDirEntry->mImageOffset - FirstResourceOffset();
+ return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE,
+ ICOState::SKIP_TO_RESOURCE, offsetToResource);
+}
+
+LexerTransition<ICOState> nsICODecoder::FinishDirEntry() {
+ MOZ_ASSERT(!mDirEntry);
+
+ if (mDirEntries.IsEmpty()) {
+ return Transition::TerminateFailure();
+ }
+
+ // If an explicit output size was specified, we'll try to select the resource
+ // that matches it best below.
+ const Maybe<OrientedIntSize> desiredSize = ExplicitOutputSize();
+
+ // Determine the biggest resource. We always use the biggest resource for the
+ // intrinsic size, and if we don't have a specific desired size, we select it
+ // as the best resource as well.
+ int32_t bestDelta = INT32_MIN;
+ IconDirEntryEx* biggestEntry = nullptr;
+
+ for (size_t i = 0; i < mDirEntries.Length(); ++i) {
+ IconDirEntryEx& e = mDirEntries[i];
+ mImageMetadata.AddNativeSize(e.mSize);
+
+ if (!biggestEntry ||
+ (e.mBitCount >= biggestEntry->mBitCount &&
+ e.mSize.width * e.mSize.height >=
+ biggestEntry->mSize.width * biggestEntry->mSize.height)) {
+ biggestEntry = &e;
+
+ if (!desiredSize) {
+ mDirEntry = &e;
+ }
+ }
+
+ if (desiredSize) {
+ // Calculate the delta between this resource's size and the desired size,
+ // so we can see if it is better than our current-best option. In the
+ // case of several equally-good resources, we use the last one. "Better"
+ // in this case is determined by |delta|, a measure of the difference in
+ // size between the entry we've found and the desired size. We will choose
+ // the smallest resource that is greater than or equal to the desired size
+ // (i.e. we assume it's better to downscale a larger icon than to upscale
+ // a smaller one).
+ int32_t delta = std::min(e.mSize.width - desiredSize->width,
+ e.mSize.height - desiredSize->height);
+ if (!mDirEntry || (e.mBitCount >= mDirEntry->mBitCount &&
+ ((bestDelta < 0 && delta >= bestDelta) ||
+ (delta >= 0 && delta <= bestDelta)))) {
+ mDirEntry = &e;
+ bestDelta = delta;
+ }
+ }
+ }
+
+ MOZ_ASSERT(mDirEntry);
+ MOZ_ASSERT(biggestEntry);
+
+ // If this is a cursor, set the hotspot. We use the hotspot from the biggest
+ // resource since we also use that resource for the intrinsic size.
+ if (mIsCursor) {
+ mImageMetadata.SetHotspot(biggestEntry->mXHotspot, biggestEntry->mYHotspot);
+ }
+
+ // We always report the biggest resource's size as the intrinsic size; this
+ // is necessary for downscale-during-decode to work since we won't even
+ // attempt to *upscale* while decoding.
+ PostSize(biggestEntry->mSize.width, biggestEntry->mSize.height);
+ if (HasError()) {
+ return Transition::TerminateFailure();
+ }
+
+ if (IsMetadataDecode()) {
+ return Transition::TerminateSuccess();
+ }
+
+ if (mDirEntry->mSize == OutputSize()) {
+ // If the resource we selected matches the output size perfectly, we don't
+ // need to do any downscaling.
+ MOZ_ASSERT_IF(desiredSize, mDirEntry->mSize == *desiredSize);
+ MOZ_ASSERT_IF(!desiredSize, mDirEntry->mSize == Size());
+ } else if (OutputSize().width < mDirEntry->mSize.width ||
+ OutputSize().height < mDirEntry->mSize.height) {
+ // Create a downscaler if we need to downscale.
+ //
+ // TODO(aosmond): This is the last user of Downscaler. We should switch this
+ // to SurfacePipe as well so we can remove the code from tree.
+ mDownscaler.emplace(OutputSize().ToUnknownSize());
+ }
+
+ size_t offsetToResource = mDirEntry->mImageOffset - FirstResourceOffset();
+ return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE,
+ ICOState::SKIP_TO_RESOURCE, offsetToResource);
+}
+
+LexerTransition<ICOState> nsICODecoder::SniffResource(const char* aData) {
+ MOZ_ASSERT(mDirEntry);
+
+ // We have BITMAPINFOSIZE bytes buffered at this point. We know an embedded
+ // BMP will have at least that many bytes by definition. We can also infer
+ // that any valid embedded PNG will contain that many bytes as well because:
+ // BITMAPINFOSIZE
+ // <
+ // signature (8 bytes) +
+ // IHDR (12 bytes header + 13 bytes data)
+ // IDAT (12 bytes header)
+
+ // We use the first PNGSIGNATURESIZE bytes to determine whether this resource
+ // is a PNG or a BMP.
+ bool isPNG =
+ !memcmp(aData, nsPNGDecoder::pngSignatureBytes, PNGSIGNATURESIZE);
+ if (isPNG) {
+ if (mDirEntry->mBytesInRes <= BITMAPINFOSIZE) {
+ return Transition::TerminateFailure();
+ }
+
+ // Prepare a new iterator for the contained decoder to advance as it wills.
+ // Cloning at the point ensures it will begin at the resource offset.
+ Maybe<SourceBufferIterator> containedIterator =
+ mLexer.Clone(*mIterator, mDirEntry->mBytesInRes);
+ if (containedIterator.isNothing()) {
+ return Transition::TerminateFailure();
+ }
+
+ // Create a PNG decoder which will do the rest of the work for us.
+ bool metadataDecode = mReturnIterator.isSome();
+ Maybe<OrientedIntSize> expectedSize =
+ metadataDecode ? Nothing() : Some(mDirEntry->mSize);
+ mContainedDecoder = DecoderFactory::CreateDecoderForICOResource(
+ DecoderType::PNG, std::move(containedIterator.ref()), WrapNotNull(this),
+ metadataDecode, expectedSize);
+
+ // Read in the rest of the PNG unbuffered.
+ size_t toRead = mDirEntry->mBytesInRes - BITMAPINFOSIZE;
+ return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
+ ICOState::READ_RESOURCE, toRead);
+ }
+
+ // Make sure we have a sane size for the bitmap information header.
+ int32_t bihSize = LittleEndian::readUint32(aData);
+ if (bihSize != static_cast<int32_t>(BITMAPINFOSIZE)) {
+ return Transition::TerminateFailure();
+ }
+
+ // Read in the rest of the bitmap information header.
+ return ReadBIH(aData);
+}
+
+LexerTransition<ICOState> nsICODecoder::ReadResource() {
+ if (!FlushContainedDecoder()) {
+ return Transition::TerminateFailure();
+ }
+
+ return Transition::ContinueUnbuffered(ICOState::READ_RESOURCE);
+}
+
+LexerTransition<ICOState> nsICODecoder::ReadBIH(const char* aData) {
+ MOZ_ASSERT(mDirEntry);
+
+ // Extract the BPP from the BIH header; it should be trusted over the one
+ // we have from the ICO header which is usually set to 0.
+ mBPP = LittleEndian::readUint16(aData + 14);
+
+ // Check to make sure we have valid color settings.
+ uint16_t numColors = GetNumColors();
+ if (numColors == uint16_t(-1)) {
+ return Transition::TerminateFailure();
+ }
+
+ // The color table is present only if BPP is <= 8.
+ MOZ_ASSERT_IF(mBPP > 8, numColors == 0);
+
+ // The ICO format when containing a BMP does not include the 14 byte
+ // bitmap file header. So we create the BMP decoder via the constructor that
+ // tells it to skip this, and pass in the required data (dataOffset) that
+ // would have been present in the header.
+ uint32_t dataOffset =
+ bmp::FILE_HEADER_LENGTH + BITMAPINFOSIZE + 4 * numColors;
+
+ // Prepare a new iterator for the contained decoder to advance as it wills.
+ // Cloning at the point ensures it will begin at the resource offset.
+ Maybe<SourceBufferIterator> containedIterator =
+ mLexer.Clone(*mIterator, mDirEntry->mBytesInRes);
+ if (containedIterator.isNothing()) {
+ return Transition::TerminateFailure();
+ }
+
+ // Create a BMP decoder which will do most of the work for us; the exception
+ // is the AND mask, which isn't present in standalone BMPs.
+ bool metadataDecode = mReturnIterator.isSome();
+ Maybe<OrientedIntSize> expectedSize =
+ metadataDecode ? Nothing() : Some(mDirEntry->mSize);
+ mContainedDecoder = DecoderFactory::CreateDecoderForICOResource(
+ DecoderType::BMP, std::move(containedIterator.ref()), WrapNotNull(this),
+ metadataDecode, expectedSize, Some(dataOffset));
+
+ RefPtr<nsBMPDecoder> bmpDecoder =
+ static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+
+ // Ensure the decoder has parsed at least the BMP's bitmap info header.
+ if (!FlushContainedDecoder()) {
+ return Transition::TerminateFailure();
+ }
+
+ // If this is a metadata decode, FinishResource will any necessary checks.
+ if (mContainedDecoder->IsMetadataDecode()) {
+ return Transition::To(ICOState::FINISHED_RESOURCE, 0);
+ }
+
+ // Do we have an AND mask on this BMP? If so, we need to read it after we read
+ // the BMP data itself.
+ uint32_t bmpDataLength = bmpDecoder->GetCompressedImageSize() + 4 * numColors;
+ bool hasANDMask = (BITMAPINFOSIZE + bmpDataLength) < mDirEntry->mBytesInRes;
+ ICOState afterBMPState =
+ hasANDMask ? ICOState::PREPARE_FOR_MASK : ICOState::FINISHED_RESOURCE;
+
+ // Read in the rest of the BMP unbuffered.
+ return Transition::ToUnbuffered(afterBMPState, ICOState::READ_RESOURCE,
+ bmpDataLength);
+}
+
+LexerTransition<ICOState> nsICODecoder::PrepareForMask() {
+ MOZ_ASSERT(mDirEntry);
+ MOZ_ASSERT(mContainedDecoder->GetDecodeDone());
+
+ // We have received all of the data required by the BMP decoder so flushing
+ // here guarantees the decode has finished.
+ if (!FlushContainedDecoder()) {
+ return Transition::TerminateFailure();
+ }
+
+ MOZ_ASSERT(mContainedDecoder->GetDecodeDone());
+
+ RefPtr<nsBMPDecoder> bmpDecoder =
+ static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+
+ uint16_t numColors = GetNumColors();
+ MOZ_ASSERT(numColors != uint16_t(-1));
+
+ // Determine the length of the AND mask.
+ uint32_t bmpLengthWithHeader =
+ BITMAPINFOSIZE + bmpDecoder->GetCompressedImageSize() + 4 * numColors;
+ MOZ_ASSERT(bmpLengthWithHeader < mDirEntry->mBytesInRes);
+ uint32_t maskLength = mDirEntry->mBytesInRes - bmpLengthWithHeader;
+
+ // If the BMP provides its own transparency, we ignore the AND mask.
+ if (bmpDecoder->HasTransparency()) {
+ return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
+ ICOState::SKIP_MASK, maskLength);
+ }
+
+ // Compute the row size for the mask.
+ mMaskRowSize = ((mDirEntry->mSize.width + 31) / 32) * 4; // + 31 to round up
+
+ // If the expected size of the AND mask is larger than its actual size, then
+ // we must have a truncated (and therefore corrupt) AND mask.
+ uint32_t expectedLength = mMaskRowSize * mDirEntry->mSize.height;
+ if (maskLength < expectedLength) {
+ return Transition::TerminateFailure();
+ }
+
+ // If we're downscaling, the mask is the wrong size for the surface we've
+ // produced, so we need to downscale the mask into a temporary buffer and then
+ // combine the mask's alpha values with the color values from the image.
+ if (mDownscaler) {
+ MOZ_ASSERT(bmpDecoder->GetImageDataLength() ==
+ mDownscaler->TargetSize().width *
+ mDownscaler->TargetSize().height * sizeof(uint32_t));
+ mMaskBuffer =
+ MakeUniqueFallible<uint8_t[]>(bmpDecoder->GetImageDataLength());
+ if (NS_WARN_IF(!mMaskBuffer)) {
+ return Transition::TerminateFailure();
+ }
+ nsresult rv = mDownscaler->BeginFrame(mDirEntry->mSize.ToUnknownSize(),
+ Nothing(), mMaskBuffer.get(),
+ /* aHasAlpha = */ true,
+ /* aFlipVertically = */ true);
+ if (NS_FAILED(rv)) {
+ return Transition::TerminateFailure();
+ }
+ }
+
+ mCurrMaskLine = mDirEntry->mSize.height;
+ return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
+}
+
+LexerTransition<ICOState> nsICODecoder::ReadMaskRow(const char* aData) {
+ MOZ_ASSERT(mDirEntry);
+
+ mCurrMaskLine--;
+
+ uint8_t sawTransparency = 0;
+
+ // Get the mask row we're reading.
+ const uint8_t* mask = reinterpret_cast<const uint8_t*>(aData);
+ const uint8_t* maskRowEnd = mask + mMaskRowSize;
+
+ // Get the corresponding row of the mask buffer (if we're downscaling) or the
+ // decoded image data (if we're not).
+ uint32_t* decoded = nullptr;
+ if (mDownscaler) {
+ // Initialize the row to all white and fully opaque.
+ memset(mDownscaler->RowBuffer(), 0xFF,
+ mDirEntry->mSize.width * sizeof(uint32_t));
+
+ decoded = reinterpret_cast<uint32_t*>(mDownscaler->RowBuffer());
+ } else {
+ RefPtr<nsBMPDecoder> bmpDecoder =
+ static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+ uint32_t* imageData = bmpDecoder->GetImageData();
+ if (!imageData) {
+ return Transition::TerminateFailure();
+ }
+
+ decoded = imageData + mCurrMaskLine * mDirEntry->mSize.width;
+ }
+
+ MOZ_ASSERT(decoded);
+ uint32_t* decodedRowEnd = decoded + mDirEntry->mSize.width;
+
+ // Iterate simultaneously through the AND mask and the image data.
+ while (mask < maskRowEnd) {
+ uint8_t idx = *mask++;
+ sawTransparency |= idx;
+ for (uint8_t bit = 0x80; bit && decoded < decodedRowEnd; bit >>= 1) {
+ // Clear pixel completely for transparency.
+ if (idx & bit) {
+ *decoded = 0;
+ }
+ decoded++;
+ }
+ }
+
+ if (mDownscaler) {
+ mDownscaler->CommitRow();
+ }
+
+ // If any bits are set in sawTransparency, then we know at least one pixel was
+ // transparent.
+ if (sawTransparency) {
+ mHasMaskAlpha = true;
+ }
+
+ if (mCurrMaskLine == 0) {
+ return Transition::To(ICOState::FINISH_MASK, 0);
+ }
+
+ return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
+}
+
+LexerTransition<ICOState> nsICODecoder::FinishMask() {
+ // If we're downscaling, we now have the appropriate alpha values in
+ // mMaskBuffer. We just need to transfer them to the image.
+ if (mDownscaler) {
+ // Retrieve the image data.
+ RefPtr<nsBMPDecoder> bmpDecoder =
+ static_cast<nsBMPDecoder*>(mContainedDecoder.get());
+ uint8_t* imageData = reinterpret_cast<uint8_t*>(bmpDecoder->GetImageData());
+ if (!imageData) {
+ return Transition::TerminateFailure();
+ }
+
+ // Iterate through the alpha values, copying from mask to image.
+ MOZ_ASSERT(mMaskBuffer);
+ MOZ_ASSERT(bmpDecoder->GetImageDataLength() > 0);
+ for (size_t i = 3; i < bmpDecoder->GetImageDataLength(); i += 4) {
+ imageData[i] = mMaskBuffer[i];
+ }
+ int32_t stride = mDownscaler->TargetSize().width * sizeof(uint32_t);
+ DebugOnly<bool> ret =
+ // We know the format is OS_RGBA because we always assume bmp's inside
+ // ico's are transparent.
+ PremultiplyData(imageData, stride, SurfaceFormat::OS_RGBA, imageData,
+ stride, SurfaceFormat::OS_RGBA,
+ mDownscaler->TargetSize());
+ MOZ_ASSERT(ret);
+ }
+
+ return Transition::To(ICOState::FINISHED_RESOURCE, 0);
+}
+
+LexerTransition<ICOState> nsICODecoder::FinishResource() {
+ MOZ_ASSERT(mDirEntry);
+
+ // We have received all of the data required by the PNG/BMP decoder so
+ // flushing here guarantees the decode has finished.
+ if (!FlushContainedDecoder()) {
+ return Transition::TerminateFailure();
+ }
+
+ MOZ_ASSERT(mContainedDecoder->GetDecodeDone());
+
+ // If it is a metadata decode, all we were trying to get was the size
+ // information missing from the dir entry.
+ if (mContainedDecoder->IsMetadataDecode()) {
+ if (mContainedDecoder->HasSize()) {
+ mDirEntry->mSize = mContainedDecoder->Size();
+ }
+ return Transition::To(ICOState::ITERATE_UNSIZED_DIR_ENTRY, 0);
+ }
+
+ // Raymond Chen says that 32bpp only are valid PNG ICOs
+ // http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
+ if (!mContainedDecoder->IsValidICOResource()) {
+ return Transition::TerminateFailure();
+ }
+
+ // This size from the resource should match that from the dir entry.
+ MOZ_ASSERT_IF(mContainedDecoder->HasSize(),
+ mContainedDecoder->Size() == mDirEntry->mSize);
+
+ return Transition::TerminateSuccess();
+}
+
+LexerResult nsICODecoder::DoDecode(SourceBufferIterator& aIterator,
+ IResumable* aOnResume) {
+ MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
+
+ return mLexer.Lex(
+ aIterator, aOnResume,
+ [=](ICOState aState, const char* aData, size_t aLength) {
+ switch (aState) {
+ case ICOState::HEADER:
+ return ReadHeader(aData);
+ case ICOState::DIR_ENTRY:
+ return ReadDirEntry(aData);
+ case ICOState::FINISHED_DIR_ENTRY:
+ return FinishDirEntry();
+ case ICOState::ITERATE_UNSIZED_DIR_ENTRY:
+ return IterateUnsizedDirEntry();
+ case ICOState::SKIP_TO_RESOURCE:
+ return Transition::ContinueUnbuffered(ICOState::SKIP_TO_RESOURCE);
+ case ICOState::FOUND_RESOURCE:
+ return Transition::To(ICOState::SNIFF_RESOURCE, BITMAPINFOSIZE);
+ case ICOState::SNIFF_RESOURCE:
+ return SniffResource(aData);
+ case ICOState::READ_RESOURCE:
+ return ReadResource();
+ case ICOState::PREPARE_FOR_MASK:
+ return PrepareForMask();
+ case ICOState::READ_MASK_ROW:
+ return ReadMaskRow(aData);
+ case ICOState::FINISH_MASK:
+ return FinishMask();
+ case ICOState::SKIP_MASK:
+ return Transition::ContinueUnbuffered(ICOState::SKIP_MASK);
+ case ICOState::FINISHED_RESOURCE:
+ return FinishResource();
+ default:
+ MOZ_CRASH("Unknown ICOState");
+ }
+ });
+}
+
+bool nsICODecoder::FlushContainedDecoder() {
+ MOZ_ASSERT(mContainedDecoder);
+
+ bool succeeded = true;
+
+ // If we run out of data, the ICO decoder will get resumed when there's more
+ // data available, as usual, so we don't need the contained decoder to get
+ // resumed too. To avoid that, we provide an IResumable which just does
+ // nothing. All the caller needs to do is flush when there is new data.
+ LexerResult result = mContainedDecoder->Decode();
+ if (result == LexerResult(TerminalState::FAILURE)) {
+ succeeded = false;
+ }
+
+ MOZ_ASSERT(result != LexerResult(Yield::OUTPUT_AVAILABLE),
+ "Unexpected yield");
+
+ // Make our state the same as the state of the contained decoder, and
+ // propagate errors.
+ mProgress |= mContainedDecoder->TakeProgress();
+ mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
+ if (mContainedDecoder->HasError()) {
+ succeeded = false;
+ }
+
+ return succeeded;
+}
+
+} // namespace image
+} // namespace mozilla