blob: cebca883096c9b8aa587c9c14ed322bac632de82 [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 "third_party/blink/renderer/platform/graphics/bitmap_image.h"
#include <algorithm>
#include <memory>
#include <utility>
#include "base/memory/scoped_refptr.h"
#include "base/metrics/histogram_macros.h"
#include "third_party/blink/renderer/platform/geometry/float_rect.h"
#include "third_party/blink/renderer/platform/graphics/bitmap_image_metrics.h"
#include "third_party/blink/renderer/platform/graphics/dark_mode_bitmap_image_classifier.h"
#include "third_party/blink/renderer/platform/graphics/deferred_image_decoder.h"
#include "third_party/blink/renderer/platform/graphics/image_observer.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_canvas.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_flags.h"
#include "third_party/blink/renderer/platform/graphics/paint/paint_image.h"
#include "third_party/blink/renderer/platform/graphics/skia/skia_utils.h"
#include "third_party/blink/renderer/platform/graphics/static_bitmap_image.h"
#include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/scheduler/public/thread_scheduler.h"
#include "third_party/blink/renderer/platform/timer.h"
#include "third_party/blink/renderer/platform/wtf/assertions.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
int GetRepetitionCountWithPolicyOverride(int actual_count,
ImageAnimationPolicy policy) {
if (actual_count == kAnimationNone ||
policy == kImageAnimationPolicyNoAnimation) {
return kAnimationNone;
}
if (actual_count == kAnimationLoopOnce ||
policy == kImageAnimationPolicyAnimateOnce) {
return kAnimationLoopOnce;
}
return actual_count;
}
BitmapImage::BitmapImage(ImageObserver* observer, bool is_multipart)
: Image(observer, is_multipart),
animation_policy_(kImageAnimationPolicyAllowed),
all_data_received_(false),
have_size_(false),
size_available_(false),
have_frame_count_(false),
repetition_count_status_(kUnknown),
repetition_count_(kAnimationNone),
frame_count_(0) {}
BitmapImage::~BitmapImage() {}
bool BitmapImage::CurrentFrameHasSingleSecurityOrigin() const {
return true;
}
void BitmapImage::DestroyDecodedData() {
cached_frame_ = PaintImage();
NotifyMemoryChanged();
}
scoped_refptr<SharedBuffer> BitmapImage::Data() {
return decoder_ ? decoder_->Data() : nullptr;
}
void BitmapImage::NotifyMemoryChanged() {
if (GetImageObserver())
GetImageObserver()->DecodedSizeChangedTo(this, TotalFrameBytes());
}
size_t BitmapImage::TotalFrameBytes() {
if (cached_frame_)
return static_cast<size_t>(Size().Area()) * sizeof(ImageFrame::PixelData);
return 0u;
}
PaintImage BitmapImage::PaintImageForTesting() {
return CreatePaintImage();
}
PaintImage BitmapImage::CreatePaintImage() {
sk_sp<PaintImageGenerator> generator =
decoder_ ? decoder_->CreateGenerator(PaintImage::kDefaultFrameIndex)
: nullptr;
if (!generator)
return PaintImage();
auto completion_state = all_data_received_
? PaintImage::CompletionState::DONE
: PaintImage::CompletionState::PARTIALLY_DONE;
auto builder =
CreatePaintImageBuilder()
.set_paint_image_generator(std::move(generator))
.set_repetition_count(GetRepetitionCountWithPolicyOverride(
RepetitionCount(), animation_policy_))
.set_is_high_bit_depth(decoder_->ImageIsHighBitDepth())
.set_completion_state(completion_state)
.set_reset_animation_sequence_id(reset_animation_sequence_id_);
return builder.TakePaintImage();
}
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);
}
// We likely don't need to confirm that this is the first time all data has
// been received as a way to avoid reporting the UMA multiple times for the
// same image. However, we err on the side of caution.
bool BitmapImage::ShouldReportByteSizeUMAs(bool data_now_completely_received) {
if (!decoder_)
return false;
// Ensures that refactoring to check truthiness of ByteSize() method is
// equivalent to the previous use of Data() and does not mess up UMAs.
DCHECK_EQ(!decoder_->ByteSize(), !decoder_->Data());
return !all_data_received_ && data_now_completely_received &&
decoder_->ByteSize() && IsSizeAvailable();
}
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);
}
// Return the image density in 0.01 "bits per pixel" rounded to the nearest
// integer.
static inline uint64_t ImageDensityInCentiBpp(IntSize size,
size_t image_size_bytes) {
uint64_t image_area = static_cast<uint64_t>(size.Width()) * size.Height();
return (static_cast<uint64_t>(image_size_bytes) * 100 * 8 + image_area / 2) /
image_area;
}
Image::SizeAvailability BitmapImage::DataChanged(bool all_data_received) {
TRACE_EVENT0("blink", "BitmapImage::dataChanged");
// If the data was updated, clear the |cached_frame_| to push it to the
// compositor thread. Its necessary to clear the frame since more data
// requires a new PaintImageGenerator instance.
cached_frame_ = PaintImage();
// Report the image density metric right after we received all the data. The
// SetData() call on the decoder_ (if there is one) should have decoded the
// images and we should know the image size at this point.
if (ShouldReportByteSizeUMAs(all_data_received) &&
decoder_->FilenameExtension() == "jpg") {
BitmapImageMetrics::CountImageJpegDensity(
std::min(Size().Width(), Size().Height()),
ImageDensityInCentiBpp(Size(), decoder_->ByteSize()));
}
// 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 decoder_ && decoder_->HasEmbeddedColorProfile();
}
String BitmapImage::FilenameExtension() const {
return decoder_ ? decoder_->FilenameExtension() : String();
}
void BitmapImage::Draw(
cc::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 = CurrentFrameOrientation();
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) {
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
"Draw LazyPixelRef", TRACE_EVENT_SCOPE_THREAD,
"LazyPixelRef", 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::PaintImageForCurrentFrame() {
if (cached_frame_)
return cached_frame_;
cached_frame_ = CreatePaintImage();
// 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_;
}
scoped_refptr<Image> BitmapImage::ImageForDefaultFrame() {
if (FrameCount() > 1) {
PaintImage paint_image = PaintImageForCurrentFrame();
if (!paint_image)
return nullptr;
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::CurrentFrameKnownToBeOpaque() {
// If the image is animated, it is being animated by the compositor and we
// don't know what the current frame is.
// TODO(khushalsagar): We could say the image is opaque if none of the frames
// have alpha.
if (MaybeAnimated())
return false;
// We ask the decoder whether the image has alpha because in some cases the
// the correct value is known after decoding. The DeferredImageDecoder caches
// the accurate value from the decoded result.
const bool frame_has_alpha =
decoder_ ? decoder_->FrameHasAlphaAtIndex(PaintImage::kDefaultFrameIndex)
: true;
return !frame_has_alpha;
}
bool BitmapImage::CurrentFrameIsComplete() {
return decoder_
? decoder_->FrameIsReceivedAtIndex(PaintImage::kDefaultFrameIndex)
: false;
}
bool BitmapImage::CurrentFrameIsLazyDecoded() {
// BitmapImage supports only lazy generated images.
return true;
}
ImageOrientation BitmapImage::CurrentFrameOrientation() const {
return decoder_ ? decoder_->OrientationAtIndex(PaintImage::kDefaultFrameIndex)
: kDefaultImageOrientation;
}
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;
// When requesting more than a single loop, repetition count is one less
// than the actual number of loops.
if (repetition_count_ > 0)
repetition_count_++;
repetition_count_status_ =
(all_data_received_ || repetition_count_ == kAnimationNone)
? kCertain
: kUncertain;
}
return repetition_count_;
}
void BitmapImage::ResetAnimation() {
cached_frame_ = PaintImage();
reset_animation_sequence_id_++;
}
bool BitmapImage::MaybeAnimated() {
if (FrameCount() > 1)
return true;
return decoder_ && decoder_->RepetitionCount() != kAnimationNone;
}
void BitmapImage::SetAnimationPolicy(ImageAnimationPolicy policy) {
if (animation_policy_ == policy)
return;
animation_policy_ = policy;
ResetAnimation();
}
DarkModeClassification BitmapImage::ClassifyImageForDarkMode(
const FloatRect& src_rect) {
DarkModeBitmapImageClassifier dark_mode_bitmap_image_classifier;
return dark_mode_bitmap_image_classifier.Classify(*this, src_rect);
}
} // namespace blink