| // 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 <memory> |
| |
| #include "base/atomic_sequence_num.h" |
| #include "base/hash.h" |
| #include "cc/paint/paint_image_builder.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_image_id; |
| base::AtomicSequenceNumber g_next_image_content_id; |
| base::AtomicSequenceNumber g_next_generator_client_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; |
| const PaintImage::GeneratorClientId PaintImage::kDefaultGeneratorClientId = 0; |
| |
| 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 (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_image_id.GetNext(); |
| } |
| |
| // static |
| PaintImage::ContentId PaintImage::GetNextContentId() { |
| return g_next_image_content_id.GetNext(); |
| } |
| |
| // static |
| PaintImage::GeneratorClientId PaintImage::GetNextGeneratorClientId() { |
| // These IDs must start from 1, since 0 is the kDefaultGeneratorClientId. |
| return g_next_generator_client_id.GetNext() + 1; |
| } |
| |
| // static |
| PaintImage PaintImage::CreateFromBitmap(SkBitmap bitmap) { |
| if (bitmap.drawsNothing()) |
| return PaintImage(); |
| |
| return PaintImageBuilder::WithDefault() |
| .set_id(PaintImage::GetNextId()) |
| .set_image(SkImage::MakeFromBitmap(bitmap), |
| PaintImage::GetNextContentId()) |
| .TakePaintImage(); |
| } |
| |
| const sk_sp<SkImage>& 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<SkiaPaintImageGenerator>( |
| paint_image_generator_, kDefaultFrameIndex, |
| kDefaultGeneratorClientId)); |
| } |
| |
| 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. |
| 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<SkColorSpace> color_space, |
| size_t frame_index, |
| GeneratorClientId client_id) 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, client_id); |
| return DecodeFromSkImage(memory, info, std::move(color_space), frame_index, |
| client_id); |
| } |
| |
| bool PaintImage::DecodeFromGenerator(void* memory, |
| SkImageInfo* info, |
| sk_sp<SkColorSpace> color_space, |
| size_t frame_index, |
| GeneratorClientId client_id) 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<char[]> n32memory( |
| new char[n32info.minRowBytes() * n32info.height()]); |
| |
| bool result = paint_image_generator_->GetPixels( |
| n32info, n32memory.get(), n32info.minRowBytes(), frame_index, client_id, |
| 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, client_id, unique_id()); |
| } |
| |
| bool PaintImage::DecodeFromSkImage(void* memory, |
| SkImageInfo* info, |
| sk_sp<SkColorSpace> color_space, |
| size_t frame_index, |
| GeneratorClientId client_id) const { |
| auto image = GetSkImageForFrame(frame_index, client_id); |
| DCHECK(image); |
| if (color_space) { |
| image = image->makeColorSpace(color_space); |
| 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<FrameMetadata>& 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<SkImage> PaintImage::GetSkImageForFrame( |
| size_t index, |
| GeneratorClientId client_id) const { |
| DCHECK_LT(index, FrameCount()); |
| |
| // |client_id| and |index| are only relevant for generator backed images which |
| // perform lazy decoding and can be multi-frame. |
| if (!paint_image_generator_) { |
| DCHECK_EQ(index, kDefaultFrameIndex); |
| return GetSkImage(); |
| } |
| |
| // The internally cached SkImage is constructed using the default frame index |
| // and GeneratorClientId. Avoid creating a new SkImage. |
| if (index == kDefaultFrameIndex && client_id == kDefaultGeneratorClientId) |
| return GetSkImage(); |
| |
| sk_sp<SkImage> image = |
| SkImage::MakeFromGenerator(std::make_unique<SkiaPaintImageGenerator>( |
| paint_image_generator_, index, client_id)); |
| 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<int>(animation_type_) |
| << " completion_state_: " << static_cast<int>(completion_state_) |
| << " subset_rect_: " << subset_rect_.ToString() |
| << " 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<uint64_t>(content_id_), |
| static_cast<uint64_t>(frame_index_)); |
| if (subset_rect_.IsEmpty()) { |
| hash_ = original_hash; |
| } else { |
| size_t subset_hash = |
| base::HashInts(static_cast<uint64_t>( |
| base::HashInts(subset_rect_.x(), subset_rect_.y())), |
| static_cast<uint64_t>(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 |