Bug 1206836 - When downscaling ICOs, downscale the AND mask as well. r=tn a=KWierso

This commit is contained in:
Seth Fowler 2015-09-21 19:52:31 -07:00
parent 8b5b12a870
commit 5f0c22c9c2
3 changed files with 91 additions and 13 deletions

View File

@ -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;

View File

@ -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<uint8_t[]>(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<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();
// 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, GetRealWidth() * sizeof(uint32_t));
decoded = reinterpret_cast<uint32_t*>(mDownscaler->RowBuffer());
} else {
nsRefPtr<nsBMPDecoder> bmpDecoder =
static_cast<nsBMPDecoder*>(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<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.
nsRefPtr<nsBMPDecoder> bmpDecoder =
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
uint8_t* imageData = reinterpret_cast<uint8_t*>(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<nsBMPDecoder> bmpDecoder =
static_cast<nsBMPDecoder*>(mContainedDecoder.get());
bmpDecoder->SetHasAlphaData();
}
return Transition::To(ICOState::FINISHED_RESOURCE, 0);
}
LexerTransition<ICOState>
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:

View File

@ -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<ICOState> ReadBMP(const char* aData, uint32_t aLen);
LexerTransition<ICOState> PrepareForMask();
LexerTransition<ICOState> ReadMaskRow(const char* aData);
LexerTransition<ICOState> FinishMask();
LexerTransition<ICOState> FinishResource();
StreamingLexer<ICOState, 32> mLexer; // The lexer.
nsRefPtr<Decoder> mContainedDecoder; // Either a BMP or PNG decoder.
UniquePtr<uint8_t[]> 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