Bug 666446, Part 9/10 - Implement methods in RasterImage in order to faciliate refresh driver-based animations. [r=dholbert,joe]

This commit is contained in:
Scott Johnson 2011-11-09 13:39:16 -08:00
parent 863ca35140
commit 63e27b0196
4 changed files with 251 additions and 166 deletions

View File

@ -55,6 +55,7 @@ Decoder::Decoder(RasterImage &aImage, imgIDecoderObserver* aObserver)
, mInFrame(false)
, mDecodeDone(false)
, mDataError(false)
, mIsAnimated(false)
{
}
@ -259,9 +260,14 @@ Decoder::PostFrameStop()
// Flush any invalidations before we finish the frame
FlushInvalidations();
// Fire notification
if (mObserver)
// Fire notifications
if (mObserver) {
mObserver->OnStopFrame(nsnull, mFrameCount - 1); // frame # is zero-indexed
if (mFrameCount > 1 && !mIsAnimated) {
mIsAnimated = true;
mObserver->OnImageIsAnimated(nsnull);
}
}
}
void

View File

@ -218,6 +218,7 @@ private:
bool mInitialized;
bool mSizeDecode;
bool mInFrame;
bool mIsAnimated;
};
} // namespace imagelib

View File

@ -175,10 +175,10 @@ namespace mozilla {
namespace imagelib {
#ifndef DEBUG
NS_IMPL_ISUPPORTS4(RasterImage, imgIContainer, nsITimerCallback, nsIProperties,
NS_IMPL_ISUPPORTS3(RasterImage, imgIContainer, nsIProperties,
nsISupportsWeakReference)
#else
NS_IMPL_ISUPPORTS5(RasterImage, imgIContainer, nsITimerCallback, nsIProperties,
NS_IMPL_ISUPPORTS4(RasterImage, imgIContainer, nsIProperties,
imgIContainerDebug, nsISupportsWeakReference)
#endif
@ -315,12 +315,167 @@ RasterImage::Init(imgIDecoderObserver *aObserver,
return NS_OK;
}
bool
RasterImage::AdvanceFrame(TimeStamp aTime, nsIntRect* aDirtyRect)
{
NS_ASSERTION(aTime <= TimeStamp::Now(),
"Given time appears to be in the future");
imgFrame* nextFrame = nsnull;
PRUint32 currentFrameIndex = mAnim->currentAnimationFrameIndex;
PRUint32 nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
PRUint32 timeout = 0;
// Figure out if we have the next full frame. This is more complicated than
// just checking for mFrames.Length() because decoders append their frames
// before they're filled in.
NS_ABORT_IF_FALSE(mDecoder || nextFrameIndex <= mFrames.Length(),
"How did we get 2 indices too far by incrementing?");
// If we don't have a decoder, we know we've got everything we're going to
// get. If we do, we only display fully-downloaded frames; everything else
// gets delayed.
bool haveFullNextFrame = !mDecoder ||
nextFrameIndex < mDecoder->GetCompleteFrameCount();
// If we're done decoding the next frame, go ahead and display it now and
// reinit with the next frame's delay time.
if (haveFullNextFrame) {
if (mFrames.Length() == nextFrameIndex) {
// End of Animation, unless we are looping forever
// If animation mode is "loop once", it's time to stop animating
if (mAnimationMode == kLoopOnceAnimMode || mLoopCount == 0) {
mAnimationFinished = PR_TRUE;
EvaluateAnimation();
}
// We may have used compositingFrame to build a frame, and then copied
// it back into mFrames[..]. If so, delete composite to save memory
if (mAnim->compositingFrame && mAnim->lastCompositedFrameIndex == -1) {
mAnim->compositingFrame = nsnull;
}
nextFrameIndex = 0;
if (mLoopCount > 0) {
mLoopCount--;
}
if (!mAnimating) {
// break out early if we are actually done animating
return false;
}
}
if (!(nextFrame = mFrames[nextFrameIndex])) {
// something wrong with the next frame, skip it
mAnim->currentAnimationFrameIndex = nextFrameIndex;
return false;
}
timeout = nextFrame->GetTimeout();
} else {
// Uh oh, the frame we want to show is currently being decoded (partial)
// Wait until the next refresh driver tick and try again
return false;
}
if (!(timeout > 0)) {
mAnimationFinished = PR_TRUE;
EvaluateAnimation();
}
imgFrame *frameToUse = nsnull;
if (nextFrameIndex == 0) {
frameToUse = nextFrame;
*aDirtyRect = mAnim->firstFrameRefreshArea;
} else {
imgFrame *curFrame = mFrames[currentFrameIndex];
if (!curFrame) {
return false;
}
// Change frame
if (NS_FAILED(DoComposite(aDirtyRect, curFrame,
nextFrame, nextFrameIndex))) {
// something went wrong, move on to next
NS_WARNING("RasterImage::AdvanceFrame(): Compositing of frame failed");
nextFrame->SetCompositingFailed(PR_TRUE);
mAnim->currentAnimationFrameIndex = nextFrameIndex;
mAnim->currentAnimationFrameTime = aTime;
return false;
}
nextFrame->SetCompositingFailed(PR_FALSE);
}
// Set currentAnimationFrameIndex at the last possible moment
mAnim->currentAnimationFrameIndex = nextFrameIndex;
mAnim->currentAnimationFrameTime = aTime;
return true;
}
//******************************************************************************
/* [notxpcom] void requestRefresh ([const] in TimeStamp aTime); */
// [notxpcom] void requestRefresh ([const] in TimeStamp aTime);
NS_IMETHODIMP_(void)
RasterImage::RequestRefresh(const mozilla::TimeStamp& aTime)
{
// TODO: Implement me as part of b666446
if (!mAnimating || !ShouldAnimate()) {
return;
}
EnsureAnimExists();
// only advance the frame if the current time is greater than or
// equal to the current frame's end time.
TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime();
bool frameAdvanced = false;
// The dirtyRect variable will contain an accumulation of the sub-rectangles
// that are dirty for each frame we advance in AdvanceFrame().
nsIntRect dirtyRect;
while (currentFrameEndTime <= aTime) {
TimeStamp oldFrameEndTime = currentFrameEndTime;
nsIntRect frameDirtyRect;
bool didAdvance = AdvanceFrame(aTime, &frameDirtyRect);
frameAdvanced = frameAdvanced || didAdvance;
currentFrameEndTime = GetCurrentImgFrameEndTime();
// Accumulate the dirty area.
dirtyRect = dirtyRect.Union(frameDirtyRect);
// if we didn't advance a frame, and our frame end time didn't change,
// then we need to break out of this loop & wait for the frame(s)
// to finish downloading
if (!frameAdvanced && (currentFrameEndTime == oldFrameEndTime)) {
break;
}
}
if (frameAdvanced) {
nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(mObserver));
if (!observer) {
NS_ERROR("Refreshing image after its imgRequest is gone");
StopAnimation();
return;
}
// Notify listeners that our frame has actually changed, but do this only
// once for all frames that we've now passed (if AdvanceFrame() was called
// more than once).
#ifdef DEBUG
mFramesNotified++;
#endif
observer->FrameChanged(this, &dirtyRect);
}
}
//******************************************************************************
@ -504,6 +659,27 @@ RasterImage::GetCurrentImgFrameIndex() const
return 0;
}
TimeStamp
RasterImage::GetCurrentImgFrameEndTime() const
{
imgFrame* currentFrame = mFrames[mAnim->currentAnimationFrameIndex];
TimeStamp currentFrameTime = mAnim->currentAnimationFrameTime;
PRInt64 timeout = currentFrame->GetTimeout();
if (timeout < 0) {
// We need to return a sentinel value in this case, because our logic
// doesn't work correctly if we have a negative timeout value. The reason
// this positive infinity was chosen was because it works with the loop in
// RequestRefresh() above.
return TimeStamp() + TimeDuration::FromMilliseconds(UINT64_MAX_VAL);
}
TimeDuration durationOfTimeout = TimeDuration::FromMilliseconds(timeout);
TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
return currentFrameEndTime;
}
imgFrame*
RasterImage::GetCurrentImgFrame()
{
@ -1186,25 +1362,18 @@ RasterImage::StartAnimation()
EnsureAnimExists();
NS_ABORT_IF_FALSE(mAnim && !mAnim->timer, "Anim must exist and not have a timer yet");
// Default timeout to 100: the timer notify code will do the right
// thing, so just get that started.
PRInt32 timeout = 100;
imgFrame *currentFrame = GetCurrentImgFrame();
imgFrame* currentFrame = GetCurrentImgFrame();
if (currentFrame) {
timeout = currentFrame->GetTimeout();
if (timeout < 0) { // -1 means display this frame forever
if (currentFrame->GetTimeout() < 0) { // -1 means display this frame forever
mAnimationFinished = true;
return NS_ERROR_ABORT;
}
// We need to set the time that this initial frame was first displayed, as
// this is used in AdvanceFrame().
mAnim->currentAnimationFrameTime = TimeStamp::Now();
}
mAnim->timer = do_CreateInstance("@mozilla.org/timer;1");
NS_ENSURE_TRUE(mAnim->timer, NS_ERROR_OUT_OF_MEMORY);
mAnim->timer->InitWithCallback(static_cast<nsITimerCallback*>(this),
timeout, nsITimer::TYPE_REPEATING_SLACK);
return NS_OK;
}
@ -1218,11 +1387,6 @@ RasterImage::StopAnimation()
if (mError)
return NS_ERROR_FAILURE;
if (mAnim->timer) {
mAnim->timer->Cancel();
mAnim->timer = nsnull;
}
return NS_OK;
}
@ -1478,127 +1642,6 @@ RasterImage::SetSourceSizeHint(PRUint32 sizeHint)
return NS_OK;
}
//******************************************************************************
/* void notify(in nsITimer timer); */
NS_IMETHODIMP
RasterImage::Notify(nsITimer *timer)
{
#ifdef DEBUG
mFramesNotified++;
#endif
// This should never happen since the timer is only set up in StartAnimation()
// after mAnim is checked to exist.
NS_ABORT_IF_FALSE(mAnim, "Need anim for Notify()");
NS_ABORT_IF_FALSE(timer, "Need timer for Notify()");
NS_ABORT_IF_FALSE(mAnim->timer == timer,
"RasterImage::Notify() called with incorrect timer");
if (!mAnimating || !ShouldAnimate())
return NS_OK;
nsCOMPtr<imgIContainerObserver> observer(do_QueryReferent(mObserver));
if (!observer) {
// the imgRequest that owns us is dead, we should die now too.
NS_ABORT_IF_FALSE(mAnimationConsumers == 0,
"If no observer, should have no consumers");
if (mAnimating)
StopAnimation();
return NS_OK;
}
if (mFrames.Length() == 0)
return NS_OK;
imgFrame *nextFrame = nsnull;
PRInt32 previousFrameIndex = mAnim->currentAnimationFrameIndex;
PRUint32 nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
PRInt32 timeout = 0;
// Figure out if we have the next full frame. This is more complicated than
// just checking for mFrames.Length() because decoders append their frames
// before they're filled in.
NS_ABORT_IF_FALSE(mDecoder || nextFrameIndex <= mFrames.Length(),
"How did we get 2 indicies too far by incrementing?");
// If we don't have a decoder, we know we've got everything we're going to get.
// If we do, we only display fully-downloaded frames; everything else gets delayed.
bool haveFullNextFrame = !mDecoder || nextFrameIndex < mDecoder->GetCompleteFrameCount();
// If we're done decoding the next frame, go ahead and display it now and
// reinit the timer with the next frame's delay time.
if (haveFullNextFrame) {
if (mFrames.Length() == nextFrameIndex) {
// End of Animation
// If animation mode is "loop once", it's time to stop animating
if (mAnimationMode == kLoopOnceAnimMode || mLoopCount == 0) {
mAnimationFinished = true;
EvaluateAnimation();
return NS_OK;
} else {
// We may have used compositingFrame to build a frame, and then copied
// it back into mFrames[..]. If so, delete composite to save memory
if (mAnim->compositingFrame && mAnim->lastCompositedFrameIndex == -1)
mAnim->compositingFrame = nsnull;
}
nextFrameIndex = 0;
if (mLoopCount > 0)
mLoopCount--;
}
if (!(nextFrame = mFrames[nextFrameIndex])) {
// something wrong with the next frame, skip it
mAnim->currentAnimationFrameIndex = nextFrameIndex;
mAnim->timer->SetDelay(100);
return NS_OK;
}
timeout = nextFrame->GetTimeout();
} else {
// Uh oh, the frame we want to show is currently being decoded (partial)
// Wait a bit and try again
mAnim->timer->SetDelay(100);
return NS_OK;
}
if (timeout > 0)
mAnim->timer->SetDelay(timeout);
else {
mAnimationFinished = true;
EvaluateAnimation();
}
nsIntRect dirtyRect;
if (nextFrameIndex == 0) {
dirtyRect = mAnim->firstFrameRefreshArea;
} else {
imgFrame *prevFrame = mFrames[previousFrameIndex];
if (!prevFrame)
return NS_OK;
// Change frame and announce it
if (NS_FAILED(DoComposite(&dirtyRect, prevFrame,
nextFrame, nextFrameIndex))) {
// something went wrong, move on to next
NS_WARNING("RasterImage::Notify(): Composing Frame Failed\n");
nextFrame->SetCompositingFailed(true);
mAnim->currentAnimationFrameIndex = nextFrameIndex;
return NS_OK;
} else {
nextFrame->SetCompositingFailed(false);
}
}
// Set currentAnimationFrameIndex at the last possible moment
mAnim->currentAnimationFrameIndex = nextFrameIndex;
// Refreshes the screen
observer->FrameChanged(this, &dirtyRect);
return NS_OK;
}
//******************************************************************************
// DoComposite gets called when the timer for animation get fired and we have to
// update the composited frame of the animation.

View File

@ -71,6 +71,7 @@
#endif
class imgIDecoder;
class imgIContainerObserver;
class nsIInputStream;
#define NS_RASTERIMAGE_CID \
@ -81,6 +82,12 @@ class nsIInputStream;
{0xb1, 0x43, 0x33, 0x40, 0xc0, 0x01, 0x12, 0xf7} \
}
/**
* It would be nice if we had a macro for this in prtypes.h.
* TODO: Place this macro in prtypes.h as PR_UINT64_MAX.
*/
#define UINT64_MAX_VAL PRUint64(-1)
/**
* Handles static and animated image containers.
*
@ -88,15 +95,27 @@ class nsIInputStream;
* @par A Quick Walk Through
* The decoder initializes this class and calls AppendFrame() to add a frame.
* Once RasterImage detects more than one frame, it starts the animation
* with StartAnimation().
* with StartAnimation(). Note that the invalidation events for RasterImage are
* generated automatically using nsRefreshDriver.
*
* @par
* StartAnimation() creates a timer. The timer calls Notify when the
* specified frame delay time is up.
* StartAnimation() initializes the animation helper object and sets the time
* the first frame was displayed to the current clock time.
*
* @par
* Notify() moves on to the next frame, sets up the new timer delay, destroys
* the old frame, and forces a redraw via observer->FrameChanged().
* When the refresh driver corresponding to the imgIContainer that this image is
* a part of notifies the RasterImage that it's time to invalidate,
* RequestRefresh() is called with a given TimeStamp to advance to. As long as
* the timeout of the given frame (the frame's "delay") plus the time that frame
* was first displayed is less than or equal to the TimeStamp given,
* RequestRefresh() calls AdvanceFrame().
*
* @par
* AdvanceFrame() is responsible for advancing a single frame of the animation.
* It can return true, meaning that the frame advanced, or false, meaning that
* the frame failed to advance (usually because the next frame hasn't been
* decoded yet). It is also responsible for performing the final animation stop
* procedure if the final frame of a non-looping animation is reached.
*
* @par
* Each frame can have a different method of removing itself. These are
@ -155,7 +174,6 @@ class imgDecodeWorker;
class Decoder;
class RasterImage : public Image
, public nsITimerCallback
, public nsIProperties
, public nsSupportsWeakReference
#ifdef DEBUG
@ -164,7 +182,6 @@ class RasterImage : public Image
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSITIMERCALLBACK
NS_DECL_NSIPROPERTIES
#ifdef DEBUG
NS_DECL_IMGICONTAINERDEBUG
@ -340,6 +357,10 @@ private:
//! Area of the first frame that needs to be redrawn on subsequent loops.
nsIntRect firstFrameRefreshArea;
PRUint32 currentAnimationFrameIndex; // 0 to numFrames-1
// the time that the animation advanced to the current frame
TimeStamp currentAnimationFrameTime;
//! Track the last composited frame for Optimizations (See DoComposite code)
PRInt32 lastCompositedFrameIndex;
/** For managing blending of frames
@ -358,23 +379,33 @@ private:
* when it's done with the current frame.
*/
nsAutoPtr<imgFrame> compositingPrevFrame;
//! Timer to animate multiframed images
nsCOMPtr<nsITimer> timer;
Anim() :
firstFrameRefreshArea(),
currentAnimationFrameIndex(0),
lastCompositedFrameIndex(-1)
{
;
}
~Anim()
{
if (timer)
timer->Cancel();
}
lastCompositedFrameIndex(-1) {}
~Anim() {}
};
/**
* Advances the animation. Typically, this will advance a single frame, but it
* may advance multiple frames. This may happen if we have infrequently
* "ticking" refresh drivers (e.g. in background tabs), or extremely short-
* lived animation frames.
*
* @param aTime the time that the animation should advance to. This will
* typically be <= TimeStamp::Now().
*
* @param [out] aDirtyRect a pointer to an nsIntRect which encapsulates the
* area to be repainted after the frame is advanced.
*
* @returns true, if the frame was successfully advanced, false if it was not
* able to be advanced (e.g. the frame to which we want to advance is
* still decoding). Note: If false is returned, then aDirtyRect will
* remain unmodified.
*/
bool AdvanceFrame(mozilla::TimeStamp aTime, nsIntRect* aDirtyRect);
/**
* Deletes and nulls out the frame in mFrames[framenum].
*
@ -391,6 +422,7 @@ private:
imgFrame* GetCurrentImgFrame();
imgFrame* GetCurrentDrawableImgFrame();
PRUint32 GetCurrentImgFrameIndex() const;
mozilla::TimeStamp GetCurrentImgFrameEndTime() const;
inline void EnsureAnimExists()
{
@ -409,9 +441,12 @@ private:
// since we didn't kill the source data in the old world either, locking
// is acceptable for the moment.
LockImage();
// Notify our observers that we are starting animation.
mStatusTracker->RecordImageIsAnimated();
}
}
/** Function for doing the frame compositing of animations
*
* @param aDirtyRect Area that the display will need to update
@ -423,7 +458,7 @@ private:
imgFrame* aPrevFrame,
imgFrame* aNextFrame,
PRInt32 aNextFrameIndex);
/** Clears an area of <aFrame> with transparent black.
*
* @param aFrame Target Frame
@ -431,7 +466,7 @@ private:
* @note Does also clears the transparancy mask
*/
static void ClearFrame(imgFrame* aFrame);
//! @overload
static void ClearFrame(imgFrame* aFrame, nsIntRect &aRect);