Changeset 28068 in webkit


Ignore:
Timestamp:
Nov 26, 2007 7:01:23 PM (16 years ago)
Author:
alp@webkit.org
Message:

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

Reviewed by Alp Toker.

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

    r28067 r28068  
     12007-11-26  Peter Kasting  <zerodpx@gmail.com>
     2
     3        Reviewed by Alp Toker.
     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-26  Adam Roben  <aroben@apple.com>
    226
  • trunk/WebCore/platform/image-decoders/ImageDecoder.h

    r27902 r28068  
    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

    r27902 r28068  
    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              for (int y = prevRect.y(); y < prevRect.bottom(); ++y) {
     250                  unsigned* const currentRow =
     251                      buffer->bytes().data() + (y * m_size.width());
     252                  for (int x = prevRect.x(); x < prevRect.right(); ++x)
     253                      buffer->setRGBA(*(currentRow + x), 0, 0, 0, 0);
     254              }
     255              if ((prevRect.width() > 0) && (prevRect.height() > 0))
     256                buffer->setHasAlpha(true);
    279257            }
    280258        }
     
    282260
    283261    // Update our status to be partially complete.
    284     buffer.setStatus(RGBA32Buffer::FramePartial);
     262    buffer->setStatus(RGBA32Buffer::FramePartial);
     263
     264    // Reset the alpha pixel tracker for this frame.
     265    m_currentBufferSawAlpha = false;
     266}
     267
     268void GIFImageDecoder::prepEmptyFrameBuffer(RGBA32Buffer* buffer) const
     269{
     270    buffer->bytes().resize(m_size.width() * m_size.height());
     271    buffer->bytes().fill(0);
     272    buffer->setHasAlpha(true);
    285273}
    286274
     
    291279                                     unsigned repeatCount) // How many times to repeat the row
    292280{
    293     // Resize to the width and height of the image.
     281    // Initialize the frame if necessary.
    294282    RGBA32Buffer& buffer = m_frameBufferCache[frameIndex];
    295     RGBA32Buffer* previousBuffer = (frameIndex > 0) ? &m_frameBufferCache[frameIndex-1] : 0;
    296     bool compositeWithPreviousFrame = previousBuffer && previousBuffer->includeInNextFrame();
    297 
    298283    if (buffer.status() == RGBA32Buffer::FrameEmpty)
    299         initFrameBuffer(buffer, previousBuffer, compositeWithPreviousFrame);
     284        initFrameBuffer(frameIndex);
    300285
    301286    // Do nothing for bogus data.
     
    322307    unsigned char* currentRowByte = rowBuffer;
    323308   
    324     bool hasAlpha = m_reader->isTransparent();
    325     bool sawAlpha = false;
    326309    while (currentRowByte != rowEnd && currDst < dstEnd) {
    327         if ((!hasAlpha || *currentRowByte != m_reader->transparentPixel()) && *currentRowByte < colorMapSize) {
     310        if ((!m_reader->isTransparent() || *currentRowByte != m_reader->transparentPixel()) && *currentRowByte < colorMapSize) {
    328311            unsigned colorIndex = *currentRowByte * 3;
    329312            unsigned red = colorMap[colorIndex];
     
    332315            RGBA32Buffer::setRGBA(*currDst, red, green, blue, 255);
    333316        } else {
    334             if (!sawAlpha) {
    335                 sawAlpha = true;
    336                 buffer.setHasAlpha(true);
    337             }
    338            
    339             if (!compositeWithPreviousFrame)
    340                 RGBA32Buffer::setRGBA(*currDst, 0, 0, 0, 0);
     317            m_currentBufferSawAlpha = true;
    341318        }
    342319        currDst++;
     
    364341}
    365342
    366 void GIFImageDecoder::frameComplete(unsigned frameIndex, unsigned frameDuration, bool includeInNextFrame)
     343void GIFImageDecoder::frameComplete(unsigned frameIndex, unsigned frameDuration, RGBA32Buffer::FrameDisposalMethod disposalMethod)
    367344{
    368345    RGBA32Buffer& buffer = m_frameBufferCache[frameIndex];
     346    buffer.ensureHeight(m_size.height());
    369347    buffer.setStatus(RGBA32Buffer::FrameComplete);
    370348    buffer.setDuration(frameDuration);
    371     buffer.setIncludeInNextFrame(includeInNextFrame);
    372     buffer.ensureHeight(m_size.height());
     349    buffer.setDisposalMethod(disposalMethod);
     350
     351    if (!m_currentBufferSawAlpha) {
     352        // The whole frame was non-transparent, so it's possible that the entire
     353        // resulting buffer was non-transparent, and we can setHasAlpha(false).
     354        if (buffer.rect().contains(IntRect(IntPoint(0, 0), m_size))) {
     355            buffer.setHasAlpha(false);
     356        } else if (frameIndex > 0) {
     357            // Tricky case.  This frame does not have alpha only if everywhere
     358            // outside its rect doesn't have alpha.  To know whether this is
     359            // true, we check the start state of the frame -- if it doesn't have
     360            // alpha, we're safe.
     361            //
     362            // First skip over prior DisposeOverwritePrevious frames (since they
     363            // don't affect the start state of this frame) the same way we do in
     364            // initFrameBuffer().
     365            const RGBA32Buffer* prevBuffer = &m_frameBufferCache[--frameIndex];
     366            while ((frameIndex > 0) &&
     367                    (prevBuffer->disposalMethod() ==
     368                        RGBA32Buffer::DisposeOverwritePrevious))
     369                prevBuffer = &m_frameBufferCache[--frameIndex];
     370
     371            // Now, if we're at a DisposeNotSpecified or DisposeKeep frame, then
     372            // we can say we have no alpha if that frame had no alpha.  But
     373            // since in initFrameBuffer() we already copied that frame's alpha
     374            // state into the current frame's, we need do nothing at all here.
     375            //
     376            // The only remaining case is a DisposeOverwriteBgcolor frame.  If
     377            // it had no alpha, and its rect is contained in the current frame's
     378            // rect, we know the current frame has no alpha.
     379            if ((prevBuffer->disposalMethod() ==
     380                    RGBA32Buffer::DisposeOverwriteBgcolor) &&
     381                    !prevBuffer->hasAlpha() &&
     382                    buffer.rect().contains(prevBuffer->rect()))
     383                buffer.setHasAlpha(false);
     384        }
     385    }
    373386}
    374387
  • trunk/WebCore/platform/image-decoders/gif/GIFImageDecoder.h

    r27902 r28068  
    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

    r27902 r28068  
    642642          // ignoring gfx control extension
    643643        }
    644         frame_reader->disposal_method = (gdispose)(((*q) >> 2) & 0x7);
     644        // NOTE: This relies on the values in the FrameDisposalMethod enum
     645        // matching those in the GIF spec!
     646        frame_reader->disposal_method = (WebCore::RGBA32Buffer::FrameDisposalMethod)(((*q) >> 2) & 0x7);
    645647        // Some specs say 3rd bit (value 4), other specs say value 3
    646648        // Let's choose 3 (the more popular)
    647649        if (frame_reader->disposal_method == 4)
    648           frame_reader->disposal_method = (gdispose)3;
     650          frame_reader->disposal_method = WebCore::RGBA32Buffer::DisposeOverwritePrevious;
    649651        frame_reader->delay_time = GETINT16(q + 1) * 10;
    650652      }
     
    883885        if (clientptr && frame_reader)
    884886          clientptr->frameComplete(images_decoded - 1, frame_reader->delay_time,
    885                                    frame_reader->disposal_method == DISPOSE_KEEP);
     887                                   frame_reader->disposal_method);
    886888
    887889        /* Clear state from this image */
  • trunk/WebCore/platform/image-decoders/gif/GIFImageReader.h

    r27902 r28068  
    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.