Bug 1196066 (Part 3) - Rewrite nsICODecoder to use StreamingLexer. r=tn

This commit is contained in:
Seth Fowler 2015-09-18 10:54:27 -07:00
parent c689ff08ee
commit 9e36930eb8
2 changed files with 428 additions and 401 deletions

View File

@ -17,10 +17,10 @@
namespace mozilla {
namespace image {
#define ICONCOUNTOFFSET 4
#define DIRENTRYOFFSET 6
#define BITMAPINFOSIZE 40
#define PREFICONSIZE 16
// Constants.
static const uint32_t ICOHEADERSIZE = 6;
static const uint32_t BITMAPINFOSIZE = 40;
static const uint32_t PREFICONSIZE = 16;
// ----------------------------------------
// Actual Data Processing
@ -57,22 +57,17 @@ nsICODecoder::GetNumColors()
return numColors;
}
nsICODecoder::nsICODecoder(RasterImage* aImage)
: Decoder(aImage)
{
mPos = mImageOffset = mCurrIcon = mNumIcons = mBPP = mRowBytes = 0;
mIsPNG = false;
mRow = nullptr;
mOldLine = mCurLine = 1; // Otherwise decoder will never start
}
nsICODecoder::~nsICODecoder()
{
if (mRow) {
free(mRow);
}
}
: Decoder(aImage)
, mLexer(Transition::To(ICOState::HEADER, ICOHEADERSIZE))
, mBestResourceDelta(INT_MIN)
, mBestResourceColorDepth(0)
, mNumIcons(0)
, mCurrIcon(0)
, mBPP(0)
, mMaskRowSize(0)
, mCurrMaskLine(0)
{ }
void
nsICODecoder::FinishInternal()
@ -80,11 +75,6 @@ nsICODecoder::FinishInternal()
// We shouldn't be called in error cases
MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
// Finish the internally used decoder as well.
if (mContainedDecoder && !mContainedDecoder->HasError()) {
mContainedDecoder->FinishInternal();
}
GetFinalStateFromContainedDecoder();
}
@ -101,6 +91,9 @@ nsICODecoder::GetFinalStateFromContainedDecoder()
return;
}
// Finish the internally used decoder.
mContainedDecoder->CompleteDecode();
mDecodeDone = mContainedDecoder->GetDecodeDone();
mDataError = mDataError || mContainedDecoder->HasDataError();
mFailCode = NS_SUCCEEDED(mFailCode) ? mContainedDecoder->GetDecoderError()
@ -109,6 +102,8 @@ nsICODecoder::GetFinalStateFromContainedDecoder()
mProgress |= mContainedDecoder->TakeProgress();
mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
mCurrentFrame = mContainedDecoder->GetCurrentFrameRef();
MOZ_ASSERT(HasError() || !mCurrentFrame || mCurrentFrame->IsImageComplete());
}
// Returns a buffer filled with the bitmap file header in little endian:
@ -206,10 +201,11 @@ nsICODecoder::FixBitmapWidth(int8_t* bih)
}
// The BMP information header's bits per pixel should be trusted
// more than what we have. Usually the ICO's BPP is set to 0
// more than what we have. Usually the ICO's BPP is set to 0.
int32_t
nsICODecoder::ExtractBPPFromBitmap(int8_t* bih)
nsICODecoder::ReadBPP(const char* aBIH)
{
const int8_t* bih = reinterpret_cast<const int8_t*>(aBIH);
int32_t bitsPerPixel;
memcpy(&bitsPerPixel, bih + 14, sizeof(bitsPerPixel));
NativeEndian::swapFromLittleEndianInPlace(&bitsPerPixel, 1);
@ -217,8 +213,9 @@ nsICODecoder::ExtractBPPFromBitmap(int8_t* bih)
}
int32_t
nsICODecoder::ExtractBIHSizeFromBitmap(int8_t* bih)
nsICODecoder::ReadBIHSize(const char* aBIH)
{
const int8_t* bih = reinterpret_cast<const int8_t*>(aBIH);
int32_t headerSize;
memcpy(&headerSize, bih, sizeof(headerSize));
NativeEndian::swapFromLittleEndianInPlace(&headerSize, 1);
@ -235,205 +232,126 @@ nsICODecoder::SetHotSpotIfCursor()
mImageMetadata.SetHotspot(mDirEntry.mXHotspot, mDirEntry.mYHotspot);
}
void
nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
LexerTransition<ICOState>
nsICODecoder::ReadHeader(const char* aData)
{
MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
MOZ_ASSERT(aBuffer);
MOZ_ASSERT(aCount > 0);
while (aCount && (mPos < ICONCOUNTOFFSET)) { // Skip to the # of icons.
if (mPos == 2) { // if the third byte is 1: This is an icon, 2: a cursor
if ((*aBuffer != 1) && (*aBuffer != 2)) {
PostDataError();
return;
}
mIsCursor = (*aBuffer == 2);
}
mPos++; aBuffer++; aCount--;
}
if (mPos == ICONCOUNTOFFSET && aCount >= 2) {
mNumIcons =
LittleEndian::readUint16(reinterpret_cast<const uint16_t*>(aBuffer));
aBuffer += 2;
mPos += 2;
aCount -= 2;
// If the third byte is 1, this is an icon. If 2, a cursor.
if ((aData[2] != 1) && (aData[2] != 2)) {
return Transition::Terminate(ICOState::FAILURE);
}
mIsCursor = (aData[2] == 2);
// The fifth and sixth bytes specify the number of resources in the file.
mNumIcons =
LittleEndian::readUint16(reinterpret_cast<const uint16_t*>(aData + 4));
if (mNumIcons == 0) {
return; // Nothing to do.
return Transition::Terminate(ICOState::SUCCESS); // Nothing to do.
}
uint16_t colorDepth = 0;
// If we didn't get a #-moz-resolution, default to PREFICONSIZE.
if (mResolution.width == 0 && mResolution.height == 0) {
mResolution.SizeTo(PREFICONSIZE, PREFICONSIZE);
}
// A measure of the difference in size between the entry we've found
// and the requested size. We will choose the smallest image that is
// >= requested size (i.e. we assume it's better to downscale a larger
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++;
// Read the directory entry.
IconDirEntry e;
memset(&e, 0, sizeof(e));
memcpy(&e.mWidth, aData, sizeof(e.mWidth));
memcpy(&e.mHeight, aData + 1, sizeof(e.mHeight));
memcpy(&e.mColorCount, aData + 2, sizeof(e.mColorCount));
memcpy(&e.mReserved, aData + 3, sizeof(e.mReserved));
memcpy(&e.mPlanes, aData + 4, sizeof(e.mPlanes));
e.mPlanes = LittleEndian::readUint16(&e.mPlanes);
memcpy(&e.mBitCount, aData + 6, sizeof(e.mBitCount));
e.mBitCount = LittleEndian::readUint16(&e.mBitCount);
memcpy(&e.mBytesInRes, aData + 8, sizeof(e.mBytesInRes));
e.mBytesInRes = LittleEndian::readUint32(&e.mBytesInRes);
memcpy(&e.mImageOffset, aData + 12, sizeof(e.mImageOffset));
e.mImageOffset = LittleEndian::readUint32(&e.mImageOffset);
// Calculate the delta between this image'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 images, 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 requested size. We will choose the smallest image
// that is >= requested size (i.e. we assume it's better to downscale a larger
// icon than to upscale a smaller one).
int32_t diff = INT_MIN;
int32_t delta = GetRealWidth(e) - mResolution.width +
GetRealHeight(e) - mResolution.height;
if (e.mBitCount >= mBestResourceColorDepth &&
((mBestResourceDelta < 0 && delta >= mBestResourceDelta) ||
(delta >= 0 && delta <= mBestResourceDelta))) {
mBestResourceDelta = delta;
// Loop through each entry's dir entry
while (mCurrIcon < mNumIcons) {
if (mPos >= DIRENTRYOFFSET + (mCurrIcon * sizeof(mDirEntryArray)) &&
mPos < DIRENTRYOFFSET + ((mCurrIcon + 1) * sizeof(mDirEntryArray))) {
uint32_t toCopy = sizeof(mDirEntryArray) -
(mPos - DIRENTRYOFFSET - mCurrIcon *
sizeof(mDirEntryArray));
if (toCopy > aCount) {
toCopy = aCount;
}
memcpy(mDirEntryArray + sizeof(mDirEntryArray) - toCopy, aBuffer, toCopy);
mPos += toCopy;
aCount -= toCopy;
aBuffer += toCopy;
}
if (aCount == 0) {
return; // Need more data
// Ensure mImageOffset is >= size of the direntry headers (bug #245631).
if (e.mImageOffset < FirstResourceOffset()) {
return Transition::Terminate(ICOState::FAILURE);
}
IconDirEntry e;
if (mPos == (DIRENTRYOFFSET + ICODIRENTRYSIZE) +
(mCurrIcon * sizeof(mDirEntryArray))) {
mCurrIcon++;
ProcessDirEntry(e);
// We can't use GetRealWidth and GetRealHeight here because those operate
// on mDirEntry, here we are going through each item in the directory.
// Calculate the delta between this image'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 images, we use the last one.
int32_t delta = (e.mWidth == 0 ? 256 : e.mWidth) - mResolution.width +
(e.mHeight == 0 ? 256 : e.mHeight) - mResolution.height;
if (e.mBitCount >= colorDepth &&
((diff < 0 && delta >= diff) || (delta >= 0 && delta <= diff))) {
diff = delta;
mImageOffset = e.mImageOffset;
// ensure mImageOffset is >= size of the direntry headers (bug #245631)
uint32_t minImageOffset = DIRENTRYOFFSET +
mNumIcons * sizeof(mDirEntryArray);
if (mImageOffset < minImageOffset) {
PostDataError();
return;
}
colorDepth = e.mBitCount;
memcpy(&mDirEntry, &e, sizeof(IconDirEntry));
}
}
mBestResourceColorDepth = e.mBitCount;
mDirEntry = e;
}
if (mPos < mImageOffset) {
// Skip to (or at least towards) the desired image offset
uint32_t toSkip = mImageOffset - mPos;
if (toSkip > aCount) {
toSkip = aCount;
}
mPos += toSkip;
aBuffer += toSkip;
aCount -= toSkip;
if (mCurrIcon == mNumIcons) {
size_t offsetToResource = mDirEntry.mImageOffset - FirstResourceOffset();
return Transition::ToUnbuffered(ICOState::FOUND_RESOURCE,
ICOState::SKIP_TO_RESOURCE,
offsetToResource);
}
// If we are within the first PNGSIGNATURESIZE bytes of the image data,
// then we have either a BMP or a PNG. We use the first PNGSIGNATURESIZE
// bytes to determine which one we have.
if (mCurrIcon == mNumIcons && mPos >= mImageOffset &&
mPos < mImageOffset + PNGSIGNATURESIZE) {
uint32_t toCopy = PNGSIGNATURESIZE - (mPos - mImageOffset);
if (toCopy > aCount) {
toCopy = aCount;
return Transition::To(ICOState::DIR_ENTRY, ICODIRENTRYSIZE);
}
LexerTransition<ICOState>
nsICODecoder::SniffResource(const char* aData)
{
// 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) {
// Create a PNG decoder which will do the rest of the work for us.
mContainedDecoder = new nsPNGDecoder(mImage);
mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
mContainedDecoder->Init();
if (!WriteToContainedDecoder(aData, PNGSIGNATURESIZE)) {
return Transition::Terminate(ICOState::FAILURE);
}
memcpy(mSignature + (mPos - mImageOffset), aBuffer, toCopy);
mPos += toCopy;
aCount -= toCopy;
aBuffer += toCopy;
mIsPNG = !memcmp(mSignature, nsPNGDecoder::pngSignatureBytes,
PNGSIGNATURESIZE);
if (mIsPNG) {
mContainedDecoder = new nsPNGDecoder(mImage);
mContainedDecoder->SetMetadataDecode(IsMetadataDecode());
mContainedDecoder->SetDecoderFlags(GetDecoderFlags());
mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
mContainedDecoder->Init();
if (!WriteToContainedDecoder(mSignature, PNGSIGNATURESIZE)) {
return;
}
}
}
// If we have a PNG, let the PNG decoder do all of the rest of the work
if (mIsPNG && mContainedDecoder && mPos >= mImageOffset + PNGSIGNATURESIZE) {
if (!WriteToContainedDecoder(aBuffer, aCount)) {
return;
if (mDirEntry.mBytesInRes <= PNGSIGNATURESIZE) {
return Transition::Terminate(ICOState::FAILURE);
}
if (!HasSize() && mContainedDecoder->HasSize()) {
nsIntSize size = mContainedDecoder->GetSize();
PostSize(size.width, size.height);
}
mPos += aCount;
aBuffer += aCount;
aCount = 0;
// Raymond Chen says that 32bpp only are valid PNG ICOs
// http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
if (!IsMetadataDecode() &&
!static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
PostDataError();
}
return;
}
// We've processed all of the icon dir entries and are within the
// bitmap info size
if (!mIsPNG && mCurrIcon == mNumIcons && mPos >= mImageOffset &&
mPos >= mImageOffset + PNGSIGNATURESIZE &&
mPos < mImageOffset + BITMAPINFOSIZE) {
// As we were decoding, we did not know if we had a PNG signature or the
// start of a bitmap information header. At this point we know we had
// a bitmap information header and not a PNG signature, so fill the bitmap
// information header with the data it should already have.
memcpy(mBIHraw, mSignature, PNGSIGNATURESIZE);
// We've found the icon.
uint32_t toCopy = sizeof(mBIHraw) - (mPos - mImageOffset);
if (toCopy > aCount) {
toCopy = aCount;
}
memcpy(mBIHraw + (mPos - mImageOffset), aBuffer, toCopy);
mPos += toCopy;
aCount -= toCopy;
aBuffer += toCopy;
}
// If we have a BMP inside the ICO and we have read the BIH header
if (!mIsPNG && mPos == mImageOffset + BITMAPINFOSIZE) {
// Make sure we have a sane value for the bitmap information header
int32_t bihSize = ExtractBIHSizeFromBitmap(reinterpret_cast<int8_t*>
(mBIHraw));
if (bihSize != BITMAPINFOSIZE) {
PostDataError();
return;
}
// We are extracting the BPP from the BIH header as it should be trusted
// over the one we have from the icon header
mBPP = ExtractBPPFromBitmap(reinterpret_cast<int8_t*>(mBIHraw));
// Init the bitmap decoder which will do most of the work for us
// It will do everything except the AND mask which isn't present in bitmaps
// bmpDecoder is for local scope ease, it will be freed by mContainedDecoder
// Read in the rest of the PNG unbuffered.
size_t toRead = mDirEntry.mBytesInRes - PNGSIGNATURESIZE;
return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
ICOState::READ_PNG,
toRead);
} else {
// 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.
nsBMPDecoder* bmpDecoder = new nsBMPDecoder(mImage);
mContainedDecoder = bmpDecoder;
bmpDecoder->SetUseAlphaData(true);
@ -442,169 +360,271 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
mContainedDecoder->SetSurfaceFlags(GetSurfaceFlags());
mContainedDecoder->Init();
// The ICO format when containing a BMP does not include the 14 byte
// bitmap file header. To use the code of the BMP decoder we need to
// generate this header ourselves and feed it to the BMP decoder.
int8_t bfhBuffer[BMPFILEHEADERSIZE];
if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
PostDataError();
return;
}
if (!WriteToContainedDecoder((const char*)bfhBuffer, sizeof(bfhBuffer))) {
return;
// Make sure we have a sane size for the bitmap information header.
int32_t bihSize = ReadBIHSize(aData);
if (bihSize != static_cast<int32_t>(BITMAPINFOSIZE)) {
return Transition::Terminate(ICOState::FAILURE);
}
// Setup the cursor hot spot if one is present
SetHotSpotIfCursor();
// Buffer the first part of the bitmap information header.
memcpy(mBIHraw, aData, PNGSIGNATURESIZE);
// Fix the ICO height from the BIH.
// Fix the height on the BIH to be /2 so our BMP decoder will understand.
if (!FixBitmapHeight(reinterpret_cast<int8_t*>(mBIHraw))) {
PostDataError();
return;
}
// Read in the rest of the bitmap information header.
return Transition::To(ICOState::READ_BIH,
BITMAPINFOSIZE - PNGSIGNATURESIZE);
}
}
// Fix the ICO width from the BIH.
if (!FixBitmapWidth(reinterpret_cast<int8_t*>(mBIHraw))) {
PostDataError();
return;
}
// Write out the BMP's bitmap info header
if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
return;
}
LexerTransition<ICOState>
nsICODecoder::ReadPNG(const char* aData, uint32_t aLen)
{
if (!WriteToContainedDecoder(aData, aLen)) {
return Transition::Terminate(ICOState::FAILURE);
}
if (!HasSize() && mContainedDecoder->HasSize()) {
nsIntSize size = mContainedDecoder->GetSize();
PostSize(size.width, size.height);
// We have the size. If we're doing a metadata decode, we're done.
if (IsMetadataDecode()) {
return;
}
// Sometimes the ICO BPP header field is not filled out
// so we should trust the contained resource over our own
// information.
mBPP = bmpDecoder->GetBitsPerPixel();
// Check to make sure we have valid color settings
uint16_t numColors = GetNumColors();
if (numColors == (uint16_t)-1) {
PostDataError();
return;
return Transition::Terminate(ICOState::SUCCESS);
}
}
// If we have a BMP
if (!mIsPNG && mContainedDecoder && mPos >= mImageOffset + BITMAPINFOSIZE) {
uint16_t numColors = GetNumColors();
if (numColors == (uint16_t)-1) {
PostDataError();
return;
}
// Feed the actual image data (not including headers) into the BMP decoder
uint32_t bmpDataOffset = mDirEntry.mImageOffset + BITMAPINFOSIZE;
uint32_t bmpDataEnd = mDirEntry.mImageOffset + BITMAPINFOSIZE +
static_cast<nsBMPDecoder*>(mContainedDecoder.get())->
GetCompressedImageSize() +
4 * numColors;
// Raymond Chen says that 32bpp only are valid PNG ICOs
// http://blogs.msdn.com/b/oldnewthing/archive/2010/10/22/10079192.aspx
if (!IsMetadataDecode() &&
!static_cast<nsPNGDecoder*>(mContainedDecoder.get())->IsValidICO()) {
return Transition::Terminate(ICOState::FAILURE);
}
// If we are feeding in the core image data, but we have not yet
// reached the ICO's 'AND buffer mask'
if (mPos >= bmpDataOffset && mPos < bmpDataEnd) {
return Transition::ContinueUnbuffered(ICOState::READ_PNG);
}
// Figure out how much data the BMP decoder wants
uint32_t toFeed = bmpDataEnd - mPos;
if (toFeed > aCount) {
toFeed = aCount;
}
if (!WriteToContainedDecoder(aBuffer, toFeed)) {
return;
}
mPos += toFeed;
aCount -= toFeed;
aBuffer += toFeed;
}
// If the bitmap is fully processed, treat any left over data as the ICO's
// 'AND buffer mask' which appears after the bitmap resource.
if (!mIsPNG && mPos >= bmpDataEnd) {
nsRefPtr<nsBMPDecoder> bmpDecoder =
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
// There may be an optional AND bit mask after the data. This is
// only used if the alpha data is not already set. The alpha data
// is used for 32bpp bitmaps as per the comment in ICODecoder.h
// The alpha mask should be checked in all other cases.
if (bmpDecoder->GetBitsPerPixel() != 32 || !bmpDecoder->HasAlphaData()) {
uint32_t rowSize = ((GetRealWidth() + 31) / 32) * 4; // + 31 to round up
if (mPos == bmpDataEnd) {
mPos++;
mRowBytes = 0;
mCurLine = GetRealHeight();
mRow = (uint8_t*)realloc(mRow, rowSize);
if (!mRow) {
PostDecoderError(NS_ERROR_OUT_OF_MEMORY);
return;
}
}
// Ensure memory has been allocated before decoding.
MOZ_ASSERT(mRow, "mRow is null");
if (!mRow) {
PostDataError();
return;
}
uint8_t sawTransparency = 0;
while (mCurLine > 0 && aCount > 0) {
uint32_t toCopy = std::min(rowSize - mRowBytes, aCount);
if (toCopy) {
memcpy(mRow + mRowBytes, aBuffer, toCopy);
aCount -= toCopy;
aBuffer += toCopy;
mRowBytes += toCopy;
}
if (rowSize == mRowBytes) {
mCurLine--;
mRowBytes = 0;
uint32_t* imageData = bmpDecoder->GetImageData();
if (!imageData) {
PostDataError();
return;
}
uint32_t* decoded = imageData + mCurLine * GetRealWidth();
uint32_t* decoded_end = decoded + GetRealWidth();
uint8_t* p = mRow;
uint8_t* p_end = mRow + rowSize;
while (p < p_end) {
uint8_t idx = *p++;
sawTransparency |= idx;
for (uint8_t bit = 0x80; bit && decoded<decoded_end; bit >>= 1) {
// Clear pixel completely for transparency.
if (idx & bit) {
*decoded = 0;
}
decoded++;
}
}
}
}
// If any bits are set in sawTransparency, then we know at least one
// pixel was transparent.
if (sawTransparency) {
PostHasTransparency();
bmpDecoder->SetHasAlphaData();
}
LexerTransition<ICOState>
nsICODecoder::ReadBIH(const char* aData)
{
// Buffer the rest of the bitmap information header.
memcpy(mBIHraw + PNGSIGNATURESIZE, aData, BITMAPINFOSIZE - PNGSIGNATURESIZE);
// Extracting the BPP from the BIH header; it should be trusted over the one
// we have from the ICO header.
mBPP = ReadBPP(mBIHraw);
// The ICO format when containing a BMP does not include the 14 byte
// bitmap file header. To use the code of the BMP decoder we need to
// generate this header ourselves and feed it to the BMP decoder.
int8_t bfhBuffer[BMPFILEHEADERSIZE];
if (!FillBitmapFileHeaderBuffer(bfhBuffer)) {
return Transition::Terminate(ICOState::FAILURE);
}
if (!WriteToContainedDecoder(reinterpret_cast<const char*>(bfhBuffer),
sizeof(bfhBuffer))) {
return Transition::Terminate(ICOState::FAILURE);
}
// Set up the cursor hot spot if one is present.
SetHotSpotIfCursor();
// Fix the ICO height from the BIH. It needs to be halved so our BMP decoder
// will understand, because the BMP decoder doesn't expect the alpha mask that
// follows the BMP data in an ICO.
if (!FixBitmapHeight(reinterpret_cast<int8_t*>(mBIHraw))) {
return Transition::Terminate(ICOState::FAILURE);
}
// Fix the ICO width from the BIH.
if (!FixBitmapWidth(reinterpret_cast<int8_t*>(mBIHraw))) {
return Transition::Terminate(ICOState::FAILURE);
}
// Write out the BMP's bitmap info header.
if (!WriteToContainedDecoder(mBIHraw, sizeof(mBIHraw))) {
return Transition::Terminate(ICOState::FAILURE);
}
nsIntSize size = mContainedDecoder->GetSize();
PostSize(size.width, size.height);
// We have the size. If we're doing a metadata decode, we're done.
if (IsMetadataDecode()) {
return Transition::Terminate(ICOState::SUCCESS);
}
// Sometimes the ICO BPP header field is not filled out so we should trust the
// contained resource over our own information.
// XXX(seth): Is this ever different than the value we obtained from
// ReadBPP() above?
nsRefPtr<nsBMPDecoder> bmpDecoder =
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
mBPP = bmpDecoder->GetBitsPerPixel();
// Check to make sure we have valid color settings.
uint16_t numColors = GetNumColors();
if (numColors == uint16_t(-1)) {
return Transition::Terminate(ICOState::FAILURE);
}
// 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_BMP,
bmpDataLength);
}
LexerTransition<ICOState>
nsICODecoder::ReadBMP(const char* aData, uint32_t aLen)
{
if (!WriteToContainedDecoder(aData, aLen)) {
return Transition::Terminate(ICOState::FAILURE);
}
return Transition::ContinueUnbuffered(ICOState::READ_BMP);
}
LexerTransition<ICOState>
nsICODecoder::PrepareForMask()
{
nsRefPtr<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 we have a 32-bpp BMP with alpha data, we ignore the AND mask. We can
// also obviously ignore it if the image has zero width or zero height.
if ((bmpDecoder->GetBitsPerPixel() == 32 && bmpDecoder->HasAlphaData()) ||
GetRealWidth() == 0 || GetRealHeight() == 0) {
return Transition::ToUnbuffered(ICOState::FINISHED_RESOURCE,
ICOState::SKIP_MASK,
maskLength);
}
// Compute the row size for the mask.
mMaskRowSize = ((GetRealWidth() + 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 * GetRealHeight();
if (maskLength < expectedLength) {
return Transition::Terminate(ICOState::FAILURE);
}
mCurrMaskLine = GetRealHeight();
return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
}
LexerTransition<ICOState>
nsICODecoder::ReadMaskRow(const char* aData)
{
mCurrMaskLine--;
nsRefPtr<nsBMPDecoder> bmpDecoder =
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
uint32_t* imageData = bmpDecoder->GetImageData();
if (!imageData) {
return Transition::Terminate(ICOState::FAILURE);
}
uint8_t sawTransparency = 0;
uint32_t* decoded = imageData + mCurrMaskLine * GetRealWidth();
uint32_t* decodedRowEnd = decoded + GetRealWidth();
const uint8_t* mask = reinterpret_cast<const uint8_t*>(aData);
const uint8_t* maskRowEnd = mask + mMaskRowSize;
// 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 any bits are set in sawTransparency, then we know at least one pixel was
// transparent.
if (sawTransparency) {
PostHasTransparency();
bmpDecoder->SetHasAlphaData();
}
if (mCurrMaskLine == 0) {
return Transition::To(ICOState::FINISHED_RESOURCE, 0);
}
return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize);
}
void
nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount)
{
MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!");
MOZ_ASSERT(aBuffer);
MOZ_ASSERT(aCount > 0);
Maybe<ICOState> terminalState =
mLexer.Lex(aBuffer, aCount,
[=](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::SKIP_TO_RESOURCE:
return Transition::ContinueUnbuffered(ICOState::SKIP_TO_RESOURCE);
case ICOState::FOUND_RESOURCE:
return Transition::To(ICOState::SNIFF_RESOURCE, PNGSIGNATURESIZE);
case ICOState::SNIFF_RESOURCE:
return SniffResource(aData);
case ICOState::READ_PNG:
return ReadPNG(aData, aLength);
case ICOState::READ_BIH:
return ReadBIH(aData);
case ICOState::READ_BMP:
return ReadBMP(aData, aLength);
case ICOState::PREPARE_FOR_MASK:
return PrepareForMask();
case ICOState::READ_MASK_ROW:
return ReadMaskRow(aData);
case ICOState::SKIP_MASK:
return Transition::ContinueUnbuffered(ICOState::SKIP_MASK);
case ICOState::FINISHED_RESOURCE:
return Transition::Terminate(ICOState::SUCCESS);
default:
MOZ_ASSERT_UNREACHABLE("Unknown ICOState");
return Transition::Terminate(ICOState::FAILURE);
}
});
if (!terminalState) {
return; // Need more data.
}
if (*terminalState == ICOState::FAILURE) {
PostDataError();
return;
}
MOZ_ASSERT(*terminalState == ICOState::SUCCESS);
}
bool
@ -614,7 +634,7 @@ nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
mProgress |= mContainedDecoder->TakeProgress();
mInvalidRect.UnionRect(mInvalidRect, mContainedDecoder->TakeInvalidRect());
if (mContainedDecoder->HasDataError()) {
mDataError = mContainedDecoder->HasDataError();
PostDataError();
}
if (mContainedDecoder->HasDecoderError()) {
PostDecoderError(mContainedDecoder->GetDecoderError());
@ -622,24 +642,5 @@ nsICODecoder::WriteToContainedDecoder(const char* aBuffer, uint32_t aCount)
return !HasError();
}
void
nsICODecoder::ProcessDirEntry(IconDirEntry& aTarget)
{
memset(&aTarget, 0, sizeof(aTarget));
memcpy(&aTarget.mWidth, mDirEntryArray, sizeof(aTarget.mWidth));
memcpy(&aTarget.mHeight, mDirEntryArray + 1, sizeof(aTarget.mHeight));
memcpy(&aTarget.mColorCount, mDirEntryArray + 2, sizeof(aTarget.mColorCount));
memcpy(&aTarget.mReserved, mDirEntryArray + 3, sizeof(aTarget.mReserved));
memcpy(&aTarget.mPlanes, mDirEntryArray + 4, sizeof(aTarget.mPlanes));
aTarget.mPlanes = LittleEndian::readUint16(&aTarget.mPlanes);
memcpy(&aTarget.mBitCount, mDirEntryArray + 6, sizeof(aTarget.mBitCount));
aTarget.mBitCount = LittleEndian::readUint16(&aTarget.mBitCount);
memcpy(&aTarget.mBytesInRes, mDirEntryArray + 8, sizeof(aTarget.mBytesInRes));
aTarget.mBytesInRes = LittleEndian::readUint32(&aTarget.mBytesInRes);
memcpy(&aTarget.mImageOffset, mDirEntryArray + 12,
sizeof(aTarget.mImageOffset));
aTarget.mImageOffset = LittleEndian::readUint32(&aTarget.mImageOffset);
}
} // namespace image
} // namespace mozilla

View File

@ -8,6 +8,7 @@
#define mozilla_image_decoders_nsICODecoder_h
#include "nsAutoPtr.h"
#include "StreamingLexer.h"
#include "Decoder.h"
#include "imgFrame.h"
#include "nsBMPDecoder.h"
@ -19,28 +20,55 @@ namespace image {
class RasterImage;
enum class ICOState
{
SUCCESS,
FAILURE,
HEADER,
DIR_ENTRY,
SKIP_TO_RESOURCE,
FOUND_RESOURCE,
SNIFF_RESOURCE,
READ_PNG,
READ_BIH,
READ_BMP,
PREPARE_FOR_MASK,
READ_MASK_ROW,
SKIP_MASK,
FINISHED_RESOURCE
};
class nsICODecoder : public Decoder
{
public:
virtual ~nsICODecoder();
virtual ~nsICODecoder() { }
// Obtains the width of the icon directory entry
uint32_t GetRealWidth() const
/// @return the width of the icon directory entry @aEntry.
static uint32_t GetRealWidth(const IconDirEntry& aEntry)
{
return mDirEntry.mWidth == 0 ? 256 : mDirEntry.mWidth;
return aEntry.mWidth == 0 ? 256 : aEntry.mWidth;
}
// Obtains the height of the icon directory entry
uint32_t GetRealHeight() const
/// @return the width of the selected directory entry (mDirEntry).
uint32_t GetRealWidth() const { return GetRealWidth(mDirEntry); }
/// @return the height of the icon directory entry @aEntry.
static uint32_t GetRealHeight(const IconDirEntry& aEntry)
{
return mDirEntry.mHeight == 0 ? 256 : mDirEntry.mHeight;
return aEntry.mHeight == 0 ? 256 : aEntry.mHeight;
}
/// @return the height of the selected directory entry (mDirEntry).
uint32_t GetRealHeight() const { return GetRealHeight(mDirEntry); }
virtual void SetResolution(const gfx::IntSize& aResolution) override
{
mResolution = aResolution;
}
/// @return The offset from the beginning of the ICO to the first resource.
size_t FirstResourceOffset() const;
virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
virtual void FinishInternal() override;
virtual void FinishWithErrorInternal() override;
@ -58,8 +86,6 @@ private:
// Gets decoder state from the contained decoder so it's visible externally.
void GetFinalStateFromContainedDecoder();
// Processes a single dir entry of the icon resource
void ProcessDirEntry(IconDirEntry& aTarget);
// Sets the hotspot property of if we have a cursor
void SetHotSpotIfCursor();
// Creates a bitmap file header buffer, returns true if successful
@ -73,36 +99,36 @@ private:
// Returns false if invalid information is contained within.
bool FixBitmapWidth(int8_t* bih);
// Extract bitmap info header size count from BMP information header
int32_t ExtractBIHSizeFromBitmap(int8_t* bih);
int32_t ReadBIHSize(const char* aBIH);
// Extract bit count from BMP information header
int32_t ExtractBPPFromBitmap(int8_t* bih);
int32_t ReadBPP(const char* aBIH);
// Calculates the row size in bytes for the AND mask table
uint32_t CalcAlphaRowSize();
// Obtains the number of colors from the BPP, mBPP must be filled in
uint16_t GetNumColors();
gfx::IntSize mResolution; // The requested -moz-resolution for this icon.
uint16_t mBPP; // Stores the images BPP
uint32_t mPos; // Keeps track of the position we have decoded up until
uint16_t mNumIcons; // Stores the number of icons in the ICO file
uint16_t mCurrIcon; // Stores the current dir entry index we are processing
uint32_t mImageOffset; // Stores the offset of the image data we want
uint8_t* mRow; // Holds one raw line of the image
int32_t mCurLine; // Line index of the image that's currently being decoded
uint32_t mRowBytes; // How many bytes of the row were already received
int32_t mOldLine; // Previous index of the line
nsRefPtr<Decoder> mContainedDecoder; // Contains either a BMP or PNG resource
LexerTransition<ICOState> ReadHeader(const char* aData);
LexerTransition<ICOState> ReadDirEntry(const char* aData);
LexerTransition<ICOState> SniffResource(const char* aData);
LexerTransition<ICOState> ReadPNG(const char* aData, uint32_t aLen);
LexerTransition<ICOState> ReadBIH(const char* aData);
LexerTransition<ICOState> ReadBMP(const char* aData, uint32_t aLen);
LexerTransition<ICOState> PrepareForMask();
LexerTransition<ICOState> ReadMaskRow(const char* aData);
char mDirEntryArray[ICODIRENTRYSIZE]; // Holds the current dir entry buffer
IconDirEntry mDirEntry; // Holds a decoded dir entry
// Holds the potential bytes that can be a PNG signature
char mSignature[PNGSIGNATURESIZE];
// Holds the potential bytes for a bitmap information header
char mBIHraw[40];
// Stores whether or not the icon file we are processing has type 1 (icon)
bool mIsCursor;
// Stores whether or not the contained resource is a PNG
bool mIsPNG;
StreamingLexer<ICOState, 32> mLexer; // The lexer.
nsRefPtr<Decoder> mContainedDecoder; // Either a BMP or PNG decoder.
gfx::IntSize mResolution; // The requested -moz-resolution.
char mBIHraw[40]; // The bitmap information header.
IconDirEntry mDirEntry; // The dir entry for the selected resource.
int32_t mBestResourceDelta; // Used to select the best resource.
uint16_t mBestResourceColorDepth; // Used to select the best resource.
uint16_t mNumIcons; // Stores the number of icons in the ICO file.
uint16_t mCurrIcon; // Stores the current dir entry index we are processing.
uint16_t mBPP; // The BPP of the resource we're decoding.
uint32_t mMaskRowSize; // The size in bytes of each row in the BMP alpha mask.
uint32_t mCurrMaskLine; // The line of the BMP alpha mask we're processing.
bool mIsCursor; // Is this ICO a cursor?
};
} // namespace image