diff --git a/image/decoders/GIF2.h b/image/decoders/GIF2.h index fb6291db7c7..c8052c40fd3 100644 --- a/image/decoders/GIF2.h +++ b/image/decoders/GIF2.h @@ -75,6 +75,8 @@ typedef struct gif_struct { // Parameters for image frame currently being decoded unsigned x_offset, y_offset; // With respect to "screen" origin unsigned height, width; + unsigned clamped_height; // Size of the frame rectangle clamped to the + unsigned clamped_width; // global logical size after x_ and y_offset. int tpixel; // Index of transparent pixel int32_t disposal_method; // Restore to background, leave in place, etc. uint32_t* local_colormap; // Per-image colormap diff --git a/image/decoders/nsGIFDecoder2.cpp b/image/decoders/nsGIFDecoder2.cpp index fa8dccae776..799f05be3e9 100644 --- a/image/decoders/nsGIFDecoder2.cpp +++ b/image/decoders/nsGIFDecoder2.cpp @@ -181,11 +181,11 @@ nsGIFDecoder2::FlushImageData() case 1: // one pass on - need to handle bottom & top rects FlushImageData(0, mCurrentRow + 1); FlushImageData(mLastFlushedRow + 1, - mGIFStruct.height - (mLastFlushedRow + 1)); + mGIFStruct.clamped_height - (mLastFlushedRow + 1)); break; default: // more than one pass on - push the whole frame - FlushImageData(0, mGIFStruct.height); + FlushImageData(0, mGIFStruct.clamped_height); } } @@ -338,11 +338,11 @@ nsGIFDecoder2::EndImageFrame() mCurrentPass = mLastFlushedPass = 0; // Only add frame if we have any rows at all - if (mGIFStruct.rows_remaining != mGIFStruct.height) { + if (mGIFStruct.rows_remaining != mGIFStruct.clamped_height) { if (mGIFStruct.rows_remaining && mGIFStruct.images_decoded) { // Clear the remaining rows (only needed for the animation frames) uint8_t* rowp = - mImageData + ((mGIFStruct.height - mGIFStruct.rows_remaining) * + mImageData + ((mGIFStruct.clamped_height - mGIFStruct.rows_remaining) * mGIFStruct.width); memset(rowp, 0, mGIFStruct.rows_remaining * mGIFStruct.width); } @@ -381,7 +381,7 @@ nsGIFDecoder2::OutputRow() int drow_end = mGIFStruct.irow; // Protect against too much image data - if ((unsigned)drow_start >= mGIFStruct.height) { + if ((unsigned)drow_start >= mGIFStruct.clamped_height) { NS_WARNING("GIF2.cpp::OutputRow - too much image data"); return 0; } @@ -401,16 +401,16 @@ nsGIFDecoder2::OutputRow() drow_end = drow_start + row_dup; // Extend if bottom edge isn't covered because of the shift upward. - if (((mGIFStruct.height - 1) - drow_end) <= row_shift) { - drow_end = mGIFStruct.height - 1; + if (((mGIFStruct.clamped_height - 1) - drow_end) <= row_shift) { + drow_end = mGIFStruct.clamped_height - 1; } // Clamp first and last rows to upper and lower edge of image. if (drow_start < 0) { drow_start = 0; } - if ((unsigned)drow_end >= mGIFStruct.height) { - drow_end = mGIFStruct.height - 1; + if ((unsigned)drow_end >= mGIFStruct.clamped_height) { + drow_end = mGIFStruct.clamped_height - 1; } } @@ -418,17 +418,17 @@ nsGIFDecoder2::OutputRow() uint8_t* rowp = GetCurrentRowBuffer(); // Convert color indices to Cairo pixels - uint8_t* from = rowp + mGIFStruct.width; - uint32_t* to = ((uint32_t*)rowp) + mGIFStruct.width; + uint8_t* from = rowp + mGIFStruct.clamped_width; + uint32_t* to = ((uint32_t*)rowp) + mGIFStruct.clamped_width; uint32_t* cmap = mColormap; - for (uint32_t c = mGIFStruct.width; c > 0; c--) { + for (uint32_t c = mGIFStruct.clamped_width; c > 0; c--) { *--to = cmap[*--from]; } // check for alpha (only for first frame) if (mGIFStruct.is_transparent && !mSawTransparency) { const uint32_t* rgb = (uint32_t*)rowp; - for (uint32_t i = mGIFStruct.width; i > 0; i--) { + for (uint32_t i = mGIFStruct.clamped_width; i > 0; i--) { if (*rgb++ == 0) { mSawTransparency = true; break; @@ -449,7 +449,7 @@ nsGIFDecoder2::OutputRow() // 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; + const uint32_t bpr = sizeof(uint32_t) * mGIFStruct.clamped_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)) { @@ -475,12 +475,12 @@ nsGIFDecoder2::OutputRow() do { // Row increments resp. per 8,8,4,2 rows mGIFStruct.irow += kjump[mGIFStruct.ipass]; - if (mGIFStruct.irow >= mGIFStruct.height) { + if (mGIFStruct.irow >= mGIFStruct.clamped_height) { // Next pass starts resp. at row 4,2,1,0 mGIFStruct.irow = 8 >> mGIFStruct.ipass; mGIFStruct.ipass++; } - } while (mGIFStruct.irow >= mGIFStruct.height); + } while (mGIFStruct.irow >= mGIFStruct.clamped_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. @@ -525,14 +525,14 @@ nsGIFDecoder2::DoLzw(const uint8_t* q) uint8_t* stack = mGIFStruct.stack; uint8_t* rowp = mGIFStruct.rowp; - uint8_t* rowend = GetCurrentRowBuffer() + mGIFStruct.width; + uint8_t* rowend = GetCurrentRowBuffer() + mGIFStruct.clamped_width; #define OUTPUT_ROW() \ PR_BEGIN_MACRO \ if (!OutputRow()) \ goto END; \ rowp = GetCurrentRowBuffer(); \ - rowend = rowp + mGIFStruct.width; \ + rowend = rowp + mGIFStruct.clamped_width; \ PR_END_MACRO for (const uint8_t* ch = q; count-- > 0; ch++) { @@ -625,6 +625,10 @@ nsGIFDecoder2::DoLzw(const uint8_t* q) *rowp++ = *--stackp & mColorMask; // ensure index is within colormap if (rowp == rowend) { OUTPUT_ROW(); + + // Consume decoded data that falls past the end of the clamped width. + stackp -= mGIFStruct.width - mGIFStruct.clamped_width; + stackp = std::max(stackp, stack); } } while (stackp > stack); } @@ -1108,6 +1112,21 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) } } + // Hack around GIFs with frame rects outside the given screen bounds. + IntRect clampedRect = + ClampToImageRect(IntRect(mGIFStruct.x_offset, mGIFStruct.y_offset, + mGIFStruct.width, mGIFStruct.height)); + if (clampedRect.IsEmpty()) { + // XXX Bug 1227546 - Maybe we should treat this as valid? + mGIFStruct.state = gif_error; + break; + } + mGIFStruct.clamped_width = clampedRect.width; + mGIFStruct.clamped_height = clampedRect.height; + + MOZ_ASSERT(mGIFStruct.clamped_width <= mGIFStruct.width); + MOZ_ASSERT(mGIFStruct.clamped_height <= mGIFStruct.height); + // 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 @@ -1170,7 +1189,7 @@ nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) // Clear state from last image mGIFStruct.irow = 0; - mGIFStruct.rows_remaining = mGIFStruct.height; + mGIFStruct.rows_remaining = mGIFStruct.clamped_height; mGIFStruct.rowp = GetCurrentRowBuffer(); // Depth of colors is determined by colormap