Bug 716140 - Make animated image formats (PNG and GIF) explicitly pause decoding and ask for a new image frame when they need new frames.

--HG--
extra : rebase_source : 3a4148d8d144784075ec88668359c333420c3d4a
This commit is contained in:
Joe Drew 2013-01-28 12:27:35 -05:00
Родитель 4d3a7dae17
Коммит 3a1ba4d188
8 изменённых файлов: 205 добавлений и 80 удалений

Просмотреть файл

@ -8,7 +8,7 @@
#define MAX_LZW_BITS 12
#define MAX_BITS 4097 /* 2^MAX_LZW_BITS+1 */
#define MAX_COLORS 256
#define MAX_HOLD_SIZE 256
#define MIN_HOLD_SIZE 256
enum { GIF_TRAILER = 0x3B }; //';'
enum { GIF_IMAGE_SEPARATOR = 0x2C }; //','
@ -28,6 +28,7 @@ typedef enum {
gif_global_colormap,
gif_image_start,
gif_image_header,
gif_image_header_continue,
gif_image_colormap,
gif_image_body,
gif_lzw_start,
@ -95,7 +96,7 @@ typedef struct gif_struct {
bool is_transparent; /* TRUE, if tpixel is valid */
uint16_t prefix[MAX_BITS]; /* LZW decoding tables */
uint8_t hold[MAX_HOLD_SIZE]; /* Accumulation buffer */
uint8_t* hold; /* Accumulation buffer */
uint32_t global_colormap[MAX_COLORS]; /* Default colormap if local not supplied */
uint8_t suffix[MAX_BITS]; /* LZW decoding tables */
uint8_t stack[MAX_BITS]; /* Base of LZW decoder stack */

Просмотреть файл

@ -55,8 +55,6 @@ namespace image {
/*
* GETN(n, s) requests at least 'n' bytes available from 'q', at start of state 's'
*
* Note, the hold will never need to be bigger than 256 bytes to gather up in the hold,
* as each GIF block (except colormaps) can never be bigger than 256 bytes.
* Colormaps are directly copied in the resp. global_colormap or the local_colormap of the PAL image frame
* So a fixed buffer in gif_struct is good enough.
* This buffer is only needed to copy left-over data from one GifWrite call to the next
@ -96,9 +94,8 @@ nsGIFDecoder2::nsGIFDecoder2(RasterImage &aImage)
nsGIFDecoder2::~nsGIFDecoder2()
{
if (mGIFStruct.local_colormap) {
moz_free(mGIFStruct.local_colormap);
}
moz_free(mGIFStruct.local_colormap);
moz_free(mGIFStruct.hold);
}
void
@ -164,10 +161,8 @@ void nsGIFDecoder2::BeginGIF()
}
//******************************************************************************
nsresult nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
void nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
{
uint32_t imageDataLength;
nsresult rv;
gfxASurface::gfxImageFormat format;
if (mGIFStruct.is_transparent)
format = gfxASurface::ImageFormatARGB32;
@ -178,47 +173,17 @@ nsresult nsGIFDecoder2::BeginImageFrame(uint16_t aDepth)
// and include transparency to allow for optimization of opaque images
if (mGIFStruct.images_decoded) {
// Image data is stored with original depth and palette
rv = mImage.EnsureFrame(mGIFStruct.images_decoded,
mGIFStruct.x_offset, mGIFStruct.y_offset,
mGIFStruct.width, mGIFStruct.height,
format, aDepth, &mImageData, &imageDataLength,
&mColormap, &mColormapSize);
// While EnsureFrame can reuse frames, we unconditionally increment
// mGIFStruct.images_decoded when we're done with a frame, so we both can
// and need to zero out the colormap and image data after every call to
// EnsureFrame.
if (NS_SUCCEEDED(rv) && mColormap) {
memset(mColormap, 0, mColormapSize);
}
NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset,
mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height,
format, aDepth);
} else {
// Regardless of depth of input, image is decoded into 24bit RGB
rv = mImage.EnsureFrame(mGIFStruct.images_decoded,
mGIFStruct.x_offset, mGIFStruct.y_offset,
mGIFStruct.width, mGIFStruct.height,
format, &mImageData, &imageDataLength);
}
if (NS_FAILED(rv))
return rv;
memset(mImageData, 0, imageDataLength);
// Tell the superclass we're starting a frame
PostFrameStart();
if (!mGIFStruct.images_decoded) {
// Send a onetime invalidation for the first frame if it has a y-axis 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);
}
NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset,
mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height,
format);
}
mCurrentFrame = mGIFStruct.images_decoded;
return NS_OK;
}
@ -585,7 +550,13 @@ nsGIFDecoder2::WriteInternal(const char *aBuffer, uint32_t aCount)
uint8_t* p = (mGIFStruct.state == gif_global_colormap) ? (uint8_t*)mGIFStruct.global_colormap :
(mGIFStruct.state == gif_image_colormap) ? (uint8_t*)mColormap :
(mGIFStruct.bytes_in_hold) ? mGIFStruct.hold : nullptr;
if (p) {
if (len == 0 && buf == nullptr) {
// We've just gotten the frame we asked for. Time to use the data we
// stashed away.
len = mGIFStruct.bytes_in_hold;
q = buf = p;
} else if (p) {
// Add what we have sofar to the block
uint32_t l = std::min(len, mGIFStruct.bytes_to_consume);
memcpy(p+mGIFStruct.bytes_in_hold, buf, l);
@ -596,8 +567,6 @@ nsGIFDecoder2::WriteInternal(const char *aBuffer, uint32_t aCount)
mGIFStruct.bytes_to_consume -= l;
return;
}
// Reset hold buffer count
mGIFStruct.bytes_in_hold = 0;
// Point 'q' to complete block in hold (or in colormap)
q = p;
}
@ -611,7 +580,7 @@ nsGIFDecoder2::WriteInternal(const char *aBuffer, uint32_t aCount)
// to point to next buffer, 'len' is adjusted accordingly.
// So that next round in for loop, q gets pointed to the next buffer.
for (;len >= mGIFStruct.bytes_to_consume; q=buf) {
for (;len >= mGIFStruct.bytes_to_consume; q=buf, mGIFStruct.bytes_in_hold = 0) {
// Eat the current block from the buffer, q keeps pointed at current block
buf += mGIFStruct.bytes_to_consume;
len -= mGIFStruct.bytes_to_consume;
@ -926,10 +895,40 @@ nsGIFDecoder2::WriteInternal(const char *aBuffer, uint32_t aCount)
}
// Mask to limit the color values within the colormap
mColorMask = 0xFF >> (8 - realDepth);
nsresult rv = BeginImageFrame(realDepth);
if (NS_FAILED(rv) || !mImageData) {
mGIFStruct.state = gif_error;
break;
BeginImageFrame(realDepth);
// We now need a new frame from the decoder framework. We leave all our
// data in the buffer as if it wasn't consumed, copy to our hold and return
// to the decoder framework.
uint32_t size = len + mGIFStruct.bytes_to_consume + mGIFStruct.bytes_in_hold;
if (size) {
if (SetHold(q, mGIFStruct.bytes_to_consume + mGIFStruct.bytes_in_hold, buf, len)) {
// Back into the decoder infrastructure so we can get called again.
GETN(9, gif_image_header_continue);
return;
}
}
}
break;
case gif_image_header_continue:
{
// While decoders can reuse frames, we unconditionally increment
// mGIFStruct.images_decoded when we're done with a frame, so we both can
// and need to zero out the colormap and image data after every new frame.
memset(mImageData, 0, mImageDataLength);
if (mColormap) {
memset(mColormap, 0, mColormapSize);
}
if (!mGIFStruct.images_decoded) {
// Send a onetime invalidation for the first frame if it has a y-axis 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 (q[8] & 0x40) {
@ -948,7 +947,16 @@ nsGIFDecoder2::WriteInternal(const char *aBuffer, uint32_t aCount)
mGIFStruct.rows_remaining = mGIFStruct.height;
mGIFStruct.rowp = mImageData;
/* bits per pixel is q[8]&0x07 */
/* Depth of colors is determined by colormap */
/* (q[8] & 0x80) indicates local colormap */
/* bits per pixel is (q[8]&0x07 + 1) when local colormap is set */
uint32_t depth = mGIFStruct.global_colormap_depth;
if (q[8] & 0x80)
depth = (q[8]&0x07) + 1;
uint32_t realDepth = depth;
while (mGIFStruct.tpixel >= (1 << realDepth) && (realDepth < 8)) {
realDepth++;
}
if (q[8] & 0x80) /* has a local colormap? */
{
@ -1027,7 +1035,7 @@ nsGIFDecoder2::WriteInternal(const char *aBuffer, uint32_t aCount)
break;
case gif_done:
PostDecodeDone();
PostDecodeDone(mGIFStruct.loop_count - 1);
mGIFOpen = false;
goto done;
@ -1046,15 +1054,22 @@ nsGIFDecoder2::WriteInternal(const char *aBuffer, uint32_t aCount)
PostDataError();
return;
}
// Copy the leftover into mGIFStruct.hold
mGIFStruct.bytes_in_hold = len;
if (len) {
// Add what we have sofar to the block
uint8_t* p = (mGIFStruct.state == gif_global_colormap) ? (uint8_t*)mGIFStruct.global_colormap :
(mGIFStruct.state == gif_image_colormap) ? (uint8_t*)mColormap :
mGIFStruct.hold;
memcpy(p, buf, len);
if (mGIFStruct.state != gif_global_colormap && mGIFStruct.state != gif_image_colormap) {
if (!SetHold(buf, len)) {
PostDataError();
return;
}
} else {
uint8_t* p = (mGIFStruct.state == gif_global_colormap) ? (uint8_t*)mGIFStruct.global_colormap :
(uint8_t*)mColormap;
memcpy(p, buf, len);
mGIFStruct.bytes_in_hold = len;
}
mGIFStruct.bytes_to_consume -= len;
}
@ -1069,6 +1084,27 @@ done:
return;
}
bool
nsGIFDecoder2::SetHold(const uint8_t* buf1, uint32_t count1, const uint8_t* buf2 /* = nullptr */, uint32_t count2 /* = 0 */)
{
// We have to handle the case that buf currently points to hold
uint8_t* newHold = (uint8_t *) moz_malloc(std::max(uint32_t(MIN_HOLD_SIZE), count1 + count2));
if (!newHold) {
mGIFStruct.state = gif_error;
return false;
}
memcpy(newHold, buf1, count1);
if (buf2) {
memcpy(newHold + count1, buf2, count2);
}
moz_free(mGIFStruct.hold);
mGIFStruct.hold = newHold;
mGIFStruct.bytes_in_hold = count1 + count2;
return true;
}
Telemetry::ID
nsGIFDecoder2::SpeedHistogram()
{

Просмотреть файл

@ -35,7 +35,7 @@ private:
* frame size information, etc. */
void BeginGIF();
nsresult BeginImageFrame(uint16_t aDepth);
void BeginImageFrame(uint16_t aDepth);
void EndImageFrame();
void FlushImageData();
void FlushImageData(uint32_t fromRow, uint32_t rows);
@ -43,6 +43,8 @@ private:
nsresult GifWrite(const uint8_t * buf, uint32_t numbytes);
uint32_t OutputRow();
bool DoLzw(const uint8_t *q);
bool SetHold(const uint8_t* buf, uint32_t count,
const uint8_t* buf2 = nullptr, uint32_t count2 = 0);
inline int ClearCode() const { return 1 << mGIFStruct.datasize; }

Просмотреть файл

@ -148,21 +148,13 @@ void nsPNGDecoder::CreateFrame(png_uint_32 x_offset, png_uint_32 y_offset,
int32_t width, int32_t height,
gfxASurface::gfxImageFormat format)
{
uint32_t imageDataLength;
nsresult rv = mImage.EnsureFrame(GetFrameCount(), x_offset, y_offset,
width, height, format,
&mImageData, &imageDataLength);
if (NS_FAILED(rv))
longjmp(png_jmpbuf(mPNG), 5); // NS_ERROR_OUT_OF_MEMORY
NeedNewFrame(GetFrameCount(), x_offset, y_offset, width, height, format);
mFrameRect.x = x_offset;
mFrameRect.y = y_offset;
mFrameRect.width = width;
mFrameRect.height = height;
// Tell the superclass we're starting a frame
PostFrameStart();
PR_LOG(GetPNGDecoderAccountingLog(), PR_LOG_DEBUG,
("PNGDecoderAccounting: nsPNGDecoder::CreateFrame -- created "
"image frame with %dx%d pixels in container %p",
@ -292,10 +284,6 @@ nsPNGDecoder::InitInternal()
void
nsPNGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount)
{
// We use gotos, so we need to declare variables here
uint32_t width = 0;
uint32_t height = 0;
NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!");
// If we only want width/height, we don't need to go through libpng
@ -322,8 +310,8 @@ nsPNGDecoder::WriteInternal(const char *aBuffer, uint32_t aCount)
}
// Grab the width and height, accounting for endianness (thanks libpng!)
width = png_get_uint_32(mHeaderBuf + WIDTH_OFFSET);
height = png_get_uint_32(mHeaderBuf + HEIGHT_OFFSET);
uint32_t width = png_get_uint_32(mHeaderBuf + WIDTH_OFFSET);
uint32_t height = png_get_uint_32(mHeaderBuf + HEIGHT_OFFSET);
// Too big?
if ((width > MOZ_PNG_MAX_DIMENSION) || (height > MOZ_PNG_MAX_DIMENSION)) {
@ -668,7 +656,12 @@ nsPNGDecoder::info_callback(png_structp png_ptr, png_infop info_ptr)
*/
png_set_crc_action(png_ptr, PNG_CRC_NO_CHANGE, PNG_CRC_ERROR_QUIT);
return;
if (!decoder->mFrameIsHidden) {
/* We know that we need a new frame, so pause input so the decoder
* infrastructure can give it to us.
*/
png_process_data_pause(png_ptr, /* save = */ 1);
}
}
void
@ -829,6 +822,11 @@ nsPNGDecoder::frame_info_callback(png_structp png_ptr, png_uint_32 frame_num)
height = png_get_next_frame_height(png_ptr, decoder->mInfo);
decoder->CreateFrame(x_offset, y_offset, width, height, decoder->format);
/* We know that we need a new frame, so pause input so the decoder
* infrastructure can give it to us.
*/
png_process_data_pause(png_ptr, /* save = */ 1);
#endif
}

Просмотреть файл

@ -59,7 +59,8 @@ public:
if (png_get_IHDR(mPNG, mInfo, &png_width, &png_height, &png_bit_depth,
&png_color_type, NULL, NULL, NULL)) {
return (png_color_type == PNG_COLOR_TYPE_RGB_ALPHA &&
return ((png_color_type == PNG_COLOR_TYPE_RGB_ALPHA ||
png_color_type == PNG_COLOR_TYPE_RGB) &&
png_bit_depth == 8);
} else {
return false;

Просмотреть файл

@ -22,6 +22,7 @@ Decoder::Decoder(RasterImage &aImage)
, mDataError(false)
, mFrameCount(0)
, mFailCode(NS_OK)
, mNeedsNewFrame(false)
, mInitialized(false)
, mSizeDecode(false)
, mInFrame(false)
@ -82,6 +83,40 @@ Decoder::Write(const char* aBuffer, uint32_t aCount)
// Pass the data along to the implementation
WriteInternal(aBuffer, aCount);
// If the decoder told us that it needs a new frame to proceed, let's create
// one and call it again.
while (mNeedsNewFrame && !HasDataError()) {
nsresult rv;
if (mNewFrameData.mPaletteDepth) {
rv = mImage.EnsureFrame(mNewFrameData.mFrameNum, mNewFrameData.mOffsetX,
mNewFrameData.mOffsetY, mNewFrameData.mWidth,
mNewFrameData.mHeight, mNewFrameData.mFormat,
mNewFrameData.mPaletteDepth,
&mImageData, &mImageDataLength,
&mColormap, &mColormapSize);
} else {
rv = mImage.EnsureFrame(mNewFrameData.mFrameNum, mNewFrameData.mOffsetX,
mNewFrameData.mOffsetY, mNewFrameData.mWidth,
mNewFrameData.mHeight, mNewFrameData.mFormat,
&mImageData, &mImageDataLength);
}
// Release our new frame data before talking to anyone else so they can
// tell us if they need yet another.
mNeedsNewFrame = false;
if (NS_SUCCEEDED(rv)) {
// We've now created our frame, so be sure we keep track of it correctly.
PostFrameStart();
// Tell the decoder to use the data it saved when it asked for a new frame.
WriteInternal(nullptr, 0);
} else {
PostDataError();
break;
}
}
}
void
@ -317,5 +352,21 @@ Decoder::PostDecoderError(nsresult aFailureCode)
NS_WARNING("Image decoding error - This is probably a bug!");
}
void
Decoder::NeedNewFrame(uint32_t framenum, uint32_t x_offset, uint32_t y_offset,
uint32_t width, uint32_t height,
gfxASurface::gfxImageFormat format,
uint8_t palette_depth /* = 0 */)
{
// Decoders should never call NeedNewFrame without yielding back to Write().
MOZ_ASSERT(!mNeedsNewFrame);
// We don't want images going back in time or skipping frames.
MOZ_ASSERT(framenum == mFrameCount || framenum == (mFrameCount + 1));
mNewFrameData = NewFrameData(framenum, x_offset, y_offset, width, height, format, palette_depth);
mNeedsNewFrame = true;
}
} // namespace image
} // namespace mozilla

Просмотреть файл

@ -184,6 +184,15 @@ protected:
void PostDataError();
void PostDecoderError(nsresult aFailCode);
// This is called by decoders when they need a new frame. These decoders
// must then save the data they have been sent but not yet processed and
// return from WriteInternal. When the new frame is created, WriteInternal
// will be called again with nullptr and 0 as arguments.
void NeedNewFrame(uint32_t frameNum, uint32_t x_offset, uint32_t y_offset,
uint32_t width, uint32_t height,
gfxASurface::gfxImageFormat format,
uint8_t palette_depth = 0);
/*
* Member variables.
*
@ -208,6 +217,32 @@ private:
nsresult mFailCode;
struct NewFrameData
{
NewFrameData()
{}
NewFrameData(uint32_t num, uint32_t offsetx, uint32_t offsety,
uint32_t width, uint32_t height,
gfxASurface::gfxImageFormat format, uint8_t paletteDepth)
: mFrameNum(num)
, mOffsetX(offsetx)
, mOffsetY(offsety)
, mWidth(width)
, mHeight(height)
, mFormat(format)
, mPaletteDepth(paletteDepth)
{}
uint32_t mFrameNum;
uint32_t mOffsetX;
uint32_t mOffsetY;
uint32_t mWidth;
uint32_t mHeight;
gfxASurface::gfxImageFormat mFormat;
uint8_t mPaletteDepth;
};
NewFrameData mNewFrameData;
bool mNeedsNewFrame;
bool mInitialized;
bool mSizeDecode;
bool mInFrame;

Просмотреть файл

@ -210,6 +210,7 @@ MOZ_PNG_get_tRNS
MOZ_PNG_get_valid
MOZ_PNG_longjmp
MOZ_PNG_process_data
MOZ_PNG_process_data_pause
MOZ_PNG_progressive_combine_row
MOZ_PNG_read_update_info
MOZ_PNG_set_cHRM