blob: de68c82b52646e7fee938fad1a848c70ef5cf684 [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 "platform/RuntimeEnabledFeatures.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/scheduler/child/web_scheduler.h"
#include "platform/wtf/PassRefPtr.h"
#include "platform/wtf/PtrUtil.h"
#include "platform/wtf/text/WTFString.h"
namespace blink {
namespace {
ColorBehavior DefaultColorBehavior() {
if (RuntimeEnabledFeatures::colorCorrectRenderingEnabled())
return ColorBehavior::Tag();
return ColorBehavior::TransformToGlobalTarget();
}
} // namespace
PassRefPtr<BitmapImage> BitmapImage::CreateWithOrientationForTesting(
const SkBitmap& bitmap,
ImageOrientation orientation) {
if (bitmap.isNull()) {
return BitmapImage::Create();
}
RefPtr<BitmapImage> result = AdoptRef(new BitmapImage(bitmap));
result->frames_[0].orientation_ = orientation;
if (orientation.UsesWidthAsHeight())
result->size_respecting_orientation_ = result->size_.TransposedSize();
return result.Release();
}
BitmapImage::BitmapImage(ImageObserver* observer)
: Image(observer),
current_frame_(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()) {}
BitmapImage::BitmapImage(const SkBitmap& bitmap, ImageObserver* observer)
: Image(observer),
size_(bitmap.width(), bitmap.height()),
current_frame_(0),
cached_frame_(SkImage::MakeFromBitmap(bitmap)),
cached_frame_index_(0),
animation_policy_(kImageAnimationPolicyAllowed),
animation_finished_(true),
all_data_received_(true),
have_size_(true),
size_available_(true),
have_frame_count_(true),
repetition_count_status_(kUnknown),
repetition_count_(kAnimationNone),
repetitions_complete_(0),
frame_count_(1),
task_runner_(Platform::Current()
->CurrentThread()
->Scheduler()
->CompositorTaskRunner()) {
// Since we don't have a decoder, we can't figure out the image orientation.
// Set m_sizeRespectingOrientation to be the same as m_size so it's not 0x0.
size_respecting_orientation_ = size_;
frames_.Grow(1);
frames_[0].has_alpha_ = !bitmap.isOpaque();
frames_[0].have_metadata_ = true;
}
BitmapImage::~BitmapImage() {
StopAnimation();
}
bool BitmapImage::CurrentFrameHasSingleSecurityOrigin() const {
return true;
}
void BitmapImage::DestroyDecodedData() {
cached_frame_.reset();
for (size_t i = 0; i < frames_.size(); ++i)
frames_[i].Clear(true);
source_.ClearCacheExceptFrame(kNotFound);
NotifyMemoryChanged();
}
PassRefPtr<SharedBuffer> BitmapImage::Data() {
return source_.Data();
}
void BitmapImage::NotifyMemoryChanged() {
if (GetImageObserver())
GetImageObserver()->DecodedSizeChangedTo(this, TotalFrameBytes());
}
size_t BitmapImage::TotalFrameBytes() {
const size_t num_frames = FrameCount();
size_t total_bytes = 0;
for (size_t i = 0; i < num_frames; ++i)
total_bytes += source_.FrameBytesAtIndex(i);
return total_bytes;
}
sk_sp<SkImage> BitmapImage::DecodeAndCacheFrame(size_t index) {
size_t num_frames = FrameCount();
if (frames_.size() < num_frames)
frames_.Grow(num_frames);
// We are caching frame snapshots. This is OK even for partially decoded
// frames, as they are cleared by dataChanged() when new data arrives.
sk_sp<SkImage> image =
source_.CreateFrameAtIndex(index, DefaultColorBehavior());
cached_frame_ = image;
cached_frame_index_ = index;
frames_[index].orientation_ = source_.OrientationAtIndex(index);
frames_[index].have_metadata_ = true;
frames_[index].is_complete_ = source_.FrameIsCompleteAtIndex(index);
if (RepetitionCount(false) != kAnimationNone)
frames_[index].duration_ = source_.FrameDurationAtIndex(index);
frames_[index].has_alpha_ = source_.FrameHasAlphaAtIndex(index);
frames_[index].frame_bytes_ = source_.FrameBytesAtIndex(index);
NotifyMemoryChanged();
return image;
}
void BitmapImage::UpdateSize() const {
if (!size_available_ || have_size_)
return;
size_ = source_.size();
size_respecting_orientation_ = source_.size(kRespectImageOrientation);
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 source_.GetHotSpot(hot_spot);
}
Image::SizeAvailability BitmapImage::SetData(PassRefPtr<SharedBuffer> data,
bool all_data_received) {
if (!data.Get())
return kSizeAvailable;
int length = data->size();
if (!length)
return kSizeAvailable;
// If ImageSource::setData() fails, we know that this is a decode error.
// Report size available so that it gets registered as such in
// ImageResourceContent.
if (!source_.SetData(std::move(data), all_data_received))
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_.reset();
}
}
// Feed all the data we've seen so far to the image decoder.
all_data_received_ = all_data_received;
have_frame_count_ = false;
return IsSizeAvailable() ? kSizeAvailable : kSizeUnavailable;
}
bool BitmapImage::HasColorProfile() const {
return source_.HasColorProfile();
}
String BitmapImage::FilenameExtension() const {
return source_.FilenameExtension();
}
void BitmapImage::Draw(
PaintCanvas* canvas,
const PaintFlags& flags,
const FloatRect& dst_rect,
const FloatRect& src_rect,
RespectImageOrientationEnum should_respect_image_orientation,
ImageClampingMode clamp_mode) {
TRACE_EVENT0("skia", "BitmapImage::draw");
PaintImage image = PaintImageForCurrentFrame();
if (!image)
return; // It's too early and we don't have an image yet.
FloatRect adjusted_src_rect = src_rect;
adjusted_src_rect.Intersect(SkRect::Make(image.sk_image()->bounds()));
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_);
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.sk_image()->uniqueID();
bool is_lazy_generated = image.sk_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_ = source_.FrameCount();
// If decoder is not initialized yet, m_source.frameCount() returns 0.
if (frame_count_)
have_frame_count_ = true;
}
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_ = source_.IsSizeAvailable();
if (size_available_ && HasVisibleImageSize(Size())) {
BitmapImageMetrics::CountDecodedImageType(source_.FilenameExtension());
if (source_.FilenameExtension() == "jpg")
BitmapImageMetrics::CountImageOrientation(
source_.OrientationAtIndex(0).Orientation());
}
return size_available_;
}
sk_sp<SkImage> BitmapImage::FrameAtIndex(size_t index) {
if (index >= FrameCount())
return nullptr;
if (index == cached_frame_index_ && cached_frame_)
return cached_frame_;
return DecodeAndCacheFrame(index);
}
bool BitmapImage::FrameIsCompleteAtIndex(size_t index) const {
if (index < frames_.size() && frames_[index].have_metadata_ &&
frames_[index].is_complete_)
return true;
return source_.FrameIsCompleteAtIndex(index);
}
float BitmapImage::FrameDurationAtIndex(size_t index) const {
if (index < frames_.size() && frames_[index].have_metadata_)
return frames_[index].duration_;
return source_.FrameDurationAtIndex(index);
}
sk_sp<SkImage> BitmapImage::ImageForCurrentFrame() {
return FrameAtIndex(CurrentFrame());
}
PassRefPtr<Image> BitmapImage::ImageForDefaultFrame() {
if (FrameCount() > 1) {
sk_sp<SkImage> first_frame = FrameAtIndex(0);
if (first_frame)
return StaticBitmapImage::Create(std::move(first_frame));
}
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;
// m_hasAlpha may change after m_haveMetadata is set to true, so always ask
// ImageSource for the value if the cached value is the default value.
bool has_alpha = source_.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(CurrentFrame());
}
return !FrameHasAlphaAtIndex(CurrentFrame());
}
bool BitmapImage::CurrentFrameIsComplete() {
return FrameIsCompleteAtIndex(CurrentFrame());
}
bool BitmapImage::CurrentFrameIsLazyDecoded() {
sk_sp<SkImage> image = FrameAtIndex(CurrentFrame());
return image && image->isLazyGenerated();
}
ImageOrientation BitmapImage::CurrentFrameOrientation() {
return FrameOrientationAtIndex(CurrentFrame());
}
ImageOrientation BitmapImage::FrameOrientationAtIndex(size_t index) {
if (frames_.size() <= index)
return kDefaultImageOrientation;
if (frames_[index].have_metadata_)
return frames_[index].orientation_;
return source_.OrientationAtIndex(index);
}
int BitmapImage::RepetitionCount(bool image_known_to_be_complete) {
if ((repetition_count_status_ == kUnknown) ||
((repetition_count_status_ == kUncertain) &&
image_known_to_be_complete)) {
// 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_ = source_.RepetitionCount();
repetition_count_status_ =
(image_known_to_be_complete || repetition_count_ == kAnimationNone)
? kCertain
: kUncertain;
}
return repetition_count_;
}
bool BitmapImage::ShouldAnimate() {
bool animated = RepetitionCount(false) != kAnimationNone &&
!animation_finished_ && GetImageObserver();
if (animated && animation_policy_ == kImageAnimationPolicyNoAnimation)
animated = false;
return animated;
}
void BitmapImage::StartAnimation(CatchUpAnimation catch_up_if_necessary) {
if (frame_timer_ || !ShouldAnimate() || FrameCount() <= 1)
return;
// If we aren't already animating, set now as the animation start time.
const double time = MonotonicallyIncreasingTime();
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_ + 1) % FrameCount();
if (!all_data_received_ && !FrameIsCompleteAtIndex(next_frame))
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 (!all_data_received_ &&
(RepetitionCount(false) == kAnimationLoopOnce ||
animation_policy_ == kImageAnimationPolicyAnimateOnce) &&
current_frame_ >= (FrameCount() - 1))
return;
// 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 current_duration = FrameDurationAtIndex(current_frame_);
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 (catch_up_if_necessary == kDoNotCatchUp ||
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);
} 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 frame_after_next = (next_frame + 1) % FrameCount();
FrameIsCompleteAtIndex(frame_after_next);
frame_after_next = (next_frame + 1) % FrameCount()) {
// Should we skip the next frame?
double frame_after_next_start_time =
desired_frame_start_time_ + FrameDurationAtIndex(next_frame);
if (time < frame_after_next_start_time)
break;
// Skip the next frame by advancing the animation forward one frame.
if (!InternalAdvanceAnimation(kSkipFramesToCatchUp)) {
DCHECK(animation_finished_);
return;
}
desired_frame_start_time_ = frame_after_next_start_time;
next_frame = frame_after_next;
}
// Post a task to advance the frame immediately. 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.
frame_timer_ = WTF::WrapUnique(new TaskRunnerTimer<BitmapImage>(
task_runner_, this, &BitmapImage::AdvanceAnimationWithoutCatchUp));
frame_timer_->StartOneShot(0, BLINK_FROM_HERE);
}
}
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_ = 0;
repetitions_complete_ = 0;
desired_frame_start_time_ = 0;
animation_finished_ = false;
cached_frame_.reset();
}
bool BitmapImage::MaybeAnimated() {
if (animation_finished_)
return false;
if (FrameCount() > 1)
return true;
return source_.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.
}
void BitmapImage::AdvanceAnimationWithoutCatchUp(TimerBase*) {
if (InternalAdvanceAnimation())
StartAnimation(kDoNotCatchUp);
}
bool BitmapImage::InternalAdvanceAnimation(AnimationAdvancement advancement) {
// 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 (advancement != kSkipFramesToCatchUp &&
GetImageObserver()->ShouldPauseAnimation(this))
return false;
if (current_frame_ + 1 < FrameCount()) {
current_frame_++;
} else {
repetitions_complete_++;
// 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.
// We don't need to special-case cAnimationLoopOnce here because it is
// 0 (see comments on its declaration in ImageAnimation.h).
if ((RepetitionCount(true) != 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_ = 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::NotifyObserversOfAnimationAdvance(TimerBase*) {
GetImageObserver()->AnimationAdvanced(this);
}
} // namespace blink