blob: a236bc555cc67cc03238212d285d90cfeb122c7a [file] [log] [blame]
/*
* Copyright (C) 2006 Samuel Weinig (sam.weinig@gmail.com)
* Copyright (C) 2004, 2005, 2006, 2008 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "platform/graphics/BitmapImage.h"
#include "base/metrics/histogram_macros.h"
#include "platform/Timer.h"
#include "platform/geometry/FloatRect.h"
#include "platform/graphics/BitmapImageMetrics.h"
#include "platform/graphics/DeferredImageDecoder.h"
#include "platform/graphics/ImageObserver.h"
#include "platform/graphics/StaticBitmapImage.h"
#include "platform/graphics/paint/PaintCanvas.h"
#include "platform/graphics/paint/PaintFlags.h"
#include "platform/graphics/paint/PaintImage.h"
#include "platform/graphics/skia/SkiaUtils.h"
#include "platform/instrumentation/PlatformInstrumentation.h"
#include "platform/instrumentation/tracing/TraceEvent.h"
#include "platform/runtime_enabled_features.h"
#include "platform/scheduler/child/web_scheduler.h"
#include "platform/wtf/Assertions.h"
#include "platform/wtf/PtrUtil.h"
#include "platform/wtf/RefPtr.h"
#include "platform/wtf/text/WTFString.h"
#include "public/web/WebSettings.h"
namespace blink {
int GetRepetitionCountWithPolicyOverride(int actual_count,
ImageAnimationPolicy policy) {
switch (policy) {
case kImageAnimationPolicyAllowed:
// Default policy, no count override.
return actual_count;
case kImageAnimationPolicyAnimateOnce:
// Only a single loop allowed.
return kAnimationLoopOnce;
case kImageAnimationPolicyNoAnimation:
// Dont animate.
return kAnimationNone;
}
NOTREACHED();
return actual_count;
}
BitmapImage::BitmapImage(ImageObserver* observer, bool is_multipart)
: Image(observer, is_multipart),
current_frame_index_(0),
cached_frame_index_(0),
animation_policy_(kImageAnimationPolicyAllowed),
animation_finished_(false),
all_data_received_(false),
have_size_(false),
size_available_(false),
have_frame_count_(false),
repetition_count_status_(kUnknown),
repetition_count_(kAnimationNone),
repetitions_complete_(0),
desired_frame_start_time_(0),
frame_count_(0),
task_runner_(Platform::Current()
->CurrentThread()
->Scheduler()
->CompositorTaskRunner()),
weak_factory_(this) {}
BitmapImage::~BitmapImage() {
StopAnimation();
}
bool BitmapImage::CurrentFrameHasSingleSecurityOrigin() const {
return true;
}
void BitmapImage::DestroyDecodedData() {
cached_frame_ = PaintImage();
for (size_t i = 0; i < frames_.size(); ++i)
frames_[i].Clear(true);
if (decoder_)
decoder_->ClearCacheExceptFrame(kNotFound);
NotifyMemoryChanged();
}
scoped_refptr<SharedBuffer> BitmapImage::Data() {
return decoder_ ? decoder_->Data() : nullptr;
}
void BitmapImage::NotifyMemoryChanged() {
if (GetImageObserver())
GetImageObserver()->DecodedSizeChangedTo(this, TotalFrameBytes());
}
size_t BitmapImage::TotalFrameBytes() {
size_t total_bytes = 0;
for (size_t i = 0; i < frames_.size(); ++i)
total_bytes += frames_[i].frame_bytes_;
return total_bytes;
}
PaintImage BitmapImage::CreateAndCacheFrame(size_t index) {
sk_sp<PaintImageGenerator> generator =
decoder_ ? decoder_->CreateGenerator(index) : nullptr;
if (!generator)
return PaintImage();
size_t num_frames = FrameCount();
if (frames_.size() < num_frames)
frames_.Grow(num_frames);
frames_[index].orientation_ = decoder_->OrientationAtIndex(index);
frames_[index].have_metadata_ = true;
frames_[index].is_complete_ = decoder_->FrameIsReceivedAtIndex(index);
if (RepetitionCount() != kAnimationNone)
frames_[index].duration_ = decoder_->FrameDurationAtIndex(index);
frames_[index].has_alpha_ = decoder_->FrameHasAlphaAtIndex(index);
frames_[index].frame_bytes_ =
decoder_->Size().Area() * sizeof(ImageFrame::PixelData);
auto completion_state = all_data_received_
? PaintImage::CompletionState::DONE
: PaintImage::CompletionState::PARTIALLY_DONE;
auto builder =
CreatePaintImageBuilder()
.set_paint_image_generator(std::move(generator))
.set_frame_index(index)
.set_repetition_count(GetRepetitionCountWithPolicyOverride(
repetition_count_, animation_policy_))
.set_completion_state(completion_state)
.set_reset_animation_sequence_id(reset_animation_sequence_id_);
// We are caching frame snapshots. This is OK even for partially decoded
// frames, as they are cleared by dataChanged() when new data arrives.
cached_frame_ = builder.TakePaintImage();
cached_frame_index_ = index;
// Create the SkImage backing for this PaintImage here to ensure that copies
// of the PaintImage share the same SkImage. Skia's caching of the decoded
// output of this image is tied to the lifetime of the SkImage. So we create
// the SkImage here and cache the PaintImage to keep the decode alive in
// skia's cache.
cached_frame_.GetSkImage();
NotifyMemoryChanged();
return cached_frame_;
}
void BitmapImage::UpdateSize() const {
if (!size_available_ || have_size_ || !decoder_)
return;
size_ = decoder_->FrameSizeAtIndex(0);
if (decoder_->OrientationAtIndex(0).UsesWidthAsHeight())
size_respecting_orientation_ = size_.TransposedSize();
else
size_respecting_orientation_ = size_;
have_size_ = true;
}
IntSize BitmapImage::Size() const {
UpdateSize();
return size_;
}
IntSize BitmapImage::SizeRespectingOrientation() const {
UpdateSize();
return size_respecting_orientation_;
}
bool BitmapImage::GetHotSpot(IntPoint& hot_spot) const {
return decoder_ && decoder_->HotSpot(hot_spot);
}
Image::SizeAvailability BitmapImage::SetData(scoped_refptr<SharedBuffer> data,
bool all_data_received) {
if (!data)
return kSizeAvailable;
int length = data->size();
if (!length)
return kSizeAvailable;
if (decoder_) {
decoder_->SetData(std::move(data), all_data_received);
return DataChanged(all_data_received);
}
bool has_enough_data = ImageDecoder::HasSufficientDataToSniffImageType(*data);
decoder_ = DeferredImageDecoder::Create(std::move(data), all_data_received,
ImageDecoder::kAlphaPremultiplied,
ColorBehavior::Tag());
// If we had enough data but couldn't create a decoder, it implies a decode
// failure.
if (has_enough_data && !decoder_)
return kSizeAvailable;
return DataChanged(all_data_received);
}
Image::SizeAvailability BitmapImage::DataChanged(bool all_data_received) {
TRACE_EVENT0("blink", "BitmapImage::dataChanged");
// Clear all partially-decoded frames. For most image formats, there is only
// one frame, but at least GIF and ICO can have more. With GIFs, the frames
// come in order and we ask to decode them in order, waiting to request a
// subsequent frame until the prior one is complete. Given that we clear
// incomplete frames here, this means there is at most one incomplete frame
// (even if we use destroyDecodedData() -- since it doesn't reset the
// metadata), and it is after all the complete frames.
//
// With ICOs, on the other hand, we may ask for arbitrary frames at
// different times (e.g. because we're displaying a higher-resolution image
// in the content area and using a lower-resolution one for the favicon),
// and the frames aren't even guaranteed to appear in the file in the same
// order as in the directory, so an arbitrary number of the frames might be
// incomplete (if we ask for frames for which we've not yet reached the
// start of the frame data), and any or none of them might be the particular
// frame affected by appending new data here. Thus we have to clear all the
// incomplete frames to be safe.
for (size_t i = 0; i < frames_.size(); ++i) {
// NOTE: Don't call frameIsCompleteAtIndex() here, that will try to
// decode any uncached (i.e. never-decoded or
// cleared-on-a-previous-pass) frames!
if (frames_[i].have_metadata_ && !frames_[i].is_complete_) {
frames_[i].Clear(true);
if (i == cached_frame_index_)
cached_frame_ = PaintImage();
}
}
// If the image is being animated by the compositor, clear the cached_frame_
// on a data update to push it to the compositor. Since we never advance the
// animation here, the |cached_frame_index_| is always the first frame and the
// |cached_frame_| might have not have been cleared in the loop above.
if (RuntimeEnabledFeatures::CompositorImageAnimationsEnabled()
&& MaybeAnimated())
cached_frame_ = PaintImage();
// Feed all the data we've seen so far to the image decoder.
all_data_received_ = all_data_received;
have_frame_count_ = false;
// Reset the cached image if the metadata has changed.
if (cached_frame_) {
PaintImage::CompletionState new_completion_state =
all_data_received_ ? PaintImage::CompletionState::DONE
: PaintImage::CompletionState::PARTIALLY_DONE;
const bool metadata_changed =
cached_frame_.repetition_count() != RepetitionCount() ||
cached_frame_.completion_state() != new_completion_state;
if (metadata_changed)
cached_frame_ = PaintImage();
}
return IsSizeAvailable() ? kSizeAvailable : kSizeUnavailable;
}
bool BitmapImage::HasColorProfile() const {
return decoder_ && decoder_->HasEmbeddedColorSpace();
}
String BitmapImage::FilenameExtension() const {
return decoder_ ? decoder_->FilenameExtension() : String();
}
void BitmapImage::Draw(
PaintCanvas* canvas,
const PaintFlags& flags,
const FloatRect& dst_rect,
const FloatRect& src_rect,
RespectImageOrientationEnum should_respect_image_orientation,
ImageClampingMode clamp_mode,
ImageDecodingMode decode_mode) {
TRACE_EVENT0("skia", "BitmapImage::draw");
PaintImage image = PaintImageForCurrentFrame();
if (!image)
return; // It's too early and we don't have an image yet.
auto paint_image_decoding_mode = ToPaintImageDecodingMode(decode_mode);
if (image.decoding_mode() != paint_image_decoding_mode) {
image = PaintImageBuilder::WithCopy(std::move(image))
.set_decoding_mode(paint_image_decoding_mode)
.TakePaintImage();
}
FloatRect adjusted_src_rect = src_rect;
adjusted_src_rect.Intersect(SkRect::MakeWH(image.width(), image.height()));
if (adjusted_src_rect.IsEmpty() || dst_rect.IsEmpty())
return; // Nothing to draw.
ImageOrientation orientation = kDefaultImageOrientation;
if (should_respect_image_orientation == kRespectImageOrientation)
orientation = FrameOrientationAtIndex(current_frame_index_);
PaintCanvasAutoRestore auto_restore(canvas, false);
FloatRect adjusted_dst_rect = dst_rect;
if (orientation != kDefaultImageOrientation) {
canvas->save();
// ImageOrientation expects the origin to be at (0, 0)
canvas->translate(adjusted_dst_rect.X(), adjusted_dst_rect.Y());
adjusted_dst_rect.SetLocation(FloatPoint());
canvas->concat(AffineTransformToSkMatrix(
orientation.TransformFromDefault(adjusted_dst_rect.Size())));
if (orientation.UsesWidthAsHeight()) {
// The destination rect will have its width and height already reversed
// for the orientation of the image, as it was needed for page layout, so
// we need to reverse it back here.
adjusted_dst_rect =
FloatRect(adjusted_dst_rect.X(), adjusted_dst_rect.Y(),
adjusted_dst_rect.Height(), adjusted_dst_rect.Width());
}
}
uint32_t unique_id = image.GetSkImage()->uniqueID();
bool is_lazy_generated = image.IsLazyGenerated();
canvas->drawImageRect(std::move(image), adjusted_src_rect, adjusted_dst_rect,
&flags,
WebCoreClampingModeToSkiaRectConstraint(clamp_mode));
if (is_lazy_generated)
PlatformInstrumentation::DidDrawLazyPixelRef(unique_id);
StartAnimation();
}
size_t BitmapImage::FrameCount() {
if (!have_frame_count_) {
frame_count_ = decoder_ ? decoder_->FrameCount() : 0;
have_frame_count_ = frame_count_ > 0;
}
return frame_count_;
}
static inline bool HasVisibleImageSize(IntSize size) {
return (size.Width() > 1 || size.Height() > 1);
}
bool BitmapImage::IsSizeAvailable() {
if (size_available_)
return true;
size_available_ = decoder_ && decoder_->IsSizeAvailable();
if (size_available_ && HasVisibleImageSize(Size())) {
BitmapImageMetrics::CountDecodedImageType(decoder_->FilenameExtension());
if (decoder_->FilenameExtension() == "jpg") {
BitmapImageMetrics::CountImageOrientation(
decoder_->OrientationAtIndex(0).Orientation());
}
}
return size_available_;
}
PaintImage BitmapImage::FrameAtIndex(size_t index) {
if (index >= FrameCount())
return PaintImage();
if (index == cached_frame_index_ && cached_frame_)
return cached_frame_;
return CreateAndCacheFrame(index);
}
bool BitmapImage::FrameIsReceivedAtIndex(size_t index) const {
if (index < frames_.size() && frames_[index].have_metadata_ &&
frames_[index].is_complete_)
return true;
return decoder_ && decoder_->FrameIsReceivedAtIndex(index);
}
TimeDelta BitmapImage::FrameDurationAtIndex(size_t index) const {
if (index < frames_.size() && frames_[index].have_metadata_)
return frames_[index].duration_;
if (!decoder_)
return TimeDelta();
return decoder_->FrameDurationAtIndex(index);
}
PaintImage BitmapImage::PaintImageForCurrentFrame() {
return FrameAtIndex(current_frame_index_);
}
scoped_refptr<Image> BitmapImage::ImageForDefaultFrame() {
if (FrameCount() > 1) {
PaintImage paint_image = FrameAtIndex(PaintImage::kDefaultFrameIndex);
if (paint_image.ShouldAnimate()) {
// To prevent the compositor from animating this image, we set the
// animation count to kAnimationNone. This makes the image essentially
// static.
paint_image = PaintImageBuilder::WithCopy(std::move(paint_image))
.set_repetition_count(kAnimationNone)
.TakePaintImage();
}
return StaticBitmapImage::Create(std::move(paint_image));
}
return Image::ImageForDefaultFrame();
}
bool BitmapImage::FrameHasAlphaAtIndex(size_t index) {
if (frames_.size() <= index)
return true;
if (frames_[index].have_metadata_ && !frames_[index].has_alpha_)
return false;
// has_alpha may change after have_metadata_ is set to true, so always ask
// |decoder_| for the value if the cached value is the default value.
bool has_alpha = !decoder_ || decoder_->FrameHasAlphaAtIndex(index);
if (frames_[index].have_metadata_)
frames_[index].has_alpha_ = has_alpha;
return has_alpha;
}
bool BitmapImage::CurrentFrameKnownToBeOpaque(MetadataMode metadata_mode) {
if (metadata_mode == kPreCacheMetadata) {
// frameHasAlphaAtIndex() conservatively returns false for uncached frames.
// To increase the chance of an accurate answer, pre-cache the current frame
// metadata.
FrameAtIndex(current_frame_index_);
}
return !FrameHasAlphaAtIndex(current_frame_index_);
}
bool BitmapImage::CurrentFrameIsComplete() {
return FrameIsReceivedAtIndex(current_frame_index_);
}
bool BitmapImage::CurrentFrameIsLazyDecoded() {
// BitmapImage supports only lazy generated images.
return true;
}
ImageOrientation BitmapImage::CurrentFrameOrientation() {
return FrameOrientationAtIndex(current_frame_index_);
}
ImageOrientation BitmapImage::FrameOrientationAtIndex(size_t index) {
if (!decoder_ || frames_.size() <= index)
return kDefaultImageOrientation;
if (frames_[index].have_metadata_)
return frames_[index].orientation_;
return decoder_->OrientationAtIndex(index);
}
int BitmapImage::RepetitionCount() {
if ((repetition_count_status_ == kUnknown) ||
((repetition_count_status_ == kUncertain) && all_data_received_)) {
// 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.
repetition_count_ = decoder_ ? decoder_->RepetitionCount() : kAnimationNone;
repetition_count_status_ =
(all_data_received_ || repetition_count_ == kAnimationNone)
? kCertain
: kUncertain;
}
return repetition_count_;
}
bool BitmapImage::ShouldAnimate() {
if (RuntimeEnabledFeatures::CompositorImageAnimationsEnabled())
return false;
bool animated = RepetitionCount() != kAnimationNone && !animation_finished_ &&
GetImageObserver();
if (animated && animation_policy_ == kImageAnimationPolicyNoAnimation)
animated = false;
return animated;
}
void BitmapImage::StartAnimation() {
last_num_frames_skipped_ =
StartAnimationInternal(MonotonicallyIncreasingTime());
if (!last_num_frames_skipped_.has_value())
return;
UMA_HISTOGRAM_COUNTS_100000("AnimatedImage.NumOfFramesSkipped.Main",
last_num_frames_skipped_.value());
}
Optional<size_t> BitmapImage::StartAnimationInternal(const double time) {
// If the |frame_timer_| is set, it indicates that a task is already pending
// to advance the current frame of the animation. We don't need to schedule
// a task to advance the animation in that case.
if (frame_timer_ || !ShouldAnimate() || FrameCount() <= 1)
return WTF::nullopt;
// If we aren't already animating, set now as the animation start time.
if (!desired_frame_start_time_)
desired_frame_start_time_ = time;
// Don't advance the animation to an incomplete frame.
size_t next_frame = (current_frame_index_ + 1) % FrameCount();
if (!FrameIsReceivedAtIndex(next_frame))
return WTF::nullopt;
// 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 (!all_data_received_ &&
(RepetitionCount() == kAnimationLoopOnce ||
animation_policy_ == kImageAnimationPolicyAnimateOnce) &&
current_frame_index_ >= (FrameCount() - 1))
return WTF::nullopt;
// 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.
// TODO(vmpstr): This function can probably deal in TimeTicks/TimeDelta
// instead.
const double current_duration =
FrameDurationAtIndex(current_frame_index_).InSecondsF();
desired_frame_start_time_ += current_duration;
// When an animated image is more than five minutes out of date, the
// user probably doesn't care about resyncing and we could burn a lot of
// time looping through frames below. Just reset the timings.
const double kCAnimationResyncCutoff = 5 * 60;
if ((time - desired_frame_start_time_) > kCAnimationResyncCutoff)
desired_frame_start_time_ = time + current_duration;
// 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 (next_frame == 0 && repetitions_complete_ == 0 &&
desired_frame_start_time_ < time)
desired_frame_start_time_ = time;
if (time < desired_frame_start_time_) {
// Haven't yet reached time for next frame to start; delay until then
frame_timer_ = WTF::WrapUnique(new TaskRunnerTimer<BitmapImage>(
task_runner_, this, &BitmapImage::AdvanceAnimation));
frame_timer_->StartOneShot(std::max(desired_frame_start_time_ - time, 0.),
BLINK_FROM_HERE);
// No frames needed to be skipped to advance to the next frame.
return Optional<size_t>(0u);
}
// 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.
// Note that |desired_frame_start_time_| is always set to the time at which
// |next_frame| should be displayed.
size_t frames_advanced = 0u;
for (; FrameIsReceivedAtIndex(next_frame);
next_frame = (current_frame_index_ + 1) % FrameCount()) {
// Should we skip the next frame?
// TODO(vmpstr): This function can probably deal in TimeTicks/TimeDelta
// instead.
if (time < desired_frame_start_time_)
break;
// Skip the next frame by advancing the animation forward one frame.
if (!InternalAdvanceAnimation(kSkipFramesToCatchUp)) {
DCHECK(animation_finished_);
// No frames skipped, we simply marked the animation as finished on the
// first attempt to advance it.
if (frames_advanced == 0u)
return WTF::nullopt;
// Don't include the |current_frame_index_|, the last frame we will be
// painting when finishing this animation, in the number of frames
// skipped.
return Optional<size_t>(frames_advanced - 1);
}
DCHECK_EQ(current_frame_index_, next_frame);
frames_advanced++;
desired_frame_start_time_ +=
FrameDurationAtIndex(current_frame_index_).InSecondsF();
}
DCHECK_GT(frames_advanced, 0u);
// Since we just advanced a bunch of frames during catch up, post a
// notification to the observers. Note this has to be async because the
// animation can happen during painting and this invalidation is required
// after the current paint.
task_runner_->PostTask(
BLINK_FROM_HERE,
WTF::Bind(&BitmapImage::NotifyObserversOfAnimationAdvance,
weak_factory_.CreateWeakPtr(), nullptr));
// Reset the |desired_frame_start_time_| to the time for starting the
// |current_frame_index_|. Whenever StartAnimationInternal decides to schedule
// the task for the next frame (which may not happen in the call below), it
// always updates the |desired_frame_start_time_| based on the current frame
// duration.
desired_frame_start_time_ -=
FrameDurationAtIndex(current_frame_index_).InSecondsF();
// Don't include the |current_frame_index_|, which will be used on the next
// paint, in the number of frames skipped.
return Optional<size_t>(frames_advanced - 1);
}
void BitmapImage::StopAnimation() {
// This timer is used to animate all occurrences of this image. Don't
// invalidate the timer unless all renderers have stopped drawing.
frame_timer_.reset();
}
void BitmapImage::ResetAnimation() {
StopAnimation();
current_frame_index_ = 0;
repetitions_complete_ = 0;
desired_frame_start_time_ = 0;
animation_finished_ = false;
cached_frame_ = PaintImage();
reset_animation_sequence_id_++;
}
bool BitmapImage::MaybeAnimated() {
if (animation_finished_)
return false;
if (FrameCount() > 1)
return true;
return decoder_ && decoder_->RepetitionCount() != kAnimationNone;
}
void BitmapImage::AdvanceTime(double delta_time_in_seconds) {
if (desired_frame_start_time_)
desired_frame_start_time_ -= delta_time_in_seconds;
else
desired_frame_start_time_ =
MonotonicallyIncreasingTime() - delta_time_in_seconds;
}
void BitmapImage::AdvanceAnimation(TimerBase*) {
InternalAdvanceAnimation();
// 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(AnimationAdvancement advancement) {
// Stop the animation.
StopAnimation();
if (!GetImageObserver())
return false;
// 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 (advancement != kSkipFramesToCatchUp &&
GetImageObserver()->ShouldPauseAnimation(this))
return false;
if (current_frame_index_ + 1 < FrameCount()) {
current_frame_index_++;
} else {
repetitions_complete_++;
// We don't need to special-case cAnimationLoopOnce here because it is
// 0 (see comments on its declaration in ImageAnimation.h).
if ((RepetitionCount() != kAnimationLoopInfinite &&
repetitions_complete_ > repetition_count_) ||
animation_policy_ == kImageAnimationPolicyAnimateOnce) {
animation_finished_ = true;
desired_frame_start_time_ = 0;
// We skipped to the last frame and cannot advance further. The
// observer will not receive animationAdvanced notifications while
// skipping but we still need to notify the observer to draw the
// last frame. Skipping frames occurs while painting so we do not
// synchronously notify the observer which could cause a layout.
if (advancement == kSkipFramesToCatchUp) {
frame_timer_ = WTF::WrapUnique(new TaskRunnerTimer<BitmapImage>(
task_runner_, this,
&BitmapImage::NotifyObserversOfAnimationAdvance));
frame_timer_->StartOneShot(0, BLINK_FROM_HERE);
}
return false;
}
// Loop the animation back to the first frame.
current_frame_index_ = 0;
}
// We need to draw this frame if we advanced to it while not skipping.
if (advancement != kSkipFramesToCatchUp)
GetImageObserver()->AnimationAdvanced(this);
return true;
}
void BitmapImage::SetAnimationPolicy(ImageAnimationPolicy policy) {
if (animation_policy_ == policy)
return;
animation_policy_ = policy;
ResetAnimation();
}
void BitmapImage::NotifyObserversOfAnimationAdvance(TimerBase*) {
if (GetImageObserver())
GetImageObserver()->AnimationAdvanced(this);
}
STATIC_ASSERT_ENUM(WebSettings::kImageAnimationPolicyAllowed,
kImageAnimationPolicyAllowed);
STATIC_ASSERT_ENUM(WebSettings::kImageAnimationPolicyAnimateOnce,
kImageAnimationPolicyAnimateOnce);
STATIC_ASSERT_ENUM(WebSettings::kImageAnimationPolicyNoAnimation,
kImageAnimationPolicyNoAnimation);
} // namespace blink