From 5f0c22c9c2f97f3a1258c66e202dd49f0b48c48e Mon Sep 17 00:00:00 2001 From: Seth Fowler Date: Mon, 21 Sep 2015 19:52:31 -0700 Subject: [PATCH] Bug 1206836 - When downscaling ICOs, downscale the AND mask as well. r=tn a=KWierso --- image/decoders/nsBMPDecoder.h | 1 + image/decoders/nsICODecoder.cpp | 99 ++++++++++++++++++++++++++++----- image/decoders/nsICODecoder.h | 4 ++ 3 files changed, 91 insertions(+), 13 deletions(-) diff --git a/image/decoders/nsBMPDecoder.h b/image/decoders/nsBMPDecoder.h index e18c5415224..f803302e016 100644 --- a/image/decoders/nsBMPDecoder.h +++ b/image/decoders/nsBMPDecoder.h @@ -40,6 +40,7 @@ public: // Obtains the internal output image buffer uint32_t* GetImageData(); + size_t GetImageDataLength() const { return mImageDataLength; } // Obtains the size of the compressed image resource int32_t GetCompressedImageSize() const; diff --git a/image/decoders/nsICODecoder.cpp b/image/decoders/nsICODecoder.cpp index c9ff612f561..1f8cf943979 100644 --- a/image/decoders/nsICODecoder.cpp +++ b/image/decoders/nsICODecoder.cpp @@ -69,6 +69,8 @@ nsICODecoder::nsICODecoder(RasterImage* aImage) , mBPP(0) , mMaskRowSize(0) , mCurrMaskLine(0) + , mIsCursor(false) + , mHasMaskAlpha(false) { } void @@ -542,6 +544,24 @@ nsICODecoder::PrepareForMask() return Transition::Terminate(ICOState::FAILURE); } + // 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 = MakeUnique(bmpDecoder->GetImageDataLength()); + nsresult rv = mDownscaler->BeginFrame(GetRealSize(), + mMaskBuffer.get(), + /* aHasAlpha = */ true, + /* aFlipVertically = */ true); + if (NS_FAILED(rv)) { + return Transition::Terminate(ICOState::FAILURE); + } + } + mCurrMaskLine = GetRealHeight(); return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize); } @@ -552,20 +572,34 @@ nsICODecoder::ReadMaskRow(const char* aData) { mCurrMaskLine--; - nsRefPtr bmpDecoder = - static_cast(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(); + + // Get the mask row we're reading. const uint8_t* mask = reinterpret_cast(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, GetRealWidth() * sizeof(uint32_t)); + + decoded = reinterpret_cast(mDownscaler->RowBuffer()); + } else { + nsRefPtr bmpDecoder = + static_cast(mContainedDecoder.get()); + uint32_t* imageData = bmpDecoder->GetImageData(); + if (!imageData) { + return Transition::Terminate(ICOState::FAILURE); + } + + decoded = imageData + mCurrMaskLine * GetRealWidth(); + } + + MOZ_ASSERT(decoded); + uint32_t* decodedRowEnd = decoded + GetRealWidth(); + // Iterate simultaneously through the AND mask and the image data. while (mask < maskRowEnd) { uint8_t idx = *mask++; @@ -579,20 +613,57 @@ nsICODecoder::ReadMaskRow(const char* aData) } } + if (mDownscaler) { + mDownscaler->CommitRow(); + } + // If any bits are set in sawTransparency, then we know at least one pixel was // transparent. if (sawTransparency) { - PostHasTransparency(); - bmpDecoder->SetHasAlphaData(); + mHasMaskAlpha = true; } if (mCurrMaskLine == 0) { - return Transition::To(ICOState::FINISHED_RESOURCE, 0); + return Transition::To(ICOState::FINISH_MASK, 0); } return Transition::To(ICOState::READ_MASK_ROW, mMaskRowSize); } +LexerTransition +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. + nsRefPtr bmpDecoder = + static_cast(mContainedDecoder.get()); + uint8_t* imageData = reinterpret_cast(bmpDecoder->GetImageData()); + if (!imageData) { + return Transition::Terminate(ICOState::FAILURE); + } + + // 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]; + } + } + + // If the mask contained any transparent pixels, record that fact. + if (mHasMaskAlpha) { + PostHasTransparency(); + + nsRefPtr bmpDecoder = + static_cast(mContainedDecoder.get()); + bmpDecoder->SetHasAlphaData(); + } + + return Transition::To(ICOState::FINISHED_RESOURCE, 0); +} + LexerTransition nsICODecoder::FinishResource() { @@ -637,6 +708,8 @@ nsICODecoder::WriteInternal(const char* aBuffer, uint32_t aCount) 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: diff --git a/image/decoders/nsICODecoder.h b/image/decoders/nsICODecoder.h index 523fb3a4fb1..8bb6f5bee2d 100644 --- a/image/decoders/nsICODecoder.h +++ b/image/decoders/nsICODecoder.h @@ -34,6 +34,7 @@ enum class ICOState READ_BMP, PREPARE_FOR_MASK, READ_MASK_ROW, + FINISH_MASK, SKIP_MASK, FINISHED_RESOURCE }; @@ -114,10 +115,12 @@ private: LexerTransition ReadBMP(const char* aData, uint32_t aLen); LexerTransition PrepareForMask(); LexerTransition ReadMaskRow(const char* aData); + LexerTransition FinishMask(); LexerTransition FinishResource(); StreamingLexer mLexer; // The lexer. nsRefPtr mContainedDecoder; // Either a BMP or PNG decoder. + UniquePtr mMaskBuffer; // A temporary buffer for the alpha mask. char mBIHraw[40]; // The bitmap information header. IconDirEntry mDirEntry; // The dir entry for the selected resource. IntSize mBiggestResourceSize; // Used to select the intrinsic size. @@ -131,6 +134,7 @@ private: 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? + bool mHasMaskAlpha; // Did the BMP alpha mask have any transparency? }; } // namespace image