Changeset 39309 in webkit


Ignore:
Timestamp:
Dec 15, 2008 12:45:46 PM (15 years ago)
Author:
pkasting@chromium.org
Message:

2008-12-15 Peter Kasting <pkasting@google.com>

Reviewed by David Hyatt.

https://bugs.webkit.org/show_bug.cgi?id=22108
Large animated GIFs weren't always animating. The code that deleted
the entire decoder after each frame of a large image was resulting in
us forgetting the loop count, breaking animations intermittently.

Instead of throwing the whole decoder away, we're more careful to just
delete frames we don't care about. This additionally addresses
problems in the Cairo and Chromium ports with excessive peak memory
use and CPU use when decoding large animated GIFs because it leads to
much less redecoding (O(n) instead of O(n2) CPU, and O(1) instead of
O(n) memory).

This change has less impact on the CG decoder, which seems to throw
away frames automatically when their external references are dropped;
this means the CG decoder didn't suffer from the peak memory usage
issue before (and still doesn't), but it also still burns excessive
CPU redecoding earlier frames, that in theory it wouldn't need to
redecode if it would judiciously save the most recent frames. At
least this patch plumbs some useful info to the ImageSource so it can
help guide the CG decoder heuristics in the future.

  • platform/graphics/BitmapImage.cpp: (WebCore::frameBytes): (WebCore::BitmapImage::destroyDecodedData): (WebCore::BitmapImage::destroyDecodedDataIfNecessary): (WebCore::BitmapImage::destroyMetadataAndNotify): (WebCore::BitmapImage::clearFrame): (WebCore::BitmapImage::cacheFrame): (WebCore::BitmapImage::dataChanged): (WebCore::BitmapImage::startAnimation): (WebCore::BitmapImage::resetAnimation): (WebCore::BitmapImage::internalAdvanceAnimation):
  • platform/graphics/BitmapImage.h:
  • platform/graphics/GeneratedImage.h: (WebCore::GeneratedImage::destroyDecodedData):
  • platform/graphics/Image.h:
  • platform/graphics/ImageSource.h:
  • platform/graphics/cairo/ImageSourceCairo.cpp: (WebCore::ImageSource::~ImageSource): (WebCore::ImageSource::clear):
  • platform/graphics/cg/ImageSourceCG.cpp: (WebCore::ImageSource::~ImageSource): (WebCore::ImageSource::clear):
  • platform/graphics/cg/PDFDocumentImage.h: (WebCore::PDFDocumentImage::destroyDecodedData):
  • platform/graphics/qt/StillImageQt.h: (WebCore::StillImage::destroyDecodedData):
  • platform/image-decoders/ImageDecoder.h: (WebCore::RGBA32Buffer::clear): (WebCore::ImageDecoder::clearFrameBufferCache):
  • platform/image-decoders/gif/GIFImageDecoder.cpp: (WebCore::GIFImageDecoder::repetitionCount): (WebCore::GIFImageDecoder::clearFrameBufferCache): (WebCore::GIFImageDecoder::initFrameBuffer):
  • platform/image-decoders/gif/GIFImageDecoder.h:
  • platform/image-decoders/gif/GIFImageReader.h: (GIFImageReader::GIFImageReader):
  • svg/graphics/SVGImage.h: (WebCore::SVGImage::destroyDecodedData):
Location:
trunk/WebCore
Files:
15 edited

Legend:

Unmodified
Added
Removed
  • trunk/WebCore/ChangeLog

    r39306 r39309  
     12008-12-15  Peter Kasting  <pkasting@google.com>
     2
     3        Reviewed by David Hyatt.
     4
     5        https://bugs.webkit.org/show_bug.cgi?id=22108
     6        Large animated GIFs weren't always animating.  The code that deleted
     7        the entire decoder after each frame of a large image was resulting in
     8        us forgetting the loop count, breaking animations intermittently.
     9
     10        Instead of throwing the whole decoder away, we're more careful to just
     11        delete frames we don't care about.  This additionally addresses
     12        problems in the Cairo and Chromium ports with excessive peak memory
     13        use and CPU use when decoding large animated GIFs because it leads to
     14        much less redecoding (O(n) instead of O(n^2) CPU, and O(1) instead of
     15        O(n) memory).
     16
     17        This change has less impact on the CG decoder, which seems to throw
     18        away frames automatically when their external references are dropped;
     19        this means the CG decoder didn't suffer from the peak memory usage
     20        issue before (and still doesn't), but it also still burns excessive
     21        CPU redecoding earlier frames, that in theory it wouldn't need to
     22        redecode if it would judiciously save the most recent frames.  At
     23        least this patch plumbs some useful info to the ImageSource so it can
     24        help guide the CG decoder heuristics in the future.
     25
     26        * platform/graphics/BitmapImage.cpp:
     27        (WebCore::frameBytes):
     28        (WebCore::BitmapImage::destroyDecodedData):
     29        (WebCore::BitmapImage::destroyDecodedDataIfNecessary):
     30        (WebCore::BitmapImage::destroyMetadataAndNotify):
     31        (WebCore::BitmapImage::clearFrame):
     32        (WebCore::BitmapImage::cacheFrame):
     33        (WebCore::BitmapImage::dataChanged):
     34        (WebCore::BitmapImage::startAnimation):
     35        (WebCore::BitmapImage::resetAnimation):
     36        (WebCore::BitmapImage::internalAdvanceAnimation):
     37        * platform/graphics/BitmapImage.h:
     38        * platform/graphics/GeneratedImage.h:
     39        (WebCore::GeneratedImage::destroyDecodedData):
     40        * platform/graphics/Image.h:
     41        * platform/graphics/ImageSource.h:
     42        * platform/graphics/cairo/ImageSourceCairo.cpp:
     43        (WebCore::ImageSource::~ImageSource):
     44        (WebCore::ImageSource::clear):
     45        * platform/graphics/cg/ImageSourceCG.cpp:
     46        (WebCore::ImageSource::~ImageSource):
     47        (WebCore::ImageSource::clear):
     48        * platform/graphics/cg/PDFDocumentImage.h:
     49        (WebCore::PDFDocumentImage::destroyDecodedData):
     50        * platform/graphics/qt/StillImageQt.h:
     51        (WebCore::StillImage::destroyDecodedData):
     52        * platform/image-decoders/ImageDecoder.h:
     53        (WebCore::RGBA32Buffer::clear):
     54        (WebCore::ImageDecoder::clearFrameBufferCache):
     55        * platform/image-decoders/gif/GIFImageDecoder.cpp:
     56        (WebCore::GIFImageDecoder::repetitionCount):
     57        (WebCore::GIFImageDecoder::clearFrameBufferCache):
     58        (WebCore::GIFImageDecoder::initFrameBuffer):
     59        * platform/image-decoders/gif/GIFImageDecoder.h:
     60        * platform/image-decoders/gif/GIFImageReader.h:
     61        (GIFImageReader::GIFImageReader):
     62        * svg/graphics/SVGImage.h:
     63        (WebCore::SVGImage::destroyDecodedData):
     64
    1652008-12-15  Cameron Zwarich  <zwarich@apple.com>
    266
  • trunk/WebCore/platform/graphics/BitmapImage.cpp

    r39185 r39309  
    3939namespace WebCore {
    4040
    41 // Animated images >5MB are considered large enough that we'll only hang on to
    42 // one frame at a time.
    43 const unsigned cLargeAnimationCutoff = 5242880;
    44 
    45 // When an animated image is more than five minutes out of date, don't try to
    46 // resync on repaint, so we don't waste CPU cycles on an edge case the user
    47 // doesn't care about.
    48 const double cAnimationResyncCutoff = 5 * 60;
     41static int frameBytes(const IntSize& frameSize)
     42{
     43    return frameSize.width() * frameSize.height() * 4;
     44}
    4945
    5046BitmapImage::BitmapImage(ImageObserver* observer)
     
    7672}
    7773
    78 void BitmapImage::destroyDecodedData(bool incremental, bool preserveNearbyFrames)
    79 {
    80     // Destroy the cached images and release them.
    81     if (m_frames.size()) {
    82         int sizeChange = 0;
    83         int frameSize = m_size.width() * m_size.height() * 4;
    84         const size_t nextFrame = (preserveNearbyFrames && frameCount()) ? ((m_currentFrame + 1) % frameCount()) : 0;
    85         for (unsigned i = incremental ? m_frames.size() - 1 : 0; i < m_frames.size(); i++) {
    86             if (m_frames[i].m_frame && (!preserveNearbyFrames || (i != m_currentFrame && i != nextFrame))) {
    87                 sizeChange -= frameSize;
    88                 m_frames[i].clear();
    89             }
    90         }
    91 
    92         // We just always invalidate our platform data, even in the incremental case.
    93         // This could be better, but it's not a big deal.
    94         m_isSolidColor = false;
    95         invalidatePlatformData();
    96        
    97         if (sizeChange) {
    98             m_decodedSize += sizeChange;
    99             if (imageObserver())
    100                 imageObserver()->decodedSizeChanged(this, sizeChange);
    101         }
    102     }
    103     if (!incremental) {
    104         // Reset the image source, since Image I/O has an underlying cache that it uses
    105         // while animating that it seems to never clear.
    106         m_source.clear();
    107         if (m_data)
    108             m_source.setData(m_data.get(), m_allDataReceived);
    109     }   
     74void BitmapImage::destroyDecodedData(bool destroyAll)
     75{
     76    int framesCleared = 0;
     77    const size_t clearBeforeFrame = destroyAll ? m_frames.size() : m_currentFrame;
     78    for (size_t i = 0; i < clearBeforeFrame; ++i)
     79        framesCleared += clearFrame(i);
     80
     81    destroyMetadataAndNotify(framesCleared);
     82
     83    m_source.clear(destroyAll, clearBeforeFrame);
     84    if (m_data)
     85        m_source.setData(m_data.get(), m_allDataReceived);
     86    return;
     87}
     88
     89void BitmapImage::destroyDecodedDataIfNecessary(bool destroyAll)
     90{
     91    // Animated images >5MB are considered large enough that we'll only hang on
     92    // to one frame at a time.
     93    static const unsigned cLargeAnimationCutoff = 5242880;
     94    if (frameCount() * frameBytes(m_size) > cLargeAnimationCutoff)
     95        destroyDecodedData(destroyAll);
     96}
     97
     98void BitmapImage::destroyMetadataAndNotify(int framesCleared)
     99{
     100    m_isSolidColor = false;
     101    invalidatePlatformData();
     102
     103    const int deltaBytes = framesCleared * -frameBytes(m_size);
     104    m_decodedSize += deltaBytes;
     105    if (deltaBytes && imageObserver())
     106        imageObserver()->decodedSizeChanged(this, deltaBytes);
     107}
     108
     109int BitmapImage::clearFrame(size_t frame)
     110{
     111    if (!m_frames[frame].m_frame)
     112        return 0;
     113
     114    m_frames[frame].clear();
     115    return 1;
    110116}
    111117
     
    128134    m_frames[index].m_hasAlpha = m_source.frameHasAlphaAtIndex(index);
    129135
    130     int sizeChange;
    131     if (index) {
    132         IntSize frameSize = m_source.frameSizeAtIndex(index);
    133         if (frameSize != m_size)
    134             m_hasUniformFrameSize = false;
    135         sizeChange = m_frames[index].m_frame ? frameSize.width() * frameSize.height() * 4 : 0;
    136     } else
    137         sizeChange = m_frames[index].m_frame ? m_size.width() * m_size.height() * 4 : 0;
    138 
    139     if (sizeChange) {
    140         m_decodedSize += sizeChange;
     136    const IntSize frameSize(index ? m_source.frameSizeAtIndex(index) : m_size);
     137    if (frameSize != m_size)
     138        m_hasUniformFrameSize = false;
     139    if (m_frames[index].m_frame) {
     140        const int deltaBytes = frameBytes(frameSize);
     141        m_decodedSize += deltaBytes;
    141142        if (imageObserver())
    142             imageObserver()->decodedSizeChanged(this, sizeChange);
     143            imageObserver()->decodedSizeChanged(this, deltaBytes);
    143144    }
    144145}
     
    162163bool BitmapImage::dataChanged(bool allDataReceived)
    163164{
    164     destroyDecodedData(true);
     165    destroyMetadataAndNotify(m_frames.isEmpty() ? 0 : clearFrame(m_frames.size() - 1));
    165166   
    166167    // Feed all the data we've seen so far to the image decoder.
     
    278279    } else {
    279280        m_desiredFrameStartTime += currentDuration;
    280         // If we're too far behind, the user probably doesn't care about
    281         // resyncing and we could burn a lot of time looping through frames
    282         // below.  Just reset the timings.
     281
     282        // When an animated image is more than five minutes out of date, the
     283        // user probably doesn't care about resyncing and we could burn a lot of
     284        // time looping through frames below.  Just reset the timings.
     285        const double cAnimationResyncCutoff = 5 * 60;
    283286        if ((time - m_desiredFrameStartTime) > cAnimationResyncCutoff)
    284287            m_desiredFrameStartTime = time + currentDuration;
     
    373376    m_desiredFrameStartTime = 0;
    374377    m_animationFinished = false;
    375     int frameSize = m_size.width() * m_size.height() * 4;
    376378   
    377379    // For extremely large animations, when the animation is reset, we just throw everything away.
    378     if (frameCount() * frameSize > cLargeAnimationCutoff)
    379         destroyDecodedData();
     380    destroyDecodedDataIfNecessary(true);
    380381}
    381382
     
    398399        return false;
    399400
    400     m_currentFrame++;
     401    ++m_currentFrame;
     402    bool advancedAnimation = true;
     403    bool destroyAll = false;
    401404    if (m_currentFrame >= frameCount()) {
    402405        ++m_repetitionsComplete;
     406
    403407        // Get the repetition count again.  If we weren't able to get a
    404408        // repetition count before, we should have decoded the whole image by
     
    407411            m_animationFinished = true;
    408412            m_desiredFrameStartTime = 0;
    409             m_currentFrame--;
    410             if (skippingFrames) {
    411                 // Uh oh.  We tried to skip past the end of the animation.  We'd
    412                 // better draw this last frame.
    413                 notifyObserverAndTrimDecodedData();
    414             }
    415             return false;
     413            --m_currentFrame;
     414            advancedAnimation = false;
     415        } else {
     416            m_currentFrame = 0;
     417            destroyAll = true;
    416418        }
    417         m_currentFrame = 0;
    418     }
    419 
    420     if (!skippingFrames)
    421         notifyObserverAndTrimDecodedData();
    422 
    423     return true;
    424 }
    425 
    426 void BitmapImage::notifyObserverAndTrimDecodedData()
    427 {
    428     // Notify our observer that the animation has advanced.
    429     imageObserver()->animationAdvanced(this);
    430 
    431     // For large animated images, go ahead and throw away frames as we go to
    432     // save footprint.
    433     int frameSize = m_size.width() * m_size.height() * 4;
    434     if (frameCount() * frameSize > cLargeAnimationCutoff) {
    435         // Destroy all of our frames and just redecode every time.  We save the
    436         // current frame since we'll need it in draw() anyway.
    437         destroyDecodedData(false, true);
    438     }
    439 }
    440 
    441 }
     419    }
     420    destroyDecodedDataIfNecessary(destroyAll);
     421
     422    // We need to draw this frame if we advanced to it while not skipping, or if
     423    // while trying to skip frames we hit the last frame and thus had to stop.
     424    if (skippingFrames != advancedAnimation)
     425        imageObserver()->animationAdvanced(this);
     426    return advancedAnimation;
     427}
     428
     429}
  • trunk/WebCore/platform/graphics/BitmapImage.h

    r39185 r39309  
    169169    void cacheFrame(size_t index);
    170170
    171     // Called to invalidate all our cached data.  If an image is loading
    172     // incrementally, we only invalidate the last cached frame.  For large
    173     // animated images, where we throw away the decoded data after every frame,
    174     // |preserveNearbyFrames| can be set to preserve the current frame's data
    175     // and eliminate some unnecessary duplicated decoding work.  This also
    176     // preserves the next frame's data, if available.  In most cases this has no
    177     // effect; either that frame isn't decoded yet, or it's already been
    178     // destroyed by a previous call.  But when we fall behind on the very first
    179     // animation loop and startAnimation() needs to "catch up" one or more
    180     // frames, this briefly preserves some of that decoding work, to ease CPU
    181     // load and make it less likely that we'll keep falling behind.
    182     virtual void destroyDecodedData(bool incremental = false, bool preserveNearbyFrames = false);
     171    // Called to invalidate cached data.  When |destroyAll| is true, we wipe out
     172    // the entire frame buffer cache and tell the image source to destroy
     173    // everything; this is used when e.g. we want to free some room in the image
     174    // cache.  If |destroyAll| is false, we only delete frames up to the current
     175    // one; this is used while animating large images to keep memory footprint
     176    // low without redecoding the whole image on every frame.
     177    virtual void destroyDecodedData(bool destroyAll = true);
     178
     179    // If the image is large enough, calls destroyDecodedData() and passes
     180    // |destroyAll| along.
     181    void destroyDecodedDataIfNecessary(bool destroyAll);
     182
     183    // Generally called by destroyDecodedData(), destroys whole-image metadata
     184    // and notifies observers that the memory footprint has (hopefully)
     185    // decreased by |framesCleared| times the size (in bytes) of a frame.
     186    void destroyMetadataAndNotify(int framesCleared);
     187
     188    // If frame |frame| is cached, clears the cache handle.  Returns the number
     189    // of frames actually cleared.
     190    int clearFrame(size_t frame);
    183191
    184192    // Whether or not size is available yet.   
     
    198206    bool internalAdvanceAnimation(bool skippingFrames);
    199207
    200     // Helper for internalAdvanceAnimation().
    201     void notifyObserverAndTrimDecodedData();
    202 
    203208    // Handle platform-specific data
    204209    void initPlatformData();
  • trunk/WebCore/platform/graphics/GeneratedImage.h

    r37612 r39309  
    5454
    5555    // Assume that generated content has no decoded data we need to worry about
    56     virtual void destroyDecodedData(bool incremental = false, bool preserveNearbyFrames = false) { }
     56    virtual void destroyDecodedData(bool destroyAll = true) { }
    5757    virtual unsigned decodedSize() const { return 0; }
    5858
  • trunk/WebCore/platform/graphics/Image.h

    r39185 r39309  
    111111    virtual String filenameExtension() const { return String(); } // null string if unknown
    112112
    113     virtual void destroyDecodedData(bool incremental = false, bool preserveNearbyFrames = false) = 0;
     113    virtual void destroyDecodedData(bool destroyAll = true) = 0;
    114114    virtual unsigned decodedSize() const = 0;
    115115
  • trunk/WebCore/platform/graphics/ImageSource.h

    r39185 r39309  
    8484    ~ImageSource();
    8585
    86     void clear();
     86    // Tells the ImageSource that the Image no longer cares about decoded frame
     87    // data -- at all (if |destroyAll| is true), or before frame
     88    // |clearBeforeFrame| (if |destroyAll| is false).  The ImageSource should
     89    // delete cached decoded data for these frames where possible to keep memory
     90    // usage low.  When |destroyAll| is true, the ImageSource should also reset
     91    // any local state so that decoding can begin again.
     92    //
     93    // Implementations that delete less than what's specified above waste
     94    // memory.  Implementations that delete more may burn CPU re-decoding frames
     95    // that could otherwise have been cached, or encounter errors if they're
     96    // asked to decode frames they can't decode due to the loss of previous
     97    // decoded frames.
     98    //
     99    // Callers should not call clear(false, n) and subsequently call
     100    // createFrameAtIndex(m) with m < n, unless they first call clear(true).
     101    // This ensures that stateful ImageSources/decoders will work properly.
     102    void clear(bool destroyAll, size_t clearBeforeFrame = 0);
    87103
    88104    bool initialized() const;
    89    
     105
    90106    void setData(SharedBuffer* data, bool allDataReceived);
    91107    String filenameExtension() const;
     
    96112
    97113    int repetitionCount();
    98    
     114
    99115    size_t frameCount() const;
    100    
     116
     117    // Callers should not call this after calling clear() with a higher index;
     118    // see comments on clear() above.
    101119    NativeImagePtr createFrameAtIndex(size_t);
    102    
     120
    103121    float frameDurationAtIndex(size_t);
    104122    bool frameHasAlphaAtIndex(size_t); // Whether or not the frame actually used any alpha.
  • trunk/WebCore/platform/graphics/cairo/ImageSourceCairo.cpp

    r39185 r39309  
    9898ImageSource::~ImageSource()
    9999{
    100     clear();
    101 }
    102 
    103 void ImageSource::clear()
    104 {
    105     delete m_decoder;
    106     m_decoder = 0;
     100    clear(true);
     101}
     102
     103void ImageSource::clear(bool destroyAll, size_t clearBeforeFrame)
     104{
     105    if (destroyAll) {
     106        delete m_decoder;
     107        m_decoder = 0;
     108        return;
     109    }
     110
     111    if (m_decoder)
     112        m_decoder->clearFrameBufferCache(clearBeforeFrame);
    107113}
    108114
  • trunk/WebCore/platform/graphics/cg/ImageSourceCG.cpp

    r39185 r39309  
    4646ImageSource::~ImageSource()
    4747{
    48     clear();
    49 }
    50 
    51 void ImageSource::clear()
    52 {
    53     if (m_decoder) {
    54         CFRelease(m_decoder);
    55         m_decoder = 0;
    56     }
     48    clear(true);
     49}
     50
     51void ImageSource::clear(bool destroyAll, size_t clearBeforeFrame)
     52{
     53    if (destroyAll) {
     54        if (m_decoder) {
     55            CFRelease(m_decoder);
     56            m_decoder = 0;
     57        }
     58        return;
     59    }
     60
     61    // TODO(pkasting): If there was an appropriate API to do so, we could
     62    // explicitly tell the CG decoder it can discard frames before
     63    // |clearBeforeFrame| (not including anything it needs to keep around
     64    // locally to continue decoding correctly).  This might help the decoder
     65    // optimize memory/CPU usage.  Right now the decoder seems to throw away
     66    // frames aggressively and then re-decode from the beginning each time, thus
     67    // using more CPU than it needs to.
    5768}
    5869
  • trunk/WebCore/platform/graphics/cg/PDFDocumentImage.h

    r37612 r39309  
    5151        // FIXME: PDF Images are underreporting decoded sizes and will be unable
    5252        // to prune because these functions are not implemented yet.
    53         virtual void destroyDecodedData(bool incremental = false, bool preserveNearbyFrames = false) { }
     53        virtual void destroyDecodedData(bool destroyAll = true) { }
    5454        virtual unsigned decodedSize() const { return 0; }
    5555
  • trunk/WebCore/platform/graphics/qt/StillImageQt.h

    r37615 r39309  
    4242        // FIXME: StillImages are underreporting decoded sizes and will be unable
    4343        // to prune because these functions are not implemented yet.
    44         virtual void destroyDecodedData(bool incremental = false, bool preserveNearbyFrames = false) { }
     44        virtual void destroyDecodedData(bool destroyAll = true) { }
    4545        virtual unsigned decodedSize() const { return 0; }
    4646
  • trunk/WebCore/platform/image-decoders/ImageDecoder.h

    r28068 r39309  
    5454                     m_disposalMethod(DisposeNotSpecified), m_hasAlpha(false)
    5555    {}
     56
     57    void clear() {
     58      m_bytes.clear();
     59      m_rect = IntRect();
     60      m_height = 0;
     61      m_status = FrameEmpty;
     62      m_duration = 0;
     63      m_disposalMethod = DisposeNotSpecified;
     64      m_hasAlpha = false;
     65    }
    5666
    5767    const RGBA32Array& bytes() const { return m_bytes; }
     
    136146    void setFailed() { m_failed = true; }
    137147
     148    // Wipe out frames in the frame buffer cache before |clearBeforeFrame|,
     149    // assuming this can be done without breaking decoding.  Different decoders
     150    // place different restrictions on what frames are safe to destroy, so this
     151    // is left to them to implement.
     152    // For convenience's sake, we provide a default (empty) implementation,
     153    // since in practice only GIFs will ever use this.
     154    virtual void clearFrameBufferCache(size_t clearBeforeFrame) { }
     155
    138156protected:
    139157    RefPtr<SharedBuffer> m_data; // The encoded data.
  • trunk/WebCore/platform/image-decoders/gif/GIFImageDecoder.cpp

    r34200 r39309  
    157157    // responsible for waiting until image decoding has finished to ask this if
    158158    // it needs an authoritative answer.  In the meantime, we should default to
    159     // "loop once", both in the reader and here.
    160     if (m_reader)
    161         m_repetitionCount = m_reader->repetitionCount();
     159    // "loop once".
     160    if (m_reader) {
     161        // Added wrinkle: ImageSource::clear() may destroy the reader, making
     162        // the result from the reader _less_ authoritative on future calls.  To
     163        // detect this, the reader returns cLoopCountNotSeen (-2) instead of
     164        // cAnimationLoopOnce (-1) when its current incarnation hasn't actually
     165        // seen a loop count yet; in this case we return our previously-cached
     166        // value.
     167        const int repetitionCount = m_reader->repetitionCount();
     168        if (repetitionCount != cLoopCountNotSeen)
     169            m_repetitionCount = repetitionCount;
     170    }
    162171    return m_repetitionCount;
    163172}
     
    173182        decode(GIFFullQuery, index+1);
    174183    return &frame;
     184}
     185
     186void GIFImageDecoder::clearFrameBufferCache(size_t clearBeforeFrame)
     187{
     188    // In some cases, like if the decoder was destroyed while animating, we
     189    // can be asked to clear more frames than we currently have.
     190    if (m_frameBufferCache.isEmpty())
     191        return;  // Nothing to do.
     192    // The "-1" here is tricky.  It does not mean that |clearBeforeFrame| is the
     193    // last frame we wish to preserve, but rather that we never want to clear
     194    // the very last frame in the cache: it's empty (so clearing it is
     195    // pointless), it's partial (so we don't want to clear it anyway), or the
     196    // cache could be enlarged with a future setData() call and it could be
     197    // needed to construct the next frame (see comments below).  Callers can
     198    // always use ImageSource::clear(true, ...) to completely free the memory in
     199    // this case.
     200    clearBeforeFrame = std::min(clearBeforeFrame, m_frameBufferCache.size() - 1);
     201    const Vector<RGBA32Buffer>::iterator end(m_frameBufferCache.begin() + clearBeforeFrame);
     202    for (Vector<RGBA32Buffer>::iterator i(m_frameBufferCache.begin()); i != end; ++i) {
     203        if (i->status() == RGBA32Buffer::FrameEmpty)
     204            continue;  // Nothing to do.
     205
     206        // The layout of frames is:
     207        // [empty frames][complete frames][partial frame][empty frames]
     208        // ...where each of these groups may be empty.  We should not clear a
     209        // partial frame since that's what's being decoded right now, and we
     210        // also should not clear the last complete frame, since it may be needed
     211        // when constructing the next frame.  Note that "i + 1" is safe since
     212        // i < end < m_frameBufferCache.end().
     213        if ((i->status() == RGBA32Buffer::FramePartial) || ((i + 1)->status() != RGBA32Buffer::FrameComplete))
     214            break;
     215
     216        i->clear();
     217    }
    175218}
    176219
     
    229272        // DisposeOverwriteBgcolor below and reset to a completely empty image.)
    230273        const RGBA32Buffer* prevBuffer = &m_frameBufferCache[--frameIndex];
     274        ASSERT(prevBuffer->status() == RGBA32Buffer::FrameComplete);
    231275        RGBA32Buffer::FrameDisposalMethod prevMethod =
    232276            prevBuffer->disposalMethod();
  • trunk/WebCore/platform/image-decoders/gif/GIFImageDecoder.h

    r34200 r39309  
    5555    virtual RGBA32Buffer* frameBufferAtIndex(size_t index);
    5656
     57    virtual void clearFrameBufferCache(size_t clearBeforeFrame);
     58
    5759    virtual unsigned frameDurationAtIndex(size_t index) { return 0; }
    5860
  • trunk/WebCore/platform/image-decoders/gif/GIFImageReader.h

    r28068 r39309  
    4747#define MAX_COLORS           256
    4848#define MAX_HOLD_SIZE        256
     49
     50const int cLoopCountNotSeen = -2;
    4951
    5052/* gif2.h 
     
    188190        screen_width = screen_height = 0;
    189191        global_colormap_size = images_decoded = images_count = 0;
    190         loop_count = -1;
     192        loop_count = cLoopCountNotSeen;
    191193        count = 0;
    192194    }
  • trunk/WebCore/svg/graphics/SVGImage.h

    r37612 r39309  
    6060        // FIXME: SVGImages are underreporting decoded sizes and will be unable
    6161        // to prune because these functions are not implemented yet.
    62         virtual void destroyDecodedData(bool incremental = false, bool preserveNearbyFrames = false) { }
     62        virtual void destroyDecodedData(bool destroyAll = true) { }
    6363        virtual unsigned decodedSize() const { return 0; }
    6464
Note: See TracChangeset for help on using the changeset viewer.