Changeset 27896 in webkit


Ignore:
Timestamp:
Nov 19, 2007 4:22:19 AM (16 years ago)
Author:
mrowe@apple.com
Message:

2007-11-19 Peter Kasting <zerodpx@gmail.com>

Reviewed by Darin Adler.

http://bugs.webkit.org/show_bug.cgi?id=15974
GIF decoding should respect frames' specified disposal methods.

  • platform/image-decoders/ImageDecoder.h: (WebCore::RGBA32Buffer::): (WebCore::RGBA32Buffer::RGBA32Buffer): (WebCore::RGBA32Buffer::disposalMethod): (WebCore::RGBA32Buffer::setDisposalMethod):
  • platform/image-decoders/gif/GIFImageDecoder.cpp: (WebCore::GIFImageDecoder::frameBufferAtIndex): (WebCore::GIFImageDecoder::initFrameBuffer): (WebCore::GIFImageDecoder::prepEmptyFrameBuffer): (WebCore::GIFImageDecoder::haveDecodedRow): (WebCore::GIFImageDecoder::frameComplete):
  • platform/image-decoders/gif/GIFImageDecoder.h:
  • platform/image-decoders/gif/GIFImageReader.cpp: (GIFImageReader::read):
  • platform/image-decoders/gif/GIFImageReader.h: (GIFFrameReader::GIFFrameReader):
Location:
trunk/WebCore
Files:
6 edited

Legend:

Unmodified
Added
Removed
  • trunk/WebCore/ChangeLog

    r27895 r27896  
     12007-11-19  Peter Kasting  <zerodpx@gmail.com>
     2
     3        Reviewed by Darin Adler.
     4
     5        http://bugs.webkit.org/show_bug.cgi?id=15974
     6        GIF decoding should respect frames' specified disposal methods.
     7
     8        * platform/image-decoders/ImageDecoder.h:
     9        (WebCore::RGBA32Buffer::):
     10        (WebCore::RGBA32Buffer::RGBA32Buffer):
     11        (WebCore::RGBA32Buffer::disposalMethod):
     12        (WebCore::RGBA32Buffer::setDisposalMethod):
     13        * platform/image-decoders/gif/GIFImageDecoder.cpp:
     14        (WebCore::GIFImageDecoder::frameBufferAtIndex):
     15        (WebCore::GIFImageDecoder::initFrameBuffer):
     16        (WebCore::GIFImageDecoder::prepEmptyFrameBuffer):
     17        (WebCore::GIFImageDecoder::haveDecodedRow):
     18        (WebCore::GIFImageDecoder::frameComplete):
     19        * platform/image-decoders/gif/GIFImageDecoder.h:
     20        * platform/image-decoders/gif/GIFImageReader.cpp:
     21        (GIFImageReader::read):
     22        * platform/image-decoders/gif/GIFImageReader.h:
     23        (GIFFrameReader::GIFFrameReader):
     24
    1252007-11-18  Brady Eidson <beidson@apple.com>
    226
  • trunk/WebCore/platform/image-decoders/ImageDecoder.h

    r27186 r27896  
    4242public:
    4343    enum FrameStatus { FrameEmpty, FramePartial, FrameComplete };
     44    enum FrameDisposalMethod {
     45        // If you change the numeric values of these, make sure you audit all
     46        // users, as some users may cast raw values to/from these constants.
     47        DisposeNotSpecified = 0,       // Leave frame in framebuffer
     48        DisposeKeep = 1,               // Leave frame in framebuffer
     49        DisposeOverwriteBgcolor = 2,   // Clear frame to transparent
     50        DisposeOverwritePrevious = 3,  // Clear frame to previous framebuffer contents
     51    };
    4452
    4553    RGBA32Buffer() : m_height(0), m_status(FrameEmpty), m_duration(0),
    46                      m_includeInNextFrame(false), m_hasAlpha(false)
     54                     m_disposalMethod(DisposeNotSpecified), m_hasAlpha(false)
    4755    {}
    4856
     
    5361    FrameStatus status() const { return m_status; }
    5462    unsigned duration() const { return m_duration; }
    55     bool includeInNextFrame() const { return m_includeInNextFrame; }
     63    FrameDisposalMethod disposalMethod() const { return m_disposalMethod; }
    5664    bool hasAlpha() const { return m_hasAlpha; }
    5765
     
    6068    void setStatus(FrameStatus s) { m_status = s; }
    6169    void setDuration(unsigned duration) { m_duration = duration; }
    62     void setIncludeInNextFrame(bool n) { m_includeInNextFrame = n; }
     70    void setDisposalMethod(FrameDisposalMethod method) { m_disposalMethod = method; }
    6371    void setHasAlpha(bool alpha) { m_hasAlpha = alpha; }
    6472
     
    8795    FrameStatus m_status; // Whether or not this frame is completely finished decoding.
    8896    unsigned m_duration; // The animation delay.
    89     bool m_includeInNextFrame; // Whether or not the next buffer should be initially populated with our data.
     97    FrameDisposalMethod m_disposalMethod; // What to do with this frame's data when initializing the next frame.
    9098    bool m_hasAlpha; // Whether or not any of the pixels in the buffer have transparency.
    9199};
  • trunk/WebCore/platform/image-decoders/gif/GIFImageDecoder.cpp

    r27642 r27896  
    159159RGBA32Buffer* GIFImageDecoder::frameBufferAtIndex(size_t index)
    160160{
    161     if (index < 0 || index >= frameCount())
     161    if (index >= frameCount())
    162162        return 0;
    163163
     
    195195}
    196196
    197 void GIFImageDecoder::initFrameBuffer(RGBA32Buffer& buffer,
    198                                       RGBA32Buffer* previousBuffer,
    199                                       bool compositeWithPreviousFrame)
     197void GIFImageDecoder::initFrameBuffer(unsigned frameIndex)
    200198{
    201199    // Initialize the frame rect in our buffer.
     
    209207        frameRect.setHeight(m_size.height() - m_reader->frameYOffset());
    210208
    211     buffer.setRect(frameRect);
    212 
    213     bool isSubRect = (frameRect.x() > 0 || frameRect.y() > 0 ||
    214                       frameRect.width() < m_size.width() ||
    215                       frameRect.height() < m_size.height());
     209    RGBA32Buffer* const buffer = &m_frameBufferCache[frameIndex];
     210    buffer->setRect(frameRect);
    216211   
    217     // Let's resize our buffer now to the correct width/height and then
    218     // initialize portions of it if needed.
    219     RGBA32Array& bytes = buffer.bytes();
    220        
    221     // If the disposal method of the previous frame said to stick around, then we need     
    222     // to copy that frame into our frame.  We also dont want to have any impact on
    223     // anything outside our frame's rect, so if we don't overlay the entire image,
    224     // then also composite with the previous frame.
    225     if (previousBuffer && (compositeWithPreviousFrame || isSubRect)) {
    226         bytes = previousBuffer->bytes();
    227         buffer.ensureHeight(m_size.height());
    228         buffer.setHasAlpha(previousBuffer->hasAlpha());
    229     }
    230     else // Resize to the width and height of the image.
    231         bytes.resize(m_size.width() * m_size.height());
    232 
    233     if (isSubRect) {
    234         // We need to go ahead and initialize the first frame to make sure
    235         // that areas outside the subrect start off transparent.
    236         if (!previousBuffer) {
    237             bytes.fill(0);
    238             buffer.setHasAlpha(true);
    239         } else if (!compositeWithPreviousFrame) {
    240             // Now this is an interesting case.  In the case where we fill
    241             // the entire image, we effectively do a full clear of the image (and thus
    242             // don't have to initialize anything in our buffer).
    243             //
    244             // However in the case where we only fill a piece of the image, two problems occur:
    245             // (1) We need to wipe out the area occupied by the previous frame, which
    246             // could also have been a subrect.
    247             // (2) Anything outside the previous frame's rect *and* outside our current
    248             // frame's rect should be left alone.
    249             // We have handled (2) by just initializing our buffer from the previous frame.
    250             // Our subrect will correctly overwrite the previous frame's contents as we
    251             // decode rows.  However that still leaves the problem of having to wipe out
    252             // the area occupied by the previous frame that does not overlap with
    253             // the new frame.
    254             if (previousBuffer->rect() != frameRect) {
    255                 // We have to restore the entire previous subframe with the first frame's contents.
    256                 RGBA32Buffer* firstBuffer = &m_frameBufferCache[0];
    257                 bool sawAlpha = buffer.hasAlpha();
    258                 IntRect prevRect = previousBuffer->rect();
    259                 unsigned end = prevRect.y() + prevRect.height();
    260 
    261                 // Given that we allocate buffers to be the same size as previous buffers,
    262                 // I think this assert should be valid.
    263                 ASSERT(IntRect(IntPoint(0,0), m_size).contains(firstBuffer->rect()));
    264 
    265                 for (unsigned i = prevRect.y(); i < end; i++) {
    266                     unsigned* curr = buffer.bytes().data() + (i * m_size.width() + prevRect.x());
    267                     unsigned* orig = firstBuffer->bytes().data() + (i * m_size.width() + prevRect.x());
    268                     unsigned* end = curr + prevRect.width();
    269                     unsigned* origEnd = orig + firstBuffer->rect().width();
    270 
    271                     while (curr != end && orig != origEnd) {
    272                         if (!sawAlpha) {
    273                             sawAlpha = true;
    274                             buffer.setHasAlpha(true);
    275                         }
    276                         *curr++ = *orig++;
    277                     }
    278                 }
     212    if (frameIndex == 0) {
     213        // This is the first frame, so we're not relying on any previous data.
     214        prepEmptyFrameBuffer(buffer);
     215    } else {
     216        // The starting state for this frame depends on the previous frame's
     217        // disposal method.
     218        //
     219        // Frames that use the DisposeOverwritePrevious method are effectively
     220        // no-ops in terms of changing the starting state of a frame compared to
     221        // the starting state of the previous frame, so skip over them.  (If the
     222        // first frame specifies this method, it will get treated like
     223        // DisposeOverwriteBgcolor below and reset to a completely empty image.)
     224        const RGBA32Buffer* prevBuffer = &m_frameBufferCache[--frameIndex];
     225        RGBA32Buffer::FrameDisposalMethod prevMethod =
     226            prevBuffer->disposalMethod();
     227        while ((frameIndex > 0) &&
     228                (prevMethod == RGBA32Buffer::DisposeOverwritePrevious)) {
     229            prevBuffer = &m_frameBufferCache[--frameIndex];
     230            prevMethod = prevBuffer->disposalMethod();
     231        }
     232
     233        if ((prevMethod == RGBA32Buffer::DisposeNotSpecified) ||
     234                (prevMethod == RGBA32Buffer::DisposeKeep)) {
     235            // Preserve the last frame as the starting state for this frame.
     236            buffer->bytes() = prevBuffer->bytes();
     237        } else {
     238            // We want to clear the previous frame to transparent, without
     239            // affecting pixels in the image outside of the frame.
     240            const IntRect& prevRect = prevBuffer->rect();
     241            if ((frameIndex == 0) ||
     242                    prevRect.contains(IntRect(IntPoint(0, 0), m_size))) {
     243                // Clearing the first frame, or a frame the size of the whole
     244                // image, results in a completely empty image.
     245                prepEmptyFrameBuffer(buffer);
     246            } else {
     247              // Copy the whole previous buffer, then clear just its frame.
     248              buffer->bytes() = prevBuffer->bytes();
     249              SkBitmap& bitmap = buffer->bitmap();
     250              for (int y = prevRect.y(); y < prevRect.bottom(); ++y) {
     251                  unsigned* const currentRow =
     252                      buffer.bytes().data() + (y * m_size.width());
     253                  for (int x = prevRect.x(); x < prevRect.right(); ++x)
     254                      buffer->setRGBA(currentRow + x, 0, 0, 0, 0);
     255              }
     256              if ((prevRect.width() > 0) && (prevRect.height() > 0))
     257                buffer->setHasAlpha(true);
    279258            }
    280259        }
     
    282261
    283262    // Update our status to be partially complete.
    284     buffer.setStatus(RGBA32Buffer::FramePartial);
     263    buffer->setStatus(RGBA32Buffer::FramePartial);
     264
     265    // Reset the alpha pixel tracker for this frame.
     266    m_currentBufferSawAlpha = false;
     267}
     268
     269void GIFImageDecoder::prepEmptyFrameBuffer(RGBA32Buffer* buffer) const
     270{
     271    buffer->bytes().resize(m_size.width() * m_size.height());
     272    buffer->bytes().fill(0);
     273    buffer->setHasAlpha(true);
    285274}
    286275
     
    291280                                     unsigned repeatCount) // How many times to repeat the row
    292281{
    293     // Resize to the width and height of the image.
     282    // Initialize the frame if necessary.
    294283    RGBA32Buffer& buffer = m_frameBufferCache[frameIndex];
    295     RGBA32Buffer* previousBuffer = (frameIndex > 0) ? &m_frameBufferCache[frameIndex-1] : 0;
    296     bool compositeWithPreviousFrame = previousBuffer && previousBuffer->includeInNextFrame();
    297 
    298284    if (buffer.status() == RGBA32Buffer::FrameEmpty)
    299         initFrameBuffer(buffer, previousBuffer, compositeWithPreviousFrame);
     285        initFrameBuffer(frameIndex);
    300286
    301287    // Do nothing for bogus data.
     
    322308    unsigned char* currentRowByte = rowBuffer;
    323309   
    324     bool hasAlpha = m_reader->isTransparent();
    325     bool sawAlpha = false;
    326310    while (currentRowByte != rowEnd && currDst < dstEnd) {
    327         if ((!hasAlpha || *currentRowByte != m_reader->transparentPixel()) && *currentRowByte < colorMapSize) {
     311        if ((!m_reader->isTransparent() || *currentRowByte != m_reader->transparentPixel()) && *currentRowByte < colorMapSize) {
    328312            unsigned colorIndex = *currentRowByte * 3;
    329313            unsigned red = colorMap[colorIndex];
     
    332316            RGBA32Buffer::setRGBA(*currDst, red, green, blue, 255);
    333317        } else {
    334             if (!sawAlpha) {
    335                 sawAlpha = true;
    336                 buffer.setHasAlpha(true);
    337             }
    338            
    339             if (!compositeWithPreviousFrame)
    340                 RGBA32Buffer::setRGBA(*currDst, 0, 0, 0, 0);
     318            m_currentBufferSawAlpha = true;
    341319        }
    342320        currDst++;
     
    364342}
    365343
    366 void GIFImageDecoder::frameComplete(unsigned frameIndex, unsigned frameDuration, bool includeInNextFrame)
     344void GIFImageDecoder::frameComplete(unsigned frameIndex, unsigned frameDuration, RGBA32Buffer::FrameDisposalMethod disposalMethod)
    367345{
    368346    RGBA32Buffer& buffer = m_frameBufferCache[frameIndex];
     347    buffer.ensureHeight(m_size.height());
    369348    buffer.setStatus(RGBA32Buffer::FrameComplete);
    370349    buffer.setDuration(frameDuration);
    371     buffer.setIncludeInNextFrame(includeInNextFrame);
    372     buffer.ensureHeight(m_size.height());
     350    buffer.setDisposalMethod(disposalMethod);
     351
     352    if (!m_currentBufferSawAlpha) {
     353        // The whole frame was non-transparent, so it's possible that the entire
     354        // resulting buffer was non-transparent, and we can setHasAlpha(false).
     355        if (buffer.rect().contains(IntRect(IntPoint(0, 0), m_size))) {
     356            buffer.setHasAlpha(false);
     357        } else if (frameIndex > 0) {
     358            // Tricky case.  This frame does not have alpha only if everywhere
     359            // outside its rect doesn't have alpha.  To know whether this is
     360            // true, we check the start state of the frame -- if it doesn't have
     361            // alpha, we're safe.
     362            //
     363            // First skip over prior DisposeOverwritePrevious frames (since they
     364            // don't affect the start state of this frame) the same way we do in
     365            // initFrameBuffer().
     366            const RGBA32Buffer* prevBuffer = &m_frameBufferCache[--frameIndex];
     367            while ((frameIndex > 0) &&
     368                    (prevBuffer->disposalMethod() ==
     369                        RGBA32Buffer::DisposeOverwritePrevious))
     370                prevBuffer = &m_frameBufferCache[--frameIndex];
     371
     372            // Now, if we're at a DisposeNotSpecified or DisposeKeep frame, then
     373            // we can say we have no alpha if that frame had no alpha.  But
     374            // since in initFrameBuffer() we already copied that frame's alpha
     375            // state into the current frame's, we need do nothing at all here.
     376            //
     377            // The only remaining case is a DisposeOverwriteBgcolor frame.  If
     378            // it had no alpha, and its rect is contained in the current frame's
     379            // rect, we know the current frame has no alpha.
     380            if ((prevBuffer->disposalMethod() ==
     381                    RGBA32Buffer::DisposeOverwriteBgcolor) &&
     382                    !prevBuffer->hasAlpha() &&
     383                    buffer.rect().contains(prevBuffer->rect()))
     384                buffer.setHasAlpha(false);
     385        }
     386    }
    373387}
    374388
  • trunk/WebCore/platform/image-decoders/gif/GIFImageDecoder.h

    r25368 r27896  
    6666    void haveDecodedRow(unsigned frameIndex, unsigned char* rowBuffer, unsigned char* rowEnd, unsigned rowNumber,
    6767                        unsigned repeatCount);
    68     void frameComplete(unsigned frameIndex, unsigned frameDuration, bool includeInNextFrame);
     68    void frameComplete(unsigned frameIndex, unsigned frameDuration, RGBA32Buffer::FrameDisposalMethod disposalMethod);
    6969    void gifComplete();
    7070
    7171private:
    72     // Called to initialize a new frame buffer (potentially compositing it
    73     // with the previous frame and/or clearing bits in our image based off
    74     // the previous frame as well).
    75     void initFrameBuffer(RGBA32Buffer& buffer,
    76                          RGBA32Buffer* previousBuffer,
    77                          bool compositeWithPreviousFrame);
     72    // Called to initialize the frame buffer with the given index, based on the
     73    // previous frame's disposal method.
     74    void initFrameBuffer(unsigned frameIndex);
     75
     76    // A helper for initFrameBuffer(), this sets the size of the buffer, and
     77    // fills it with transparent pixels.
     78    void prepEmptyFrameBuffer(RGBA32Buffer* buffer) const;
    7879
    7980    bool m_frameCountValid;
     81    bool m_currentBufferSawAlpha;
    8082    mutable GIFImageDecoderPrivate* m_reader;
    8183};
  • trunk/WebCore/platform/image-decoders/gif/GIFImageReader.cpp

    r27346 r27896  
    640640          // ignoring gfx control extension
    641641        }
    642         frame_reader->disposal_method = (gdispose)(((*q) >> 2) & 0x7);
     642        // NOTE: This relies on the values in the FrameDisposalMethod enum
     643        // matching those in the GIF spec!
     644        frame_reader->disposal_method = (WebCore::RGBA32Buffer::FrameDisposalMethod)(((*q) >> 2) & 0x7);
    643645        // Some specs say 3rd bit (value 4), other specs say value 3
    644646        // Let's choose 3 (the more popular)
    645647        if (frame_reader->disposal_method == 4)
    646           frame_reader->disposal_method = (gdispose)3;
     648          frame_reader->disposal_method = WebCore::RGBA32Buffer::DisposeOverwritePrevious;
    647649        frame_reader->delay_time = GETINT16(q + 1) * 10;
    648650      }
     
    881883        if (clientptr && frame_reader)
    882884          clientptr->frameComplete(images_decoded - 1, frame_reader->delay_time,
    883                                    frame_reader->disposal_method == DISPOSE_KEEP);
     885                                   frame_reader->disposal_method);
    884886
    885887        /* Clear state from this image */
  • trunk/WebCore/platform/image-decoders/gif/GIFImageReader.h

    r13835 r27896  
    7777} gstate;
    7878
    79 /* "Disposal" method indicates how the image should be handled in the
    80    framebuffer before the subsequent image is displayed. */
    81 typedef enum
    82 {
    83     DISPOSE_NOT_SPECIFIED      = 0,
    84     DISPOSE_KEEP               = 1, /* Leave it in the framebuffer */
    85     DISPOSE_OVERWRITE_BGCOLOR  = 2, /* Overwrite with background color */
    86     DISPOSE_OVERWRITE_PREVIOUS = 3  /* Save-under */
    87 } gdispose;
    88 
    8979struct GIFFrameReader {
    9080    /* LZW decoder state machine */
     
    112102    unsigned int height, width;
    113103    int tpixel;                 /* Index of transparent pixel */
    114     gdispose disposal_method;   /* Restore to background, leave in place, etc.*/
     104    WebCore::RGBA32Buffer::FrameDisposalMethod disposal_method;   /* Restore to background, leave in place, etc.*/
    115105    unsigned char *local_colormap;    /* Per-image colormap */
    116106    int local_colormap_size;    /* Size of local colormap array. */
     
    141131        x_offset = y_offset = width = height = 0;
    142132        tpixel = 0;
    143         disposal_method = DISPOSE_NOT_SPECIFIED;
     133        disposal_method = WebCore::RGBA32Buffer::DisposeNotSpecified;
    144134
    145135        local_colormap = 0;
Note: See TracChangeset for help on using the changeset viewer.