Bug 1194058 (Part 2) - Add downscale-during-decode support for the GIF decoder. r=tn

This commit is contained in:
Seth Fowler 2015-09-17 15:03:34 -07:00
parent c8ccf76b56
commit dcfc39c17a
3 changed files with 156 additions and 25 deletions

View File

@ -39,7 +39,8 @@ ShouldDownscaleDuringDecode(const nsCString& aMimeType)
return type == DecoderType::JPEG ||
type == DecoderType::ICON ||
type == DecoderType::PNG ||
type == DecoderType::BMP;
type == DecoderType::BMP ||
type == DecoderType::GIF;
}
static uint32_t

View File

@ -99,6 +99,54 @@ nsGIFDecoder2::~nsGIFDecoder2()
free(mGIFStruct.hold);
}
nsresult
nsGIFDecoder2::SetTargetSize(const nsIntSize& aSize)
{
// Make sure the size is reasonable.
if (MOZ_UNLIKELY(aSize.width <= 0 || aSize.height <= 0)) {
return NS_ERROR_FAILURE;
}
// Create a downscaler that we'll filter our output through.
mDownscaler.emplace(aSize);
return NS_OK;
}
uint8_t*
nsGIFDecoder2::GetCurrentRowBuffer()
{
if (!mDownscaler) {
MOZ_ASSERT(!mDeinterlacer, "Deinterlacer without downscaler?");
uint32_t bpp = mGIFStruct.images_decoded == 0 ? sizeof(uint32_t)
: sizeof(uint8_t);
return mImageData + mGIFStruct.irow * mGIFStruct.width * bpp;
}
if (!mDeinterlacer) {
return mDownscaler->RowBuffer();
}
return mDeinterlacer->RowBuffer(mGIFStruct.irow);
}
uint8_t*
nsGIFDecoder2::GetRowBuffer(uint32_t aRow)
{
MOZ_ASSERT(mGIFStruct.images_decoded == 0,
"Calling GetRowBuffer on a frame other than the first suggests "
"we're deinterlacing animated frames");
MOZ_ASSERT(!mDownscaler || mDeinterlacer,
"Can't get buffer for a specific row if downscaling "
"but not deinterlacing");
if (mDownscaler) {
return mDeinterlacer->RowBuffer(aRow);
}
return mImageData + aRow * mGIFStruct.width * sizeof(uint32_t);
}
void
nsGIFDecoder2::FinishInternal()
{
@ -128,6 +176,15 @@ nsGIFDecoder2::FlushImageData(uint32_t fromRow, uint32_t rows)
void
nsGIFDecoder2::FlushImageData()
{
if (mDownscaler) {
if (mDownscaler->HasInvalidation()) {
DownscalerInvalidRect invalidRect = mDownscaler->TakeInvalidRect();
PostInvalidation(invalidRect.mOriginalSizeRect,
Some(invalidRect.mTargetSizeRect));
}
return;
}
switch (mCurrentPass - mLastFlushedPass) {
case 0: // same pass
if (mCurrentRow - mLastFlushedRow) {
@ -202,21 +259,44 @@ nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
CheckForTransparency(frameRect);
// Make sure there's no animation if we're downscaling.
MOZ_ASSERT_IF(mDownscaler, !GetImageMetadata().HasAnimation());
IntSize targetSize = mDownscaler ? mDownscaler->TargetSize()
: GetSize();
// Rescale the frame rect for the target size.
IntRect targetFrameRect = frameRect;
if (mDownscaler) {
IntSize originalSize = GetSize();
targetFrameRect.ScaleRoundOut(double(targetSize.width) / originalSize.width,
double(targetSize.height) / originalSize.height);
}
// Use correct format, RGB for first frame, PAL for following frames
// and include transparency to allow for optimization of opaque images
nsresult rv = NS_OK;
if (mGIFStruct.images_decoded) {
// Image data is stored with original depth and palette.
rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(),
frameRect, format, aDepth);
rv = AllocateFrame(mGIFStruct.images_decoded, targetSize,
targetFrameRect, format, aDepth);
} else {
// Regardless of depth of input, the first frame is decoded into 24bit RGB.
rv = AllocateFrame(mGIFStruct.images_decoded, GetSize(),
frameRect, format);
rv = AllocateFrame(mGIFStruct.images_decoded, targetSize,
targetFrameRect, format);
}
mCurrentFrameIndex = mGIFStruct.images_decoded;
if (NS_FAILED(rv)) {
return rv;
}
if (mDownscaler) {
rv = mDownscaler->BeginFrame(frameRect.Size(), mImageData,
mGIFStruct.is_transparent);
}
return rv;
}
@ -237,10 +317,15 @@ nsGIFDecoder2::EndImageFrame()
// This will clear the remaining bits of the placeholder. (Bug 37589)
const uint32_t realFrameHeight = mGIFStruct.height + mGIFStruct.y_offset;
if (realFrameHeight < mGIFStruct.screen_height) {
nsIntRect r(0, realFrameHeight,
mGIFStruct.screen_width,
mGIFStruct.screen_height - realFrameHeight);
PostInvalidation(r);
if (mDownscaler) {
IntRect targetRect = IntRect(IntPoint(), mDownscaler->TargetSize());
PostInvalidation(IntRect(IntPoint(), GetSize()), Some(targetRect));
} else {
nsIntRect r(0, realFrameHeight,
mGIFStruct.screen_width,
mGIFStruct.screen_height - realFrameHeight);
PostInvalidation(r);
}
}
// The first frame was preallocated with alpha; if it wasn't transparent, we
@ -291,8 +376,11 @@ nsGIFDecoder2::EndImageFrame()
uint32_t
nsGIFDecoder2::OutputRow()
{
int drow_start, drow_end;
drow_start = drow_end = mGIFStruct.irow;
// Initialize the region in which we're duplicating rows (for the
// Haeberli-inspired hack below) to |irow|, which is the row we're writing to
// now.
int drow_start = mGIFStruct.irow;
int drow_end = mGIFStruct.irow;
// Protect against too much image data
if ((unsigned)drow_start >= mGIFStruct.height) {
@ -329,8 +417,7 @@ nsGIFDecoder2::OutputRow()
}
// Row to process
const uint32_t bpr = sizeof(uint32_t) * mGIFStruct.width;
uint8_t* rowp = mImageData + (mGIFStruct.irow * bpr);
uint8_t* rowp = GetCurrentRowBuffer();
// Convert color indices to Cairo pixels
uint8_t* from = rowp + mGIFStruct.width;
@ -351,12 +438,24 @@ nsGIFDecoder2::OutputRow()
}
}
// Duplicate rows
// If we're downscaling but not deinterlacing, we're done with this row and
// can commit it now. Otherwise, we'll let Deinterlacer do the committing
// when we call PropagatePassToDownscaler() at the end of this pass.
if (mDownscaler && !mDeinterlacer) {
mDownscaler->CommitRow();
}
if (drow_end > drow_start) {
// irow is the current row filled
// Duplicate rows if needed to reduce the "venetian blind" effect mentioned
// above. This writes out scanlines of the image in a way that isn't ordered
// vertically, which is incompatible with the filter that we use for
// downscale-during-decode, so we can't do this if we're downscaling.
MOZ_ASSERT_IF(mDownscaler, mDeinterlacer);
const uint32_t bpr = sizeof(uint32_t) * mGIFStruct.width;
for (int r = drow_start; r <= drow_end; r++) {
// Skip the row we wrote to above; that's what we're copying *from*.
if (r != int(mGIFStruct.irow)) {
memcpy(mImageData + (r * bpr), rowp, bpr);
memcpy(GetRowBuffer(r), rowp, bpr);
}
}
}
@ -369,9 +468,12 @@ nsGIFDecoder2::OutputRow()
}
if (!mGIFStruct.interlaced) {
MOZ_ASSERT(!mDeinterlacer);
mGIFStruct.irow++;
} else {
static const uint8_t kjump[5] = { 1, 8, 8, 4, 2 };
int currentPass = mGIFStruct.ipass;
do {
// Row increments resp. per 8,8,4,2 rows
mGIFStruct.irow += kjump[mGIFStruct.ipass];
@ -381,6 +483,15 @@ nsGIFDecoder2::OutputRow()
mGIFStruct.ipass++;
}
} while (mGIFStruct.irow >= mGIFStruct.height);
// We've finished a pass. If we're downscaling, it's time to propagate the
// rows we've decoded so far from our Deinterlacer to our Downscaler.
if (mGIFStruct.ipass > currentPass && mDownscaler) {
MOZ_ASSERT(mDeinterlacer);
mDeinterlacer->PropagatePassToDownscaler(*mDownscaler);
FlushImageData();
mDownscaler->ResetForNextProgressivePass();
}
}
return --mGIFStruct.rows_remaining;
@ -413,17 +524,13 @@ nsGIFDecoder2::DoLzw(const uint8_t* q)
uint8_t* stack = mGIFStruct.stack;
uint8_t* rowp = mGIFStruct.rowp;
uint32_t bpr = mGIFStruct.width;
if (!mGIFStruct.images_decoded) {
bpr *= sizeof(uint32_t);
}
uint8_t* rowend = mImageData + (bpr * mGIFStruct.irow) + mGIFStruct.width;
uint8_t* rowend = GetCurrentRowBuffer() + mGIFStruct.width;
#define OUTPUT_ROW() \
PR_BEGIN_MACRO \
if (!OutputRow()) \
goto END; \
rowp = mImageData + mGIFStruct.irow * bpr; \
rowp = GetCurrentRowBuffer(); \
rowend = rowp + mGIFStruct.width; \
PR_END_MACRO
@ -934,12 +1041,19 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
// below, so that RasterImage can detect that this happened.
PostIsAnimated(/* aFirstFrameTimeout = */ 0);
}
if (IsFirstFrameDecode()) {
// We're about to get a second frame, but we only want the first. Stop
// decoding now.
mGIFStruct.state = gif_done;
break;
}
if (mDownscaler) {
MOZ_ASSERT_UNREACHABLE("Doing downscale-during-decode "
"for an animated image?");
mDownscaler.reset();
}
}
// Get image offsets, with respect to the screen origin
@ -1025,14 +1139,22 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
// offset. Otherwise, the area may never be refreshed and the
// placeholder will remain on the screen. (Bug 37589)
if (mGIFStruct.y_offset > 0) {
nsIntRect r(0, 0, mGIFStruct.screen_width, mGIFStruct.y_offset);
PostInvalidation(r);
if (mDownscaler) {
IntRect targetRect = IntRect(IntPoint(), mDownscaler->TargetSize());
PostInvalidation(IntRect(IntPoint(), GetSize()), Some(targetRect));
} else {
nsIntRect r(0, 0, mGIFStruct.screen_width, mGIFStruct.y_offset);
PostInvalidation(r);
}
}
}
if (q[8] & 0x40) {
mGIFStruct.interlaced = true;
mGIFStruct.ipass = 1;
if (mDownscaler) {
mDeinterlacer.emplace(mDownscaler->OriginalSize());
}
} else {
mGIFStruct.interlaced = false;
mGIFStruct.ipass = 0;
@ -1044,7 +1166,7 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount)
// Clear state from last image
mGIFStruct.irow = 0;
mGIFStruct.rows_remaining = mGIFStruct.height;
mGIFStruct.rowp = mImageData;
mGIFStruct.rowp = GetCurrentRowBuffer();
// Depth of colors is determined by colormap
// (q[8] & 0x80) indicates local colormap

View File

@ -8,6 +8,7 @@
#define mozilla_image_decoders_nsGIFDecoder2_h
#include "Decoder.h"
#include "Downscaler.h"
#include "GIF2.h"
#include "nsCOMPtr.h"
@ -24,6 +25,8 @@ class nsGIFDecoder2 : public Decoder
public:
~nsGIFDecoder2();
nsresult SetTargetSize(const nsIntSize& aSize) override;
virtual void WriteInternal(const char* aBuffer, uint32_t aCount) override;
virtual void FinishInternal() override;
virtual Telemetry::ID SpeedHistogram() override;
@ -34,6 +37,9 @@ private:
// Decoders should only be instantiated via DecoderFactory.
explicit nsGIFDecoder2(RasterImage* aImage);
uint8_t* GetCurrentRowBuffer();
uint8_t* GetRowBuffer(uint32_t aRow);
// These functions will be called when the decoder has a decoded row,
// frame size information, etc.
void BeginGIF();
@ -67,6 +73,8 @@ private:
bool mSawTransparency;
gif_struct mGIFStruct;
Maybe<Downscaler> mDownscaler;
Maybe<Deinterlacer> mDeinterlacer;
};
} // namespace image