Skia: Add skia::DrawGainmapImage function
Add the helper function skia::DrawGainmapImage, which draws a gainmap image the same way an ordinary image would be drawn by SkCanvas::drawImage. This requires extra helper functions to deal with tiling the two input textures. Add the helper class skia::Tiling, which can be used to divide a destination rectangle for a drawRect call into several sub-rectangles. This is for use with SkShaders that sample from multiple SkImages, some of which might be too large to fit into a texture. Add tests for this functionality. Make skia::DrawGainmapImageRect use this generic functionality. Bug: 328665503 Change-Id: I27442d1b0df871f88795687b155f30d9f862f930 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5401806 Commit-Queue: ccameron chromium <ccameron@chromium.org> Reviewed-by: Brian Osman <brianosman@google.com> Cr-Commit-Position: refs/heads/main@{#1281537}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
da3f998168
commit
aad1e4596d
@ -26,6 +26,7 @@
|
||||
#include "cc/paint/paint_op_writer.h"
|
||||
#include "cc/paint/paint_record.h"
|
||||
#include "cc/paint/skottie_serialization_history.h"
|
||||
#include "skia/ext/draw_gainmap_image.h"
|
||||
#include "third_party/skia/include/core/SkAnnotation.h"
|
||||
#include "third_party/skia/include/core/SkCanvas.h"
|
||||
#include "third_party/skia/include/core/SkColorFilter.h"
|
||||
@ -37,7 +38,6 @@
|
||||
#include "third_party/skia/include/core/SkTiledImageUtils.h"
|
||||
#include "third_party/skia/include/core/SkVertices.h"
|
||||
#include "third_party/skia/include/docs/SkPDFDocument.h"
|
||||
#include "third_party/skia/include/private/SkGainmapShader.h"
|
||||
#include "third_party/skia/include/private/chromium/Slug.h"
|
||||
#include "ui/gfx/color_conversion_sk_filter_cache.h"
|
||||
#include "ui/gfx/geometry/skia_conversions.h"
|
||||
@ -1146,38 +1146,6 @@ class DrawImageToneMapUtil {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
static void AddGainmapShaderToPaint(SkPaint& paint,
|
||||
const PaintImage& image,
|
||||
const SkRect& src_rect,
|
||||
const SkSamplingOptions& sampling,
|
||||
const SkRect& dst_rect,
|
||||
sk_sp<SkColorSpace> dst_color_space) {
|
||||
if (!dst_color_space) {
|
||||
dst_color_space = SkColorSpace::MakeSRGB();
|
||||
}
|
||||
|
||||
// Let `gainmap_rect` be the rect in `gainmap_sk_image` that corresponds to
|
||||
// `src_rect` in `cached_sk_image_`.
|
||||
SkRect gainmap_rect = src_rect;
|
||||
{
|
||||
float scale_x = image.gainmap_sk_image_->width() /
|
||||
static_cast<float>(image.cached_sk_image_->width());
|
||||
float scale_y = image.gainmap_sk_image_->height() /
|
||||
static_cast<float>(image.cached_sk_image_->height());
|
||||
gainmap_rect.fLeft *= scale_x;
|
||||
gainmap_rect.fRight *= scale_x;
|
||||
gainmap_rect.fTop *= scale_y;
|
||||
gainmap_rect.fBottom *= scale_y;
|
||||
}
|
||||
|
||||
sk_sp<SkShader> shader = SkGainmapShader::Make(
|
||||
image.cached_sk_image_, src_rect, sampling, image.gainmap_sk_image_,
|
||||
gainmap_rect, sampling, image.gainmap_info_.value(), dst_rect,
|
||||
image.target_hdr_headroom_, std::move(dst_color_space));
|
||||
DCHECK(shader);
|
||||
paint.setShader(std::move(shader));
|
||||
}
|
||||
|
||||
static bool UseGlobalToneMapFilter(const PaintImage& image) {
|
||||
return image.use_global_tone_map_ && image.cached_sk_image_ &&
|
||||
image.cached_sk_image_->colorSpace();
|
||||
@ -1231,15 +1199,11 @@ void DrawImageOp::RasterWithFlags(const DrawImageOp* op,
|
||||
}
|
||||
|
||||
// If this uses a gainmap shader, then replace DrawImage with a shader.
|
||||
// TODO(b/41483652): This needs to tile images that don't fit into textures.
|
||||
if (DrawImageToneMapUtil::UseGainmapShader(op->image)) {
|
||||
SkRect src = SkRect::MakeIWH(sk_image->width(), sk_image->height());
|
||||
SkRect dst = SkRect::MakeXYWH(op->left, op->top, sk_image->width(),
|
||||
sk_image->height());
|
||||
DrawImageToneMapUtil::AddGainmapShaderToPaint(
|
||||
paint, op->image, src, op->sampling, dst,
|
||||
canvas->imageInfo().refColorSpace());
|
||||
canvas->drawRect(dst, paint);
|
||||
skia::DrawGainmapImage(
|
||||
canvas, op->image.cached_sk_image_, op->image.gainmap_sk_image_,
|
||||
op->image.gainmap_info_.value(), op->image.target_hdr_headroom_,
|
||||
op->left, op->top, op->sampling, paint);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1338,14 +1302,11 @@ void DrawImageRectOp::RasterWithFlags(const DrawImageRectOp* op,
|
||||
|
||||
// If the PaintImage uses a gainmap shader, then replace DrawImage with a
|
||||
// shader.
|
||||
// TODO(b/41483652): This needs to tile images that don't fit into
|
||||
// textures.
|
||||
if (DrawImageToneMapUtil::UseGainmapShader(op->image)) {
|
||||
SkPaint gainmap_paint = p;
|
||||
DrawImageToneMapUtil::AddGainmapShaderToPaint(
|
||||
gainmap_paint, op->image, adjusted_src, op->sampling, op->dst,
|
||||
c->imageInfo().refColorSpace());
|
||||
c->drawRect(op->dst, gainmap_paint);
|
||||
skia::DrawGainmapImageRect(
|
||||
c, op->image.cached_sk_image_, op->image.gainmap_sk_image_,
|
||||
op->image.gainmap_info_.value(), op->image.target_hdr_headroom_,
|
||||
adjusted_src, op->dst, op->sampling, p);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -235,8 +235,10 @@ component("skia") {
|
||||
"ext/benchmarking_canvas.h",
|
||||
"ext/cicp.h",
|
||||
"ext/convolver.h",
|
||||
"ext/draw_gainmap_image.h",
|
||||
"ext/event_tracer_impl.h",
|
||||
"ext/font_utils.h",
|
||||
"ext/geometry.h",
|
||||
"ext/image_operations.h",
|
||||
"ext/legacy_display_globals.h",
|
||||
"ext/opacity_filter_canvas.h",
|
||||
@ -259,8 +261,10 @@ component("skia") {
|
||||
"ext/benchmarking_canvas.cc",
|
||||
"ext/cicp.cc",
|
||||
"ext/convolver.cc",
|
||||
"ext/draw_gainmap_image.cc",
|
||||
"ext/event_tracer_impl.cc",
|
||||
"ext/font_utils.cc",
|
||||
"ext/geometry.cc",
|
||||
"ext/google_logging.cc",
|
||||
"ext/image_operations.cc",
|
||||
"ext/legacy_display_globals.cc",
|
||||
@ -963,6 +967,7 @@ source_set("test_fonts") {
|
||||
test("skia_unittests") {
|
||||
sources = [
|
||||
"ext/convolver_unittest.cc",
|
||||
"ext/geometry_unittest.cc",
|
||||
"ext/image_operations_unittest.cc",
|
||||
"ext/platform_canvas_unittest.cc",
|
||||
"ext/recursive_gaussian_convolution_unittest.cc",
|
||||
|
111
skia/ext/draw_gainmap_image.cc
Normal file
111
skia/ext/draw_gainmap_image.cc
Normal file
@ -0,0 +1,111 @@
|
||||
// Copyright 2024 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "skia/ext/draw_gainmap_image.h"
|
||||
|
||||
#include "skia/ext/geometry.h"
|
||||
#include "third_party/skia/include/core/SkCanvas.h"
|
||||
#include "third_party/skia/include/core/SkColorSpace.h"
|
||||
#include "third_party/skia/include/core/SkShader.h"
|
||||
#include "third_party/skia/include/gpu/GrRecordingContext.h"
|
||||
#include "third_party/skia/include/private/SkGainmapShader.h"
|
||||
|
||||
namespace skia {
|
||||
|
||||
void DrawGainmapImageRect(SkCanvas* canvas,
|
||||
sk_sp<SkImage> base_image,
|
||||
sk_sp<SkImage> gain_image,
|
||||
const SkGainmapInfo& gainmap_info,
|
||||
float hdr_headroom,
|
||||
const SkRect& source_rect,
|
||||
const SkRect& dest_rect,
|
||||
const SkSamplingOptions& sampling,
|
||||
const SkPaint& paint) {
|
||||
// Compute `dest_rect_clipped` to be intersected with the pre-image of the
|
||||
// clip rect of `canvas`.
|
||||
SkRect dest_rect_clipped = dest_rect;
|
||||
const SkMatrix& dest_to_device = canvas->getTotalMatrix();
|
||||
SkMatrix device_to_dest;
|
||||
if (dest_to_device.invert(&device_to_dest)) {
|
||||
SkRect dest_clip_rect;
|
||||
device_to_dest.mapRect(&dest_clip_rect,
|
||||
SkRect::Make(canvas->getDeviceClipBounds()));
|
||||
dest_rect_clipped.intersect(dest_clip_rect);
|
||||
}
|
||||
|
||||
// Compute the input rect for the base and gainmap images.
|
||||
SkRect base_rect =
|
||||
skia::ScaleSkRectProportional(source_rect, dest_rect, dest_rect_clipped);
|
||||
SkRect gain_rect = skia::ScaleSkRectProportional(
|
||||
SkRect::Make(gain_image->bounds()), SkRect::Make(base_image->bounds()),
|
||||
base_rect);
|
||||
|
||||
// Compute a tiling that ensures that the base and gainmap images fit on the
|
||||
// GPU.
|
||||
const std::vector<SkRect> source_rects = {base_rect, gain_rect};
|
||||
const std::vector<sk_sp<SkImage>> source_images = {base_image, gain_image};
|
||||
int max_texture_size = 0;
|
||||
if (auto* context = canvas->recordingContext()) {
|
||||
max_texture_size = context->maxTextureSize();
|
||||
} else if (auto* recorder = canvas->recorder()) {
|
||||
// TODO(b/279234024): Retrieve correct max texture size for graphite.
|
||||
max_texture_size = 8192;
|
||||
}
|
||||
skia::Tiling tiling(dest_rect_clipped, source_rects, source_images,
|
||||
max_texture_size);
|
||||
|
||||
// Draw tile-by-tile.
|
||||
for (int tx = 0; tx < tiling.GetTileCountX(); ++tx) {
|
||||
for (int ty = 0; ty < tiling.GetTileCountY(); ++ty) {
|
||||
// Retrieve the tile geometry.
|
||||
SkRect tile_dest_rect;
|
||||
std::vector<SkRect> tile_source_rects;
|
||||
std::vector<std::optional<SkIRect>> tile_subset_rects;
|
||||
tiling.GetTileRect(tx, ty, tile_dest_rect, tile_source_rects,
|
||||
tile_subset_rects);
|
||||
|
||||
// Extract the images' subsets (if needed).
|
||||
auto tile_source_images = source_images;
|
||||
for (size_t i = 0; i < 2; ++i) {
|
||||
if (!tile_subset_rects[i].has_value()) {
|
||||
continue;
|
||||
}
|
||||
if (tile_subset_rects[i]->isEmpty()) {
|
||||
tile_source_images[i] = nullptr;
|
||||
} else {
|
||||
tile_source_images[i] = tile_source_images[i]->makeSubset(
|
||||
nullptr, tile_subset_rects[i].value());
|
||||
}
|
||||
}
|
||||
|
||||
// Draw the tile.
|
||||
SkPaint tile_paint = paint;
|
||||
auto shader = SkGainmapShader::Make(
|
||||
tile_source_images[0], tile_source_rects[0], sampling,
|
||||
tile_source_images[1], tile_source_rects[1], sampling, gainmap_info,
|
||||
tile_dest_rect, hdr_headroom, canvas->imageInfo().refColorSpace());
|
||||
tile_paint.setShader(std::move(shader));
|
||||
canvas->drawRect(tile_dest_rect, tile_paint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DrawGainmapImage(SkCanvas* canvas,
|
||||
sk_sp<SkImage> base_image,
|
||||
sk_sp<SkImage> gainmap_image,
|
||||
const SkGainmapInfo& gainmap_info,
|
||||
float hdr_headroom,
|
||||
SkScalar left,
|
||||
SkScalar top,
|
||||
const SkSamplingOptions& sampling,
|
||||
const SkPaint& paint) {
|
||||
SkRect source_rect = SkRect::Make(base_image->bounds());
|
||||
SkRect dest_rect =
|
||||
SkRect::MakeXYWH(left, top, base_image->width(), base_image->height());
|
||||
DrawGainmapImageRect(canvas, std::move(base_image), std::move(gainmap_image),
|
||||
gainmap_info, hdr_headroom, source_rect, dest_rect,
|
||||
sampling, paint);
|
||||
}
|
||||
|
||||
} // namespace skia
|
44
skia/ext/draw_gainmap_image.h
Normal file
44
skia/ext/draw_gainmap_image.h
Normal file
@ -0,0 +1,44 @@
|
||||
// Copyright 2024 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SKIA_EXT_DRAW_GAINMAP_IMAGE_H_
|
||||
#define SKIA_EXT_DRAW_GAINMAP_IMAGE_H_
|
||||
|
||||
#include "skia/config/SkUserConfig.h"
|
||||
#include "third_party/skia/include/core/SkImage.h"
|
||||
|
||||
struct SkGainmapInfo;
|
||||
class SkCanvas;
|
||||
class SkImage;
|
||||
class SkPaint;
|
||||
|
||||
namespace skia {
|
||||
|
||||
// Function to perform the equivalent of SkCanvas::drawImageRect for a gainmap
|
||||
// image. This will tile `base_image` and `gainmap_image` as needed.
|
||||
SK_API void DrawGainmapImageRect(SkCanvas* canvas,
|
||||
sk_sp<SkImage> base_image,
|
||||
sk_sp<SkImage> gainmap_image,
|
||||
const SkGainmapInfo& gainmap_info,
|
||||
float hdr_headroom,
|
||||
const SkRect& source_rect,
|
||||
const SkRect& dest_rect,
|
||||
const SkSamplingOptions& sampling,
|
||||
const SkPaint& paint);
|
||||
|
||||
// Function to perform the equivalent of SkCanvas::drawImage for a gainmap
|
||||
// image. This will tile `base_image` and `gainmap_image` as needed.
|
||||
SK_API void DrawGainmapImage(SkCanvas* canvas,
|
||||
sk_sp<SkImage> base_image,
|
||||
sk_sp<SkImage> gainmap_image,
|
||||
const SkGainmapInfo& gainmap_info,
|
||||
float hdr_headroom,
|
||||
SkScalar left,
|
||||
SkScalar top,
|
||||
const SkSamplingOptions& sampling,
|
||||
const SkPaint& paint);
|
||||
|
||||
} // namespace skia
|
||||
|
||||
#endif // SKIA_EXT_DRAW_GAINMAP_IMAGE_H_
|
130
skia/ext/geometry.cc
Normal file
130
skia/ext/geometry.cc
Normal file
@ -0,0 +1,130 @@
|
||||
// Copyright 2024 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "skia/ext/geometry.h"
|
||||
|
||||
#include "third_party/skia/include/core/SkMatrix.h"
|
||||
#include "third_party/skia/include/core/SkRect.h"
|
||||
|
||||
namespace skia {
|
||||
|
||||
SkRect ScaleSkRectProportional(const SkRect& output_bounds,
|
||||
const SkRect& input_bounds,
|
||||
const SkRect& input_rect) {
|
||||
float scale_x = output_bounds.width() / input_bounds.width();
|
||||
float scale_y = output_bounds.height() / input_bounds.height();
|
||||
return SkRect::MakeLTRB(
|
||||
(input_rect.fLeft - input_bounds.fLeft) * scale_x + output_bounds.fLeft,
|
||||
(input_rect.fTop - input_bounds.fTop) * scale_y + output_bounds.fTop,
|
||||
(input_rect.fRight - input_bounds.fRight) * scale_x +
|
||||
output_bounds.fRight,
|
||||
(input_rect.fBottom - input_bounds.fBottom) * scale_y +
|
||||
output_bounds.fBottom);
|
||||
}
|
||||
|
||||
Tiling::Tiling(const SkRect& dest_rect,
|
||||
std::vector<SkRect> source_rects,
|
||||
std::vector<sk_sp<SkImage>> source_images,
|
||||
int32_t source_max_size)
|
||||
: dest_rect_(dest_rect),
|
||||
source_count_(source_rects.size()),
|
||||
source_rects_(std::move(source_rects)),
|
||||
source_images_(std::move(source_images)) {
|
||||
if (source_max_size == 0) {
|
||||
// A maximum size of 0 is specified for software rasterization.
|
||||
tile_size_ = SkSize::Make(dest_rect_.width(), dest_rect_.height());
|
||||
} else {
|
||||
// Compute the amount by which `dest_rect_` would need to be scaled down to
|
||||
// ensure all of `source_rects` fit in `source_max_size`.
|
||||
float tile_dest_width = dest_rect_.width();
|
||||
float tile_dest_height = dest_rect_.height();
|
||||
for (size_t i = 0; i < source_count_; ++i) {
|
||||
// Source images that are already textures don't need to be tiled for
|
||||
// upload.
|
||||
if (source_images_[i]->isTextureBacked()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Find the dest tile width and height that corresponds to sampling
|
||||
// `source_max_size` pixels. In this computation, add a padding of 2
|
||||
// pixels to the source rect, to account for sampling pixels outside
|
||||
// of the rect.
|
||||
float kPadding = 2.f;
|
||||
tile_dest_width = std::min(
|
||||
tile_dest_width,
|
||||
dest_rect_.width() *
|
||||
(source_max_size / (source_rects_[i].width() + kPadding)));
|
||||
tile_dest_height = std::min(
|
||||
tile_dest_height,
|
||||
dest_rect_.height() *
|
||||
(source_max_size / (source_rects_[i].height() + kPadding)));
|
||||
}
|
||||
|
||||
// Prefer integer-sized tiles, so round down to the nearest integer, unless
|
||||
// that would take us to zero.
|
||||
if (tile_dest_width > 1.f) {
|
||||
tile_dest_width = std::floor(tile_dest_width);
|
||||
}
|
||||
if (tile_dest_height > 1.f) {
|
||||
tile_dest_height = std::floor(tile_dest_height);
|
||||
}
|
||||
|
||||
tile_size_ = SkSize::Make(tile_dest_width, tile_dest_height);
|
||||
}
|
||||
tile_count_ =
|
||||
SkISize::Make(std::ceil(dest_rect_.width() / tile_size_.width()),
|
||||
std::ceil(dest_rect_.height() / tile_size_.height()));
|
||||
}
|
||||
|
||||
Tiling::~Tiling() = default;
|
||||
|
||||
void Tiling::GetTileRect(
|
||||
int x,
|
||||
int y,
|
||||
SkRect& tile_dest_rect,
|
||||
std::vector<SkRect>& tile_source_rects,
|
||||
std::vector<std::optional<SkIRect>>& tile_source_subset_rects) {
|
||||
// Compute the destination tile rect.
|
||||
tile_dest_rect =
|
||||
SkRect::MakeLTRB(dest_rect_.x() + x * tile_size_.width(),
|
||||
dest_rect_.y() + y * tile_size_.height(),
|
||||
dest_rect_.x() + (x + 1) * tile_size_.width(),
|
||||
dest_rect_.y() + (y + 1) * tile_size_.height());
|
||||
tile_dest_rect.intersect(dest_rect_);
|
||||
|
||||
tile_source_rects.resize(source_count_);
|
||||
tile_source_subset_rects.resize(source_count_);
|
||||
for (size_t i = 0; i < source_rects_.size(); ++i) {
|
||||
// Compute the tile's source rect to have the same relationship to the
|
||||
// source rect as the tile's dest rect has to the dest rect.
|
||||
SkRect source_rect =
|
||||
ScaleSkRectProportional(source_rects_[i], dest_rect_, tile_dest_rect);
|
||||
|
||||
const auto& image = source_images_[i];
|
||||
|
||||
// If the image is texture-backed, then use it directly.
|
||||
if (image->isTextureBacked()) {
|
||||
tile_source_rects[i] = source_rect;
|
||||
tile_source_subset_rects[i] = std::nullopt;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise, find the subset of the image that could be sampled, and report
|
||||
// that subset rectangle and the source rectangle to sample it.
|
||||
SkIRect subset_rect = SkIRect::MakeEmpty();
|
||||
source_rect.roundOut(&subset_rect);
|
||||
if (subset_rect.intersect(image->bounds())) {
|
||||
source_rect =
|
||||
skia::ScaleSkRectProportional(SkRect::Make(subset_rect.size()),
|
||||
SkRect::Make(subset_rect), source_rect);
|
||||
} else {
|
||||
subset_rect = SkIRect::MakeEmpty();
|
||||
source_rect = SkRect::MakeEmpty();
|
||||
}
|
||||
tile_source_rects[i] = source_rect;
|
||||
tile_source_subset_rects[i] = subset_rect;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace skia
|
77
skia/ext/geometry.h
Normal file
77
skia/ext/geometry.h
Normal file
@ -0,0 +1,77 @@
|
||||
// Copyright 2024 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SKIA_EXT_GEOMETRY_H_
|
||||
#define SKIA_EXT_GEOMETRY_H_
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "skia/config/SkUserConfig.h"
|
||||
#include "third_party/skia/include/core/SkImage.h"
|
||||
#include "third_party/skia/include/core/SkRect.h"
|
||||
#include "third_party/skia/include/core/SkSize.h"
|
||||
|
||||
namespace skia {
|
||||
|
||||
// Returns a rect that has the same relationship to `output_bounds` as
|
||||
// `input_rect` has to `input_bounds`. The only requirement for valid
|
||||
// results is that `output_bounds` and `input_bounds` be non-empty.
|
||||
SK_API SkRect ScaleSkRectProportional(const SkRect& output_bounds,
|
||||
const SkRect& input_bounds,
|
||||
const SkRect& input_rect);
|
||||
|
||||
// Helper structure for tiling a shader that has several input SkImages.
|
||||
// In order for a shader executing in an accelerated context to sample
|
||||
// from an SkImage, the SkImage must be upload-able to a texture. If
|
||||
// an SkImage is too large to fit in a texture, then the draw call using
|
||||
// the shader must be chopped multiple tiled draw calls.
|
||||
class SK_API Tiling {
|
||||
public:
|
||||
// Create a tiling for sampling `source_rects` when writing to
|
||||
// `dest_rect`. This will split `dest_rect` into tiles such that the
|
||||
// corresponding rect of each of `source_rects` will not have width
|
||||
// or height greater than `source_max_size`.
|
||||
Tiling(const SkRect& dest_rect,
|
||||
std::vector<SkRect> source_rects,
|
||||
std::vector<sk_sp<SkImage>> source_images,
|
||||
int32_t source_max_size);
|
||||
~Tiling();
|
||||
|
||||
// Return the number of horizontal and vertical tiles in the tiling.
|
||||
int GetTileCountX() const { return tile_count_.width(); }
|
||||
int GetTileCountY() const { return tile_count_.height(); }
|
||||
|
||||
// For the tile at the specified coordiantes, populate:
|
||||
// - `tile_dest_rect` with the destination rect for the tile
|
||||
// - `tile_source_rects` with the source rect for the tile
|
||||
// - `tile_subset_rects` with the subset of the source image to use. If no
|
||||
// subset of the image should be taken then it will be empty.
|
||||
void GetTileRect(
|
||||
int x,
|
||||
int y,
|
||||
SkRect& tile_dest_rect,
|
||||
std::vector<SkRect>& tile_source_rects,
|
||||
std::vector<std::optional<SkIRect>>& tile_source_subset_rects);
|
||||
|
||||
private:
|
||||
// The destination rectangle for the drawRect call.
|
||||
SkRect dest_rect_;
|
||||
|
||||
// The length of both `source_rects_` and `source_images_`.
|
||||
size_t source_count_ = 0;
|
||||
|
||||
// The input rects that `source_images_` are sampled from.
|
||||
std::vector<SkRect> source_rects_;
|
||||
|
||||
// The input images being sampled.
|
||||
std::vector<sk_sp<SkImage>> source_images_;
|
||||
|
||||
// The computed size of the tiles and number of tiles.
|
||||
SkSize tile_size_;
|
||||
SkISize tile_count_;
|
||||
};
|
||||
|
||||
} // namespace skia
|
||||
|
||||
#endif // SKIA_EXT_GEOMETRY_H_
|
296
skia/ext/geometry_unittest.cc
Normal file
296
skia/ext/geometry_unittest.cc
Normal file
@ -0,0 +1,296 @@
|
||||
// Copyright 2024 The Chromium Authors
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "skia/ext/geometry.h"
|
||||
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "third_party/skia/include/core/SkBitmap.h"
|
||||
|
||||
namespace skia {
|
||||
|
||||
namespace {
|
||||
|
||||
TEST(Geometry, ScaleRectProportional) {
|
||||
SkRect output_rect;
|
||||
|
||||
output_rect = ScaleSkRectProportional(SkRect::MakeXYWH(0, 0, 20, 20),
|
||||
SkRect::MakeXYWH(0, 0, 40, 40),
|
||||
SkRect::MakeXYWH(10, 10, 20, 20));
|
||||
EXPECT_EQ(output_rect, SkRect::MakeXYWH(5, 5, 10, 10));
|
||||
|
||||
output_rect = ScaleSkRectProportional(SkRect::MakeXYWH(0, 10, 20, 80),
|
||||
SkRect::MakeXYWH(10, 20, 40, 80),
|
||||
SkRect::MakeXYWH(0, 30, 60, 20));
|
||||
EXPECT_EQ(output_rect, SkRect::MakeXYWH(-5, 20, 30, 20));
|
||||
}
|
||||
|
||||
TEST(Geometry, TileSimple) {
|
||||
SkBitmap bm0;
|
||||
SkBitmap bm1;
|
||||
|
||||
// Simple tiling that divides the region into two tiles.
|
||||
bm0.allocPixels(SkImageInfo::MakeN32Premul(20, 40));
|
||||
bm1.allocPixels(SkImageInfo::MakeN32Premul(10, 20));
|
||||
SkRect dest_rect = SkRect::MakeXYWH(10, 10, 20, 40);
|
||||
std::vector<SkRect> source_rects = {
|
||||
SkRect::MakeXYWH(0, 0, 20, 40),
|
||||
SkRect::MakeXYWH(0, 0, 10, 20),
|
||||
};
|
||||
std::vector<sk_sp<SkImage>> source_images = {
|
||||
SkImages::RasterFromBitmap(bm0),
|
||||
SkImages::RasterFromBitmap(bm1),
|
||||
};
|
||||
int32_t source_max_size = 22;
|
||||
|
||||
Tiling t(dest_rect, source_rects, source_images, source_max_size);
|
||||
EXPECT_EQ(1, t.GetTileCountX());
|
||||
EXPECT_EQ(2, t.GetTileCountY());
|
||||
|
||||
SkRect tile_dest_rect;
|
||||
std::vector<SkRect> tile_source_rects;
|
||||
std::vector<std::optional<SkIRect>> tile_subset_rects;
|
||||
|
||||
t.GetTileRect(0, 0, tile_dest_rect, tile_source_rects, tile_subset_rects);
|
||||
EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(10, 10, 20, 20));
|
||||
EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 20, 20));
|
||||
EXPECT_EQ(tile_source_rects[1], SkRect::MakeXYWH(0, 0, 10, 10));
|
||||
EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(0, 0, 20, 20));
|
||||
EXPECT_EQ(tile_subset_rects[1].value(), SkIRect::MakeXYWH(0, 0, 10, 10));
|
||||
|
||||
t.GetTileRect(0, 1, tile_dest_rect, tile_source_rects, tile_subset_rects);
|
||||
EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(10, 30, 20, 20));
|
||||
EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 20, 20));
|
||||
EXPECT_EQ(tile_source_rects[1], SkRect::MakeXYWH(0, 0, 10, 10));
|
||||
EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(0, 20, 20, 20));
|
||||
EXPECT_EQ(tile_subset_rects[1].value(), SkIRect::MakeXYWH(0, 10, 10, 10));
|
||||
}
|
||||
|
||||
TEST(Geometry, TileSmallX) {
|
||||
SkBitmap bm0;
|
||||
|
||||
// A tiling that has very different width and height.
|
||||
bm0.allocPixels(SkImageInfo::MakeN32Premul(5, 50));
|
||||
SkRect dest_rect = SkRect::MakeXYWH(0, 0, 5, 50);
|
||||
std::vector<SkRect> source_rects = {
|
||||
SkRect::MakeXYWH(0, 0, 5, 50),
|
||||
};
|
||||
std::vector<sk_sp<SkImage>> source_images = {
|
||||
SkImages::RasterFromBitmap(bm0),
|
||||
};
|
||||
int32_t source_max_size = 21;
|
||||
|
||||
Tiling t(dest_rect, source_rects, source_images, source_max_size);
|
||||
EXPECT_EQ(1, t.GetTileCountX());
|
||||
EXPECT_EQ(3, t.GetTileCountY());
|
||||
|
||||
SkRect tile_dest_rect;
|
||||
std::vector<SkRect> tile_source_rects;
|
||||
std::vector<std::optional<SkIRect>> tile_subset_rects;
|
||||
|
||||
t.GetTileRect(0, 0, tile_dest_rect, tile_source_rects, tile_subset_rects);
|
||||
EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(0, 0, 5, 20));
|
||||
EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 5, 20));
|
||||
EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(0, 0, 5, 20));
|
||||
|
||||
t.GetTileRect(0, 1, tile_dest_rect, tile_source_rects, tile_subset_rects);
|
||||
EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(0, 20, 5, 20));
|
||||
EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 5, 20));
|
||||
EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(0, 20, 5, 20));
|
||||
|
||||
t.GetTileRect(0, 2, tile_dest_rect, tile_source_rects, tile_subset_rects);
|
||||
EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(0, 40, 5, 10));
|
||||
EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 5, 10));
|
||||
EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(0, 40, 5, 10));
|
||||
}
|
||||
|
||||
TEST(Geometry, TileSmallY) {
|
||||
SkBitmap bm0;
|
||||
|
||||
// A tiling that has very different width and height.
|
||||
bm0.allocPixels(SkImageInfo::MakeN32Premul(50, 5));
|
||||
SkRect dest_rect = SkRect::MakeXYWH(0, 0, 50, 5);
|
||||
std::vector<SkRect> source_rects = {
|
||||
SkRect::MakeXYWH(0, 0, 50, 5),
|
||||
};
|
||||
std::vector<sk_sp<SkImage>> source_images = {
|
||||
SkImages::RasterFromBitmap(bm0),
|
||||
};
|
||||
int32_t source_max_size = 21;
|
||||
|
||||
Tiling t(dest_rect, source_rects, source_images, source_max_size);
|
||||
EXPECT_EQ(3, t.GetTileCountX());
|
||||
EXPECT_EQ(1, t.GetTileCountY());
|
||||
|
||||
SkRect tile_dest_rect;
|
||||
std::vector<SkRect> tile_source_rects;
|
||||
std::vector<std::optional<SkIRect>> tile_subset_rects;
|
||||
|
||||
t.GetTileRect(0, 0, tile_dest_rect, tile_source_rects, tile_subset_rects);
|
||||
EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(0, 0, 20, 5));
|
||||
EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 20, 5));
|
||||
EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(0, 0, 20, 5));
|
||||
|
||||
t.GetTileRect(1, 0, tile_dest_rect, tile_source_rects, tile_subset_rects);
|
||||
EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(20, 0, 20, 5));
|
||||
EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 20, 5));
|
||||
EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(20, 0, 20, 5));
|
||||
|
||||
t.GetTileRect(2, 0, tile_dest_rect, tile_source_rects, tile_subset_rects);
|
||||
EXPECT_EQ(tile_dest_rect, SkRect::MakeXYWH(40, 0, 10, 5));
|
||||
EXPECT_EQ(tile_source_rects[0], SkRect::MakeXYWH(0, 0, 10, 5));
|
||||
EXPECT_EQ(tile_subset_rects[0].value(), SkIRect::MakeXYWH(40, 0, 10, 5));
|
||||
}
|
||||
|
||||
TEST(Geometry, Overflow) {
|
||||
SkBitmap bm0;
|
||||
SkBitmap bm1;
|
||||
|
||||
// The tiling will overflow the bounds of bm1. Ensure that its subsets
|
||||
// are set to empty instead of overflowing values.
|
||||
bm0.allocPixels(SkImageInfo::MakeN32Premul(40, 80));
|
||||
bm1.allocPixels(SkImageInfo::MakeN32Premul(10, 20));
|
||||
SkRect dest_rect = SkRect::MakeXYWH(10, 10, 20, 40);
|
||||
std::vector<SkRect> source_rects = {
|
||||
SkRect::MakeXYWH(10, 10, 20, 40),
|
||||
SkRect::MakeXYWH(5, 5, 10, 20),
|
||||
};
|
||||
std::vector<sk_sp<SkImage>> source_images = {
|
||||
SkImages::RasterFromBitmap(bm0),
|
||||
SkImages::RasterFromBitmap(bm1),
|
||||
};
|
||||
int32_t source_max_size = 15;
|
||||
|
||||
Tiling t(dest_rect, source_rects, source_images, source_max_size);
|
||||
|
||||
constexpr int kTilesX = 2;
|
||||
constexpr int kTilesY = 3;
|
||||
EXPECT_EQ(kTilesX, t.GetTileCountX());
|
||||
EXPECT_EQ(kTilesY, t.GetTileCountY());
|
||||
|
||||
// Save the output from all tiles for comparison.
|
||||
SkRect t_dest[kTilesX][kTilesY];
|
||||
SkRect t_base[kTilesX][kTilesY];
|
||||
SkRect t_gain[kTilesX][kTilesY];
|
||||
SkIRect s_base[kTilesX][kTilesY];
|
||||
SkIRect s_gain[kTilesX][kTilesY];
|
||||
|
||||
for (int x = 0; x < kTilesX; ++x) {
|
||||
for (int y = 0; y < kTilesY; ++y) {
|
||||
SkRect tile_dest_rect;
|
||||
std::vector<SkRect> tile_source_rects;
|
||||
std::vector<std::optional<SkIRect>> tile_subset_rects;
|
||||
t.GetTileRect(x, y, tile_dest_rect, tile_source_rects, tile_subset_rects);
|
||||
t_dest[x][y] = tile_dest_rect;
|
||||
t_base[x][y] = tile_source_rects[0];
|
||||
t_gain[x][y] = tile_source_rects[1];
|
||||
s_base[x][y] = tile_subset_rects[0].value();
|
||||
s_gain[x][y] = tile_subset_rects[1].value();
|
||||
}
|
||||
}
|
||||
|
||||
EXPECT_EQ(t_dest[0][0], SkRect::MakeLTRB(10, 10, 23, 24));
|
||||
EXPECT_EQ(t_dest[0][1], SkRect::MakeLTRB(10, 24, 23, 38));
|
||||
EXPECT_EQ(t_dest[0][2], SkRect::MakeLTRB(10, 38, 23, 50));
|
||||
EXPECT_EQ(t_dest[1][0], SkRect::MakeLTRB(23, 10, 30, 24));
|
||||
EXPECT_EQ(t_dest[1][1], SkRect::MakeLTRB(23, 24, 30, 38));
|
||||
EXPECT_EQ(t_dest[1][2], SkRect::MakeLTRB(23, 38, 30, 50));
|
||||
|
||||
EXPECT_EQ(t_base[0][0], SkRect::MakeLTRB(0, 0, 13, 14));
|
||||
EXPECT_EQ(t_base[0][1], SkRect::MakeLTRB(0, 0, 13, 14));
|
||||
EXPECT_EQ(t_base[0][2], SkRect::MakeLTRB(0, 0, 13, 12));
|
||||
EXPECT_EQ(t_base[1][0], SkRect::MakeLTRB(0, 0, 7, 14));
|
||||
EXPECT_EQ(t_base[1][1], SkRect::MakeLTRB(0, 0, 7, 14));
|
||||
EXPECT_EQ(t_base[1][2], SkRect::MakeLTRB(0, 0, 7, 12));
|
||||
|
||||
EXPECT_EQ(t_gain[0][0], SkRect::MakeLTRB(0, 0, 6.5, 7));
|
||||
EXPECT_EQ(t_gain[0][1], SkRect::MakeLTRB(0, 0, 6.5, 7));
|
||||
EXPECT_EQ(t_gain[0][2], SkRect::MakeLTRB(0, 0, 6.5, 6));
|
||||
EXPECT_EQ(t_gain[1][0], SkRect::MakeEmpty());
|
||||
EXPECT_EQ(t_gain[1][1], SkRect::MakeEmpty());
|
||||
EXPECT_EQ(t_gain[1][2], SkRect::MakeEmpty());
|
||||
|
||||
EXPECT_EQ(s_base[0][0], SkIRect::MakeLTRB(10, 10, 23, 24));
|
||||
EXPECT_EQ(s_base[0][1], SkIRect::MakeLTRB(10, 24, 23, 38));
|
||||
EXPECT_EQ(s_base[0][2], SkIRect::MakeLTRB(10, 38, 23, 50));
|
||||
EXPECT_EQ(s_base[1][0], SkIRect::MakeLTRB(23, 10, 30, 24));
|
||||
EXPECT_EQ(s_base[1][1], SkIRect::MakeLTRB(23, 24, 30, 38));
|
||||
EXPECT_EQ(s_base[1][2], SkIRect::MakeLTRB(23, 38, 30, 50));
|
||||
|
||||
EXPECT_EQ(s_gain[0][0], SkIRect::MakeLTRB(5, 5, 10, 12));
|
||||
EXPECT_EQ(s_gain[0][1], SkIRect::MakeLTRB(5, 12, 10, 19));
|
||||
EXPECT_EQ(s_gain[0][2], SkIRect::MakeLTRB(5, 19, 10, 20));
|
||||
EXPECT_EQ(s_gain[1][0], SkIRect::MakeEmpty());
|
||||
EXPECT_EQ(s_gain[1][1], SkIRect::MakeEmpty());
|
||||
EXPECT_EQ(s_gain[1][2], SkIRect::MakeEmpty());
|
||||
}
|
||||
|
||||
TEST(Geometry, Fractional) {
|
||||
SkBitmap bm0;
|
||||
SkBitmap bm1;
|
||||
|
||||
// Test where the source rects are not pixel-aligned.
|
||||
bm0.allocPixels(SkImageInfo::MakeN32Premul(30, 50));
|
||||
SkRect dest_rect = SkRect::MakeXYWH(0, 0, 20, 40);
|
||||
std::vector<SkRect> source_rects = {
|
||||
SkRect::MakeXYWH(5.5, 5.5, 20, 40),
|
||||
};
|
||||
std::vector<sk_sp<SkImage>> source_images = {
|
||||
SkImages::RasterFromBitmap(bm0),
|
||||
};
|
||||
int32_t source_max_size = 20;
|
||||
|
||||
Tiling t(dest_rect, source_rects, source_images, source_max_size);
|
||||
|
||||
constexpr int kTilesX = 2;
|
||||
constexpr int kTilesY = 3;
|
||||
EXPECT_EQ(kTilesX, t.GetTileCountX());
|
||||
EXPECT_EQ(kTilesY, t.GetTileCountY());
|
||||
|
||||
// Save the output from all tiles for comparison.
|
||||
SkRect out_dest[kTilesX][kTilesY];
|
||||
SkRect out_source[kTilesX][kTilesY];
|
||||
SkIRect out_subset[kTilesX][kTilesY];
|
||||
|
||||
for (int x = 0; x < kTilesX; ++x) {
|
||||
for (int y = 0; y < kTilesY; ++y) {
|
||||
SkRect tile_dest_rect;
|
||||
std::vector<SkRect> tile_source_rects;
|
||||
std::vector<std::optional<SkIRect>> tile_subset_rects;
|
||||
t.GetTileRect(x, y, tile_dest_rect, tile_source_rects, tile_subset_rects);
|
||||
out_dest[x][y] = tile_dest_rect;
|
||||
out_source[x][y] = tile_source_rects[0];
|
||||
out_subset[x][y] = tile_subset_rects[0].value();
|
||||
}
|
||||
}
|
||||
|
||||
// The subsets for the image will be overlapping (e.g, the
|
||||
// first entry ends at Y=25, then the next one starts at
|
||||
// Y=24).
|
||||
EXPECT_EQ(out_subset[0][0], SkIRect::MakeLTRB(5, 5, 24, 25));
|
||||
EXPECT_EQ(out_subset[0][1], SkIRect::MakeLTRB(5, 24, 24, 44));
|
||||
EXPECT_EQ(out_subset[0][2], SkIRect::MakeLTRB(5, 43, 24, 46));
|
||||
EXPECT_EQ(out_subset[1][0], SkIRect::MakeLTRB(23, 5, 26, 25));
|
||||
EXPECT_EQ(out_subset[1][1], SkIRect::MakeLTRB(23, 24, 26, 44));
|
||||
EXPECT_EQ(out_subset[1][2], SkIRect::MakeLTRB(23, 43, 26, 46));
|
||||
|
||||
// All of the sources are aligned to a half-pixel.
|
||||
EXPECT_EQ(out_source[0][0], SkRect::MakeLTRB(0.5, 0.5, 18.5, 19.5));
|
||||
EXPECT_EQ(out_source[0][1], SkRect::MakeLTRB(0.5, 0.5, 18.5, 19.5));
|
||||
EXPECT_EQ(out_source[0][2], SkRect::MakeLTRB(0.5, 0.5, 18.5, 2.5));
|
||||
EXPECT_EQ(out_source[1][0], SkRect::MakeLTRB(0.5, 0.5, 2.5, 19.5));
|
||||
EXPECT_EQ(out_source[1][1], SkRect::MakeLTRB(0.5, 0.5, 2.5, 19.5));
|
||||
EXPECT_EQ(out_source[1][2], SkRect::MakeLTRB(0.5, 0.5, 2.5, 2.5));
|
||||
|
||||
// The destination tiles cover `dest_rect`.
|
||||
EXPECT_EQ(out_dest[0][0], SkRect::MakeLTRB(0, 0, 18, 19));
|
||||
EXPECT_EQ(out_dest[0][1], SkRect::MakeLTRB(0, 19, 18, 38));
|
||||
EXPECT_EQ(out_dest[0][2], SkRect::MakeLTRB(0, 38, 18, 40));
|
||||
EXPECT_EQ(out_dest[1][0], SkRect::MakeLTRB(18, 0, 20, 19));
|
||||
EXPECT_EQ(out_dest[1][1], SkRect::MakeLTRB(18, 19, 20, 38));
|
||||
EXPECT_EQ(out_dest[1][2], SkRect::MakeLTRB(18, 38, 20, 40));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
} // namespace skia
|
Reference in New Issue
Block a user