// Copyright 2017 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "cc/paint/paint_image.h" #include #include "base/atomic_sequence_num.h" #include "base/hash.h" #include "cc/paint/paint_image_generator.h" #include "cc/paint/paint_record.h" #include "cc/paint/skia_paint_image_generator.h" #include "ui/gfx/skia_util.h" namespace cc { namespace { base::AtomicSequenceNumber g_next_id_; base::AtomicSequenceNumber g_next_content_id_; } // namespace const PaintImage::Id PaintImage::kNonLazyStableId = -1; const size_t PaintImage::kDefaultFrameIndex = 0u; const PaintImage::Id PaintImage::kInvalidId = -2; const PaintImage::ContentId PaintImage::kInvalidContentId = -1; PaintImage::PaintImage() = default; PaintImage::PaintImage(const PaintImage& other) = default; PaintImage::PaintImage(PaintImage&& other) = default; PaintImage::~PaintImage() = default; PaintImage& PaintImage::operator=(const PaintImage& other) = default; PaintImage& PaintImage::operator=(PaintImage&& other) = default; bool PaintImage::operator==(const PaintImage& other) const { if (sk_image_ != other.sk_image_) return false; if (paint_record_ != other.paint_record_) return false; if (paint_record_rect_ != other.paint_record_rect_) return false; if (content_id_ != other.content_id_) return false; if (paint_image_generator_ != other.paint_image_generator_) return false; if (id_ != other.id_) return false; if (animation_type_ != other.animation_type_) return false; if (completion_state_ != other.completion_state_) return false; if (subset_rect_ != other.subset_rect_) return false; if (frame_index_ != other.frame_index_) return false; if (is_multipart_ != other.is_multipart_) return false; return true; } // static PaintImage::DecodingMode PaintImage::GetConservative(DecodingMode one, DecodingMode two) { if (one == two) return one; if (one == DecodingMode::kSync || two == DecodingMode::kSync) return DecodingMode::kSync; if (one == DecodingMode::kUnspecified || two == DecodingMode::kUnspecified) return DecodingMode::kUnspecified; DCHECK_EQ(one, DecodingMode::kAsync); DCHECK_EQ(two, DecodingMode::kAsync); return DecodingMode::kAsync; } // static PaintImage::Id PaintImage::GetNextId() { return g_next_id_.GetNext(); } // static PaintImage::ContentId PaintImage::GetNextContentId() { return g_next_content_id_.GetNext(); } const sk_sp& PaintImage::GetSkImage() const { return cached_sk_image_; } PaintImage PaintImage::MakeSubset(const gfx::Rect& subset) const { DCHECK(!subset.IsEmpty()); // If the subset is the same as the image bounds, we can return the same // image. gfx::Rect bounds(width(), height()); if (bounds == subset) return *this; DCHECK(bounds.Contains(subset)) << "Subset should not be greater than the image bounds"; PaintImage result(*this); result.subset_rect_ = subset; // Store the subset from the original image. result.subset_rect_.Offset(subset_rect_.x(), subset_rect_.y()); // Creating the |cached_sk_image_| using the SkImage from the original // PaintImage is an optimization to allow re-use of the original decode for // image subsets in skia, for cases that rely on skia's image decode cache. result.cached_sk_image_ = GetSkImage()->makeSubset(gfx::RectToSkIRect(subset)); return result; } void PaintImage::CreateSkImage() { DCHECK(!cached_sk_image_); if (sk_image_) { cached_sk_image_ = sk_image_; } else if (paint_record_) { cached_sk_image_ = SkImage::MakeFromPicture( ToSkPicture(paint_record_, gfx::RectToSkRect(paint_record_rect_)), SkISize::Make(paint_record_rect_.width(), paint_record_rect_.height()), nullptr, nullptr, SkImage::BitDepth::kU8, SkColorSpace::MakeSRGB()); } else if (paint_image_generator_) { cached_sk_image_ = SkImage::MakeFromGenerator(std::make_unique( paint_image_generator_, frame_index_)); } if (!subset_rect_.IsEmpty() && cached_sk_image_) { cached_sk_image_ = cached_sk_image_->makeSubset(gfx::RectToSkIRect(subset_rect_)); } } SkISize PaintImage::GetSupportedDecodeSize( const SkISize& requested_size) const { // TODO(vmpstr): If this image is using subset_rect, then we don't support // decoding to any scale other than the original. See the comment in Decode() // explaining this in more detail. // TODO(vmpstr): For now, always decode to the original size. This can be // enabled with the following code, and should be done as a follow-up. // if (paint_image_generator_ && subset_rect_.IsEmpty()) // return paint_image_generator_->GetSupportedDecodeSize(requested_size); return SkISize::Make(width(), height()); } bool PaintImage::Decode(void* memory, SkImageInfo* info, sk_sp color_space, size_t frame_index) const { // We only support decode to supported decode size. DCHECK(info->dimensions() == GetSupportedDecodeSize(info->dimensions())); // We don't support SkImageInfo's with color spaces on them. Color spaces // should always be passed via the |color_space| arg. DCHECK(!info->colorSpace()); // TODO(vmpstr): If we're using a subset_rect_ then the info specifies the // requested size relative to the subset. However, the generator isn't aware // of this subsetting and would need a size that is relative to the original // image size. We could still implement this case, but we need to convert the // requested size into the space of the original image. For now, fallback to // DecodeFromSkImage(). if (paint_image_generator_ && subset_rect_.IsEmpty()) return DecodeFromGenerator(memory, info, std::move(color_space), frame_index); return DecodeFromSkImage(memory, info, std::move(color_space), frame_index); } bool PaintImage::DecodeFromGenerator(void* memory, SkImageInfo* info, sk_sp color_space, size_t frame_index) const { DCHECK(subset_rect_.IsEmpty()); // First convert the info to have the requested color space, since the decoder // will convert this for us. *info = info->makeColorSpace(std::move(color_space)); if (info->colorType() != kN32_SkColorType) { // Since the decoders only support N32 color types, make one of those and // decode into temporary memory. Then read the bitmap which will convert it // to the target color type. SkImageInfo n32info = info->makeColorType(kN32_SkColorType); std::unique_ptr n32memory( new char[n32info.minRowBytes() * n32info.height()]); bool result = paint_image_generator_->GetPixels(n32info, n32memory.get(), n32info.minRowBytes(), frame_index, unique_id()); if (!result) return false; // The following block will use Skia to do the color type conversion from // N32 to the destination color type. Since color space conversion was // already done in GetPixels() above, remove the color space information // first in case Skia tries to use it for something. In practice, n32info // and *info color spaces match, so it should work without removing the // color spaces, but better be safe. SkImageInfo n32info_no_colorspace = n32info.makeColorSpace(nullptr); SkImageInfo info_no_colorspace = info->makeColorSpace(nullptr); SkBitmap bitmap; bitmap.installPixels(n32info_no_colorspace, n32memory.get(), n32info.minRowBytes()); return bitmap.readPixels(info_no_colorspace, memory, info->minRowBytes(), 0, 0); } return paint_image_generator_->GetPixels(*info, memory, info->minRowBytes(), frame_index, unique_id()); } bool PaintImage::DecodeFromSkImage(void* memory, SkImageInfo* info, sk_sp color_space, size_t frame_index) const { auto image = GetSkImageForFrame(frame_index); DCHECK(image); if (color_space) { image = image->makeColorSpace(color_space, SkTransferFunctionBehavior::kIgnore); if (!image) return false; } // Note that the readPixels has to happen before converting the info to the // given color space, since it can produce incorrect results. bool result = image->readPixels(*info, memory, info->minRowBytes(), 0, 0, SkImage::kDisallow_CachingHint); *info = info->makeColorSpace(std::move(color_space)); return result; } bool PaintImage::ShouldAnimate() const { return animation_type_ == AnimationType::ANIMATED && repetition_count_ != kAnimationNone && FrameCount() > 1; } PaintImage::FrameKey PaintImage::GetKeyForFrame(size_t frame_index) const { DCHECK_LT(frame_index, FrameCount()); // Query the content id that uniquely identifies the content for this frame // from the content provider. ContentId content_id = kInvalidContentId; if (paint_image_generator_) content_id = paint_image_generator_->GetContentIdForFrame(frame_index); else if (paint_record_ || sk_image_) content_id = content_id_; DCHECK_NE(content_id, kInvalidContentId); return FrameKey(content_id, frame_index, subset_rect_); } const std::vector& PaintImage::GetFrameMetadata() const { DCHECK_EQ(animation_type_, AnimationType::ANIMATED); DCHECK(paint_image_generator_); return paint_image_generator_->GetFrameMetadata(); } size_t PaintImage::FrameCount() const { if (!GetSkImage()) return 0u; return paint_image_generator_ ? paint_image_generator_->GetFrameMetadata().size() : 1u; } sk_sp PaintImage::GetSkImageForFrame(size_t index) const { DCHECK_LT(index, FrameCount()); if (index == frame_index_) return GetSkImage(); sk_sp image = SkImage::MakeFromGenerator( std::make_unique(paint_image_generator_, index)); if (!subset_rect_.IsEmpty()) image = image->makeSubset(gfx::RectToSkIRect(subset_rect_)); return image; } std::string PaintImage::ToString() const { std::ostringstream str; str << "sk_image_: " << sk_image_ << " paint_record_: " << paint_record_ << " paint_record_rect_: " << paint_record_rect_.ToString() << " paint_image_generator_: " << paint_image_generator_ << " id_: " << id_ << " animation_type_: " << static_cast(animation_type_) << " completion_state_: " << static_cast(completion_state_) << " subset_rect_: " << subset_rect_.ToString() << " frame_index_: " << frame_index_ << " is_multipart_: " << is_multipart_; return str.str(); } PaintImage::FrameKey::FrameKey(ContentId content_id, size_t frame_index, gfx::Rect subset_rect) : content_id_(content_id), frame_index_(frame_index), subset_rect_(subset_rect) { size_t original_hash = base::HashInts(static_cast(content_id_), static_cast(frame_index_)); if (subset_rect_.IsEmpty()) { hash_ = original_hash; } else { size_t subset_hash = base::HashInts(static_cast( base::HashInts(subset_rect_.x(), subset_rect_.y())), static_cast(base::HashInts( subset_rect_.width(), subset_rect_.height()))); hash_ = base::HashInts(original_hash, subset_hash); } } bool PaintImage::FrameKey::operator==(const FrameKey& other) const { return content_id_ == other.content_id_ && frame_index_ == other.frame_index_ && subset_rect_ == other.subset_rect_; } bool PaintImage::FrameKey::operator!=(const FrameKey& other) const { return !(*this == other); } std::string PaintImage::FrameKey::ToString() const { std::ostringstream str; str << "content_id: " << content_id_ << "," << "frame_index: " << frame_index_ << "," << "subset_rect: " << subset_rect_.ToString(); return str.str(); } } // namespace cc