blob: c1a3b57ccb532bfc4affac0e30c903625af7c0ca [file] [log] [blame]
// Copyright 2015 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/discardable_image_map.h"
#include <stddef.h>
#include <algorithm>
#include <limits>
#include "base/containers/adapters.h"
#include "base/memory/ptr_util.h"
#include "cc/base/math_util.h"
#include "cc/paint/display_item_list.h"
#include "third_party/skia/include/core/SkPath.h"
#include "third_party/skia/include/utils/SkNWayCanvas.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/skia_util.h"
namespace cc {
SkRect MapRect(const SkMatrix& matrix, const SkRect& src) {
SkRect dst;
matrix.mapRect(&dst, src);
return dst;
}
// Returns a rect clamped to |max_size|. Note that |paint_rect| should intersect
// or be contained by a rect defined by (0, 0) and |max_size|.
gfx::Rect SafeClampPaintRectToSize(const SkRect& paint_rect,
const gfx::Size& max_size) {
// bounds_rect.x() + bounds_rect.width() (aka bounds_rect.right()) might
// overflow integer bounds, so do custom intersect, since gfx::Rect::Intersect
// uses bounds_rect.right().
gfx::RectF bounds_rect = gfx::SkRectToRectF(paint_rect);
float x_offset_if_negative = bounds_rect.x() < 0.f ? bounds_rect.x() : 0.f;
float y_offset_if_negative = bounds_rect.y() < 0.f ? bounds_rect.y() : 0.f;
bounds_rect.set_x(std::max(0.f, bounds_rect.x()));
bounds_rect.set_y(std::max(0.f, bounds_rect.y()));
// Verify that the rects intersect or that bound_rect is contained by
// max_size.
DCHECK_GE(bounds_rect.width(), -x_offset_if_negative);
DCHECK_GE(bounds_rect.height(), -y_offset_if_negative);
DCHECK_GE(max_size.width(), bounds_rect.x());
DCHECK_GE(max_size.height(), bounds_rect.y());
bounds_rect.set_width(std::min(bounds_rect.width() + x_offset_if_negative,
max_size.width() - bounds_rect.x()));
bounds_rect.set_height(std::min(bounds_rect.height() + y_offset_if_negative,
max_size.height() - bounds_rect.y()));
return gfx::ToEnclosingRect(bounds_rect);
}
namespace {
// We're using an NWay canvas with no added canvases, so in effect
// non-overridden functions are no-ops.
class DiscardableImagesMetadataCanvas : public SkNWayCanvas {
public:
DiscardableImagesMetadataCanvas(
int width,
int height,
std::vector<std::pair<DrawImage, gfx::Rect>>* image_set,
std::unordered_map<ImageId, gfx::Rect>* image_id_to_rect)
: SkNWayCanvas(width, height),
image_set_(image_set),
image_id_to_rect_(image_id_to_rect),
canvas_bounds_(SkRect::MakeIWH(width, height)),
canvas_size_(width, height) {}
protected:
// we need to "undo" the behavior of SkNWayCanvas, which will try to forward
// it.
void onDrawPicture(const SkPicture* picture,
const SkMatrix* matrix,
const SkPaint* paint) override {
SkCanvas::onDrawPicture(picture, matrix, paint);
}
void onDrawImage(const SkImage* image,
SkScalar x,
SkScalar y,
const SkPaint* paint) override {
const SkMatrix& ctm = getTotalMatrix();
AddImage(
sk_ref_sp(image), SkRect::MakeIWH(image->width(), image->height()),
MapRect(ctm, SkRect::MakeXYWH(x, y, image->width(), image->height())),
ctm, paint);
}
void onDrawImageRect(const SkImage* image,
const SkRect* src,
const SkRect& dst,
const SkPaint* paint,
SrcRectConstraint) override {
const SkMatrix& ctm = getTotalMatrix();
SkRect src_storage;
if (!src) {
src_storage = SkRect::MakeIWH(image->width(), image->height());
src = &src_storage;
}
SkMatrix matrix;
matrix.setRectToRect(*src, dst, SkMatrix::kFill_ScaleToFit);
matrix.preConcat(ctm);
AddImage(sk_ref_sp(image), *src, MapRect(ctm, dst), matrix, paint);
}
void onDrawImageNine(const SkImage* image,
const SkIRect& center,
const SkRect& dst,
const SkPaint* paint) override {
// No cc embedder issues image nine calls.
NOTREACHED();
}
void onDrawRect(const SkRect& r, const SkPaint& paint) override {
AddPaintImage(r, paint);
}
void onDrawPath(const SkPath& path, const SkPaint& paint) override {
AddPaintImage(path.getBounds(), paint);
}
void onDrawOval(const SkRect& r, const SkPaint& paint) override {
AddPaintImage(r, paint);
}
void onDrawArc(const SkRect& r,
SkScalar start_angle,
SkScalar sweep_angle,
bool use_center,
const SkPaint& paint) override {
AddPaintImage(r, paint);
}
void onDrawRRect(const SkRRect& rr, const SkPaint& paint) override {
AddPaintImage(rr.rect(), paint);
}
SaveLayerStrategy getSaveLayerStrategy(const SaveLayerRec& rec) override {
saved_paints_.push_back(rec.fPaint ? *rec.fPaint : SkPaint());
return SkNWayCanvas::getSaveLayerStrategy(rec);
}
void willSave() override {
saved_paints_.push_back(SkPaint());
return SkNWayCanvas::willSave();
}
void willRestore() override {
DCHECK_GT(saved_paints_.size(), 0u);
saved_paints_.pop_back();
SkNWayCanvas::willRestore();
}
private:
bool ComputePaintBounds(const SkRect& rect,
const SkPaint* current_paint,
SkRect* paint_bounds) {
*paint_bounds = rect;
if (current_paint) {
if (!current_paint->canComputeFastBounds())
return false;
*paint_bounds =
current_paint->computeFastBounds(*paint_bounds, paint_bounds);
}
for (const auto& paint : base::Reversed(saved_paints_)) {
if (!paint.canComputeFastBounds())
return false;
*paint_bounds = paint.computeFastBounds(*paint_bounds, paint_bounds);
}
return true;
}
void AddImage(sk_sp<const SkImage> image,
const SkRect& src_rect,
const SkRect& rect,
const SkMatrix& matrix,
const SkPaint* paint) {
if (!image->isLazyGenerated())
return;
SkRect paint_rect;
bool computed_paint_bounds = ComputePaintBounds(rect, paint, &paint_rect);
if (!computed_paint_bounds) {
// TODO(vmpstr): UMA this case.
paint_rect = canvas_bounds_;
}
if (!paint_rect.intersects(canvas_bounds_))
return;
SkFilterQuality filter_quality = kNone_SkFilterQuality;
if (paint) {
filter_quality = paint->getFilterQuality();
}
SkIRect src_irect;
src_rect.roundOut(&src_irect);
gfx::Rect image_rect = SafeClampPaintRectToSize(paint_rect, canvas_size_);
// During raster, we use the device clip bounds on the canvas, which outsets
// the actual clip by 1 due to the possibility of antialiasing. Account for
// this here by outsetting the image rect by 1. Note that this only affects
// queries into the rtree, which will now return images that only touch the
// bounds of the query rect.
//
// Note that it's not sufficient for us to inset the device clip bounds at
// raster time, since we might be sending a larger-than-one-item display
// item to skia, which means that skia will internally determine whether to
// raster the picture (using device clip bounds that are outset).
image_rect.Inset(-1, -1);
// The true target color space will be assigned when it is known, in
// GetDiscardableImagesInRect.
gfx::ColorSpace target_color_space;
(*image_id_to_rect_)[image->uniqueID()].Union(image_rect);
image_set_->push_back(
std::make_pair(DrawImage(std::move(image), src_irect, filter_quality,
matrix, target_color_space),
image_rect));
}
// Currently this function only handles extracting images from SkImageShaders
// embedded in SkPaints. Other embedded image cases, such as SkPictures,
// are not yet handled.
void AddPaintImage(const SkRect& rect, const SkPaint& paint) {
SkShader* shader = paint.getShader();
if (shader) {
SkMatrix matrix;
SkShader::TileMode xy[2];
SkImage* image = shader->isAImage(&matrix, xy);
if (image) {
const SkMatrix& ctm = getTotalMatrix();
matrix.postConcat(ctm);
// TODO(ericrk): Handle cases where we only need a sub-rect from the
// image. crbug.com/671821
AddImage(sk_ref_sp(image), SkRect::MakeFromIRect(image->bounds()),
MapRect(ctm, rect), matrix, &paint);
}
}
}
std::vector<std::pair<DrawImage, gfx::Rect>>* image_set_;
std::unordered_map<ImageId, gfx::Rect>* image_id_to_rect_;
const SkRect canvas_bounds_;
const gfx::Size canvas_size_;
std::vector<SkPaint> saved_paints_;
};
} // namespace
DiscardableImageMap::DiscardableImageMap() {}
DiscardableImageMap::~DiscardableImageMap() {}
std::unique_ptr<SkCanvas> DiscardableImageMap::BeginGeneratingMetadata(
const gfx::Size& bounds) {
DCHECK(all_images_.empty());
return base::MakeUnique<DiscardableImagesMetadataCanvas>(
bounds.width(), bounds.height(), &all_images_, &image_id_to_rect_);
}
void DiscardableImageMap::EndGeneratingMetadata() {
images_rtree_.Build(all_images_,
[](const std::pair<DrawImage, gfx::Rect>& image) {
return image.second;
});
}
void DiscardableImageMap::GetDiscardableImagesInRect(
const gfx::Rect& rect,
float contents_scale,
const gfx::ColorSpace& target_color_space,
std::vector<DrawImage>* images) const {
std::vector<size_t> indices;
images_rtree_.Search(rect, &indices);
for (size_t index : indices) {
images->push_back(all_images_[index]
.first.ApplyScale(contents_scale)
.ApplyTargetColorSpace(target_color_space));
}
}
gfx::Rect DiscardableImageMap::GetRectForImage(ImageId image_id) const {
const auto& it = image_id_to_rect_.find(image_id);
return it == image_id_to_rect_.end() ? gfx::Rect() : it->second;
}
DiscardableImageMap::ScopedMetadataGenerator::ScopedMetadataGenerator(
DiscardableImageMap* image_map,
const gfx::Size& bounds)
: image_map_(image_map),
metadata_canvas_(image_map->BeginGeneratingMetadata(bounds)) {}
DiscardableImageMap::ScopedMetadataGenerator::~ScopedMetadataGenerator() {
image_map_->EndGeneratingMetadata();
}
} // namespace cc