void Decoder::Init() { // No re-initializing MOZ_ASSERT(!mInitialized, "Can't re-initialize a decoder!"); // It doesn't make sense to decode anything but the first frame if we can't // store anything in the SurfaceCache, since only the last frame we decode // will be retrievable. MOZ_ASSERT(ShouldUseSurfaceCache() || IsFirstFrameDecode()); // Implementation-specific initialization InitInternal(); mInitialized = true; }
void nsGIFDecoder2::WriteInternal(const char* aBuffer, uint32_t aCount) { MOZ_ASSERT(!HasError(), "Shouldn't call WriteInternal after error!"); // These variables changed names; renaming would make a much bigger patch :( const uint8_t* buf = (const uint8_t*)aBuffer; uint32_t len = aCount; const uint8_t* q = buf; // Add what we have sofar to the block // If previous call to me left something in the hold first complete current // block, or if we are filling the colormaps, first complete the colormap 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 (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); if (l < mGIFStruct.bytes_to_consume) { // Not enough in 'buf' to complete current block, get more mGIFStruct.bytes_in_hold += l; mGIFStruct.bytes_to_consume -= l; return; } // Point 'q' to complete block in hold (or in colormap) q = p; } // Invariant: // 'q' is start of current to be processed block (hold, colormap or buf) // 'bytes_to_consume' is number of bytes to consume from 'buf' // 'buf' points to the bytes to be consumed from the input buffer // 'len' is number of bytes left in input buffer from position 'buf'. // At entrance of the for loop will 'buf' will be moved 'bytes_to_consume' // 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, 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; switch (mGIFStruct.state) { case gif_lzw: if (!DoLzw(q)) { mGIFStruct.state = gif_error; break; } GETN(1, gif_sub_block); break; case gif_lzw_start: { // Make sure the transparent pixel is transparent in the colormap if (mGIFStruct.is_transparent) { // Save old value so we can restore it later if (mColormap == mGIFStruct.global_colormap) { mOldColor = mColormap[mGIFStruct.tpixel]; } mColormap[mGIFStruct.tpixel] = 0; } // Initialize LZW parser/decoder mGIFStruct.datasize = *q; const int clear_code = ClearCode(); if (mGIFStruct.datasize > MAX_LZW_BITS || clear_code >= MAX_BITS) { mGIFStruct.state = gif_error; break; } mGIFStruct.avail = clear_code + 2; mGIFStruct.oldcode = -1; mGIFStruct.codesize = mGIFStruct.datasize + 1; mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1; mGIFStruct.datum = mGIFStruct.bits = 0; // init the tables for (int i = 0; i < clear_code; i++) { mGIFStruct.suffix[i] = i; } mGIFStruct.stackp = mGIFStruct.stack; GETN(1, gif_sub_block); } break; // All GIF files begin with "GIF87a" or "GIF89a" case gif_type: if (!strncmp((char*)q, "GIF89a", 6)) { mGIFStruct.version = 89; } else if (!strncmp((char*)q, "GIF87a", 6)) { mGIFStruct.version = 87; } else { mGIFStruct.state = gif_error; break; } GETN(7, gif_global_header); break; case gif_global_header: // This is the height and width of the "screen" or // frame into which images are rendered. The // individual images can be smaller than the // screen size and located with an origin anywhere // within the screen. mGIFStruct.screen_width = GETINT16(q); mGIFStruct.screen_height = GETINT16(q + 2); mGIFStruct.global_colormap_depth = (q[4]&0x07) + 1; if (IsMetadataDecode()) { MOZ_ASSERT(!mGIFOpen, "Gif should not be open at this point"); PostSize(mGIFStruct.screen_width, mGIFStruct.screen_height); return; } // screen_bgcolor is not used //mGIFStruct.screen_bgcolor = q[5]; // q[6] = Pixel Aspect Ratio // Not used // float aspect = (float)((q[6] + 15) / 64.0); if (q[4] & 0x80) { // Get the global colormap const uint32_t size = (3 << mGIFStruct.global_colormap_depth); if (len < size) { // Use 'hold' pattern to get the global colormap GETN(size, gif_global_colormap); break; } // Copy everything, go to colormap state to do CMS correction memcpy(mGIFStruct.global_colormap, buf, size); buf += size; len -= size; GETN(0, gif_global_colormap); break; } GETN(1, gif_image_start); break; case gif_global_colormap: // Everything is already copied into global_colormap // Convert into Cairo colors including CMS transformation ConvertColormap(mGIFStruct.global_colormap, 1<<mGIFStruct.global_colormap_depth); GETN(1, gif_image_start); break; case gif_image_start: switch (*q) { case GIF_TRAILER: mGIFStruct.state = gif_done; break; case GIF_EXTENSION_INTRODUCER: GETN(2, gif_extension); break; case GIF_IMAGE_SEPARATOR: GETN(9, gif_image_header); break; default: // If we get anything other than GIF_IMAGE_SEPARATOR, // GIF_EXTENSION_INTRODUCER, or GIF_TRAILER, there is extraneous data // between blocks. The GIF87a spec tells us to keep reading // until we find an image separator, but GIF89a says such // a file is corrupt. We follow GIF89a and bail out. if (mGIFStruct.images_decoded > 0) { // The file is corrupt, but one or more images have // been decoded correctly. In this case, we proceed // as if the file were correctly terminated and set // the state to gif_done, so the GIF will display. mGIFStruct.state = gif_done; } else { // No images decoded, there is nothing to display. mGIFStruct.state = gif_error; } } break; case gif_extension: mGIFStruct.bytes_to_consume = q[1]; if (mGIFStruct.bytes_to_consume) { switch (*q) { case GIF_GRAPHIC_CONTROL_LABEL: // The GIF spec mandates that the GIFControlExtension header block // length is 4 bytes, and the parser for this block reads 4 bytes, // so we must enforce that the buffer contains at least this many // bytes. If the GIF specifies a different length, we allow that, so // long as it's larger; the additional data will simply be ignored. mGIFStruct.state = gif_control_extension; mGIFStruct.bytes_to_consume = std::max(mGIFStruct.bytes_to_consume, 4u); break; // The GIF spec also specifies the lengths of the following two // extensions' headers (as 12 and 11 bytes, respectively). Because // we ignore the plain text extension entirely and sanity-check the // actual length of the application extension header before reading it, // we allow GIFs to deviate from these values in either direction. This // is important for real-world compatibility, as GIFs in the wild exist // with application extension headers that are both shorter and longer // than 11 bytes. case GIF_APPLICATION_EXTENSION_LABEL: mGIFStruct.state = gif_application_extension; break; case GIF_PLAIN_TEXT_LABEL: mGIFStruct.state = gif_skip_block; break; case GIF_COMMENT_LABEL: mGIFStruct.state = gif_consume_comment; break; default: mGIFStruct.state = gif_skip_block; } } else { GETN(1, gif_image_start); } break; case gif_consume_block: if (!*q) { GETN(1, gif_image_start); } else { GETN(*q, gif_skip_block); } break; case gif_skip_block: GETN(1, gif_consume_block); break; case gif_control_extension: mGIFStruct.is_transparent = *q & 0x1; mGIFStruct.tpixel = q[3]; mGIFStruct.disposal_method = ((*q) >> 2) & 0x7; if (mGIFStruct.disposal_method == 4) { // Some specs say 3rd bit (value 4), other specs say value 3. // Let's choose 3 (the more popular). mGIFStruct.disposal_method = 3; } else if (mGIFStruct.disposal_method > 4) { // This GIF is using a disposal method which is undefined in the spec. // Treat it as DisposalMethod::NOT_SPECIFIED. mGIFStruct.disposal_method = 0; } { DisposalMethod method = DisposalMethod(mGIFStruct.disposal_method); if (method == DisposalMethod::CLEAR_ALL || method == DisposalMethod::CLEAR) { // We may have to display the background under this image during // animation playback, so we regard it as transparent. PostHasTransparency(); } } mGIFStruct.delay_time = GETINT16(q + 1) * 10; GETN(1, gif_consume_block); break; case gif_comment_extension: if (*q) { GETN(*q, gif_consume_comment); } else { GETN(1, gif_image_start); } break; case gif_consume_comment: GETN(1, gif_comment_extension); break; case gif_application_extension: // Check for netscape application extension if (mGIFStruct.bytes_to_consume == 11 && (!strncmp((char*)q, "NETSCAPE2.0", 11) || !strncmp((char*)q, "ANIMEXTS1.0", 11))) { GETN(1, gif_netscape_extension_block); } else { GETN(1, gif_consume_block); } break; // Netscape-specific GIF extension: animation looping case gif_netscape_extension_block: if (*q) { // We might need to consume 3 bytes in // gif_consume_netscape_extension, so make sure we have at least that. GETN(std::max(3, static_cast<int>(*q)), gif_consume_netscape_extension); } else { GETN(1, gif_image_start); } break; // Parse netscape-specific application extensions case gif_consume_netscape_extension: switch (q[0] & 7) { case 1: // Loop entire animation specified # of times. Only read the // loop count during the first iteration. mGIFStruct.loop_count = GETINT16(q + 1); GETN(1, gif_netscape_extension_block); break; case 2: // Wait for specified # of bytes to enter buffer // Don't do this, this extension doesn't exist (isn't used at all) // and doesn't do anything, as our streaming/buffering takes care // of it all... // See: http://semmix.pl/color/exgraf/eeg24.htm GETN(1, gif_netscape_extension_block); break; default: // 0,3-7 are yet to be defined netscape extension codes mGIFStruct.state = gif_error; } break; case gif_image_header: { if (mGIFStruct.images_decoded > 0 && IsFirstFrameDecode()) { // We're about to get a second frame, but we only want the first. Stop // decoding now. mGIFStruct.state = gif_done; break; } // Get image offsets, with respect to the screen origin mGIFStruct.x_offset = GETINT16(q); mGIFStruct.y_offset = GETINT16(q + 2); // Get image width and height. mGIFStruct.width = GETINT16(q + 4); mGIFStruct.height = GETINT16(q + 6); if (!mGIFStruct.images_decoded) { // Work around broken GIF files where the logical screen // size has weird width or height. We assume that GIF87a // files don't contain animations. if ((mGIFStruct.screen_height < mGIFStruct.height) || (mGIFStruct.screen_width < mGIFStruct.width) || (mGIFStruct.version == 87)) { mGIFStruct.screen_height = mGIFStruct.height; mGIFStruct.screen_width = mGIFStruct.width; mGIFStruct.x_offset = 0; mGIFStruct.y_offset = 0; } // Create the image container with the right size. BeginGIF(); if (HasError()) { // Setting the size led to an error. mGIFStruct.state = gif_error; return; } // If we were doing a metadata decode, we're done. if (IsMetadataDecode()) { return; } } // Work around more broken GIF files that have zero image width or height if (!mGIFStruct.height || !mGIFStruct.width) { mGIFStruct.height = mGIFStruct.screen_height; mGIFStruct.width = mGIFStruct.screen_width; if (!mGIFStruct.height || !mGIFStruct.width) { mGIFStruct.state = gif_error; break; } } // 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++; } // Mask to limit the color values within the colormap mColorMask = 0xFF >> (8 - realDepth); if (NS_FAILED(BeginImageFrame(realDepth))) { mGIFStruct.state = gif_error; return; } // FALL THROUGH } 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) { mGIFStruct.interlaced = true; mGIFStruct.ipass = 1; } else { mGIFStruct.interlaced = false; mGIFStruct.ipass = 0; } // Only apply the Haeberli display hack on the first frame mGIFStruct.progressive_display = (mGIFStruct.images_decoded == 0); // Clear state from last image mGIFStruct.irow = 0; mGIFStruct.rows_remaining = mGIFStruct.height; mGIFStruct.rowp = mImageData; // 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++; } // has a local colormap? if (q[8] & 0x80) { mGIFStruct.local_colormap_size = 1 << depth; if (!mGIFStruct.images_decoded) { // First frame has local colormap, allocate space for it // as the image frame doesn't have its own palette mColormapSize = sizeof(uint32_t) << realDepth; if (!mGIFStruct.local_colormap) { mGIFStruct.local_colormap = (uint32_t*)moz_xmalloc(mColormapSize); } mColormap = mGIFStruct.local_colormap; } const uint32_t size = 3 << depth; if (mColormapSize > size) { // Clear the notfilled part of the colormap memset(((uint8_t*)mColormap) + size, 0, mColormapSize - size); } if (len < size) { // Use 'hold' pattern to get the image colormap GETN(size, gif_image_colormap); break; } // Copy everything, go to colormap state to do CMS correction memcpy(mColormap, buf, size); buf += size; len -= size; GETN(0, gif_image_colormap); break; } else { // Switch back to the global palette if (mGIFStruct.images_decoded) { // Copy global colormap into the palette of current frame memcpy(mColormap, mGIFStruct.global_colormap, mColormapSize); } else { mColormap = mGIFStruct.global_colormap; } } GETN(1, gif_lzw_start); } break; case gif_image_colormap: // Everything is already copied into local_colormap // Convert into Cairo colors including CMS transformation ConvertColormap(mColormap, mGIFStruct.local_colormap_size); GETN(1, gif_lzw_start); break; case gif_sub_block: mGIFStruct.count = *q; if (mGIFStruct.count) { // Still working on the same image: Process next LZW data block // Make sure there are still rows left. If the GIF data // is corrupt, we may not get an explicit terminator. if (!mGIFStruct.rows_remaining) { #ifdef DONT_TOLERATE_BROKEN_GIFS mGIFStruct.state = gif_error; break; #else // This is an illegal GIF, but we remain tolerant. GETN(1, gif_sub_block); #endif if (mGIFStruct.count == GIF_TRAILER) { // Found a terminator anyway, so consider the image done GETN(1, gif_done); break; } } GETN(mGIFStruct.count, gif_lzw); } else { // See if there are any more images in this sequence. EndImageFrame(); GETN(1, gif_image_start); } break; case gif_done: MOZ_ASSERT(!IsMetadataDecode(), "Metadata decodes shouldn't reach gif_done"); FinishInternal(); goto done; case gif_error: PostDataError(); return; // We shouldn't ever get here. default: MOZ_ASSERT_UNREACHABLE("Unexpected mGIFStruct.state"); PostDecoderError(NS_ERROR_UNEXPECTED); return; } } // if an error state is set but no data remains, code flow reaches here if (mGIFStruct.state == gif_error) { PostDataError(); return; } // Copy the leftover into mGIFStruct.hold if (len) { // Add what we have sofar to the block 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; } // We want to flush before returning if we're on the first frame done: if (!mGIFStruct.images_decoded) { FlushImageData(); mLastFlushedRow = mCurrentRow; mLastFlushedPass = mCurrentPass; } }