mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 21:31:04 +00:00
Bug 717872 - Move all image animation logic into a new class, FrameAnimator, and use it from RasterImage. r=seth
This patch moves the logic of moving from one frame to another (and tracking what frame is current, etc) to a separate class, FrameAnimator. Deciding *whether* to animate, and actually calling that animation code, is left to RasterImage, but the animation itself is driven by FrameAnimator.
This commit is contained in:
parent
1a2ca2d673
commit
cb1e8505e5
264
image/src/FrameAnimator.cpp
Normal file
264
image/src/FrameAnimator.cpp
Normal file
@ -0,0 +1,264 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "FrameAnimator.h"
|
||||
|
||||
#include "imgIContainer.h"
|
||||
|
||||
using namespace mozilla::image;
|
||||
using namespace mozilla;
|
||||
|
||||
FrameAnimator::FrameAnimator(FrameBlender& aFrameBlender)
|
||||
: mCurrentAnimationFrameIndex(0)
|
||||
, mLoopCount(-1)
|
||||
, mFrameBlender(aFrameBlender)
|
||||
, mAnimationMode(imgIContainer::kNormalAnimMode)
|
||||
, mDoneDecoding(false)
|
||||
{
|
||||
}
|
||||
|
||||
uint32_t
|
||||
FrameAnimator::GetSingleLoopTime() const
|
||||
{
|
||||
// If we aren't done decoding, we don't know the image's full play time.
|
||||
if (!mDoneDecoding) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If we're not looping, a single loop time has no meaning
|
||||
if (mAnimationMode != imgIContainer::kNormalAnimMode) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t looptime = 0;
|
||||
for (uint32_t i = 0; i < mFrameBlender.GetNumFrames(); ++i) {
|
||||
int32_t timeout = mFrameBlender.RawGetFrame(i)->GetTimeout();
|
||||
if (timeout > 0) {
|
||||
looptime += static_cast<uint32_t>(timeout);
|
||||
} else {
|
||||
// If we have a frame that never times out, we're probably in an error
|
||||
// case, but let's handle it more gracefully.
|
||||
NS_WARNING("Negative frame timeout - how did this happen?");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return looptime;
|
||||
}
|
||||
|
||||
TimeStamp
|
||||
FrameAnimator::GetCurrentImgFrameEndTime() const
|
||||
{
|
||||
imgFrame* currentFrame = mFrameBlender.RawGetFrame(mCurrentAnimationFrameIndex);
|
||||
TimeStamp currentFrameTime = mCurrentAnimationFrameTime;
|
||||
int64_t 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() below.
|
||||
return TimeStamp() +
|
||||
TimeDuration::FromMilliseconds(static_cast<double>(UINT64_MAX));
|
||||
}
|
||||
|
||||
TimeDuration durationOfTimeout =
|
||||
TimeDuration::FromMilliseconds(static_cast<double>(timeout));
|
||||
TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
|
||||
|
||||
return currentFrameEndTime;
|
||||
}
|
||||
|
||||
FrameAnimator::RefreshResult
|
||||
FrameAnimator::AdvanceFrame(TimeStamp aTime)
|
||||
{
|
||||
NS_ASSERTION(aTime <= TimeStamp::Now(),
|
||||
"Given time appears to be in the future");
|
||||
|
||||
uint32_t currentFrameIndex = mCurrentAnimationFrameIndex;
|
||||
uint32_t nextFrameIndex = currentFrameIndex + 1;
|
||||
uint32_t timeout = 0;
|
||||
|
||||
RefreshResult ret;
|
||||
|
||||
// If we're done decoding, we know we've got everything we're going to get.
|
||||
// If we aren't, we only display fully-downloaded frames; everything else
|
||||
// gets delayed.
|
||||
bool needToWait = !mDoneDecoding &&
|
||||
mFrameBlender.RawGetFrame(nextFrameIndex) &&
|
||||
!mFrameBlender.RawGetFrame(nextFrameIndex)->ImageComplete();
|
||||
|
||||
if (needToWait) {
|
||||
// Uh oh, the frame we want to show is currently being decoded (partial)
|
||||
// Wait until the next refresh driver tick and try again
|
||||
return ret;
|
||||
} else {
|
||||
// If we're done decoding the next frame, go ahead and display it now and
|
||||
// reinit with the next frame's delay time.
|
||||
if (mFrameBlender.GetNumFrames() == nextFrameIndex) {
|
||||
// End of Animation, unless we are looping forever
|
||||
|
||||
// If animation mode is "loop once", it's time to stop animating
|
||||
if (mAnimationMode == imgIContainer::kLoopOnceAnimMode || mLoopCount == 0) {
|
||||
ret.animationFinished = true;
|
||||
}
|
||||
|
||||
nextFrameIndex = 0;
|
||||
|
||||
if (mLoopCount > 0) {
|
||||
mLoopCount--;
|
||||
}
|
||||
|
||||
// If we're done, exit early.
|
||||
if (ret.animationFinished) {
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
timeout = mFrameBlender.GetFrame(nextFrameIndex)->GetTimeout();
|
||||
}
|
||||
|
||||
// Bad data
|
||||
if (!(timeout > 0)) {
|
||||
ret.animationFinished = true;
|
||||
ret.error = true;
|
||||
}
|
||||
|
||||
if (nextFrameIndex == 0) {
|
||||
ret.dirtyRect = mFirstFrameRefreshArea;
|
||||
} else {
|
||||
// Change frame
|
||||
if (!mFrameBlender.DoBlend(&ret.dirtyRect, currentFrameIndex, nextFrameIndex)) {
|
||||
// something went wrong, move on to next
|
||||
NS_WARNING("FrameAnimator::AdvanceFrame(): Compositing of frame failed");
|
||||
mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(true);
|
||||
mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();
|
||||
mCurrentAnimationFrameIndex = nextFrameIndex;
|
||||
|
||||
ret.error = true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(false);
|
||||
}
|
||||
|
||||
mCurrentAnimationFrameTime = GetCurrentImgFrameEndTime();
|
||||
|
||||
// If we can get closer to the current time by a multiple of the image's loop
|
||||
// time, we should.
|
||||
uint32_t loopTime = GetSingleLoopTime();
|
||||
if (loopTime > 0) {
|
||||
TimeDuration delay = aTime - mCurrentAnimationFrameTime;
|
||||
if (delay.ToMilliseconds() > loopTime) {
|
||||
// Explicitly use integer division to get the floor of the number of
|
||||
// loops.
|
||||
uint32_t loops = static_cast<uint32_t>(delay.ToMilliseconds()) / loopTime;
|
||||
mCurrentAnimationFrameTime += TimeDuration::FromMilliseconds(loops * loopTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Set currentAnimationFrameIndex at the last possible moment
|
||||
mCurrentAnimationFrameIndex = nextFrameIndex;
|
||||
|
||||
// If we're here, we successfully advanced the frame.
|
||||
ret.frameAdvanced = true;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
FrameAnimator::RefreshResult
|
||||
FrameAnimator::RequestRefresh(const mozilla::TimeStamp& aTime)
|
||||
{
|
||||
// only advance the frame if the current time is greater than or
|
||||
// equal to the current frame's end time.
|
||||
TimeStamp currentFrameEndTime = GetCurrentImgFrameEndTime();
|
||||
|
||||
// By default, an empty RefreshResult.
|
||||
RefreshResult ret;
|
||||
|
||||
while (currentFrameEndTime <= aTime) {
|
||||
TimeStamp oldFrameEndTime = currentFrameEndTime;
|
||||
|
||||
RefreshResult frameRes = AdvanceFrame(aTime);
|
||||
|
||||
// Accumulate our result for returning to callers.
|
||||
ret.Accumulate(frameRes);
|
||||
|
||||
currentFrameEndTime = GetCurrentImgFrameEndTime();
|
||||
|
||||
// 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 (!frameRes.frameAdvanced && (currentFrameEndTime == oldFrameEndTime)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::ResetAnimation()
|
||||
{
|
||||
mCurrentAnimationFrameIndex = 0;
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::SetDoneDecoding(bool aDone)
|
||||
{
|
||||
mDoneDecoding = aDone;
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::SetAnimationMode(uint16_t aAnimationMode)
|
||||
{
|
||||
mAnimationMode = aAnimationMode;
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::InitAnimationFrameTimeIfNecessary()
|
||||
{
|
||||
if (mCurrentAnimationFrameTime.IsNull()) {
|
||||
mCurrentAnimationFrameTime = TimeStamp::Now();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::SetAnimationFrameTime(const TimeStamp& aTime)
|
||||
{
|
||||
mCurrentAnimationFrameTime = aTime;
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::SetFirstFrameRefreshArea(const nsIntRect& aRect)
|
||||
{
|
||||
mFirstFrameRefreshArea = aRect;
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::UnionFirstFrameRefreshArea(const nsIntRect& aRect)
|
||||
{
|
||||
mFirstFrameRefreshArea.UnionRect(mFirstFrameRefreshArea, aRect);
|
||||
}
|
||||
|
||||
void
|
||||
FrameAnimator::SetLoopCount(int loopcount)
|
||||
{
|
||||
mLoopCount = loopcount;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
FrameAnimator::GetCurrentAnimationFrameIndex() const
|
||||
{
|
||||
return mCurrentAnimationFrameIndex;
|
||||
}
|
||||
|
||||
nsIntRect
|
||||
FrameAnimator::GetFirstFrameRefreshArea() const
|
||||
{
|
||||
return mFirstFrameRefreshArea;
|
||||
}
|
||||
|
||||
|
178
image/src/FrameAnimator.h
Normal file
178
image/src/FrameAnimator.h
Normal file
@ -0,0 +1,178 @@
|
||||
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_imagelib_FrameAnimator_h_
|
||||
#define mozilla_imagelib_FrameAnimator_h_
|
||||
|
||||
#include "mozilla/TimeStamp.h"
|
||||
#include "FrameBlender.h"
|
||||
#include "nsRect.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace image {
|
||||
|
||||
class FrameAnimator
|
||||
{
|
||||
public:
|
||||
FrameAnimator(FrameBlender& aBlender);
|
||||
|
||||
/**
|
||||
* Return value from RequestRefresh. Tells callers what happened in that call
|
||||
* to RequestRefresh.
|
||||
*/
|
||||
struct RefreshResult
|
||||
{
|
||||
// The dirty rectangle to be re-drawn after this RequestRefresh().
|
||||
nsIntRect dirtyRect;
|
||||
|
||||
// Whether any frame changed, and hence the dirty rect was set.
|
||||
bool frameAdvanced : 1;
|
||||
|
||||
// Whether the animation has finished playing.
|
||||
bool animationFinished : 1;
|
||||
|
||||
// Whether an error has occurred when trying to advance a frame. Note that
|
||||
// errors do not, on their own, end the animation.
|
||||
bool error : 1;
|
||||
|
||||
RefreshResult()
|
||||
: frameAdvanced(false)
|
||||
, animationFinished(false)
|
||||
, error(false)
|
||||
{}
|
||||
|
||||
void Accumulate(const RefreshResult& other)
|
||||
{
|
||||
frameAdvanced = frameAdvanced || other.frameAdvanced;
|
||||
animationFinished = animationFinished || other.animationFinished;
|
||||
error = error || other.error;
|
||||
dirtyRect = dirtyRect.Union(other.dirtyRect);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Re-evaluate what frame we're supposed to be on, and do whatever blending
|
||||
* is necessary to get us to that frame.
|
||||
*
|
||||
* Returns the result of that blending, including whether the current frame
|
||||
* changed and what the resulting dirty rectangle is.
|
||||
*/
|
||||
RefreshResult RequestRefresh(const mozilla::TimeStamp& aTime);
|
||||
|
||||
/**
|
||||
* Call when this image is finished decoding so we know that there aren't any
|
||||
* more frames coming.
|
||||
*/
|
||||
void SetDoneDecoding(bool aDone);
|
||||
|
||||
/**
|
||||
* Call when you need to re-start animating. Ensures we start from the first
|
||||
* frame.
|
||||
*/
|
||||
void ResetAnimation();
|
||||
|
||||
/**
|
||||
* Number of times to loop the image.
|
||||
* @note -1 means forever.
|
||||
*/
|
||||
void SetLoopCount(int32_t aLoopCount);
|
||||
|
||||
/**
|
||||
* The animation mode of the image.
|
||||
*
|
||||
* Constants defined in imgIContainer.idl.
|
||||
*/
|
||||
void SetAnimationMode(uint16_t aAnimationMode);
|
||||
|
||||
/**
|
||||
* Set the area to refresh when we loop around to the first frame.
|
||||
*/
|
||||
void SetFirstFrameRefreshArea(const nsIntRect& aRect);
|
||||
|
||||
/**
|
||||
* Union the area to refresh when we loop around to the first frame with this
|
||||
* rect.
|
||||
*/
|
||||
void UnionFirstFrameRefreshArea(const nsIntRect& aRect);
|
||||
|
||||
/**
|
||||
* If the animation frame time has not yet been set, set it to
|
||||
* TimeStamp::Now().
|
||||
*/
|
||||
void InitAnimationFrameTimeIfNecessary();
|
||||
|
||||
/**
|
||||
* Set the animation frame time to @aTime.
|
||||
*/
|
||||
void SetAnimationFrameTime(const TimeStamp& aTime);
|
||||
|
||||
/**
|
||||
* The current frame we're on, from 0 to (numFrames - 1).
|
||||
*/
|
||||
uint32_t GetCurrentAnimationFrameIndex() const;
|
||||
|
||||
/**
|
||||
* Get the area we refresh when we loop around to the first frame.
|
||||
*/
|
||||
nsIntRect GetFirstFrameRefreshArea() const;
|
||||
|
||||
private: // methods
|
||||
/**
|
||||
* Gets the length of a single loop of this image, in milliseconds.
|
||||
*
|
||||
* If this image is not finished decoding, is not animated, or it is animated
|
||||
* but does not loop, returns 0.
|
||||
*/
|
||||
uint32_t GetSingleLoopTime() const;
|
||||
|
||||
/**
|
||||
* 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().
|
||||
*
|
||||
* @returns a RefreshResult that shows whether the frame was successfully
|
||||
* advanced, and its resulting dirty rect.
|
||||
*/
|
||||
RefreshResult AdvanceFrame(mozilla::TimeStamp aTime);
|
||||
|
||||
/**
|
||||
* Get the time the frame we're currently displaying is supposed to end.
|
||||
*
|
||||
* In the error case, returns an "infinity" timestamp.
|
||||
*/
|
||||
mozilla::TimeStamp GetCurrentImgFrameEndTime() const;
|
||||
|
||||
private: // data
|
||||
//! Area of the first frame that needs to be redrawn on subsequent loops.
|
||||
nsIntRect mFirstFrameRefreshArea;
|
||||
|
||||
//! the time that the animation advanced to the current frame
|
||||
TimeStamp mCurrentAnimationFrameTime;
|
||||
|
||||
//! The current frame index we're on. 0 to (numFrames - 1).
|
||||
uint32_t mCurrentAnimationFrameIndex;
|
||||
|
||||
//! number of loops remaining before animation stops (-1 no stop)
|
||||
int32_t mLoopCount;
|
||||
|
||||
//! All the frames of the image, shared with our owner
|
||||
FrameBlender& mFrameBlender;
|
||||
|
||||
//! The animation mode of this image. Constants defined in imgIContainer.
|
||||
uint16_t mAnimationMode;
|
||||
|
||||
//! Whether this image is done being decoded.
|
||||
bool mDoneDecoding;
|
||||
};
|
||||
|
||||
} // namespace image
|
||||
} // namespace mozilla
|
||||
|
||||
#endif /* mozilla_imagelib_FrameAnimator_h_ */
|
@ -24,6 +24,7 @@
|
||||
#include "nsIThreadPool.h"
|
||||
#include "nsXPCOMCIDInternal.h"
|
||||
#include "nsIObserverService.h"
|
||||
#include "FrameAnimator.h"
|
||||
|
||||
#include "nsPNGDecoder.h"
|
||||
#include "nsGIFDecoder2.h"
|
||||
@ -390,7 +391,6 @@ RasterImage::RasterImage(imgStatusTracker* aStatusTracker,
|
||||
mFrameDecodeFlags(DECODE_FLAGS_DEFAULT),
|
||||
mMultipartDecodedFrame(nullptr),
|
||||
mAnim(nullptr),
|
||||
mLoopCount(-1),
|
||||
mLockCount(0),
|
||||
mDecodeCount(0),
|
||||
#ifdef DEBUG
|
||||
@ -528,135 +528,6 @@ RasterImage::Init(const char* aMimeType,
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
uint32_t
|
||||
RasterImage::GetSingleLoopTime() const
|
||||
{
|
||||
if (!mAnim) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If we aren't done decoding, we don't know the image's full play time.
|
||||
if (!mHasBeenDecoded) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// If we're not looping, a single loop time has no meaning
|
||||
if (mLoopCount == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t looptime = 0;
|
||||
for (uint32_t i = 0; i < GetNumFrames(); ++i) {
|
||||
int32_t timeout = mFrameBlender.RawGetFrame(i)->GetTimeout();
|
||||
if (timeout > 0) {
|
||||
looptime += static_cast<uint32_t>(timeout);
|
||||
} else {
|
||||
// If we have a frame that never times out, we're probably in an error
|
||||
// case, but let's handle it more gracefully.
|
||||
NS_WARNING("Negative frame timeout - how did this happen?");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
return looptime;
|
||||
}
|
||||
|
||||
bool
|
||||
RasterImage::AdvanceFrame(TimeStamp aTime, nsIntRect* aDirtyRect)
|
||||
{
|
||||
NS_ASSERTION(aTime <= TimeStamp::Now(),
|
||||
"Given time appears to be in the future");
|
||||
|
||||
uint32_t currentFrameIndex = mAnim->currentAnimationFrameIndex;
|
||||
uint32_t nextFrameIndex = mAnim->currentAnimationFrameIndex + 1;
|
||||
uint32_t timeout = 0;
|
||||
|
||||
// Figure out if we have the next full frame. This is more complicated than
|
||||
// just checking GetNumFrames() because decoders append their frames
|
||||
// before they're filled in.
|
||||
NS_ABORT_IF_FALSE(mDecoder || nextFrameIndex <= GetNumFrames(),
|
||||
"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 = (mMultipart && mBytesDecoded == 0) || !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 (GetNumFrames() == 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 = true;
|
||||
EvaluateAnimation();
|
||||
}
|
||||
|
||||
nextFrameIndex = 0;
|
||||
|
||||
if (mLoopCount > 0) {
|
||||
mLoopCount--;
|
||||
}
|
||||
|
||||
if (!mAnimating) {
|
||||
// break out early if we are actually done animating
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
timeout = mFrameBlender.GetFrame(nextFrameIndex)->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 = true;
|
||||
EvaluateAnimation();
|
||||
}
|
||||
|
||||
if (nextFrameIndex == 0) {
|
||||
*aDirtyRect = mAnim->firstFrameRefreshArea;
|
||||
} else {
|
||||
// Change frame
|
||||
if (!mFrameBlender.DoBlend(aDirtyRect, currentFrameIndex, nextFrameIndex)) {
|
||||
// something went wrong, move on to next
|
||||
NS_WARNING("RasterImage::AdvanceFrame(): Compositing of frame failed");
|
||||
mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(true);
|
||||
mAnim->currentAnimationFrameTime = GetCurrentImgFrameEndTime();
|
||||
mAnim->currentAnimationFrameIndex = nextFrameIndex;
|
||||
return false;
|
||||
}
|
||||
|
||||
mFrameBlender.RawGetFrame(nextFrameIndex)->SetCompositingFailed(false);
|
||||
}
|
||||
|
||||
mAnim->currentAnimationFrameTime = GetCurrentImgFrameEndTime();
|
||||
|
||||
// If we can get closer to the current time by a multiple of the image's loop
|
||||
// time, we should.
|
||||
uint32_t loopTime = GetSingleLoopTime();
|
||||
if (loopTime > 0) {
|
||||
TimeDuration delay = aTime - mAnim->currentAnimationFrameTime;
|
||||
if (delay.ToMilliseconds() > loopTime) {
|
||||
// Explicitly use integer division to get the floor of the number of
|
||||
// loops.
|
||||
uint32_t loops = static_cast<uint32_t>(delay.ToMilliseconds()) / loopTime;
|
||||
mAnim->currentAnimationFrameTime += TimeDuration::FromMilliseconds(loops * loopTime);
|
||||
}
|
||||
}
|
||||
|
||||
// Set currentAnimationFrameIndex at the last possible moment
|
||||
mAnim->currentAnimationFrameIndex = nextFrameIndex;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
// [notxpcom] void requestRefresh ([const] in TimeStamp aTime);
|
||||
NS_IMETHODIMP_(void)
|
||||
@ -668,34 +539,12 @@ RasterImage::RequestRefresh(const mozilla::TimeStamp& aTime)
|
||||
|
||||
EvaluateAnimation();
|
||||
|
||||
// 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 (!didAdvance && (currentFrameEndTime == oldFrameEndTime)) {
|
||||
break;
|
||||
}
|
||||
FrameAnimator::RefreshResult res;
|
||||
if (mAnim) {
|
||||
res = mAnim->RequestRefresh(aTime);
|
||||
}
|
||||
|
||||
if (frameAdvanced) {
|
||||
if (res.frameAdvanced) {
|
||||
// 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).
|
||||
@ -708,7 +557,12 @@ RasterImage::RequestRefresh(const mozilla::TimeStamp& aTime)
|
||||
// Explicitly call this on mStatusTracker so we're sure to not interfere
|
||||
// with the decoding process
|
||||
if (mStatusTracker)
|
||||
mStatusTracker->FrameChanged(&dirtyRect);
|
||||
mStatusTracker->FrameChanged(&res.dirtyRect);
|
||||
}
|
||||
|
||||
if (res.animationFinished) {
|
||||
mAnimationFinished = true;
|
||||
EvaluateAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@ -833,34 +687,11 @@ uint32_t
|
||||
RasterImage::GetCurrentImgFrameIndex() const
|
||||
{
|
||||
if (mAnim)
|
||||
return mAnim->currentAnimationFrameIndex;
|
||||
return mAnim->GetCurrentAnimationFrameIndex();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
TimeStamp
|
||||
RasterImage::GetCurrentImgFrameEndTime() const
|
||||
{
|
||||
imgFrame* currentFrame = mFrameBlender.RawGetFrame(mAnim->currentAnimationFrameIndex);
|
||||
TimeStamp currentFrameTime = mAnim->currentAnimationFrameTime;
|
||||
int64_t 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(static_cast<double>(UINT64_MAX));
|
||||
}
|
||||
|
||||
TimeDuration durationOfTimeout =
|
||||
TimeDuration::FromMilliseconds(static_cast<double>(timeout));
|
||||
TimeStamp currentFrameEndTime = currentFrameTime + durationOfTimeout;
|
||||
|
||||
return currentFrameEndTime;
|
||||
}
|
||||
|
||||
imgFrame*
|
||||
RasterImage::GetCurrentImgFrame()
|
||||
{
|
||||
@ -1230,6 +1061,30 @@ RasterImage::OutOfProcessSizeOfDecoded() const
|
||||
NULL);
|
||||
}
|
||||
|
||||
void
|
||||
RasterImage::EnsureAnimExists()
|
||||
{
|
||||
if (!mAnim) {
|
||||
|
||||
// Create the animation context
|
||||
mAnim = new FrameAnimator(mFrameBlender);
|
||||
|
||||
// We don't support discarding animated images (See bug 414259).
|
||||
// Lock the image and throw away the key.
|
||||
//
|
||||
// Note that this is inefficient, since we could get rid of the source
|
||||
// data too. However, doing this is actually hard, because we're probably
|
||||
// calling ensureAnimExists mid-decode, and thus we're decoding out of
|
||||
// the source buffer. Since we're going to fix this anyway later, and
|
||||
// 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.
|
||||
CurrentStatusTracker().RecordImageIsAnimated();
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
RasterImage::InternalAddFrameHelper(uint32_t framenum, imgFrame *aFrame,
|
||||
uint8_t **imageData, uint32_t *imageLength,
|
||||
@ -1310,15 +1165,13 @@ RasterImage::InternalAddFrame(uint32_t framenum,
|
||||
int32_t frameDisposalMethod = mFrameBlender.RawGetFrame(0)->GetFrameDisposalMethod();
|
||||
if (frameDisposalMethod == FrameBlender::kDisposeClear ||
|
||||
frameDisposalMethod == FrameBlender::kDisposeRestorePrevious)
|
||||
mAnim->firstFrameRefreshArea = mFrameBlender.RawGetFrame(0)->GetRect();
|
||||
mAnim->SetFirstFrameRefreshArea(mFrameBlender.RawGetFrame(0)->GetRect());
|
||||
}
|
||||
|
||||
// Calculate firstFrameRefreshArea
|
||||
// Some gifs are huge but only have a small area that they animate
|
||||
// We only need to refresh that small area when Frame 0 comes around again
|
||||
nsIntRect frameRect = frame->GetRect();
|
||||
mAnim->firstFrameRefreshArea.UnionRect(mAnim->firstFrameRefreshArea,
|
||||
frameRect);
|
||||
mAnim->UnionFirstFrameRefreshArea(frame->GetRect());
|
||||
|
||||
rv = InternalAddFrameHelper(framenum, frame.forget(), imageData, imageLength,
|
||||
paletteData, paletteLength, aRetFrame);
|
||||
@ -1541,9 +1394,22 @@ RasterImage::DecodingComplete()
|
||||
}
|
||||
}
|
||||
|
||||
if (mAnim) {
|
||||
mAnim->SetDoneDecoding(true);
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
NS_IMETHODIMP
|
||||
RasterImage::SetAnimationMode(uint16_t aAnimationMode)
|
||||
{
|
||||
if (mAnim) {
|
||||
mAnim->SetAnimationMode(aAnimationMode);
|
||||
}
|
||||
return SetAnimationModeInternal(aAnimationMode);
|
||||
}
|
||||
|
||||
//******************************************************************************
|
||||
/* void StartAnimation () */
|
||||
nsresult
|
||||
@ -1565,9 +1431,7 @@ RasterImage::StartAnimation()
|
||||
|
||||
// We need to set the time that this initial frame was first displayed, as
|
||||
// this is used in AdvanceFrame().
|
||||
if (mAnim->currentAnimationFrameTime.IsNull()) {
|
||||
mAnim->currentAnimationFrameTime = TimeStamp::Now();
|
||||
}
|
||||
mAnim->InitAnimationFrameTimeIfNecessary();
|
||||
}
|
||||
|
||||
return NS_OK;
|
||||
@ -1595,7 +1459,7 @@ RasterImage::ResetAnimation()
|
||||
return NS_ERROR_FAILURE;
|
||||
|
||||
if (mAnimationMode == kDontAnimMode ||
|
||||
!mAnim || mAnim->currentAnimationFrameIndex == 0)
|
||||
!mAnim || mAnim->GetCurrentAnimationFrameIndex() == 0)
|
||||
return NS_OK;
|
||||
|
||||
mAnimationFinished = false;
|
||||
@ -1604,8 +1468,10 @@ RasterImage::ResetAnimation()
|
||||
StopAnimation();
|
||||
|
||||
mFrameBlender.ResetAnimation();
|
||||
if (mAnim) {
|
||||
mAnim->ResetAnimation();
|
||||
}
|
||||
|
||||
mAnim->currentAnimationFrameIndex = 0;
|
||||
UpdateImageContainer();
|
||||
|
||||
// Note - We probably want to kick off a redecode somewhere around here when
|
||||
@ -1613,7 +1479,8 @@ RasterImage::ResetAnimation()
|
||||
|
||||
// Update display if we were animating before
|
||||
if (mAnimating && mStatusTracker) {
|
||||
mStatusTracker->FrameChanged(&(mAnim->firstFrameRefreshArea));
|
||||
nsIntRect rect = mAnim->GetFirstFrameRefreshArea();
|
||||
mStatusTracker->FrameChanged(&rect);
|
||||
}
|
||||
|
||||
if (ShouldAnimate()) {
|
||||
@ -1635,7 +1502,7 @@ RasterImage::SetAnimationStartTime(const mozilla::TimeStamp& aTime)
|
||||
if (mError || mAnimating || !mAnim)
|
||||
return;
|
||||
|
||||
mAnim->currentAnimationFrameTime = aTime;
|
||||
mAnim->SetAnimationFrameTime(aTime);
|
||||
}
|
||||
|
||||
NS_IMETHODIMP_(float)
|
||||
@ -1644,7 +1511,7 @@ RasterImage::GetFrameIndex(uint32_t aWhichFrame)
|
||||
MOZ_ASSERT(aWhichFrame <= FRAME_MAX_VALUE, "Invalid argument");
|
||||
return (aWhichFrame == FRAME_FIRST || !mAnim)
|
||||
? 0.0f
|
||||
: mAnim->currentAnimationFrameIndex;
|
||||
: mAnim->GetCurrentAnimationFrameIndex();
|
||||
}
|
||||
|
||||
void
|
||||
@ -1653,11 +1520,9 @@ RasterImage::SetLoopCount(int32_t aLoopCount)
|
||||
if (mError)
|
||||
return;
|
||||
|
||||
// -1 infinite
|
||||
// 0 no looping, one iteration
|
||||
// 1 one loop, two iterations
|
||||
// ...
|
||||
mLoopCount = aLoopCount;
|
||||
if (mAnim) {
|
||||
mAnim->SetLoopCount(aLoopCount);
|
||||
}
|
||||
}
|
||||
|
||||
nsresult
|
||||
@ -1929,6 +1794,10 @@ RasterImage::OnNewSourceData()
|
||||
mWantFullDecode = true;
|
||||
mDecodeRequest = nullptr;
|
||||
|
||||
if (mAnim) {
|
||||
mAnim->SetDoneDecoding(false);
|
||||
}
|
||||
|
||||
// We always need the size first.
|
||||
rv = InitDecoder(/* aDoSizeDecode = */ true);
|
||||
CONTAINER_ENSURE_SUCCESS(rv);
|
||||
|
@ -120,10 +120,6 @@ class nsIThreadPool;
|
||||
* @par
|
||||
* The mAnim structure has members only needed for animated images, so
|
||||
* it's not allocated until the second frame is added.
|
||||
*
|
||||
* @note
|
||||
* mAnimationMode and mLoopCount are not in the mAnim structure because
|
||||
* they have public setters.
|
||||
*/
|
||||
|
||||
class ScaleRequest;
|
||||
@ -137,6 +133,7 @@ class Image;
|
||||
namespace image {
|
||||
|
||||
class Decoder;
|
||||
class FrameAnimator;
|
||||
|
||||
class RasterImage : public ImageResource
|
||||
, public nsIProperties
|
||||
@ -324,20 +321,6 @@ private:
|
||||
|
||||
nsresult OnImageDataCompleteCore(nsIRequest* aRequest, nsISupports*, nsresult aStatus);
|
||||
|
||||
struct Anim
|
||||
{
|
||||
//! Area of the first frame that needs to be redrawn on subsequent loops.
|
||||
nsIntRect firstFrameRefreshArea;
|
||||
uint32_t currentAnimationFrameIndex; // 0 to numFrames-1
|
||||
|
||||
// the time that the animation advanced to the current frame
|
||||
TimeStamp currentAnimationFrameTime;
|
||||
|
||||
Anim() :
|
||||
currentAnimationFrameIndex(0)
|
||||
{}
|
||||
};
|
||||
|
||||
/**
|
||||
* Each RasterImage has a pointer to one or zero heap-allocated
|
||||
* DecodeRequests.
|
||||
@ -545,33 +528,6 @@ private:
|
||||
uint32_t aFlags,
|
||||
gfxImageSurface **_retval);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
/**
|
||||
* Gets the length of a single loop of this image, in milliseconds.
|
||||
*
|
||||
* If this image is not finished decoding, is not animated, or it is animated
|
||||
* but does not loop, returns 0.
|
||||
*/
|
||||
uint32_t GetSingleLoopTime() const;
|
||||
|
||||
/**
|
||||
* Deletes and nulls out the frame in mFrames[framenum].
|
||||
*
|
||||
@ -587,33 +543,11 @@ private:
|
||||
imgFrame* GetDrawableImgFrame(uint32_t framenum);
|
||||
imgFrame* GetCurrentImgFrame();
|
||||
uint32_t GetCurrentImgFrameIndex() const;
|
||||
mozilla::TimeStamp GetCurrentImgFrameEndTime() const;
|
||||
|
||||
size_t SizeOfDecodedWithComputedFallbackIfHeap(gfxASurface::MemoryLocation aLocation,
|
||||
mozilla::MallocSizeOf aMallocSizeOf) const;
|
||||
|
||||
inline void EnsureAnimExists()
|
||||
{
|
||||
if (!mAnim) {
|
||||
|
||||
// Create the animation context
|
||||
mAnim = new Anim();
|
||||
|
||||
// We don't support discarding animated images (See bug 414259).
|
||||
// Lock the image and throw away the key.
|
||||
//
|
||||
// Note that this is inefficient, since we could get rid of the source
|
||||
// data too. However, doing this is actually hard, because we're probably
|
||||
// calling ensureAnimExists mid-decode, and thus we're decoding out of
|
||||
// the source buffer. Since we're going to fix this anyway later, and
|
||||
// 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.
|
||||
CurrentStatusTracker().RecordImageIsAnimated();
|
||||
}
|
||||
}
|
||||
void EnsureAnimExists();
|
||||
|
||||
nsresult InternalAddFrameHelper(uint32_t framenum, imgFrame *frame,
|
||||
uint8_t **imageData, uint32_t *imageLength,
|
||||
@ -671,10 +605,7 @@ private: // data
|
||||
// IMPORTANT: if you use mAnim in a method, call EnsureImageIsDecoded() first to ensure
|
||||
// that the frames actually exist (they may have been discarded to save memory, or
|
||||
// we maybe decoding on draw).
|
||||
RasterImage::Anim* mAnim;
|
||||
|
||||
//! # loops remaining before animation stops (-1 no stop)
|
||||
int32_t mLoopCount;
|
||||
FrameAnimator* mAnim;
|
||||
|
||||
// Discard members
|
||||
uint32_t mLockCount;
|
||||
@ -791,10 +722,6 @@ inline NS_IMETHODIMP RasterImage::GetAnimationMode(uint16_t *aAnimationMode) {
|
||||
return GetAnimationModeInternal(aAnimationMode);
|
||||
}
|
||||
|
||||
inline NS_IMETHODIMP RasterImage::SetAnimationMode(uint16_t aAnimationMode) {
|
||||
return SetAnimationModeInternal(aAnimationMode);
|
||||
}
|
||||
|
||||
// Asynchronous Decode Requestor
|
||||
//
|
||||
// We use this class when someone calls requestDecode() from within a decode
|
||||
|
@ -509,7 +509,7 @@ nsresult imgFrame::ImageUpdated(const nsIntRect &aUpdateRect)
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
bool imgFrame::GetIsDirty()
|
||||
bool imgFrame::GetIsDirty() const
|
||||
{
|
||||
MutexAutoLock lock(mDirtyMutex);
|
||||
return mDirty;
|
||||
@ -796,8 +796,11 @@ void imgFrame::SetBlendMethod(int32_t aBlendMethod)
|
||||
mBlendMethod = (int8_t)aBlendMethod;
|
||||
}
|
||||
|
||||
// This can be called from any thread.
|
||||
bool imgFrame::ImageComplete() const
|
||||
{
|
||||
MutexAutoLock lock(mDirtyMutex);
|
||||
|
||||
return mDecoded.IsEqualInterior(nsIntRect(mOffset, mSize));
|
||||
}
|
||||
|
||||
|
@ -41,7 +41,7 @@ public:
|
||||
uint32_t aImageFlags = imgIContainer::FLAG_NONE);
|
||||
|
||||
nsresult ImageUpdated(const nsIntRect &aUpdateRect);
|
||||
bool GetIsDirty();
|
||||
bool GetIsDirty() const;
|
||||
|
||||
nsIntRect GetRect() const;
|
||||
gfxASurface::gfxImageFormat GetFormat() const;
|
||||
@ -152,7 +152,7 @@ private: // data
|
||||
|
||||
nsIntRect mDecoded;
|
||||
|
||||
mozilla::Mutex mDirtyMutex;
|
||||
mutable mozilla::Mutex mDirtyMutex;
|
||||
|
||||
// The palette and image data for images that are paletted, since Cairo
|
||||
// doesn't support these images.
|
||||
|
@ -17,6 +17,7 @@ CPP_SOURCES += [
|
||||
'ClippedImage.cpp',
|
||||
'Decoder.cpp',
|
||||
'DiscardTracker.cpp',
|
||||
'FrameAnimator.cpp',
|
||||
'FrameBlender.cpp',
|
||||
'FrameSequence.cpp',
|
||||
'FrozenImage.cpp',
|
||||
|
Loading…
Reference in New Issue
Block a user