mirror of
				https://github.com/mozilla/gecko-dev.git
				synced 2025-11-04 02:09:05 +02:00 
			
		
		
		
	
		
			
				
	
	
		
			709 lines
		
	
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			709 lines
		
	
	
	
		
			25 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/* 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
 |