michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: /* michael@0: The Graphics Interchange Format(c) is the copyright property of CompuServe michael@0: Incorporated. Only CompuServe Incorporated is authorized to define, redefine, michael@0: enhance, alter, modify or change in any way the definition of the format. michael@0: michael@0: CompuServe Incorporated hereby grants a limited, non-exclusive, royalty-free michael@0: license for the use of the Graphics Interchange Format(sm) in computer michael@0: software; computer software utilizing GIF(sm) must acknowledge ownership of the michael@0: Graphics Interchange Format and its Service Mark by CompuServe Incorporated, in michael@0: User and Technical Documentation. Computer software utilizing GIF, which is michael@0: distributed or may be distributed without User or Technical Documentation must michael@0: display to the screen or printer a message acknowledging ownership of the michael@0: Graphics Interchange Format and the Service Mark by CompuServe Incorporated; in michael@0: this case, the acknowledgement may be displayed in an opening screen or leading michael@0: banner, or a closing screen or trailing banner. A message such as the following michael@0: may be used: michael@0: michael@0: "The Graphics Interchange Format(c) is the Copyright property of michael@0: CompuServe Incorporated. GIF(sm) is a Service Mark property of michael@0: CompuServe Incorporated." michael@0: michael@0: For further information, please contact : michael@0: michael@0: CompuServe Incorporated michael@0: Graphics Technology Department michael@0: 5000 Arlington Center Boulevard michael@0: Columbus, Ohio 43220 michael@0: U. S. A. michael@0: michael@0: CompuServe Incorporated maintains a mailing list with all those individuals and michael@0: organizations who wish to receive copies of this document when it is corrected michael@0: or revised. This service is offered free of charge; please provide us with your michael@0: mailing address. michael@0: */ michael@0: michael@0: #include michael@0: michael@0: #include "nsGIFDecoder2.h" michael@0: #include "nsIInputStream.h" michael@0: #include "RasterImage.h" michael@0: michael@0: #include "gfxColor.h" michael@0: #include "gfxPlatform.h" michael@0: #include "qcms.h" michael@0: #include michael@0: michael@0: namespace mozilla { michael@0: namespace image { michael@0: michael@0: /* michael@0: * GETN(n, s) requests at least 'n' bytes available from 'q', at start of state 's' michael@0: * michael@0: * Colormaps are directly copied in the resp. global_colormap or the local_colormap of the PAL image frame michael@0: * So a fixed buffer in gif_struct is good enough. michael@0: * This buffer is only needed to copy left-over data from one GifWrite call to the next michael@0: */ michael@0: #define GETN(n,s) \ michael@0: PR_BEGIN_MACRO \ michael@0: mGIFStruct.bytes_to_consume = (n); \ michael@0: mGIFStruct.state = (s); \ michael@0: PR_END_MACRO michael@0: michael@0: /* Get a 16-bit value stored in little-endian format */ michael@0: #define GETINT16(p) ((p)[1]<<8|(p)[0]) michael@0: ////////////////////////////////////////////////////////////////////// michael@0: // GIF Decoder Implementation michael@0: michael@0: nsGIFDecoder2::nsGIFDecoder2(RasterImage &aImage) michael@0: : Decoder(aImage) michael@0: , mCurrentRow(-1) michael@0: , mLastFlushedRow(-1) michael@0: , mOldColor(0) michael@0: , mCurrentFrame(-1) michael@0: , mCurrentPass(0) michael@0: , mLastFlushedPass(0) michael@0: , mGIFOpen(false) michael@0: , mSawTransparency(false) michael@0: { michael@0: // Clear out the structure, excluding the arrays michael@0: memset(&mGIFStruct, 0, sizeof(mGIFStruct)); michael@0: michael@0: // Initialize as "animate once" in case no NETSCAPE2.0 extension is found michael@0: mGIFStruct.loop_count = 1; michael@0: michael@0: // Start with the version (GIF89a|GIF87a) michael@0: mGIFStruct.state = gif_type; michael@0: mGIFStruct.bytes_to_consume = 6; michael@0: } michael@0: michael@0: nsGIFDecoder2::~nsGIFDecoder2() michael@0: { michael@0: moz_free(mGIFStruct.local_colormap); michael@0: moz_free(mGIFStruct.hold); michael@0: } michael@0: michael@0: void michael@0: nsGIFDecoder2::FinishInternal() michael@0: { michael@0: NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call FinishInternal after error!"); michael@0: michael@0: // If the GIF got cut off, handle it anyway michael@0: if (!IsSizeDecode() && mGIFOpen) { michael@0: if (mCurrentFrame == mGIFStruct.images_decoded) michael@0: EndImageFrame(); michael@0: PostDecodeDone(mGIFStruct.loop_count - 1); michael@0: mGIFOpen = false; michael@0: } michael@0: } michael@0: michael@0: // Push any new rows according to mCurrentPass/mLastFlushedPass and michael@0: // mCurrentRow/mLastFlushedRow. Note: caller is responsible for michael@0: // updating mlastFlushed{Row,Pass}. michael@0: void michael@0: nsGIFDecoder2::FlushImageData(uint32_t fromRow, uint32_t rows) michael@0: { michael@0: nsIntRect r(mGIFStruct.x_offset, mGIFStruct.y_offset + fromRow, mGIFStruct.width, rows); michael@0: PostInvalidation(r); michael@0: } michael@0: michael@0: void michael@0: nsGIFDecoder2::FlushImageData() michael@0: { michael@0: switch (mCurrentPass - mLastFlushedPass) { michael@0: case 0: // same pass michael@0: if (mCurrentRow - mLastFlushedRow) michael@0: FlushImageData(mLastFlushedRow + 1, mCurrentRow - mLastFlushedRow); michael@0: break; michael@0: michael@0: case 1: // one pass on - need to handle bottom & top rects michael@0: FlushImageData(0, mCurrentRow + 1); michael@0: FlushImageData(mLastFlushedRow + 1, mGIFStruct.height - (mLastFlushedRow + 1)); michael@0: break; michael@0: michael@0: default: // more than one pass on - push the whole frame michael@0: FlushImageData(0, mGIFStruct.height); michael@0: } michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: // GIF decoder callback methods. Part of public API for GIF2 michael@0: //****************************************************************************** michael@0: michael@0: //****************************************************************************** michael@0: void nsGIFDecoder2::BeginGIF() michael@0: { michael@0: if (mGIFOpen) michael@0: return; michael@0: michael@0: mGIFOpen = true; michael@0: michael@0: PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height); michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: void nsGIFDecoder2::BeginImageFrame(uint16_t aDepth) michael@0: { michael@0: gfxImageFormat format; michael@0: if (mGIFStruct.is_transparent) michael@0: format = gfxImageFormat::ARGB32; michael@0: else michael@0: format = gfxImageFormat::RGB24; michael@0: michael@0: MOZ_ASSERT(HasSize()); michael@0: michael@0: // Use correct format, RGB for first frame, PAL for following frames michael@0: // and include transparency to allow for optimization of opaque images michael@0: if (mGIFStruct.images_decoded) { michael@0: // Image data is stored with original depth and palette michael@0: NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset, michael@0: mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height, michael@0: format, aDepth); michael@0: } michael@0: michael@0: // Our first full frame is automatically created by the image decoding michael@0: // infrastructure. Just use it as long as it matches up. michael@0: else if (!GetCurrentFrame()->GetRect().IsEqualEdges(nsIntRect(mGIFStruct.x_offset, michael@0: mGIFStruct.y_offset, michael@0: mGIFStruct.width, michael@0: mGIFStruct.height))) { michael@0: // Regardless of depth of input, image is decoded into 24bit RGB michael@0: NeedNewFrame(mGIFStruct.images_decoded, mGIFStruct.x_offset, michael@0: mGIFStruct.y_offset, mGIFStruct.width, mGIFStruct.height, michael@0: format); michael@0: } else { michael@0: // Our preallocated frame matches up, with the possible exception of alpha. michael@0: if (format == gfxImageFormat::RGB24) { michael@0: GetCurrentFrame()->SetHasNoAlpha(); michael@0: } michael@0: } michael@0: michael@0: mCurrentFrame = mGIFStruct.images_decoded; michael@0: } michael@0: michael@0: michael@0: //****************************************************************************** michael@0: void nsGIFDecoder2::EndImageFrame() michael@0: { michael@0: FrameBlender::FrameAlpha alpha = FrameBlender::kFrameHasAlpha; michael@0: michael@0: // First flush all pending image data michael@0: if (!mGIFStruct.images_decoded) { michael@0: // Only need to flush first frame michael@0: FlushImageData(); michael@0: michael@0: // If the first frame is smaller in height than the entire image, send an michael@0: // invalidation for the area it does not have data for. michael@0: // This will clear the remaining bits of the placeholder. (Bug 37589) michael@0: const uint32_t realFrameHeight = mGIFStruct.height + mGIFStruct.y_offset; michael@0: if (realFrameHeight < mGIFStruct.screen_height) { michael@0: nsIntRect r(0, realFrameHeight, michael@0: mGIFStruct.screen_width, michael@0: mGIFStruct.screen_height - realFrameHeight); michael@0: PostInvalidation(r); michael@0: } michael@0: // This transparency check is only valid for first frame michael@0: if (mGIFStruct.is_transparent && !mSawTransparency) { michael@0: alpha = FrameBlender::kFrameOpaque; michael@0: } michael@0: } michael@0: mCurrentRow = mLastFlushedRow = -1; michael@0: mCurrentPass = mLastFlushedPass = 0; michael@0: michael@0: // Only add frame if we have any rows at all michael@0: if (mGIFStruct.rows_remaining != mGIFStruct.height) { michael@0: if (mGIFStruct.rows_remaining && mGIFStruct.images_decoded) { michael@0: // Clear the remaining rows (only needed for the animation frames) michael@0: uint8_t *rowp = mImageData + ((mGIFStruct.height - mGIFStruct.rows_remaining) * mGIFStruct.width); michael@0: memset(rowp, 0, mGIFStruct.rows_remaining * mGIFStruct.width); michael@0: } michael@0: } michael@0: michael@0: // Unconditionally increment images_decoded, because we unconditionally michael@0: // append frames in BeginImageFrame(). This ensures that images_decoded michael@0: // always refers to the frame in mImage we're currently decoding, michael@0: // even if some of them weren't decoded properly and thus are blank. michael@0: mGIFStruct.images_decoded++; michael@0: michael@0: // Tell the superclass we finished a frame michael@0: PostFrameStop(alpha, michael@0: FrameBlender::FrameDisposalMethod(mGIFStruct.disposal_method), michael@0: mGIFStruct.delay_time); michael@0: michael@0: // Reset the transparent pixel michael@0: if (mOldColor) { michael@0: mColormap[mGIFStruct.tpixel] = mOldColor; michael@0: mOldColor = 0; michael@0: } michael@0: michael@0: mCurrentFrame = -1; michael@0: } michael@0: michael@0: michael@0: //****************************************************************************** michael@0: // Send the data to the display front-end. michael@0: uint32_t nsGIFDecoder2::OutputRow() michael@0: { michael@0: int drow_start, drow_end; michael@0: drow_start = drow_end = mGIFStruct.irow; michael@0: michael@0: /* Protect against too much image data */ michael@0: if ((unsigned)drow_start >= mGIFStruct.height) { michael@0: NS_WARNING("GIF2.cpp::OutputRow - too much image data"); michael@0: return 0; michael@0: } michael@0: michael@0: if (!mGIFStruct.images_decoded) { michael@0: /* michael@0: * Haeberli-inspired hack for interlaced GIFs: Replicate lines while michael@0: * displaying to diminish the "venetian-blind" effect as the image is michael@0: * loaded. Adjust pixel vertical positions to avoid the appearance of the michael@0: * image crawling up the screen as successive passes are drawn. michael@0: */ michael@0: if (mGIFStruct.progressive_display && mGIFStruct.interlaced && (mGIFStruct.ipass < 4)) { michael@0: /* ipass = 1,2,3 results in resp. row_dup = 7,3,1 and row_shift = 3,1,0 */ michael@0: const uint32_t row_dup = 15 >> mGIFStruct.ipass; michael@0: const uint32_t row_shift = row_dup >> 1; michael@0: michael@0: drow_start -= row_shift; michael@0: drow_end = drow_start + row_dup; michael@0: michael@0: /* Extend if bottom edge isn't covered because of the shift upward. */ michael@0: if (((mGIFStruct.height - 1) - drow_end) <= row_shift) michael@0: drow_end = mGIFStruct.height - 1; michael@0: michael@0: /* Clamp first and last rows to upper and lower edge of image. */ michael@0: if (drow_start < 0) michael@0: drow_start = 0; michael@0: if ((unsigned)drow_end >= mGIFStruct.height) michael@0: drow_end = mGIFStruct.height - 1; michael@0: } michael@0: michael@0: // Row to process michael@0: const uint32_t bpr = sizeof(uint32_t) * mGIFStruct.width; michael@0: uint8_t *rowp = mImageData + (mGIFStruct.irow * bpr); michael@0: michael@0: // Convert color indices to Cairo pixels michael@0: uint8_t *from = rowp + mGIFStruct.width; michael@0: uint32_t *to = ((uint32_t*)rowp) + mGIFStruct.width; michael@0: uint32_t *cmap = mColormap; michael@0: for (uint32_t c = mGIFStruct.width; c > 0; c--) { michael@0: *--to = cmap[*--from]; michael@0: } michael@0: michael@0: // check for alpha (only for first frame) michael@0: if (mGIFStruct.is_transparent && !mSawTransparency) { michael@0: const uint32_t *rgb = (uint32_t*)rowp; michael@0: for (uint32_t i = mGIFStruct.width; i > 0; i--) { michael@0: if (*rgb++ == 0) { michael@0: mSawTransparency = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Duplicate rows michael@0: if (drow_end > drow_start) { michael@0: // irow is the current row filled michael@0: for (int r = drow_start; r <= drow_end; r++) { michael@0: if (r != int(mGIFStruct.irow)) { michael@0: memcpy(mImageData + (r * bpr), rowp, bpr); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: mCurrentRow = drow_end; michael@0: mCurrentPass = mGIFStruct.ipass; michael@0: if (mGIFStruct.ipass == 1) michael@0: mLastFlushedPass = mGIFStruct.ipass; // interlaced starts at 1 michael@0: michael@0: if (!mGIFStruct.interlaced) { michael@0: mGIFStruct.irow++; michael@0: } else { michael@0: static const uint8_t kjump[5] = { 1, 8, 8, 4, 2 }; michael@0: do { michael@0: // Row increments resp. per 8,8,4,2 rows michael@0: mGIFStruct.irow += kjump[mGIFStruct.ipass]; michael@0: if (mGIFStruct.irow >= mGIFStruct.height) { michael@0: // Next pass starts resp. at row 4,2,1,0 michael@0: mGIFStruct.irow = 8 >> mGIFStruct.ipass; michael@0: mGIFStruct.ipass++; michael@0: } michael@0: } while (mGIFStruct.irow >= mGIFStruct.height); michael@0: } michael@0: michael@0: return --mGIFStruct.rows_remaining; michael@0: } michael@0: michael@0: //****************************************************************************** michael@0: /* Perform Lempel-Ziv-Welch decoding */ michael@0: bool michael@0: nsGIFDecoder2::DoLzw(const uint8_t *q) michael@0: { michael@0: if (!mGIFStruct.rows_remaining) michael@0: return true; michael@0: michael@0: /* Copy all the decoder state variables into locals so the compiler michael@0: * won't worry about them being aliased. The locals will be homed michael@0: * back into the GIF decoder structure when we exit. michael@0: */ michael@0: int avail = mGIFStruct.avail; michael@0: int bits = mGIFStruct.bits; michael@0: int codesize = mGIFStruct.codesize; michael@0: int codemask = mGIFStruct.codemask; michael@0: int count = mGIFStruct.count; michael@0: int oldcode = mGIFStruct.oldcode; michael@0: const int clear_code = ClearCode(); michael@0: uint8_t firstchar = mGIFStruct.firstchar; michael@0: int32_t datum = mGIFStruct.datum; michael@0: uint16_t *prefix = mGIFStruct.prefix; michael@0: uint8_t *stackp = mGIFStruct.stackp; michael@0: uint8_t *suffix = mGIFStruct.suffix; michael@0: uint8_t *stack = mGIFStruct.stack; michael@0: uint8_t *rowp = mGIFStruct.rowp; michael@0: michael@0: uint32_t bpr = mGIFStruct.width; michael@0: if (!mGIFStruct.images_decoded) michael@0: bpr *= sizeof(uint32_t); michael@0: uint8_t *rowend = mImageData + (bpr * mGIFStruct.irow) + mGIFStruct.width; michael@0: michael@0: #define OUTPUT_ROW() \ michael@0: PR_BEGIN_MACRO \ michael@0: if (!OutputRow()) \ michael@0: goto END; \ michael@0: rowp = mImageData + mGIFStruct.irow * bpr; \ michael@0: rowend = rowp + mGIFStruct.width; \ michael@0: PR_END_MACRO michael@0: michael@0: for (const uint8_t* ch = q; count-- > 0; ch++) michael@0: { michael@0: /* Feed the next byte into the decoder's 32-bit input buffer. */ michael@0: datum += ((int32_t) *ch) << bits; michael@0: bits += 8; michael@0: michael@0: /* Check for underflow of decoder's 32-bit input buffer. */ michael@0: while (bits >= codesize) michael@0: { michael@0: /* Get the leading variable-length symbol from the data stream */ michael@0: int code = datum & codemask; michael@0: datum >>= codesize; michael@0: bits -= codesize; michael@0: michael@0: /* Reset the dictionary to its original state, if requested */ michael@0: if (code == clear_code) { michael@0: codesize = mGIFStruct.datasize + 1; michael@0: codemask = (1 << codesize) - 1; michael@0: avail = clear_code + 2; michael@0: oldcode = -1; michael@0: continue; michael@0: } michael@0: michael@0: /* Check for explicit end-of-stream code */ michael@0: if (code == (clear_code + 1)) { michael@0: /* end-of-stream should only appear after all image data */ michael@0: return (mGIFStruct.rows_remaining == 0); michael@0: } michael@0: michael@0: if (oldcode == -1) { michael@0: if (code >= MAX_BITS) michael@0: return false; michael@0: *rowp++ = suffix[code] & mColorMask; // ensure index is within colormap michael@0: if (rowp == rowend) michael@0: OUTPUT_ROW(); michael@0: michael@0: firstchar = oldcode = code; michael@0: continue; michael@0: } michael@0: michael@0: int incode = code; michael@0: if (code >= avail) { michael@0: *stackp++ = firstchar; michael@0: code = oldcode; michael@0: michael@0: if (stackp >= stack + MAX_BITS) michael@0: return false; michael@0: } michael@0: michael@0: while (code >= clear_code) michael@0: { michael@0: if ((code >= MAX_BITS) || (code == prefix[code])) michael@0: return false; michael@0: michael@0: *stackp++ = suffix[code]; michael@0: code = prefix[code]; michael@0: michael@0: if (stackp == stack + MAX_BITS) michael@0: return false; michael@0: } michael@0: michael@0: *stackp++ = firstchar = suffix[code]; michael@0: michael@0: /* Define a new codeword in the dictionary. */ michael@0: if (avail < 4096) { michael@0: prefix[avail] = oldcode; michael@0: suffix[avail] = firstchar; michael@0: avail++; michael@0: michael@0: /* If we've used up all the codewords of a given length michael@0: * increase the length of codewords by one bit, but don't michael@0: * exceed the specified maximum codeword size of 12 bits. michael@0: */ michael@0: if (((avail & codemask) == 0) && (avail < 4096)) { michael@0: codesize++; michael@0: codemask += avail; michael@0: } michael@0: } michael@0: oldcode = incode; michael@0: michael@0: /* Copy the decoded data out to the scanline buffer. */ michael@0: do { michael@0: *rowp++ = *--stackp & mColorMask; // ensure index is within colormap michael@0: if (rowp == rowend) michael@0: OUTPUT_ROW(); michael@0: } while (stackp > stack); michael@0: } michael@0: } michael@0: michael@0: END: michael@0: michael@0: /* Home the local copies of the GIF decoder state variables */ michael@0: mGIFStruct.avail = avail; michael@0: mGIFStruct.bits = bits; michael@0: mGIFStruct.codesize = codesize; michael@0: mGIFStruct.codemask = codemask; michael@0: mGIFStruct.count = count; michael@0: mGIFStruct.oldcode = oldcode; michael@0: mGIFStruct.firstchar = firstchar; michael@0: mGIFStruct.datum = datum; michael@0: mGIFStruct.stackp = stackp; michael@0: mGIFStruct.rowp = rowp; michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * Expand the colormap from RGB to Packed ARGB as needed by Cairo. michael@0: * And apply any LCMS transformation. michael@0: */ michael@0: static void ConvertColormap(uint32_t *aColormap, uint32_t aColors) michael@0: { michael@0: // Apply CMS transformation if enabled and available michael@0: if (gfxPlatform::GetCMSMode() == eCMSMode_All) { michael@0: qcms_transform *transform = gfxPlatform::GetCMSRGBTransform(); michael@0: if (transform) michael@0: qcms_transform_data(transform, aColormap, aColormap, aColors); michael@0: } michael@0: // Convert from the GIF's RGB format to the Cairo format. michael@0: // Work from end to begin, because of the in-place expansion michael@0: uint8_t *from = ((uint8_t *)aColormap) + 3 * aColors; michael@0: uint32_t *to = aColormap + aColors; michael@0: michael@0: // Convert color entries to Cairo format michael@0: michael@0: // set up for loops below michael@0: if (!aColors) return; michael@0: uint32_t c = aColors; michael@0: michael@0: // copy as bytes until source pointer is 32-bit-aligned michael@0: // NB: can't use 32-bit reads, they might read off the end of the buffer michael@0: for (; (NS_PTR_TO_UINT32(from) & 0x3) && c; --c) { michael@0: from -= 3; michael@0: *--to = gfxPackedPixel(0xFF, from[0], from[1], from[2]); michael@0: } michael@0: michael@0: // bulk copy of pixels. michael@0: while (c >= 4) { michael@0: from -= 12; michael@0: to -= 4; michael@0: c -= 4; michael@0: GFX_BLOCK_RGB_TO_FRGB(from,to); michael@0: } michael@0: michael@0: // copy remaining pixel(s) michael@0: // NB: can't use 32-bit reads, they might read off the end of the buffer michael@0: while (c--) { michael@0: from -= 3; michael@0: *--to = gfxPackedPixel(0xFF, from[0], from[1], from[2]); michael@0: } michael@0: } michael@0: michael@0: void michael@0: nsGIFDecoder2::WriteInternal(const char *aBuffer, uint32_t aCount, DecodeStrategy) michael@0: { michael@0: NS_ABORT_IF_FALSE(!HasError(), "Shouldn't call WriteInternal after error!"); michael@0: michael@0: // These variables changed names, and renaming would make a much bigger patch :( michael@0: const uint8_t *buf = (const uint8_t *)aBuffer; michael@0: uint32_t len = aCount; michael@0: michael@0: const uint8_t *q = buf; michael@0: michael@0: // Add what we have sofar to the block michael@0: // If previous call to me left something in the hold first complete current block michael@0: // Or if we are filling the colormaps, first complete the colormap michael@0: uint8_t* p = (mGIFStruct.state == gif_global_colormap) ? (uint8_t*)mGIFStruct.global_colormap : michael@0: (mGIFStruct.state == gif_image_colormap) ? (uint8_t*)mColormap : michael@0: (mGIFStruct.bytes_in_hold) ? mGIFStruct.hold : nullptr; michael@0: michael@0: if (len == 0 && buf == nullptr) { michael@0: // We've just gotten the frame we asked for. Time to use the data we michael@0: // stashed away. michael@0: len = mGIFStruct.bytes_in_hold; michael@0: q = buf = p; michael@0: } else if (p) { michael@0: // Add what we have sofar to the block michael@0: uint32_t l = std::min(len, mGIFStruct.bytes_to_consume); michael@0: memcpy(p+mGIFStruct.bytes_in_hold, buf, l); michael@0: michael@0: if (l < mGIFStruct.bytes_to_consume) { michael@0: // Not enough in 'buf' to complete current block, get more michael@0: mGIFStruct.bytes_in_hold += l; michael@0: mGIFStruct.bytes_to_consume -= l; michael@0: return; michael@0: } michael@0: // Point 'q' to complete block in hold (or in colormap) michael@0: q = p; michael@0: } michael@0: michael@0: // Invariant: michael@0: // 'q' is start of current to be processed block (hold, colormap or buf) michael@0: // 'bytes_to_consume' is number of bytes to consume from 'buf' michael@0: // 'buf' points to the bytes to be consumed from the input buffer michael@0: // 'len' is number of bytes left in input buffer from position 'buf'. michael@0: // At entrance of the for loop will 'buf' will be moved 'bytes_to_consume' michael@0: // to point to next buffer, 'len' is adjusted accordingly. michael@0: // So that next round in for loop, q gets pointed to the next buffer. michael@0: michael@0: for (;len >= mGIFStruct.bytes_to_consume; q=buf, mGIFStruct.bytes_in_hold = 0) { michael@0: // Eat the current block from the buffer, q keeps pointed at current block michael@0: buf += mGIFStruct.bytes_to_consume; michael@0: len -= mGIFStruct.bytes_to_consume; michael@0: michael@0: switch (mGIFStruct.state) michael@0: { michael@0: case gif_lzw: michael@0: if (!DoLzw(q)) { michael@0: mGIFStruct.state = gif_error; michael@0: break; michael@0: } michael@0: GETN(1, gif_sub_block); michael@0: break; michael@0: michael@0: case gif_lzw_start: michael@0: { michael@0: // Make sure the transparent pixel is transparent in the colormap michael@0: if (mGIFStruct.is_transparent) { michael@0: // Save old value so we can restore it later michael@0: if (mColormap == mGIFStruct.global_colormap) michael@0: mOldColor = mColormap[mGIFStruct.tpixel]; michael@0: mColormap[mGIFStruct.tpixel] = 0; michael@0: } michael@0: michael@0: /* Initialize LZW parser/decoder */ michael@0: mGIFStruct.datasize = *q; michael@0: const int clear_code = ClearCode(); michael@0: if (mGIFStruct.datasize > MAX_LZW_BITS || michael@0: clear_code >= MAX_BITS) { michael@0: mGIFStruct.state = gif_error; michael@0: break; michael@0: } michael@0: michael@0: mGIFStruct.avail = clear_code + 2; michael@0: mGIFStruct.oldcode = -1; michael@0: mGIFStruct.codesize = mGIFStruct.datasize + 1; michael@0: mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1; michael@0: mGIFStruct.datum = mGIFStruct.bits = 0; michael@0: michael@0: /* init the tables */ michael@0: for (int i = 0; i < clear_code; i++) michael@0: mGIFStruct.suffix[i] = i; michael@0: michael@0: mGIFStruct.stackp = mGIFStruct.stack; michael@0: michael@0: GETN(1, gif_sub_block); michael@0: } michael@0: break; michael@0: michael@0: /* All GIF files begin with "GIF87a" or "GIF89a" */ michael@0: case gif_type: michael@0: if (!strncmp((char*)q, "GIF89a", 6)) { michael@0: mGIFStruct.version = 89; michael@0: } else if (!strncmp((char*)q, "GIF87a", 6)) { michael@0: mGIFStruct.version = 87; michael@0: } else { michael@0: mGIFStruct.state = gif_error; michael@0: break; michael@0: } michael@0: GETN(7, gif_global_header); michael@0: break; michael@0: michael@0: case gif_global_header: michael@0: /* This is the height and width of the "screen" or michael@0: * frame into which images are rendered. The michael@0: * individual images can be smaller than the michael@0: * screen size and located with an origin anywhere michael@0: * within the screen. michael@0: */ michael@0: michael@0: mGIFStruct.screen_width = GETINT16(q); michael@0: mGIFStruct.screen_height = GETINT16(q + 2); michael@0: mGIFStruct.global_colormap_depth = (q[4]&0x07) + 1; michael@0: michael@0: if (IsSizeDecode()) { michael@0: MOZ_ASSERT(!mGIFOpen, "Gif should not be open at this point"); michael@0: PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height); michael@0: return; michael@0: } michael@0: michael@0: // screen_bgcolor is not used michael@0: //mGIFStruct.screen_bgcolor = q[5]; michael@0: // q[6] = Pixel Aspect Ratio michael@0: // Not used michael@0: // float aspect = (float)((q[6] + 15) / 64.0); michael@0: michael@0: if (q[4] & 0x80) { /* global map */ michael@0: // Get the global colormap michael@0: const uint32_t size = (3 << mGIFStruct.global_colormap_depth); michael@0: if (len < size) { michael@0: // Use 'hold' pattern to get the global colormap michael@0: GETN(size, gif_global_colormap); michael@0: break; michael@0: } michael@0: // Copy everything, go to colormap state to do CMS correction michael@0: memcpy(mGIFStruct.global_colormap, buf, size); michael@0: buf += size; michael@0: len -= size; michael@0: GETN(0, gif_global_colormap); michael@0: break; michael@0: } michael@0: michael@0: GETN(1, gif_image_start); michael@0: break; michael@0: michael@0: case gif_global_colormap: michael@0: // Everything is already copied into global_colormap michael@0: // Convert into Cairo colors including CMS transformation michael@0: ConvertColormap(mGIFStruct.global_colormap, 1< 0) { michael@0: /* The file is corrupt, but one or more images have michael@0: * been decoded correctly. In this case, we proceed michael@0: * as if the file were correctly terminated and set michael@0: * the state to gif_done, so the GIF will display. michael@0: */ michael@0: mGIFStruct.state = gif_done; michael@0: } else { michael@0: /* No images decoded, there is nothing to display. */ michael@0: mGIFStruct.state = gif_error; michael@0: } michael@0: } michael@0: break; michael@0: michael@0: case gif_extension: michael@0: mGIFStruct.bytes_to_consume = q[1]; michael@0: if (mGIFStruct.bytes_to_consume) { michael@0: switch (*q) { michael@0: case GIF_GRAPHIC_CONTROL_LABEL: michael@0: // The GIF spec mandates that the GIFControlExtension header block length is 4 bytes, michael@0: // and the parser for this block reads 4 bytes, so we must enforce that the buffer michael@0: // contains at least this many bytes. If the GIF specifies a different length, we michael@0: // allow that, so long as it's larger; the additional data will simply be ignored. michael@0: mGIFStruct.state = gif_control_extension; michael@0: mGIFStruct.bytes_to_consume = std::max(mGIFStruct.bytes_to_consume, 4u); michael@0: break; michael@0: michael@0: // The GIF spec also specifies the lengths of the following two extensions' headers michael@0: // (as 12 and 11 bytes, respectively). Because we ignore the plain text extension entirely michael@0: // and sanity-check the actual length of the application extension header before reading it, michael@0: // we allow GIFs to deviate from these values in either direction. This is important for michael@0: // real-world compatibility, as GIFs in the wild exist with application extension headers michael@0: // that are both shorter and longer than 11 bytes. michael@0: case GIF_APPLICATION_EXTENSION_LABEL: michael@0: mGIFStruct.state = gif_application_extension; michael@0: break; michael@0: michael@0: case GIF_PLAIN_TEXT_LABEL: michael@0: mGIFStruct.state = gif_skip_block; michael@0: break; michael@0: michael@0: case GIF_COMMENT_LABEL: michael@0: mGIFStruct.state = gif_consume_comment; michael@0: break; michael@0: michael@0: default: michael@0: mGIFStruct.state = gif_skip_block; michael@0: } michael@0: } else { michael@0: GETN(1, gif_image_start); michael@0: } michael@0: break; michael@0: michael@0: case gif_consume_block: michael@0: if (!*q) michael@0: GETN(1, gif_image_start); michael@0: else michael@0: GETN(*q, gif_skip_block); michael@0: break; michael@0: michael@0: case gif_skip_block: michael@0: GETN(1, gif_consume_block); michael@0: break; michael@0: michael@0: case gif_control_extension: michael@0: mGIFStruct.is_transparent = *q & 0x1; michael@0: mGIFStruct.tpixel = q[3]; michael@0: mGIFStruct.disposal_method = ((*q) >> 2) & 0x7; michael@0: // Some specs say 3rd bit (value 4), other specs say value 3 michael@0: // Let's choose 3 (the more popular) michael@0: if (mGIFStruct.disposal_method == 4) michael@0: mGIFStruct.disposal_method = 3; michael@0: mGIFStruct.delay_time = GETINT16(q + 1) * 10; michael@0: GETN(1, gif_consume_block); michael@0: break; michael@0: michael@0: case gif_comment_extension: michael@0: if (*q) michael@0: GETN(*q, gif_consume_comment); michael@0: else michael@0: GETN(1, gif_image_start); michael@0: break; michael@0: michael@0: case gif_consume_comment: michael@0: GETN(1, gif_comment_extension); michael@0: break; michael@0: michael@0: case gif_application_extension: michael@0: /* Check for netscape application extension */ michael@0: if (mGIFStruct.bytes_to_consume == 11 && michael@0: (!strncmp((char*)q, "NETSCAPE2.0", 11) || michael@0: !strncmp((char*)q, "ANIMEXTS1.0", 11))) michael@0: GETN(1, gif_netscape_extension_block); michael@0: else michael@0: GETN(1, gif_consume_block); michael@0: break; michael@0: michael@0: /* Netscape-specific GIF extension: animation looping */ michael@0: case gif_netscape_extension_block: michael@0: if (*q) michael@0: // We might need to consume 3 bytes in michael@0: // gif_consume_netscape_extension, so make sure we have at least that. michael@0: GETN(std::max(3, static_cast(*q)), gif_consume_netscape_extension); michael@0: else michael@0: GETN(1, gif_image_start); michael@0: break; michael@0: michael@0: /* Parse netscape-specific application extensions */ michael@0: case gif_consume_netscape_extension: michael@0: switch (q[0] & 7) { michael@0: case 1: michael@0: /* Loop entire animation specified # of times. Only read the michael@0: loop count during the first iteration. */ michael@0: mGIFStruct.loop_count = GETINT16(q + 1); michael@0: GETN(1, gif_netscape_extension_block); michael@0: break; michael@0: michael@0: case 2: michael@0: /* Wait for specified # of bytes to enter buffer */ michael@0: // Don't do this, this extension doesn't exist (isn't used at all) michael@0: // and doesn't do anything, as our streaming/buffering takes care of it all... michael@0: // See: http://semmix.pl/color/exgraf/eeg24.htm michael@0: GETN(1, gif_netscape_extension_block); michael@0: break; michael@0: michael@0: default: michael@0: // 0,3-7 are yet to be defined netscape extension codes michael@0: mGIFStruct.state = gif_error; michael@0: } michael@0: break; michael@0: michael@0: case gif_image_header: michael@0: { michael@0: /* Get image offsets, with respect to the screen origin */ michael@0: mGIFStruct.x_offset = GETINT16(q); michael@0: mGIFStruct.y_offset = GETINT16(q + 2); michael@0: michael@0: /* Get image width and height. */ michael@0: mGIFStruct.width = GETINT16(q + 4); michael@0: mGIFStruct.height = GETINT16(q + 6); michael@0: michael@0: if (!mGIFStruct.images_decoded) { michael@0: /* Work around broken GIF files where the logical screen michael@0: * size has weird width or height. We assume that GIF87a michael@0: * files don't contain animations. michael@0: */ michael@0: if ((mGIFStruct.screen_height < mGIFStruct.height) || michael@0: (mGIFStruct.screen_width < mGIFStruct.width) || michael@0: (mGIFStruct.version == 87)) { michael@0: mGIFStruct.screen_height = mGIFStruct.height; michael@0: mGIFStruct.screen_width = mGIFStruct.width; michael@0: mGIFStruct.x_offset = 0; michael@0: mGIFStruct.y_offset = 0; michael@0: } michael@0: // Create the image container with the right size. michael@0: BeginGIF(); michael@0: if (HasError()) { michael@0: // Setting the size led to an error. michael@0: mGIFStruct.state = gif_error; michael@0: return; michael@0: } michael@0: michael@0: // If we were doing a size decode, we're done michael@0: if (IsSizeDecode()) michael@0: return; michael@0: } michael@0: michael@0: /* Work around more broken GIF files that have zero image michael@0: width or height */ michael@0: if (!mGIFStruct.height || !mGIFStruct.width) { michael@0: mGIFStruct.height = mGIFStruct.screen_height; michael@0: mGIFStruct.width = mGIFStruct.screen_width; michael@0: if (!mGIFStruct.height || !mGIFStruct.width) { michael@0: mGIFStruct.state = gif_error; michael@0: break; michael@0: } michael@0: } michael@0: michael@0: /* Depth of colors is determined by colormap */ michael@0: /* (q[8] & 0x80) indicates local colormap */ michael@0: /* bits per pixel is (q[8]&0x07 + 1) when local colormap is set */ michael@0: uint32_t depth = mGIFStruct.global_colormap_depth; michael@0: if (q[8] & 0x80) michael@0: depth = (q[8]&0x07) + 1; michael@0: uint32_t realDepth = depth; michael@0: while (mGIFStruct.tpixel >= (1 << realDepth) && (realDepth < 8)) { michael@0: realDepth++; michael@0: } michael@0: // Mask to limit the color values within the colormap michael@0: mColorMask = 0xFF >> (8 - realDepth); michael@0: BeginImageFrame(realDepth); michael@0: michael@0: if (NeedsNewFrame()) { michael@0: // We now need a new frame from the decoder framework. We leave all our michael@0: // data in the buffer as if it wasn't consumed, copy to our hold and return michael@0: // to the decoder framework. michael@0: uint32_t size = len + mGIFStruct.bytes_to_consume + mGIFStruct.bytes_in_hold; michael@0: if (size) { michael@0: if (SetHold(q, mGIFStruct.bytes_to_consume + mGIFStruct.bytes_in_hold, buf, len)) { michael@0: // Back into the decoder infrastructure so we can get called again. michael@0: GETN(9, gif_image_header_continue); michael@0: return; michael@0: } michael@0: } michael@0: break; michael@0: } else { michael@0: // FALL THROUGH michael@0: } michael@0: } michael@0: michael@0: case gif_image_header_continue: michael@0: { michael@0: // While decoders can reuse frames, we unconditionally increment michael@0: // mGIFStruct.images_decoded when we're done with a frame, so we both can michael@0: // and need to zero out the colormap and image data after every new frame. michael@0: memset(mImageData, 0, mImageDataLength); michael@0: if (mColormap) { michael@0: memset(mColormap, 0, mColormapSize); michael@0: } michael@0: michael@0: if (!mGIFStruct.images_decoded) { michael@0: // Send a onetime invalidation for the first frame if it has a y-axis offset. michael@0: // Otherwise, the area may never be refreshed and the placeholder will remain michael@0: // on the screen. (Bug 37589) michael@0: if (mGIFStruct.y_offset > 0) { michael@0: nsIntRect r(0, 0, mGIFStruct.screen_width, mGIFStruct.y_offset); michael@0: PostInvalidation(r); michael@0: } michael@0: } michael@0: michael@0: if (q[8] & 0x40) { michael@0: mGIFStruct.interlaced = true; michael@0: mGIFStruct.ipass = 1; michael@0: } else { michael@0: mGIFStruct.interlaced = false; michael@0: mGIFStruct.ipass = 0; michael@0: } michael@0: michael@0: /* Only apply the Haeberli display hack on the first frame */ michael@0: mGIFStruct.progressive_display = (mGIFStruct.images_decoded == 0); michael@0: michael@0: /* Clear state from last image */ michael@0: mGIFStruct.irow = 0; michael@0: mGIFStruct.rows_remaining = mGIFStruct.height; michael@0: mGIFStruct.rowp = mImageData; michael@0: michael@0: /* Depth of colors is determined by colormap */ michael@0: /* (q[8] & 0x80) indicates local colormap */ michael@0: /* bits per pixel is (q[8]&0x07 + 1) when local colormap is set */ michael@0: uint32_t depth = mGIFStruct.global_colormap_depth; michael@0: if (q[8] & 0x80) michael@0: depth = (q[8]&0x07) + 1; michael@0: uint32_t realDepth = depth; michael@0: while (mGIFStruct.tpixel >= (1 << realDepth) && (realDepth < 8)) { michael@0: realDepth++; michael@0: } michael@0: michael@0: if (q[8] & 0x80) /* has a local colormap? */ michael@0: { michael@0: mGIFStruct.local_colormap_size = 1 << depth; michael@0: if (!mGIFStruct.images_decoded) { michael@0: // First frame has local colormap, allocate space for it michael@0: // as the image frame doesn't have its own palette michael@0: mColormapSize = sizeof(uint32_t) << realDepth; michael@0: if (!mGIFStruct.local_colormap) { michael@0: mGIFStruct.local_colormap = (uint32_t*)moz_xmalloc(mColormapSize); michael@0: } michael@0: mColormap = mGIFStruct.local_colormap; michael@0: } michael@0: const uint32_t size = 3 << depth; michael@0: if (mColormapSize > size) { michael@0: // Clear the notfilled part of the colormap michael@0: memset(((uint8_t*)mColormap) + size, 0, mColormapSize - size); michael@0: } michael@0: if (len < size) { michael@0: // Use 'hold' pattern to get the image colormap michael@0: GETN(size, gif_image_colormap); michael@0: break; michael@0: } michael@0: // Copy everything, go to colormap state to do CMS correction michael@0: memcpy(mColormap, buf, size); michael@0: buf += size; michael@0: len -= size; michael@0: GETN(0, gif_image_colormap); michael@0: break; michael@0: } else { michael@0: /* Switch back to the global palette */ michael@0: if (mGIFStruct.images_decoded) { michael@0: // Copy global colormap into the palette of current frame michael@0: memcpy(mColormap, mGIFStruct.global_colormap, mColormapSize); michael@0: } else { michael@0: mColormap = mGIFStruct.global_colormap; michael@0: } michael@0: } michael@0: GETN(1, gif_lzw_start); michael@0: } michael@0: break; michael@0: michael@0: case gif_image_colormap: michael@0: // Everything is already copied into local_colormap michael@0: // Convert into Cairo colors including CMS transformation michael@0: ConvertColormap(mColormap, mGIFStruct.local_colormap_size); michael@0: GETN(1, gif_lzw_start); michael@0: break; michael@0: michael@0: case gif_sub_block: michael@0: mGIFStruct.count = *q; michael@0: if (mGIFStruct.count) { michael@0: /* Still working on the same image: Process next LZW data block */ michael@0: /* Make sure there are still rows left. If the GIF data */ michael@0: /* is corrupt, we may not get an explicit terminator. */ michael@0: if (!mGIFStruct.rows_remaining) { michael@0: #ifdef DONT_TOLERATE_BROKEN_GIFS michael@0: mGIFStruct.state = gif_error; michael@0: break; michael@0: #else michael@0: /* This is an illegal GIF, but we remain tolerant. */ michael@0: GETN(1, gif_sub_block); michael@0: #endif michael@0: if (mGIFStruct.count == GIF_TRAILER) { michael@0: /* Found a terminator anyway, so consider the image done */ michael@0: GETN(1, gif_done); michael@0: break; michael@0: } michael@0: } michael@0: GETN(mGIFStruct.count, gif_lzw); michael@0: } else { michael@0: /* See if there are any more images in this sequence. */ michael@0: EndImageFrame(); michael@0: GETN(1, gif_image_start); michael@0: } michael@0: break; michael@0: michael@0: case gif_done: michael@0: MOZ_ASSERT(!IsSizeDecode(), "Size decodes shouldn't reach gif_done"); michael@0: FinishInternal(); michael@0: goto done; michael@0: michael@0: case gif_error: michael@0: PostDataError(); michael@0: return; michael@0: michael@0: // We shouldn't ever get here. michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: michael@0: // if an error state is set but no data remains, code flow reaches here michael@0: if (mGIFStruct.state == gif_error) { michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: michael@0: // Copy the leftover into mGIFStruct.hold michael@0: if (len) { michael@0: // Add what we have sofar to the block michael@0: if (mGIFStruct.state != gif_global_colormap && mGIFStruct.state != gif_image_colormap) { michael@0: if (!SetHold(buf, len)) { michael@0: PostDataError(); michael@0: return; michael@0: } michael@0: } else { michael@0: uint8_t* p = (mGIFStruct.state == gif_global_colormap) ? (uint8_t*)mGIFStruct.global_colormap : michael@0: (uint8_t*)mColormap; michael@0: memcpy(p, buf, len); michael@0: mGIFStruct.bytes_in_hold = len; michael@0: } michael@0: michael@0: mGIFStruct.bytes_to_consume -= len; michael@0: } michael@0: michael@0: // We want to flush before returning if we're on the first frame michael@0: done: michael@0: if (!mGIFStruct.images_decoded) { michael@0: FlushImageData(); michael@0: mLastFlushedRow = mCurrentRow; michael@0: mLastFlushedPass = mCurrentPass; michael@0: } michael@0: michael@0: return; michael@0: } michael@0: michael@0: bool michael@0: nsGIFDecoder2::SetHold(const uint8_t* buf1, uint32_t count1, const uint8_t* buf2 /* = nullptr */, uint32_t count2 /* = 0 */) michael@0: { michael@0: // We have to handle the case that buf currently points to hold michael@0: uint8_t* newHold = (uint8_t *) moz_malloc(std::max(uint32_t(MIN_HOLD_SIZE), count1 + count2)); michael@0: if (!newHold) { michael@0: mGIFStruct.state = gif_error; michael@0: return false; michael@0: } michael@0: michael@0: memcpy(newHold, buf1, count1); michael@0: if (buf2) { michael@0: memcpy(newHold + count1, buf2, count2); michael@0: } michael@0: michael@0: moz_free(mGIFStruct.hold); michael@0: mGIFStruct.hold = newHold; michael@0: mGIFStruct.bytes_in_hold = count1 + count2; michael@0: return true; michael@0: } michael@0: michael@0: Telemetry::ID michael@0: nsGIFDecoder2::SpeedHistogram() michael@0: { michael@0: return Telemetry::IMAGE_DECODE_SPEED_GIF; michael@0: } michael@0: michael@0: michael@0: } // namespace image michael@0: } // namespace mozilla