Commit e8a5a6b7 authored by pkasting@chromium.org's avatar pkasting@chromium.org
Browse files

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

        Reviewed by David Hyatt.

        https://bugs.webkit.org/show_bug.cgi?id=19663 (Second attempt)
        Account for paint and timer lag when animating images.  Also pretend
        that images whose animations were paused (by becoming invisible)
        continued to animate, by "catching up" to the correct frame when they're
        shown again.

        * platform/graphics/BitmapImage.cpp:
        (WebCore::BitmapImage::BitmapImage):
        (WebCore::BitmapImage::destroyDecodedData):
        (WebCore::BitmapImage::cacheFrame):
        (WebCore::BitmapImage::frameIsCompleteAtIndex):
        (WebCore::BitmapImage::frameDurationAtIndex):
        (WebCore::BitmapImage::frameHasAlphaAtIndex):
        (WebCore::BitmapImage::repetitionCount):
        (WebCore::BitmapImage::shouldAnimate):
        (WebCore::BitmapImage::startAnimation):
        (WebCore::BitmapImage::resetAnimation):
        (WebCore::BitmapImage::advanceAnimation):
        (WebCore::BitmapImage::internalAdvanceAnimation):
        (WebCore::BitmapImage::notifyObserverAndTrimDecodedData):
        * platform/graphics/BitmapImage.h:
        (WebCore::FrameData::FrameData):
        (WebCore::BitmapImage::):
        * platform/graphics/GeneratedImage.h:
        (WebCore::GeneratedImage::destroyDecodedData):
        * platform/graphics/Image.h:
        * platform/graphics/cairo/ImageCairo.cpp:
        (WebCore::FrameData::clear):
        (WebCore::BitmapImage::BitmapImage):
        (WebCore::BitmapImage::draw):
        * platform/graphics/cg/ImageCG.cpp:
        (WebCore::FrameData::clear):
        (WebCore::BitmapImage::BitmapImage):
        (WebCore::BitmapImage::draw):
        * platform/graphics/cg/PDFDocumentImage.h:
        (WebCore::PDFDocumentImage::destroyDecodedData):
        * platform/graphics/qt/ImageQt.cpp:
        (WebCore::FrameData::clear):
        (WebCore::BitmapImage::draw):
        * platform/graphics/wx/ImageWx.cpp:
        (WebCore::FrameData::clear):
        (WebCore::BitmapImage::draw):
        * svg/graphics/SVGImage.h:
        (WebCore::SVGImage::destroyDecodedData):



git-svn-id: http://svn.webkit.org/repository/webkit/trunk@37612 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent cf22dbac
2008-10-15 Peter Kasting <pkasting@google.com>
Reviewed by David Hyatt.
https://bugs.webkit.org/show_bug.cgi?id=19663 (Second attempt)
Account for paint and timer lag when animating images. Also pretend
that images whose animations were paused (by becoming invisible)
continued to animate, by "catching up" to the correct frame when they're
shown again.
* platform/graphics/BitmapImage.cpp:
(WebCore::BitmapImage::BitmapImage):
(WebCore::BitmapImage::destroyDecodedData):
(WebCore::BitmapImage::cacheFrame):
(WebCore::BitmapImage::frameIsCompleteAtIndex):
(WebCore::BitmapImage::frameDurationAtIndex):
(WebCore::BitmapImage::frameHasAlphaAtIndex):
(WebCore::BitmapImage::repetitionCount):
(WebCore::BitmapImage::shouldAnimate):
(WebCore::BitmapImage::startAnimation):
(WebCore::BitmapImage::resetAnimation):
(WebCore::BitmapImage::advanceAnimation):
(WebCore::BitmapImage::internalAdvanceAnimation):
(WebCore::BitmapImage::notifyObserverAndTrimDecodedData):
* platform/graphics/BitmapImage.h:
(WebCore::FrameData::FrameData):
(WebCore::BitmapImage::):
* platform/graphics/GeneratedImage.h:
(WebCore::GeneratedImage::destroyDecodedData):
* platform/graphics/Image.h:
* platform/graphics/cairo/ImageCairo.cpp:
(WebCore::FrameData::clear):
(WebCore::BitmapImage::BitmapImage):
(WebCore::BitmapImage::draw):
* platform/graphics/cg/ImageCG.cpp:
(WebCore::FrameData::clear):
(WebCore::BitmapImage::BitmapImage):
(WebCore::BitmapImage::draw):
* platform/graphics/cg/PDFDocumentImage.h:
(WebCore::PDFDocumentImage::destroyDecodedData):
* platform/graphics/qt/ImageQt.cpp:
(WebCore::FrameData::clear):
(WebCore::BitmapImage::draw):
* platform/graphics/wx/ImageWx.cpp:
(WebCore::FrameData::clear):
(WebCore::BitmapImage::draw):
* svg/graphics/SVGImage.h:
(WebCore::SVGImage::destroyDecodedData):
2008-10-14 Maxime Britto <britto@apple.com>
 
Reviewed by Darin Adler, tweaked and landed by Beth.
......
......@@ -31,6 +31,7 @@
#include "ImageObserver.h"
#include "IntRect.h"
#include "PlatformString.h"
#include "SystemTime.h"
#include "Timer.h"
#include <wtf/Vector.h>
#include "MIMETypeRegistry.h"
......@@ -41,15 +42,21 @@ namespace WebCore {
// one frame at a time.
const unsigned cLargeAnimationCutoff = 5242880;
// When an animated image is more than five minutes out of date, don't try to
// resync on repaint, so we don't waste CPU cycles on an edge case the user
// doesn't care about.
const double cAnimationResyncCutoff = 5 * 60;
BitmapImage::BitmapImage(ImageObserver* observer)
: Image(observer)
, m_currentFrame(0)
, m_frames(0)
, m_frameTimer(0)
, m_repetitionCount(0)
, m_repetitionCount(cAnimationNone)
, m_repetitionCountStatus(Unknown)
, m_repetitionsComplete(0)
, m_desiredFrameStartTime(0)
, m_isSolidColor(false)
, m_animatingImageType(true)
, m_animationFinished(false)
, m_allDataReceived(false)
, m_haveSize(false)
......@@ -68,14 +75,15 @@ BitmapImage::~BitmapImage()
stopAnimation();
}
void BitmapImage::destroyDecodedData(bool incremental)
void BitmapImage::destroyDecodedData(bool incremental, bool preserveNearbyFrames)
{
// Destroy the cached images and release them.
if (m_frames.size()) {
int sizeChange = 0;
int frameSize = m_size.width() * m_size.height() * 4;
const size_t nextFrame = (preserveNearbyFrames && frameCount()) ? ((m_currentFrame + 1) % frameCount()) : 0;
for (unsigned i = incremental ? m_frames.size() - 1 : 0; i < m_frames.size(); i++) {
if (m_frames[i].m_frame) {
if (m_frames[i].m_frame && (!preserveNearbyFrames || (i != m_currentFrame && i != nextFrame))) {
sizeChange -= frameSize;
m_frames[i].clear();
}
......@@ -106,16 +114,6 @@ void BitmapImage::cacheFrame(size_t index)
size_t numFrames = frameCount();
ASSERT(m_decodedSize == 0 || numFrames > 1);
if (!m_frames.size() && shouldAnimate()) {
// Snag the repetition count. Note that the repetition count may not be
// accurate yet for GIFs; if we haven't gotten the count from the source
// image yet, it will default to cAnimationLoopOnce, and we'll try and
// read it again once the whole image is decoded.
m_repetitionCount = m_source.repetitionCount();
if (m_repetitionCount == cAnimationNone)
m_animatingImageType = false;
}
if (m_frames.size() < numFrames)
m_frames.grow(numFrames);
......@@ -123,7 +121,9 @@ void BitmapImage::cacheFrame(size_t index)
if (numFrames == 1 && m_frames[index].m_frame)
checkForSolidColor();
if (shouldAnimate())
m_frames[index].m_haveMetadata = true;
m_frames[index].m_isComplete = m_source.frameIsCompleteAtIndex(index);
if (repetitionCount(false) != cAnimationNone)
m_frames[index].m_duration = m_source.frameDurationAtIndex(index);
m_frames[index].m_hasAlpha = m_source.frameHasAlphaAtIndex(index);
......@@ -207,12 +207,23 @@ NativeImagePtr BitmapImage::frameAtIndex(size_t index)
return m_frames[index].m_frame;
}
bool BitmapImage::frameIsCompleteAtIndex(size_t index)
{
if (index >= frameCount())
return true;
if (index >= m_frames.size() || !m_frames[index].m_haveMetadata)
cacheFrame(index);
return m_frames[index].m_isComplete;
}
float BitmapImage::frameDurationAtIndex(size_t index)
{
if (index >= frameCount())
return 0;
if (index >= m_frames.size() || !m_frames[index].m_frame)
if (index >= m_frames.size() || !m_frames[index].m_haveMetadata)
cacheFrame(index);
return m_frames[index].m_duration;
......@@ -223,35 +234,122 @@ bool BitmapImage::frameHasAlphaAtIndex(size_t index)
if (index >= frameCount())
return true;
if (index >= m_frames.size() || !m_frames[index].m_frame)
if (index >= m_frames.size() || !m_frames[index].m_haveMetadata)
cacheFrame(index);
return m_frames[index].m_hasAlpha;
}
int BitmapImage::repetitionCount(bool imageKnownToBeComplete)
{
if ((m_repetitionCountStatus == Unknown) || ((m_repetitionCountStatus == Uncertain) && imageKnownToBeComplete)) {
// Snag the repetition count. If |imageKnownToBeComplete| is false, the
// repetition count may not be accurate yet for GIFs; in this case the
// decoder will default to cAnimationLoopOnce, and we'll try and read
// the count again once the whole image is decoded.
m_repetitionCount = m_source.repetitionCount();
m_repetitionCountStatus = (imageKnownToBeComplete || m_repetitionCount == cAnimationNone) ? Certain : Uncertain;
}
return m_repetitionCount;
}
bool BitmapImage::shouldAnimate()
{
return (m_animatingImageType && !m_animationFinished && imageObserver());
return (repetitionCount(false) != cAnimationNone && !m_animationFinished && imageObserver());
}
void BitmapImage::startAnimation()
void BitmapImage::startAnimation(bool catchUpIfNecessary)
{
if (m_frameTimer || !shouldAnimate() || frameCount() <= 1)
return;
// Don't advance the animation until the current frame has completely loaded.
if (!m_source.frameIsCompleteAtIndex(m_currentFrame))
// Determine time for next frame to start. By ignoring paint and timer lag
// in this calculation, we make the animation appear to run at its desired
// rate regardless of how fast it's being repainted.
const double currentDuration = frameDurationAtIndex(m_currentFrame);
const double time = currentTime();
if (m_desiredFrameStartTime == 0) {
m_desiredFrameStartTime = time + currentDuration;
} else {
m_desiredFrameStartTime += currentDuration;
// If we're too far behind, the user probably doesn't care about
// resyncing and we could burn a lot of time looping through frames
// below. Just reset the timings.
if ((time - m_desiredFrameStartTime) > cAnimationResyncCutoff)
m_desiredFrameStartTime = time + currentDuration;
}
// Don't advance the animation to an incomplete frame.
size_t nextFrame = (m_currentFrame + 1) % frameCount();
if (!frameIsCompleteAtIndex(nextFrame))
return;
// Don't advance past the last frame if we haven't decoded the whole image
// yet and our repetition count is potentially unset. The repetition count
// in a GIF can potentially come after all the rest of the image data, so
// wait on it.
if (!m_allDataReceived && m_repetitionCount == cAnimationLoopOnce && m_currentFrame >= (frameCount() - 1))
if (!m_allDataReceived && repetitionCount(false) == cAnimationLoopOnce && m_currentFrame >= (frameCount() - 1))
return;
m_frameTimer = new Timer<BitmapImage>(this, &BitmapImage::advanceAnimation);
m_frameTimer->startOneShot(frameDurationAtIndex(m_currentFrame));
// The image may load more slowly than it's supposed to animate, so that by
// the time we reach the end of the first repetition, we're well behind.
// Clamp the desired frame start time in this case, so that we don't skip
// frames (or whole iterations) trying to "catch up". This is a tradeoff:
// It guarantees users see the whole animation the second time through and
// don't miss any repetitions, and is closer to what other browsers do; on
// the other hand, it makes animations "less accurate" for pages that try to
// sync an image and some other resource (e.g. audio), especially if users
// switch tabs (and thus stop drawing the animation, which will pause it)
// during that initial loop, then switch back later.
if (nextFrame == 0 && m_repetitionsComplete == 0 && m_desiredFrameStartTime < time)
m_desiredFrameStartTime = time;
if (!catchUpIfNecessary || time < m_desiredFrameStartTime) {
// Haven't yet reached time for next frame to start; delay until then.
m_frameTimer = new Timer<BitmapImage>(this, &BitmapImage::advanceAnimation);
m_frameTimer->startOneShot(std::max(m_desiredFrameStartTime - time, 0.));
} else {
// We've already reached or passed the time for the next frame to start.
// See if we've also passed the time for frames after that to start, in
// case we need to skip some frames entirely. Remember not to advance
// to an incomplete frame.
for (size_t frameAfterNext = (nextFrame + 1) % frameCount(); frameIsCompleteAtIndex(frameAfterNext); frameAfterNext = (nextFrame + 1) % frameCount()) {
// Should we skip the next frame?
double frameAfterNextStartTime = m_desiredFrameStartTime + frameDurationAtIndex(nextFrame);
if (time < frameAfterNextStartTime)
break;
// Yes; skip over it without notifying our observers.
if (!internalAdvanceAnimation(true))
return;
m_desiredFrameStartTime = frameAfterNextStartTime;
nextFrame = frameAfterNext;
}
// Draw the next frame immediately. Note that m_desiredFrameStartTime
// may be in the past, meaning the next time through this function we'll
// kick off the next advancement sooner than this frame's duration would
// suggest.
if (internalAdvanceAnimation(false)) {
// The image region has been marked dirty, but once we return to our
// caller, draw() will clear it, and nothing will cause the
// animation to advance again. We need to start the timer for the
// next frame running, or the animation can hang. (Compare this
// with when advanceAnimation() is called, and the region is dirtied
// while draw() is not in the callstack, meaning draw() gets called
// to update the region and thus startAnimation() is reached again.)
// NOTE: For large images with slow or heavily-loaded systems,
// throwing away data as we go (see destroyDecodedData()) means we
// can spend so much time re-decoding data above that by the time we
// reach here we're behind again. If we let startAnimation() run
// the catch-up code again, we can get long delays without painting
// as we race the timer, or even infinite recursion. In this
// situation the best we can do is to simply change frames as fast
// as possible, so force startAnimation() to set a zero-delay timer
// and bail out if we're not caught up.
startAnimation(false);
}
}
}
void BitmapImage::stopAnimation()
......@@ -267,6 +365,7 @@ void BitmapImage::resetAnimation()
stopAnimation();
m_currentFrame = 0;
m_repetitionsComplete = 0;
m_desiredFrameStartTime = 0;
m_animationFinished = false;
int frameSize = m_size.width() * m_size.height() * 4;
......@@ -276,48 +375,62 @@ void BitmapImage::resetAnimation()
}
void BitmapImage::advanceAnimation(Timer<BitmapImage>* timer)
{
internalAdvanceAnimation(false);
// At this point the image region has been marked dirty, and if it's
// onscreen, we'll soon make a call to draw(), which will call
// startAnimation() again to keep the animation moving.
}
bool BitmapImage::internalAdvanceAnimation(bool skippingFrames)
{
// Stop the animation.
stopAnimation();
// See if anyone is still paying attention to this animation. If not, we don't
// advance and will remain suspended at the current frame until the animation is resumed.
if (imageObserver()->shouldPauseAnimation(this))
return;
if (!skippingFrames && imageObserver()->shouldPauseAnimation(this))
return false;
m_currentFrame++;
if (m_currentFrame >= frameCount()) {
m_repetitionsComplete += 1;
++m_repetitionsComplete;
// Get the repetition count again. If we weren't able to get a
// repetition count before, we should have decoded the whole image by
// now, so it should now be available.
m_repetitionCount = m_source.repetitionCount();
if (m_repetitionCount && m_repetitionsComplete >= m_repetitionCount) {
if (repetitionCount(true) && m_repetitionsComplete >= m_repetitionCount) {
m_animationFinished = true;
m_desiredFrameStartTime = 0;
m_currentFrame--;
return;
if (skippingFrames) {
// Uh oh. We tried to skip past the end of the animation. We'd
// better draw this last frame.
notifyObserverAndTrimDecodedData();
}
return false;
}
m_currentFrame = 0;
}
if (!skippingFrames)
notifyObserverAndTrimDecodedData();
return true;
}
void BitmapImage::notifyObserverAndTrimDecodedData()
{
// Notify our observer that the animation has advanced.
imageObserver()->animationAdvanced(this);
// For large animated images, go ahead and throw away frames as we go to save
// footprint.
// For large animated images, go ahead and throw away frames as we go to
// save footprint.
int frameSize = m_size.width() * m_size.height() * 4;
if (frameCount() * frameSize > cLargeAnimationCutoff) {
// Destroy all of our frames and just redecode every time.
destroyDecodedData();
// Go ahead and decode the next frame.
frameAtIndex(m_currentFrame);
// Destroy all of our frames and just redecode every time. We save the
// current frame since we'll need it in draw() anyway.
destroyDecodedData(false, true);
}
// We do not advance the animation explicitly. We rely on a subsequent draw of the image
// to force a request for the next frame via startAnimation(). This allows images that move offscreen while
// scrolling to stop animating (thus saving memory from additional decoded frames and
// CPU time spent doing the decoding).
}
}
......@@ -66,6 +66,8 @@ template <typename T> class Timer;
struct FrameData : Noncopyable {
FrameData()
: m_frame(0)
, m_haveMetadata(false)
, m_isComplete(false)
, m_duration(0)
, m_hasAlpha(true)
{
......@@ -79,6 +81,8 @@ struct FrameData : Noncopyable {
void clear();
NativeImagePtr m_frame;
bool m_haveMetadata;
bool m_isComplete;
float m_duration;
bool m_hasAlpha;
};
......@@ -136,6 +140,12 @@ public:
virtual NativeImagePtr nativeImageForCurrentFrame() { return frameAtIndex(currentFrame()); }
protected:
enum RepetitionCountStatus {
Unknown, // We haven't checked the source's repetition count.
Uncertain, // We have a repetition count, but it might be wrong (some GIFs have a count after the image data, and will report "loop once" until all data has been decoded).
Certain, // The repetition count is known to be correct.
};
BitmapImage(NativeImagePtr, ImageObserver* = 0);
BitmapImage(ImageObserver* = 0);
......@@ -150,24 +160,45 @@ protected:
size_t currentFrame() const { return m_currentFrame; }
size_t frameCount();
NativeImagePtr frameAtIndex(size_t);
bool frameIsCompleteAtIndex(size_t);
float frameDurationAtIndex(size_t);
bool frameHasAlphaAtIndex(size_t);
// Decodes and caches a frame. Never accessed except internally.
void cacheFrame(size_t index);
// Called to invalidate all our cached data. If an image is loading incrementally, we only
// invalidate the last cached frame.
virtual void destroyDecodedData(bool incremental = false);
// Called to invalidate all our cached data. If an image is loading
// incrementally, we only invalidate the last cached frame. For large
// animated images, where we throw away the decoded data after every frame,
// |preserveNearbyFrames| can be set to preserve the current frame's data
// and eliminate some unnecessary duplicated decoding work. This also
// preserves the next frame's data, if available. In most cases this has no
// effect; either that frame isn't decoded yet, or it's already been
// destroyed by a previous call. But when we fall behind on the very first
// animation loop and startAnimation() needs to "catch up" one or more
// frames, this briefly preserves some of that decoding work, to ease CPU
// load and make it less likely that we'll keep falling behind.
virtual void destroyDecodedData(bool incremental = false, bool preserveNearbyFrames = false);
// Whether or not size is available yet.
bool isSizeAvailable();
// Animation.
int repetitionCount(bool imageKnownToBeComplete); // |imageKnownToBeComplete| should be set if the caller knows the entire image has been decoded.
bool shouldAnimate();
virtual void startAnimation();
virtual void startAnimation(bool catchUpIfNecessary = true);
void advanceAnimation(Timer<BitmapImage>*);
// Function that does the real work of advancing the animation. When
// skippingFrames is true, we're in the middle of a loop trying to skip over
// a bunch of animation frames, so we should not do things like decode each
// one or notify our observers.
// Returns whether the animation was advanced.
bool internalAdvanceAnimation(bool skippingFrames);
// Helper for internalAdvanceAnimation().
void notifyObserverAndTrimDecodedData();
// Handle platform-specific data
void initPlatformData();
void invalidatePlatformData();
......@@ -185,8 +216,10 @@ protected:
Vector<FrameData> m_frames; // An array of the cached frames of the animation. We have to ref frames to pin them in the cache.
Timer<BitmapImage>* m_frameTimer;
int m_repetitionCount; // How many total animation loops we should do.
int m_repetitionCount; // How many total animation loops we should do. This will be cAnimationNone if this image type is incapable of animation.
RepetitionCountStatus m_repetitionCountStatus;
int m_repetitionsComplete; // How many repetitions we've finished.
double m_desiredFrameStartTime; // The system time at which we hope to see the next call to startAnimation().
#if PLATFORM(MAC)
mutable RetainPtr<NSImage> m_nsImage; // A cached NSImage of frame 0. Only built lazily if someone actually queries for one.
......@@ -196,7 +229,6 @@ protected:
Color m_solidColor; // If we're a 1x1 solid color, this is the color to use to fill.
bool m_isSolidColor; // Whether or not we are a 1x1 solid image.
bool m_animatingImageType; // Whether or not we're an image type that is capable of animating (GIF).
bool m_animationFinished; // Whether or not we've completed the entire animation.
bool m_allDataReceived; // Whether or not we've received all our data.
......
......@@ -53,7 +53,7 @@ public:
virtual IntSize size() const { return m_size; }
// Assume that generated content has no decoded data we need to worry about
virtual void destroyDecodedData(bool incremental = false) { }
virtual void destroyDecodedData(bool incremental = false, bool preserveNearbyFrames = false) { }
virtual unsigned decodedSize() const { return 0; }
protected:
......
......@@ -108,7 +108,7 @@ public:
bool setData(PassRefPtr<SharedBuffer> data, bool allDataReceived);
virtual bool dataChanged(bool allDataReceived) { return false; }
virtual void destroyDecodedData(bool incremental = false) = 0;
virtual void destroyDecodedData(bool incremental = false, bool preserveNearbyFrames = false) = 0;
virtual unsigned decodedSize() const = 0;
SharedBuffer* data() { return m_data.get(); }
......
......@@ -43,8 +43,9 @@ void FrameData::clear()
if (m_frame) {
cairo_surface_destroy(m_frame);
m_frame = 0;
m_duration = 0.;
m_hasAlpha = true;
// NOTE: We purposefully don't reset metadata here, so that even if we
// throw away previously-decoded data, animation loops can still access
// properties like frame durations without re-decoding.
}
}
......@@ -53,7 +54,8 @@ BitmapImage::BitmapImage(cairo_surface_t* surface, ImageObserver* observer)
, m_currentFrame(0)
, m_frames(0)
, m_frameTimer(0)
, m_repetitionCount(0)
, m_repetitionCount(cAnimationNone)
, m_repetitionCountStatus(Unknown)
, m_repetitionsComplete(0)
, m_isSolidColor(false)
, m_animatingImageType(false)
......@@ -82,13 +84,15 @@ BitmapImage::BitmapImage(cairo_surface_t* surface, ImageObserver* observer)
void BitmapImage::draw(GraphicsContext* context, const FloatRect& dst, const FloatRect& src, CompositeOperator op)
{
FloatRect srcRect(src);
FloatRect dstRect(dst);
startAnimation();
cairo_surface_t* image = frameAtIndex(m_currentFrame);
if (!image) // If it's too early we won't have an image yet.
return;
FloatRect srcRect(src);
FloatRect dstRect(dst);
if (mayFillWithSolidColor()) {
fillWithSolidColor(context, dstRect, solidColor(), op);
return;
......@@ -131,8 +135,6 @@ void BitmapImage::draw(GraphicsContext* context, const FloatRect& dst, const Flo
cairo_restore(cr);
startAnimation();
if (imageObserver())
imageObserver()->didDraw(this);
}
......
......@@ -52,8 +52,9 @@ void FrameData::clear()
if (m_frame) {
CGImageRelease(m_frame);
m_frame = 0;
m_duration = 0.0f;
m_hasAlpha = true;
// NOTE: We purposefully don't reset metadata here, so that even if we
// throw away previously-decoded data, animation loops can still access
// properties like frame durations without re-decoding.
}
}
......@@ -66,10 +67,10 @@ BitmapImage::BitmapImage(CGImageRef cgImage, ImageObserver* observer)
, m_currentFrame(0)
, m_frames(0)
, m_frameTimer(0)
, m_repetitionCount(0)
, m_repetitionCount(cAnimationNone)
, m_repetitionCountStatus(Unknown)
, m_repetitionsComplete(0)
, m_isSolidColor(false)
, m_animatingImageType(false)
, m_animationFinished(true)
, m_allDataReceived(true)
, m_haveSize(true)
......@@ -129,6 +130,8 @@ CGImageRef BitmapImage::getCGImageRef()
void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& destRect, const FloatRect& srcRect, CompositeOperator compositeOp)
{
startAnimation();
CGImageRef image = frameAtIndex(m_currentFrame);
if (!image) // If it's too early we won't have an image yet.
return;
......@@ -193,8 +196,6 @@ void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& destRect, const F
ctxt->restore();
startAnimation();
if (imageObserver())
imageObserver()->didDraw(this);
}
......
......@@ -50,7 +50,7 @@ namespace WebCore {
// FIXME: PDF Images are underreporting decoded sizes and will be unable
// to prune because these functions are not implemented yet.
virtual void destroyDecodedData(bool incremental = false) { }
virtual void destroyDecodedData(bool incremental = false, bool preserveNearbyFrames = false) { }
virtual unsigned decodedSize() const { return 0; }
virtual IntSize size() const;
......
......@@ -73,8 +73,9 @@ void FrameData::clear()
{
if (m_frame) {
m_frame = 0;
m_duration = 0.0f;
m_hasAlpha = true;
// NOTE: We purposefully don't reset metadata here, so that even if we
// throw away previously-decoded data, animation loops can still access
// properties like frame durations without re-decoding.
}
}
......@@ -108,6 +109,8 @@ void BitmapImage::invalidatePlatformData()
void BitmapImage::draw(GraphicsContext* ctxt, const FloatRect& dst,
const FloatRect& src, CompositeOperator op)
{