diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /image/decoders/nsICODecoder.cpp | |
parent | Initial commit. (diff) | |
download | firefox-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.cpp | 709 |
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 |