mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
267 lines
7.4 KiB
C++
267 lines
7.4 KiB
C++
/* -*- 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 "FrameBlender.h"
|
|
|
|
#include "imgIContainer.h"
|
|
|
|
using namespace mozilla::image;
|
|
using namespace mozilla;
|
|
|
|
FrameAnimator::FrameAnimator(FrameBlender& aFrameBlender,
|
|
uint16_t aAnimationMode)
|
|
: mCurrentAnimationFrameIndex(0)
|
|
, mLoopCounter(-1)
|
|
, mFrameBlender(aFrameBlender)
|
|
, mAnimationMode(aAnimationMode)
|
|
, mDoneDecoding(false)
|
|
{
|
|
}
|
|
|
|
int32_t
|
|
FrameAnimator::GetSingleLoopTime() const
|
|
{
|
|
// If we aren't done decoding, we don't know the image's full play time.
|
|
if (!mDoneDecoding) {
|
|
return -1;
|
|
}
|
|
|
|
// If we're not looping, a single loop time has no meaning
|
|
if (mAnimationMode != imgIContainer::kNormalAnimMode) {
|
|
return -1;
|
|
}
|
|
|
|
uint32_t looptime = 0;
|
|
for (uint32_t i = 0; i < mFrameBlender.GetNumFrames(); ++i) {
|
|
int32_t timeout = mFrameBlender.GetTimeoutForFrame(i);
|
|
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 -1;
|
|
}
|
|
}
|
|
|
|
return looptime;
|
|
}
|
|
|
|
TimeStamp
|
|
FrameAnimator::GetCurrentImgFrameEndTime() const
|
|
{
|
|
TimeStamp currentFrameTime = mCurrentAnimationFrameTime;
|
|
int32_t timeout = mFrameBlender.GetTimeoutForFrame(mCurrentAnimationFrameIndex);
|
|
|
|
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. We use
|
|
// one year in the future as the sentinel because it works with the loop
|
|
// in RequestRefresh() below.
|
|
// XXX(seth): It'd be preferable to make our logic work correctly with
|
|
// negative timeouts.
|
|
return TimeStamp::NowLoRes() +
|
|
TimeDuration::FromMilliseconds(31536000.0);
|
|
}
|
|
|
|
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;
|
|
int32_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 canDisplay = mDoneDecoding ||
|
|
(mFrameBlender.RawGetFrame(nextFrameIndex) &&
|
|
mFrameBlender.RawGetFrame(nextFrameIndex)->ImageComplete());
|
|
|
|
if (!canDisplay) {
|
|
// 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;
|
|
}
|
|
|
|
// 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 an animation loop...
|
|
|
|
// If we are not looping forever, initialize the loop counter
|
|
if (mLoopCounter < 0 && mFrameBlender.GetLoopCount() >= 0) {
|
|
mLoopCounter = mFrameBlender.GetLoopCount();
|
|
}
|
|
|
|
// If animation mode is "loop once", or we're at end of loop counter, it's time to stop animating
|
|
if (mAnimationMode == imgIContainer::kLoopOnceAnimMode || mLoopCounter == 0) {
|
|
ret.animationFinished = true;
|
|
}
|
|
|
|
nextFrameIndex = 0;
|
|
|
|
if (mLoopCounter > 0) {
|
|
mLoopCounter--;
|
|
}
|
|
|
|
// If we're done, exit early.
|
|
if (ret.animationFinished) {
|
|
return ret;
|
|
}
|
|
}
|
|
|
|
timeout = mFrameBlender.GetTimeoutForFrame(nextFrameIndex);
|
|
|
|
// 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);
|
|
}
|
|
|
|
uint32_t
|
|
FrameAnimator::GetCurrentAnimationFrameIndex() const
|
|
{
|
|
return mCurrentAnimationFrameIndex;
|
|
}
|
|
|
|
nsIntRect
|
|
FrameAnimator::GetFirstFrameRefreshArea() const
|
|
{
|
|
return mFirstFrameRefreshArea;
|
|
}
|
|
|
|
|