0

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:
Christopher Cameron
2024-04-02 23:26:48 +00:00
committed by Chromium LUCI CQ
parent da3f998168
commit aad1e4596d
7 changed files with 672 additions and 48 deletions

@ -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",

@ -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

@ -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

@ -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

@ -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_

@ -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