1 /*
2 * Copyright 2023 Google LLC
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "include/core/SkBitmap.h"
9 #include "include/core/SkBlendMode.h"
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkClipOp.h"
12 #include "include/core/SkColor.h"
13 #include "include/core/SkColorFilter.h"
14 #include "include/core/SkColorSpace.h"
15 #include "include/core/SkColorType.h"
16 #include "include/core/SkData.h"
17 #include "include/core/SkImage.h"
18 #include "include/core/SkImageInfo.h"
19 #include "include/core/SkMatrix.h"
20 #include "include/core/SkPaint.h"
21 #include "include/core/SkPoint.h"
22 #include "include/core/SkRect.h"
23 #include "include/core/SkRefCnt.h"
24 #include "include/core/SkSamplingOptions.h"
25 #include "include/core/SkScalar.h"
26 #include "include/core/SkSize.h"
27 #include "include/core/SkString.h"
28 #include "include/core/SkTileMode.h"
29 #include "include/private/SkColorData.h"
30 #include "include/private/base/SkAssert.h"
31 #include "include/private/base/SkDebug.h"
32 #include "include/private/base/SkTArray.h"
33 #include "include/private/base/SkTo.h"
34 #include "src/core/SkDevice.h"
35 #include "src/core/SkImageFilterTypes.h"
36 #include "src/core/SkMatrixPriv.h"
37 #include "src/core/SkRectPriv.h"
38 #include "src/core/SkSpecialImage.h"
39 #include "src/effects/colorfilters/SkColorFilterBase.h"
40 #include "src/gpu/ganesh/image/GrImageUtils.h"
41 #include "tests/CtsEnforcement.h"
42 #include "tests/Test.h"
43 #include "tools/EncodeUtils.h"
44 #include "tools/gpu/ContextType.h"
45
46 #include <cmath>
47 #include <initializer_list>
48 #include <optional>
49 #include <string>
50 #include <utility>
51 #include <variant>
52 #include <vector>
53
54
55 #if defined(SK_GRAPHITE)
56 #include "include/gpu/graphite/Context.h"
57 #include "src/gpu/graphite/ContextPriv.h"
58 #include "src/gpu/graphite/RecorderPriv.h"
59 #include "src/gpu/graphite/SpecialImage_Graphite.h"
60 #include "src/gpu/graphite/TextureProxyView.h"
61 #include "src/gpu/graphite/TextureUtils.h"
62 #endif
63
64
65 #if defined(SK_GANESH)
66 #include "include/gpu/ganesh/GrDirectContext.h"
67 #include "include/gpu/ganesh/GrRecordingContext.h"
68 #include "include/gpu/ganesh/GrTypes.h"
69 struct GrContextOptions;
70 #endif
71
72 class SkShader;
73
74 using namespace skia_private;
75 using namespace skif;
76
77 // NOTE: Not in anonymous so that FilterResult can friend it
78 class FilterResultTestAccess {
79 using BoundsAnalysis = FilterResult::BoundsAnalysis;
80 public:
Draw(const skif::Context & ctx,SkDevice * device,const skif::FilterResult & image,bool preserveDeviceState)81 static void Draw(const skif::Context& ctx,
82 SkDevice* device,
83 const skif::FilterResult& image,
84 bool preserveDeviceState) {
85 image.draw(ctx, device, preserveDeviceState, /*blender=*/nullptr);
86 }
87
AsShader(const skif::Context & ctx,const skif::FilterResult & image,const skif::LayerSpace<SkIRect> & sampleBounds)88 static sk_sp<SkShader> AsShader(const skif::Context& ctx,
89 const skif::FilterResult& image,
90 const skif::LayerSpace<SkIRect>& sampleBounds) {
91 return image.asShader(ctx, FilterResult::kDefaultSampling,
92 FilterResult::ShaderFlags::kNone, sampleBounds);
93 }
94
StrictShader(const skif::Context & ctx,const skif::FilterResult & image)95 static sk_sp<SkShader> StrictShader(const skif::Context& ctx,
96 const skif::FilterResult& image) {
97 auto analysis = image.analyzeBounds(ctx.desiredOutput());
98 if (analysis & FilterResult::BoundsAnalysis::kRequiresLayerCrop) {
99 // getAnalyzedShaderView() doesn't include the layer crop, this will be handled by
100 // the FilterResultImageResolver.
101 return nullptr;
102 } else {
103 // Add flags to ensure no deferred effects or clamping logic are optimized away.
104 analysis |= BoundsAnalysis::kDstBoundsNotCovered;
105 analysis |= BoundsAnalysis::kRequiresShaderTiling;
106 if (image.tileMode() == SkTileMode::kDecal) {
107 analysis |= BoundsAnalysis::kRequiresDecalInLayerSpace;
108 }
109 return image.getAnalyzedShaderView(ctx, image.sampling(), analysis);
110 }
111 }
112
Rescale(const skif::Context & ctx,const skif::FilterResult & image,const skif::LayerSpace<SkSize> scale)113 static skif::FilterResult Rescale(const skif::Context& ctx,
114 const skif::FilterResult& image,
115 const skif::LayerSpace<SkSize> scale) {
116 return image.rescale(ctx, scale, /*enforceDecal=*/false);
117 }
118
TrackStats(skif::Context * ctx,skif::Stats * stats)119 static void TrackStats(skif::Context* ctx, skif::Stats* stats) {
120 ctx->fStats = stats;
121 }
122
IsIntegerTransform(const skif::FilterResult & image)123 static bool IsIntegerTransform(const skif::FilterResult& image) {
124 SkMatrix m = SkMatrix(image.fTransform);
125 return m.isTranslate() &&
126 SkScalarIsInt(m.getTranslateX()) &&
127 SkScalarIsInt(m.getTranslateY());
128 }
129
DeferredScaleFactors(const skif::FilterResult & image)130 static std::optional<std::pair<float, float>> DeferredScaleFactors(
131 const skif::FilterResult& image) {
132 float scaleFactors[2];
133 if (SkMatrix(image.fTransform).getMinMaxScales(scaleFactors)) {
134 return {{scaleFactors[0], scaleFactors[1]}};
135 } else {
136 return {};
137 }
138 }
139
140 enum class ShaderSampleMode {
141 kFast,
142 kShaderClamp,
143 kShaderTile
144 };
GetExpectedShaderSampleMode(const skif::Context & ctx,const skif::FilterResult & image,bool actionSupportsDirectDrawing)145 static ShaderSampleMode GetExpectedShaderSampleMode(const skif::Context& ctx,
146 const skif::FilterResult& image,
147 bool actionSupportsDirectDrawing) {
148 if (!image) {
149 return ShaderSampleMode::kFast;
150 }
151 auto analysis = image.analyzeBounds(ctx.desiredOutput());
152 bool mustFillDecal = image.tileMode() == SkTileMode::kDecal &&
153 (analysis & BoundsAnalysis::kDstBoundsNotCovered) &&
154 !actionSupportsDirectDrawing;
155 if ((analysis & BoundsAnalysis::kHasLayerFillingEffect) || mustFillDecal) {
156 // The image won't be drawn directly so some form of shader is needed. The faster clamp
157 // can be used when clamping explicitly or decal-with-transparent-padding.
158 if (image.tileMode() == SkTileMode::kClamp ||
159 (image.tileMode() == SkTileMode::kDecal &&
160 image.fBoundary == FilterResult::PixelBoundary::kTransparent)) {
161 return ShaderSampleMode::kShaderClamp;
162 } else {
163 // These cases should be covered by the more expensive shader tiling.
164 return ShaderSampleMode::kShaderTile;
165 }
166 }
167 // If we got here, it will be drawn directly but a clamp can be needed if the data outside
168 // the image is unknown and sampling might pull those values in accidentally.
169 if (image.fBoundary == FilterResult::PixelBoundary::kUnknown) {
170 return ShaderSampleMode::kShaderClamp;
171 } else {
172 return ShaderSampleMode::kFast;
173 }
174 }
175 };
176
177 namespace {
178
179 // Parameters controlling the fuzziness matching of expected and actual images.
180 // NOTE: When image fuzzy diffing fails it will print the expected image, the actual image, and
181 // an "error" image where all bad pixels have been set to red. You can select all three base64
182 // encoded PNGs, copy them, and run the following command to view in detail:
183 // xsel -o | viewer --file stdin
184
185 static constexpr float kRGBTolerance = 8.f / 255.f;
186 static constexpr float kAATolerance = 2.f / 255.f;
187 static constexpr float kDefaultMaxAllowedPercentImageDiff = 1.f;
188 static const float kFuzzyKernel[3][3] = {{0.9f, 0.9f, 0.9f},
189 {0.9f, 1.0f, 0.9f},
190 {0.9f, 0.9f, 0.9f}};
191 static_assert(std::size(kFuzzyKernel) == std::size(kFuzzyKernel[0]), "Kernel must be square");
192 static constexpr int kKernelSize = std::size(kFuzzyKernel);
193
194 static constexpr bool kLogAllBitmaps = false; // Spammy, recommend limiting test cases being run
195
colorfilter_equals(const SkColorFilter * actual,const SkColorFilter * expected)196 bool colorfilter_equals(const SkColorFilter* actual, const SkColorFilter* expected) {
197 if (!actual || !expected) {
198 return !actual && !expected; // both null
199 }
200 // The two filter objects are equal if they serialize to the same structure
201 sk_sp<SkData> actualData = actual->serialize();
202 sk_sp<SkData> expectedData = expected->serialize();
203 return actualData && actualData->equals(expectedData.get());
204 }
205
clear_device(SkDevice * device)206 void clear_device(SkDevice* device) {
207 SkPaint p;
208 p.setColor4f(SkColors::kTransparent, /*colorSpace=*/nullptr);
209 p.setBlendMode(SkBlendMode::kSrc);
210 device->drawPaint(p);
211 }
212
213 static constexpr SkTileMode kTileModes[4] = {SkTileMode::kClamp,
214 SkTileMode::kRepeat,
215 SkTileMode::kMirror,
216 SkTileMode::kDecal};
217
218 enum class Expect {
219 kDeferredImage, // i.e. modified properties of FilterResult instead of rendering
220 kNewImage, // i.e. rendered a new image before modifying other properties
221 kEmptyImage, // i.e. everything is transparent black
222 };
223
224 class ApplyAction {
225 struct TransformParams {
226 LayerSpace<SkMatrix> fMatrix;
227 SkSamplingOptions fSampling;
228 };
229 struct CropParams {
230 LayerSpace<SkIRect> fRect;
231 SkTileMode fTileMode;
232 // Sometimes the expected bounds due to cropping and tiling are too hard to automate with
233 // simple test code.
234 std::optional<LayerSpace<SkIRect>> fExpectedBounds;
235 };
236 struct RescaleParams {
237 LayerSpace<SkSize> fScale;
238 };
239
240 public:
ApplyAction(const SkMatrix & transform,const SkSamplingOptions & sampling,Expect expectation,const SkSamplingOptions & expectedSampling,SkTileMode expectedTileMode,sk_sp<SkColorFilter> expectedColorFilter)241 ApplyAction(const SkMatrix& transform,
242 const SkSamplingOptions& sampling,
243 Expect expectation,
244 const SkSamplingOptions& expectedSampling,
245 SkTileMode expectedTileMode,
246 sk_sp<SkColorFilter> expectedColorFilter)
247 : fAction{TransformParams{LayerSpace<SkMatrix>(transform), sampling}}
248 , fExpectation(expectation)
249 , fExpectedSampling(expectedSampling)
250 , fExpectedTileMode(expectedTileMode)
251 , fExpectedColorFilter(std::move(expectedColorFilter)) {}
252
ApplyAction(const SkIRect & cropRect,SkTileMode tileMode,std::optional<LayerSpace<SkIRect>> expectedBounds,Expect expectation,const SkSamplingOptions & expectedSampling,SkTileMode expectedTileMode,sk_sp<SkColorFilter> expectedColorFilter)253 ApplyAction(const SkIRect& cropRect,
254 SkTileMode tileMode,
255 std::optional<LayerSpace<SkIRect>> expectedBounds,
256 Expect expectation,
257 const SkSamplingOptions& expectedSampling,
258 SkTileMode expectedTileMode,
259 sk_sp<SkColorFilter> expectedColorFilter)
260 : fAction{CropParams{LayerSpace<SkIRect>(cropRect), tileMode, expectedBounds}}
261 , fExpectation(expectation)
262 , fExpectedSampling(expectedSampling)
263 , fExpectedTileMode(expectedTileMode)
264 , fExpectedColorFilter(std::move(expectedColorFilter)) {}
265
ApplyAction(sk_sp<SkColorFilter> colorFilter,Expect expectation,const SkSamplingOptions & expectedSampling,SkTileMode expectedTileMode,sk_sp<SkColorFilter> expectedColorFilter)266 ApplyAction(sk_sp<SkColorFilter> colorFilter,
267 Expect expectation,
268 const SkSamplingOptions& expectedSampling,
269 SkTileMode expectedTileMode,
270 sk_sp<SkColorFilter> expectedColorFilter)
271 : fAction(std::move(colorFilter))
272 , fExpectation(expectation)
273 , fExpectedSampling(expectedSampling)
274 , fExpectedTileMode(expectedTileMode)
275 , fExpectedColorFilter(std::move(expectedColorFilter)) {}
276
ApplyAction(LayerSpace<SkSize> scale,Expect expectation,const SkSamplingOptions & expectedSampling,SkTileMode expectedTileMode,sk_sp<SkColorFilter> expectedColorFilter)277 ApplyAction(LayerSpace<SkSize> scale,
278 Expect expectation,
279 const SkSamplingOptions& expectedSampling,
280 SkTileMode expectedTileMode,
281 sk_sp<SkColorFilter> expectedColorFilter)
282 : fAction(RescaleParams{scale})
283 , fExpectation(expectation)
284 , fExpectedSampling(expectedSampling)
285 , fExpectedTileMode(expectedTileMode)
286 , fExpectedColorFilter(std::move(expectedColorFilter)) {}
287
288 // Test-simplified logic for bounds propagation similar to how image filters calculate bounds
289 // while evaluating a filter DAG, which is outside of skif::FilterResult's responsibilities.
requiredInput(const LayerSpace<SkIRect> & desiredOutput) const290 LayerSpace<SkIRect> requiredInput(const LayerSpace<SkIRect>& desiredOutput) const {
291 if (auto* t = std::get_if<TransformParams>(&fAction)) {
292 LayerSpace<SkIRect> out;
293 return t->fMatrix.inverseMapRect(desiredOutput, &out)
294 ? out : LayerSpace<SkIRect>::Empty();
295 } else if (auto* c = std::get_if<CropParams>(&fAction)) {
296 LayerSpace<SkIRect> intersection = c->fRect;
297 if (c->fTileMode == SkTileMode::kDecal && !intersection.intersect(desiredOutput)) {
298 intersection = LayerSpace<SkIRect>::Empty();
299 }
300 return intersection;
301 } else if (std::holds_alternative<sk_sp<SkColorFilter>>(fAction) ||
302 std::holds_alternative<RescaleParams>(fAction)) {
303 return desiredOutput;
304 }
305 SkUNREACHABLE;
306 }
307
308 // Performs the action to be tested
apply(const Context & ctx,const FilterResult & in) const309 FilterResult apply(const Context& ctx, const FilterResult& in) const {
310 if (auto* t = std::get_if<TransformParams>(&fAction)) {
311 return in.applyTransform(ctx, t->fMatrix, t->fSampling);
312 } else if (auto* c = std::get_if<CropParams>(&fAction)) {
313 return in.applyCrop(ctx, c->fRect, c->fTileMode);
314 } else if (auto* cf = std::get_if<sk_sp<SkColorFilter>>(&fAction)) {
315 return in.applyColorFilter(ctx, *cf);
316 } else if (auto* s = std::get_if<RescaleParams>(&fAction)) {
317 return FilterResultTestAccess::Rescale(ctx, in, s->fScale);
318 }
319 SkUNREACHABLE;
320 }
321
expectation() const322 Expect expectation() const { return fExpectation; }
expectedSampling() const323 const SkSamplingOptions& expectedSampling() const { return fExpectedSampling; }
expectedTileMode() const324 SkTileMode expectedTileMode() const { return fExpectedTileMode; }
expectedColorFilter() const325 const SkColorFilter* expectedColorFilter() const { return fExpectedColorFilter.get(); }
326
expectedOffscreenSurfaces(const FilterResult & source) const327 std::vector<int> expectedOffscreenSurfaces(const FilterResult& source) const {
328 if (fExpectation != Expect::kNewImage) {
329 return {0};
330 }
331 if (auto* s = std::get_if<RescaleParams>(&fAction)) {
332 float minScale = std::min(s->fScale.width(), s->fScale.height());
333 if (minScale >= 1.f - 0.001f) {
334 return {1};
335 } else {
336 auto deferredScale = FilterResultTestAccess::DeferredScaleFactors(source);
337 int steps = 0;
338 if (deferredScale && std::get<0>(*deferredScale) <= 0.9f) {
339 steps++;
340 }
341
342 do {
343 steps++;
344 minScale *= 2.f;
345 } while(minScale < 0.9f);
346
347
348 // Rescaling periodic tiling may require scaling further than the value stored in
349 // the action to hit pixel integer bounds, which may trigger one more pass.
350 SkTileMode srcTileMode = source.tileMode();
351 if (srcTileMode == SkTileMode::kRepeat || srcTileMode == SkTileMode::kMirror) {
352 return {steps, steps + 1};
353 } else {
354 return {steps};
355 }
356 }
357 } else {
358 return {1};
359 }
360 }
361
expectedSampleMode(const Context & ctx,const FilterResult & source) const362 FilterResultTestAccess::ShaderSampleMode expectedSampleMode(const Context& ctx,
363 const FilterResult& source) const {
364 bool actionSupportsDirectDrawing = true;
365 if (std::holds_alternative<RescaleParams>(fAction)) {
366 // rescale() normally does not draw directly; the exception is if the source image has
367 // a scale factor that requires a pre-resolve. If that happens 'source' is not really
368 // the source of the rescale steps, and `source` can be drawn directly by the resolve.
369 auto scales = FilterResultTestAccess::DeferredScaleFactors(source);
370 if (!scales || scales->first > 0.5f) {
371 actionSupportsDirectDrawing = false; // no pre-resolve
372 }
373 }
374 return FilterResultTestAccess::GetExpectedShaderSampleMode(
375 ctx, source, actionSupportsDirectDrawing);
376 }
377
expectedBounds(const LayerSpace<SkIRect> & inputBounds) const378 LayerSpace<SkIRect> expectedBounds(const LayerSpace<SkIRect>& inputBounds) const {
379 // This assumes anything outside 'inputBounds' is transparent black.
380 if (auto* t = std::get_if<TransformParams>(&fAction)) {
381 if (inputBounds.isEmpty()) {
382 return LayerSpace<SkIRect>::Empty();
383 }
384 return t->fMatrix.mapRect(inputBounds);
385 } else if (auto* c = std::get_if<CropParams>(&fAction)) {
386 if (c->fExpectedBounds) {
387 return *c->fExpectedBounds;
388 }
389
390 LayerSpace<SkIRect> intersection = c->fRect;
391 if (!intersection.intersect(inputBounds)) {
392 return LayerSpace<SkIRect>::Empty();
393 }
394 return c->fTileMode == SkTileMode::kDecal
395 ? intersection : LayerSpace<SkIRect>(SkRectPriv::MakeILarge());
396 } else if (auto* cf = std::get_if<sk_sp<SkColorFilter>>(&fAction)) {
397 if (as_CFB(*cf)->affectsTransparentBlack()) {
398 // Fills out infinitely
399 return LayerSpace<SkIRect>(SkRectPriv::MakeILarge());
400 } else {
401 return inputBounds;
402 }
403 } else if (std::holds_alternative<RescaleParams>(fAction)) {
404 return inputBounds;
405 }
406 SkUNREACHABLE;
407 }
408
renderExpectedImage(const Context & ctx,sk_sp<SkSpecialImage> source,LayerSpace<SkIPoint> origin,const LayerSpace<SkIRect> & desiredOutput) const409 sk_sp<SkSpecialImage> renderExpectedImage(const Context& ctx,
410 sk_sp<SkSpecialImage> source,
411 LayerSpace<SkIPoint> origin,
412 const LayerSpace<SkIRect>& desiredOutput) const {
413 Expect effectiveExpectation = fExpectation;
414 SkISize size(desiredOutput.size());
415 if (desiredOutput.isEmpty()) {
416 size = {1, 1};
417 effectiveExpectation = Expect::kEmptyImage;
418 }
419
420 auto device = ctx.backend()->makeDevice(size, ctx.refColorSpace());
421 if (!device) {
422 return nullptr;
423 }
424 SkCanvas canvas{device};
425 canvas.clear(SK_ColorTRANSPARENT);
426 canvas.translate(-desiredOutput.left(), -desiredOutput.top());
427
428 if (effectiveExpectation != Expect::kEmptyImage) {
429 SkASSERT(source);
430 LayerSpace<SkIRect> sourceBounds{
431 SkIRect::MakeXYWH(origin.x(), origin.y(), source->width(), source->height())};
432 LayerSpace<SkIRect> expectedBounds = this->expectedBounds(sourceBounds);
433
434 canvas.clipIRect(SkIRect(expectedBounds), SkClipOp::kIntersect);
435
436 SkPaint paint;
437 paint.setAntiAlias(true);
438 paint.setBlendMode(SkBlendMode::kSrc);
439 // Start with NN to match exact subsetting FilterResult does for deferred images
440 SkSamplingOptions sampling = {};
441 SkTileMode tileMode = SkTileMode::kDecal;
442 if (auto* t = std::get_if<TransformParams>(&fAction)) {
443 SkMatrix m{t->fMatrix};
444 // FilterResult treats default/bilerp filtering as NN when it has an integer
445 // translation, so only change 'sampling' when that is not the case.
446 if (!m.isTranslate() ||
447 !SkScalarIsInt(m.getTranslateX()) ||
448 !SkScalarIsInt(m.getTranslateY())) {
449 sampling = t->fSampling;
450 }
451 canvas.concat(m);
452 } else if (auto* c = std::get_if<CropParams>(&fAction)) {
453 LayerSpace<SkIRect> imageBounds(
454 SkIRect::MakeXYWH(origin.x(), origin.y(),
455 source->width(), source->height()));
456 if (c->fTileMode == SkTileMode::kDecal || imageBounds.contains(c->fRect)) {
457 // Extract a subset of the image
458 SkAssertResult(imageBounds.intersect(c->fRect));
459 source = source->makeSubset({imageBounds.left() - origin.x(),
460 imageBounds.top() - origin.y(),
461 imageBounds.right() - origin.x(),
462 imageBounds.bottom() - origin.y()});
463 origin = imageBounds.topLeft();
464 } else {
465 // A non-decal tile mode where the image doesn't cover the crop requires the
466 // image to be padded out with transparency so the tiling matches 'fRect'.
467 SkISize paddedSize = SkISize(c->fRect.size());
468 auto paddedDevice = ctx.backend()->makeDevice(paddedSize, ctx.refColorSpace());
469 clear_device(paddedDevice.get());
470 paddedDevice->drawSpecial(source.get(),
471 SkMatrix::Translate(origin.x() - c->fRect.left(),
472 origin.y() - c->fRect.top()),
473 /*sampling=*/{},
474 /*paint=*/{});
475 source = paddedDevice->snapSpecial(SkIRect::MakeSize(paddedSize));
476 origin = c->fRect.topLeft();
477 }
478 tileMode = c->fTileMode;
479 } else if (auto* cf = std::get_if<sk_sp<SkColorFilter>>(&fAction)) {
480 paint.setColorFilter(*cf);
481 } else if (auto* s = std::get_if<RescaleParams>(&fAction)) {
482 // Don't redraw with an identity scale since sampling errors creep in on some GPUs
483 if (s->fScale.width() != 1.f || s->fScale.height() != 1.f) {
484 int origSrcWidth = source->width();
485 int origSrcHeight = source->height();
486 SkISize lowResSize = {sk_float_ceil2int(origSrcWidth * s->fScale.width()),
487 sk_float_ceil2int(origSrcHeight * s->fScale.height())};
488
489 while (source->width() != lowResSize.width() ||
490 source->height() != lowResSize.height()) {
491 float sx = std::max(0.5f, lowResSize.width() / (float) source->width());
492 float sy = std::max(0.5f, lowResSize.height() / (float) source->height());
493 SkISize stepSize = {sk_float_ceil2int(source->width() * sx),
494 sk_float_ceil2int(source->height() * sy)};
495 auto stepDevice = ctx.backend()->makeDevice(stepSize, ctx.refColorSpace());
496 clear_device(stepDevice.get());
497 stepDevice->drawSpecial(source.get(),
498 SkMatrix::Scale(sx, sy),
499 SkFilterMode::kLinear,
500 /*paint=*/{});
501 source = stepDevice->snapSpecial(SkIRect::MakeSize(stepSize));
502 }
503
504 // Adjust to draw the low-res image upscaled to fill the original image bounds
505 sampling = SkFilterMode::kLinear;
506 tileMode = SkTileMode::kClamp;
507 canvas.translate(origin.x(), origin.y());
508 canvas.scale(origSrcWidth / (float) source->width(),
509 origSrcHeight / (float) source->height());
510 origin = LayerSpace<SkIPoint>({0, 0});
511 }
512 }
513 // else it's a rescale action, but for the expected image leave it unmodified.
514 paint.setShader(source->asShader(tileMode,
515 sampling,
516 SkMatrix::Translate(origin.x(), origin.y())));
517 canvas.drawPaint(paint);
518 }
519 return device->snapSpecial(SkIRect::MakeSize(size));
520 }
521
522 private:
523 // Action
524 std::variant<TransformParams, // for applyTransform()
525 CropParams, // for applyCrop()
526 sk_sp<SkColorFilter>,// for applyColorFilter()
527 RescaleParams // for rescale()
528 > fAction;
529
530 // Expectation
531 Expect fExpectation;
532 SkSamplingOptions fExpectedSampling;
533 SkTileMode fExpectedTileMode;
534 sk_sp<SkColorFilter> fExpectedColorFilter;
535 // The expected desired outputs and layer bounds are calculated automatically based on the
536 // action type and parameters to simplify test case specification.
537 };
538
539
540 class FilterResultImageResolver {
541 public:
542 enum class Method {
543 kImageAndOffset,
544 kDrawToCanvas,
545 kShader,
546 kClippedShader,
547 kStrictShader // Only used to check image correctness when stats reported an optimization
548 };
549
FilterResultImageResolver(Method method)550 FilterResultImageResolver(Method method) : fMethod(method) {}
551
methodName() const552 const char* methodName() const {
553 switch (fMethod) {
554 case Method::kImageAndOffset: return "imageAndOffset";
555 case Method::kDrawToCanvas: return "drawToCanvas";
556 case Method::kShader: return "asShader";
557 case Method::kClippedShader: return "asShaderClipped";
558 case Method::kStrictShader: return "strictShader";
559 }
560 SkUNREACHABLE;
561 }
562
resolve(const Context & ctx,const FilterResult & image) const563 std::pair<sk_sp<SkSpecialImage>, SkIPoint> resolve(const Context& ctx,
564 const FilterResult& image) const {
565 if (fMethod == Method::kImageAndOffset) {
566 SkIPoint origin;
567 sk_sp<SkSpecialImage> resolved = image.imageAndOffset(ctx, &origin);
568 return {resolved, origin};
569 } else {
570 if (ctx.desiredOutput().isEmpty()) {
571 return {nullptr, {}};
572 }
573
574 auto device = ctx.backend()->makeDevice(SkISize(ctx.desiredOutput().size()),
575 ctx.refColorSpace());
576 SkASSERT(device);
577
578 SkCanvas canvas{device};
579 canvas.clear(SK_ColorTRANSPARENT);
580 canvas.translate(-ctx.desiredOutput().left(), -ctx.desiredOutput().top());
581
582 if (fMethod > Method::kDrawToCanvas) {
583 sk_sp<SkShader> shader;
584 if (fMethod == Method::kShader) {
585 // asShader() applies layer bounds by resolving automatically
586 // (e.g. kDrawToCanvas), if sampleBounds is larger than the layer bounds. Since
587 // we want to test the unclipped shader version, pass in layerBounds() for
588 // sampleBounds and add a clip to the canvas instead.
589 canvas.clipIRect(SkIRect(image.layerBounds()));
590 shader = FilterResultTestAccess::AsShader(ctx, image, image.layerBounds());
591 } else if (fMethod == Method::kClippedShader) {
592 shader = FilterResultTestAccess::AsShader(ctx, image, ctx.desiredOutput());
593 } else {
594 shader = FilterResultTestAccess::StrictShader(ctx, image);
595 if (!shader) {
596 auto [pixels, origin] = this->resolve(
597 ctx.withNewDesiredOutput(image.layerBounds()), image);
598 shader = FilterResultTestAccess::StrictShader(
599 ctx, FilterResult(std::move(pixels), LayerSpace<SkIPoint>(origin)));
600 }
601 }
602
603 SkPaint paint;
604 paint.setShader(std::move(shader));
605 canvas.drawPaint(paint);
606 } else {
607 SkASSERT(fMethod == Method::kDrawToCanvas);
608 FilterResultTestAccess::Draw(ctx, device.get(), image,
609 /*preserveDeviceState=*/false);
610 }
611
612 return {device->snapSpecial(SkIRect::MakeWH(ctx.desiredOutput().width(),
613 ctx.desiredOutput().height())),
614 SkIPoint(ctx.desiredOutput().topLeft())};
615 }
616 }
617
618 private:
619 Method fMethod;
620 };
621
622 class TestRunner {
623 static constexpr SkColorType kColorType = kRGBA_8888_SkColorType;
624 using ResolveMethod = FilterResultImageResolver::Method;
625 public:
626 // Raster-backed TestRunner
TestRunner(skiatest::Reporter * reporter)627 TestRunner(skiatest::Reporter* reporter)
628 : fReporter(reporter)
629 , fBackend(skif::MakeRasterBackend(/*surfaceProps=*/{}, kColorType)) {}
630
631 // Ganesh-backed TestRunner
632 #if defined(SK_GANESH)
TestRunner(skiatest::Reporter * reporter,GrDirectContext * context)633 TestRunner(skiatest::Reporter* reporter, GrDirectContext* context)
634 : fReporter(reporter)
635 , fDirectContext(context)
636 , fBackend(skif::MakeGaneshBackend(sk_ref_sp(context),
637 kTopLeft_GrSurfaceOrigin,
638 /*surfaceProps=*/{},
639 kColorType)) {}
640 #endif
641
642 // Graphite-backed TestRunner
643 #if defined(SK_GRAPHITE)
TestRunner(skiatest::Reporter * reporter,skgpu::graphite::Recorder * recorder)644 TestRunner(skiatest::Reporter* reporter, skgpu::graphite::Recorder* recorder)
645 : fReporter(reporter)
646 , fRecorder(recorder)
647 , fBackend(skif::MakeGraphiteBackend(recorder, /*surfaceProps=*/{}, kColorType)) {}
648 #endif
649
650 // Let TestRunner be passed in to places that take a Reporter* or to REPORTER_ASSERT etc.
operator skiatest::Reporter*() const651 operator skiatest::Reporter*() const { return fReporter; }
operator ->() const652 skiatest::Reporter* operator->() const { return fReporter; }
653
backend() const654 skif::Backend* backend() const { return fBackend.get(); }
refBackend() const655 sk_sp<skif::Backend> refBackend() const { return fBackend; }
656
compareImages(const skif::Context & ctx,SkSpecialImage * expectedImage,SkIPoint expectedOrigin,const FilterResult & actual,float allowedPercentImageDiff,int transparentCheckBorderTolerance)657 bool compareImages(const skif::Context& ctx,
658 SkSpecialImage* expectedImage,
659 SkIPoint expectedOrigin,
660 const FilterResult& actual,
661 float allowedPercentImageDiff,
662 int transparentCheckBorderTolerance) {
663 if (!expectedImage) {
664 // For pathological desired outputs, we can't actually produce an expected image so
665 // just carry on w/o validating.
666 return true;
667 }
668
669 SkBitmap expectedBM = this->readPixels(expectedImage);
670
671 // Resolve actual using all 4 methods to ensure they are approximately equal to the expected
672 // (which is used as a proxy for being approximately equal to each other).
673 return this->compareImages(ctx, expectedBM, expectedOrigin, actual,
674 ResolveMethod::kImageAndOffset,
675 allowedPercentImageDiff, transparentCheckBorderTolerance) &&
676 this->compareImages(ctx, expectedBM, expectedOrigin, actual,
677 ResolveMethod::kDrawToCanvas,
678 allowedPercentImageDiff, transparentCheckBorderTolerance) &&
679 this->compareImages(ctx, expectedBM, expectedOrigin, actual,
680 ResolveMethod::kShader,
681 allowedPercentImageDiff, transparentCheckBorderTolerance) &&
682 this->compareImages(ctx, expectedBM, expectedOrigin, actual,
683 ResolveMethod::kClippedShader,
684 allowedPercentImageDiff, transparentCheckBorderTolerance);
685 }
686
validateOptimizedImage(const skif::Context & ctx,const FilterResult & actual)687 bool validateOptimizedImage(const skif::Context& ctx, const FilterResult& actual) {
688 FilterResultImageResolver expectedResolver{ResolveMethod::kStrictShader};
689 auto [expectedImage, expectedOrigin] = expectedResolver.resolve(ctx, actual);
690 SkBitmap expectedBM = this->readPixels(expectedImage.get());
691 return this->compareImages(ctx, expectedBM, expectedOrigin, actual,
692 ResolveMethod::kImageAndOffset,
693 /*allowedPercentImageDiff=*/0.0f,
694 /*transparentCheckBorderTolerance=*/0);
695 }
696
createSourceImage(SkISize size,sk_sp<SkColorSpace> colorSpace)697 sk_sp<SkSpecialImage> createSourceImage(SkISize size, sk_sp<SkColorSpace> colorSpace) {
698 sk_sp<SkDevice> sourceSurface = fBackend->makeDevice(size, std::move(colorSpace));
699
700 const SkColor colors[] = { SK_ColorMAGENTA,
701 SK_ColorRED,
702 SK_ColorYELLOW,
703 SK_ColorGREEN,
704 SK_ColorCYAN,
705 SK_ColorBLUE };
706 SkMatrix rotation = SkMatrix::RotateDeg(15.f, {size.width() / 2.f,
707 size.height() / 2.f});
708
709 SkCanvas canvas{sourceSurface};
710 canvas.clear(SK_ColorBLACK);
711 canvas.concat(rotation);
712
713 int color = 0;
714 SkRect coverBounds;
715 SkRect dstBounds = SkRect::Make(canvas.imageInfo().bounds());
716 SkAssertResult(SkMatrixPriv::InverseMapRect(rotation, &coverBounds, dstBounds));
717
718 float sz = size.width() <= 16.f || size.height() <= 16.f ? 2.f : 8.f;
719 for (float y = coverBounds.fTop; y < coverBounds.fBottom; y += sz) {
720 for (float x = coverBounds.fLeft; x < coverBounds.fRight; x += sz) {
721 SkPaint p;
722 p.setColor(colors[(color++) % std::size(colors)]);
723 canvas.drawRect(SkRect::MakeXYWH(x, y, sz, sz), p);
724 }
725 }
726
727 return sourceSurface->snapSpecial(SkIRect::MakeSize(size));
728 }
729
730 private:
731
compareImages(const skif::Context & ctx,const SkBitmap & expected,SkIPoint expectedOrigin,const FilterResult & actual,ResolveMethod method,float allowedPercentImageDiff,int transparentCheckBorderTolerance)732 bool compareImages(const skif::Context& ctx, const SkBitmap& expected, SkIPoint expectedOrigin,
733 const FilterResult& actual, ResolveMethod method,
734 float allowedPercentImageDiff, int transparentCheckBorderTolerance) {
735 FilterResultImageResolver resolver{method};
736 auto [actualImage, actualOrigin] = resolver.resolve(ctx, actual);
737
738 SkBitmap actualBM = this->readPixels(actualImage.get()); // empty if actualImage is null
739 TArray<SkIPoint> badPixels;
740 if (!this->compareBitmaps(expected, expectedOrigin, actualBM, actualOrigin,
741 allowedPercentImageDiff, transparentCheckBorderTolerance,
742 &badPixels)) {
743 if (!fLoggedErrorImage) {
744 SkDebugf("FilterResult comparison failed for method %s\n", resolver.methodName());
745 this->logBitmaps(expected, actualBM, badPixels);
746 fLoggedErrorImage = true;
747 }
748 return false;
749 } else if (kLogAllBitmaps) {
750 this->logBitmaps(expected, actualBM, badPixels);
751 }
752 return true;
753 }
754
755
compareBitmaps(const SkBitmap & expected,SkIPoint expectedOrigin,const SkBitmap & actual,SkIPoint actualOrigin,float allowedPercentImageDiff,int transparentCheckBorderTolerance,TArray<SkIPoint> * badPixels)756 bool compareBitmaps(const SkBitmap& expected,
757 SkIPoint expectedOrigin,
758 const SkBitmap& actual,
759 SkIPoint actualOrigin,
760 float allowedPercentImageDiff,
761 int transparentCheckBorderTolerance,
762 TArray<SkIPoint>* badPixels) {
763 SkIRect excludeTransparentCheck; // region in expectedBM that can be non-transparent
764 if (actual.empty()) {
765 // A null image in a FilterResult is equivalent to transparent black, so we should
766 // expect the contents of 'expectedImage' to be transparent black.
767 excludeTransparentCheck = SkIRect::MakeEmpty();
768 } else {
769 // The actual image bounds should be contained in the expected image's bounds.
770 SkIRect actualBounds = SkIRect::MakeXYWH(actualOrigin.x(), actualOrigin.y(),
771 actual.width(), actual.height());
772 SkIRect expectedBounds = SkIRect::MakeXYWH(expectedOrigin.x(), expectedOrigin.y(),
773 expected.width(), expected.height());
774 const bool contained = expectedBounds.contains(actualBounds);
775 REPORTER_ASSERT(fReporter, contained,
776 "actual image [%d %d %d %d] not contained within expected [%d %d %d %d]",
777 actualBounds.fLeft, actualBounds.fTop,
778 actualBounds.fRight, actualBounds.fBottom,
779 expectedBounds.fLeft, expectedBounds.fTop,
780 expectedBounds.fRight, expectedBounds.fBottom);
781 if (!contained) {
782 return false;
783 }
784
785 // The actual pixels should match fairly closely with the expected, allowing for minor
786 // differences from consolidating actions into a single render, etc.
787 int errorCount = 0;
788 SkIPoint offset = actualOrigin - expectedOrigin;
789 for (int y = 0; y < actual.height(); ++y) {
790 for (int x = 0; x < actual.width(); ++x) {
791 SkIPoint ep = {x + offset.x(), y + offset.y()};
792 SkColor4f expectedColor = expected.getColor4f(ep.fX, ep.fY);
793 SkColor4f actualColor = actual.getColor4f(x, y);
794 if (actualColor != expectedColor &&
795 !this->approxColor(this->boxFilter(actual, x, y),
796 this->boxFilter(expected, ep.fX, ep.fY))) {
797 badPixels->push_back(ep);
798 errorCount++;
799 }
800 }
801 }
802
803 const int totalCount = expected.width() * expected.height();
804 const float percentError = 100.f * errorCount / (float) totalCount;
805 const bool approxMatch = percentError <= allowedPercentImageDiff;
806
807 REPORTER_ASSERT(fReporter, approxMatch,
808 "%d pixels were too different from %d total (%f %% vs. %f %%)",
809 errorCount, totalCount, percentError, allowedPercentImageDiff);
810 if (!approxMatch) {
811 return false;
812 }
813
814 // The expected pixels outside of the actual bounds should be transparent, otherwise
815 // the actual image is not returning enough data.
816 excludeTransparentCheck = actualBounds.makeOffset(-expectedOrigin);
817 // Add per-test padding to the exclusion, which is used when there is upscaling in the
818 // expected image that bleeds beyond the layer bounds, but is hard to enforce in the
819 // simplified expectation rendering.
820 excludeTransparentCheck.outset(transparentCheckBorderTolerance,
821 transparentCheckBorderTolerance);
822 }
823
824 int badTransparencyCount = 0;
825 for (int y = 0; y < expected.height(); ++y) {
826 for (int x = 0; x < expected.width(); ++x) {
827 if (!excludeTransparentCheck.isEmpty() && excludeTransparentCheck.contains(x, y)) {
828 continue;
829 }
830
831 // If we are on the edge of the transparency exclusion bounds, allow pixels to be
832 // up to 2 off to account for sloppy GPU rendering (seen on some Android devices).
833 // This is still visually "transparent" and definitely make sure that
834 // off-transparency does not extend across the entire surface (tolerance = 0).
835 const bool onEdge = !excludeTransparentCheck.isEmpty() &&
836 excludeTransparentCheck.makeOutset(1, 1).contains(x, y);
837 if (!this->approxColor(expected.getColor4f(x, y), SkColors::kTransparent,
838 onEdge ? kAATolerance : 0.f)) {
839 badPixels->push_back({x, y});
840 badTransparencyCount++;
841 }
842 }
843 }
844
845 REPORTER_ASSERT(fReporter, badTransparencyCount == 0, "Unexpected non-transparent pixels");
846 return badTransparencyCount == 0;
847 }
848
approxColor(const SkColor4f & a,const SkColor4f & b,float tolerance=kRGBTolerance) const849 bool approxColor(const SkColor4f& a,
850 const SkColor4f& b,
851 float tolerance = kRGBTolerance) const {
852 SkPMColor4f apm = a.premul();
853 SkPMColor4f bpm = b.premul();
854 // Calculate red-mean, a lowcost approximation of color difference that gives reasonable
855 // results for the types of acceptable differences resulting from collapsing compatible
856 // SkSamplingOptions or slightly different AA on shape boundaries.
857 // See https://www.compuphase.com/cmetric.htm
858 float r = (apm.fR + bpm.fR) / 2.f;
859 float dr = (apm.fR - bpm.fR);
860 float dg = (apm.fG - bpm.fG);
861 float db = (apm.fB - bpm.fB);
862 float delta = sqrt((2.f + r)*dr*dr + 4.f*dg*dg + (2.f + (1.f - r))*db*db);
863 return delta <= tolerance;
864 }
865
boxFilter(const SkBitmap & bm,int x,int y) const866 SkColor4f boxFilter(const SkBitmap& bm, int x, int y) const {
867 static constexpr int kKernelOffset = kKernelSize / 2;
868 SkPMColor4f sum = {0.f, 0.f, 0.f, 0.f};
869 float netWeight = 0.f;
870 for (int sy = y - kKernelOffset; sy <= y + kKernelOffset; ++sy) {
871 for (int sx = x - kKernelOffset; sx <= x + kKernelOffset; ++sx) {
872 float weight = kFuzzyKernel[sy - y + kKernelOffset][sx - x + kKernelOffset];
873
874 if (sx < 0 || sx >= bm.width() || sy < 0 || sy >= bm.height()) {
875 // Treat outside image as transparent black, this is necessary to get
876 // consistent comparisons between expected and actual images where the actual
877 // is cropped as tightly as possible.
878 netWeight += weight;
879 continue;
880 }
881
882 SkPMColor4f c = bm.getColor4f(sx, sy).premul() * weight;
883 sum.fR += c.fR;
884 sum.fG += c.fG;
885 sum.fB += c.fB;
886 sum.fA += c.fA;
887 netWeight += weight;
888 }
889 }
890 SkASSERT(netWeight > 0.f);
891 return sum.unpremul() * (1.f / netWeight);
892 }
893
readPixels(const SkSpecialImage * specialImage) const894 SkBitmap readPixels(const SkSpecialImage* specialImage) const {
895 if (!specialImage) {
896 return SkBitmap(); // an empty bitmap
897 }
898
899 [[maybe_unused]] int srcX = specialImage->subset().fLeft;
900 [[maybe_unused]] int srcY = specialImage->subset().fTop;
901 SkImageInfo ii = SkImageInfo::Make(specialImage->dimensions(),
902 specialImage->colorInfo());
903 SkBitmap bm;
904 bm.allocPixels(ii);
905 #if defined(SK_GANESH)
906 if (fDirectContext) {
907 // Ganesh backed, just use the SkImage::readPixels API
908 SkASSERT(specialImage->isGaneshBacked());
909 sk_sp<SkImage> image = specialImage->asImage();
910 SkAssertResult(image->readPixels(fDirectContext, bm.pixmap(), srcX, srcY));
911 } else
912 #endif
913 #if defined(SK_GRAPHITE)
914 if (fRecorder) {
915 // Graphite backed, so use the private testing-only synchronous API
916 SkASSERT(specialImage->isGraphiteBacked());
917 auto view = skgpu::graphite::AsView(specialImage->asImage());
918 auto proxyII = ii.makeWH(view.width(), view.height());
919 SkAssertResult(fRecorder->priv().context()->priv().readPixels(
920 bm.pixmap(), view.proxy(), proxyII, srcX, srcY));
921 } else
922 #endif
923 {
924 // Assume it's raster backed, so use AsBitmap directly
925 SkAssertResult(SkSpecialImages::AsBitmap(specialImage, &bm));
926 }
927
928 return bm;
929 }
930
logBitmaps(const SkBitmap & expected,const SkBitmap & actual,const TArray<SkIPoint> & badPixels)931 void logBitmaps(const SkBitmap& expected,
932 const SkBitmap& actual,
933 const TArray<SkIPoint>& badPixels) {
934 SkString expectedURL;
935 ToolUtils::BitmapToBase64DataURI(expected, &expectedURL);
936 SkDebugf("Expected:\n%s\n\n", expectedURL.c_str());
937
938 if (!actual.empty()) {
939 SkString actualURL;
940 ToolUtils::BitmapToBase64DataURI(actual, &actualURL);
941 SkDebugf("Actual:\n%s\n\n", actualURL.c_str());
942 } else {
943 SkDebugf("Actual: null (fully transparent)\n\n");
944 }
945
946 if (!badPixels.empty()) {
947 SkBitmap error = expected;
948 error.allocPixels();
949 SkAssertResult(expected.readPixels(error.pixmap()));
950 for (auto p : badPixels) {
951 error.erase(SkColors::kRed, SkIRect::MakeXYWH(p.fX, p.fY, 1, 1));
952 }
953 SkString markedURL;
954 ToolUtils::BitmapToBase64DataURI(error, &markedURL);
955 SkDebugf("Errors:\n%s\n\n", markedURL.c_str());
956 }
957 }
958
959 skiatest::Reporter* fReporter;
960 #if defined(SK_GANESH)
961 GrDirectContext* fDirectContext = nullptr;
962 #endif
963 #if defined(SK_GRAPHITE)
964 skgpu::graphite::Recorder* fRecorder = nullptr;
965 #endif
966
967 sk_sp<skif::Backend> fBackend;
968
969 bool fLoggedErrorImage = false; // only do this once per test runner
970 };
971
972 class TestCase {
973 public:
TestCase(TestRunner & runner,std::string name,float allowedPercentImageDiff=kDefaultMaxAllowedPercentImageDiff,int transparentCheckBorderTolerance=0)974 TestCase(TestRunner& runner,
975 std::string name,
976 float allowedPercentImageDiff=kDefaultMaxAllowedPercentImageDiff,
977 int transparentCheckBorderTolerance=0)
978 : fRunner(runner)
979 , fName(name)
980 , fAllowedPercentImageDiff(allowedPercentImageDiff)
981 , fTransparentCheckBorderTolerance(transparentCheckBorderTolerance)
982 , fSourceBounds(LayerSpace<SkIRect>::Empty())
983 , fDesiredOutput(LayerSpace<SkIRect>::Empty()) {}
984
source(const SkIRect & bounds)985 TestCase& source(const SkIRect& bounds) {
986 fSourceBounds = LayerSpace<SkIRect>(bounds);
987 return *this;
988 }
989
990
applyCrop(const SkIRect & crop,Expect expectation)991 TestCase& applyCrop(const SkIRect& crop, Expect expectation) {
992 return this->applyCrop(crop, SkTileMode::kDecal, expectation);
993 }
994
applyCrop(const SkIRect & crop,SkTileMode tileMode,Expect expectation,std::optional<SkTileMode> expectedTileMode={},std::optional<SkIRect> expectedBounds={})995 TestCase& applyCrop(const SkIRect& crop,
996 SkTileMode tileMode,
997 Expect expectation,
998 std::optional<SkTileMode> expectedTileMode = {},
999 std::optional<SkIRect> expectedBounds = {}) {
1000 // Fill-in automated expectations, which is to equal 'tileMode' when not overridden.
1001 if (!expectedTileMode) {
1002 expectedTileMode = tileMode;
1003 }
1004 std::optional<LayerSpace<SkIRect>> expectedLayerBounds;
1005 if (expectedBounds) {
1006 expectedLayerBounds = LayerSpace<SkIRect>(*expectedBounds);
1007 }
1008 fActions.emplace_back(crop, tileMode, expectedLayerBounds, expectation,
1009 this->getDefaultExpectedSampling(expectation),
1010 *expectedTileMode,
1011 this->getDefaultExpectedColorFilter(expectation));
1012 return *this;
1013 }
1014
applyTransform(const SkMatrix & matrix,Expect expectation)1015 TestCase& applyTransform(const SkMatrix& matrix, Expect expectation) {
1016 return this->applyTransform(matrix, FilterResult::kDefaultSampling, expectation);
1017 }
1018
applyTransform(const SkMatrix & matrix,const SkSamplingOptions & sampling,Expect expectation,std::optional<SkSamplingOptions> expectedSampling={})1019 TestCase& applyTransform(const SkMatrix& matrix,
1020 const SkSamplingOptions& sampling,
1021 Expect expectation,
1022 std::optional<SkSamplingOptions> expectedSampling = {}) {
1023 // Fill-in automated expectations, which is simply that if it's not explicitly provided we
1024 // assume the result's sampling equals what was passed to applyTransform().
1025 if (!expectedSampling.has_value()) {
1026 expectedSampling = sampling;
1027 }
1028 fActions.emplace_back(matrix, sampling, expectation, *expectedSampling,
1029 this->getDefaultExpectedTileMode(expectation,
1030 /*cfAffectsTransparency=*/false),
1031 this->getDefaultExpectedColorFilter(expectation));
1032 return *this;
1033 }
1034
applyColorFilter(sk_sp<SkColorFilter> colorFilter,Expect expectation,std::optional<sk_sp<SkColorFilter>> expectedColorFilter={})1035 TestCase& applyColorFilter(sk_sp<SkColorFilter> colorFilter,
1036 Expect expectation,
1037 std::optional<sk_sp<SkColorFilter>> expectedColorFilter = {}) {
1038 // The expected color filter is the composition of the default expectation (e.g. last
1039 // color filter or null for a new image) and the new 'colorFilter'. Compose() automatically
1040 // returns 'colorFilter' if the inner filter is null.
1041 if (!expectedColorFilter.has_value()) {
1042 expectedColorFilter = SkColorFilters::Compose(
1043 colorFilter, this->getDefaultExpectedColorFilter(expectation));
1044 }
1045 const bool affectsTransparent = as_CFB(colorFilter)->affectsTransparentBlack();
1046 fActions.emplace_back(std::move(colorFilter), expectation,
1047 this->getDefaultExpectedSampling(expectation),
1048 this->getDefaultExpectedTileMode(expectation, affectsTransparent),
1049 std::move(*expectedColorFilter));
1050 return *this;
1051 }
1052
rescale(SkSize scale,Expect expectation,std::optional<SkTileMode> expectedTileMode={})1053 TestCase& rescale(SkSize scale,
1054 Expect expectation,
1055 std::optional<SkTileMode> expectedTileMode = {}) {
1056 SkASSERT(!fActions.empty());
1057 if (!expectedTileMode) {
1058 expectedTileMode = this->getDefaultExpectedTileMode(expectation,
1059 /*cfAffectsTransparency=*/false);
1060 }
1061 fActions.emplace_back(skif::LayerSpace<SkSize>(scale), expectation,
1062 this->getDefaultExpectedSampling(expectation),
1063 *expectedTileMode,
1064 this->getDefaultExpectedColorFilter(expectation));
1065 return *this;
1066 }
1067
run(const SkIRect & requestedOutput) const1068 void run(const SkIRect& requestedOutput) const {
1069 skiatest::ReporterContext caseLabel(fRunner, fName);
1070 this->run(requestedOutput, /*backPropagateDesiredOutput=*/true);
1071 this->run(requestedOutput, /*backPropagateDesiredOutput=*/false);
1072 }
1073
run(const SkIRect & requestedOutput,bool backPropagateDesiredOutput) const1074 void run(const SkIRect& requestedOutput, bool backPropagateDesiredOutput) const {
1075 SkASSERT(!fActions.empty()); // It's a bad test case if there aren't any actions
1076
1077 skiatest::ReporterContext backPropagate(
1078 fRunner, SkStringPrintf("backpropagate output: %d", backPropagateDesiredOutput));
1079
1080 auto desiredOutput = LayerSpace<SkIRect>(requestedOutput);
1081 std::vector<LayerSpace<SkIRect>> desiredOutputs;
1082 desiredOutputs.resize(fActions.size(), desiredOutput);
1083 if (!backPropagateDesiredOutput) {
1084 // Set the desired output to be equal to the expected output so that there is no
1085 // further restriction of what's computed for early actions to then be ruled out by
1086 // subsequent actions.
1087 auto inputBounds = fSourceBounds;
1088 for (int i = 0; i < (int) fActions.size() - 1; ++i) {
1089 desiredOutputs[i] = fActions[i].expectedBounds(inputBounds);
1090 // If the output for the ith action is infinite, leave it for now and expand the
1091 // input bounds for action i+1. The infinite bounds will be replaced by the
1092 // back-propagated desired output of the next action.
1093 if (SkIRect(desiredOutputs[i]) == SkRectPriv::MakeILarge()) {
1094 inputBounds.outset(LayerSpace<SkISize>({25, 25}));
1095 } else {
1096 inputBounds = desiredOutputs[i];
1097 }
1098 }
1099 }
1100 // Fill out regular back-propagated desired outputs and cleanup infinite outputs
1101 for (int i = (int) fActions.size() - 2; i >= 0; --i) {
1102 if (backPropagateDesiredOutput ||
1103 SkIRect(desiredOutputs[i]) == SkRectPriv::MakeILarge()) {
1104 desiredOutputs[i] = fActions[i+1].requiredInput(desiredOutputs[i+1]);
1105 }
1106 }
1107
1108 // Create the source image
1109 sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB();
1110 FilterResult source;
1111 if (!fSourceBounds.isEmpty()) {
1112 source = FilterResult(fRunner.createSourceImage(SkISize(fSourceBounds.size()),
1113 colorSpace),
1114 fSourceBounds.topLeft());
1115 }
1116
1117 Context baseContext{fRunner.refBackend(),
1118 skif::Mapping{SkMatrix::I()},
1119 skif::LayerSpace<SkIRect>::Empty(),
1120 source,
1121 colorSpace.get(),
1122 /*stats=*/nullptr};
1123
1124 // Applying modifiers to FilterResult might produce a new image, but hopefully it's
1125 // able to merge properties and even re-order operations to minimize the number of offscreen
1126 // surfaces that it creates. To validate that this is producing an equivalent image, we
1127 // track what to expect by rendering each action every time without any optimization.
1128 sk_sp<SkSpecialImage> expectedImage = source.refImage();
1129 LayerSpace<SkIPoint> expectedOrigin = source.layerBounds().topLeft();
1130 // The expected image can't ever be null, so we produce a transparent black image instead.
1131 if (!expectedImage) {
1132 sk_sp<SkDevice> expectedSurface = fRunner.backend()->makeDevice({1, 1}, colorSpace);
1133 clear_device(expectedSurface.get());
1134 expectedImage = expectedSurface->snapSpecial(SkIRect::MakeWH(1, 1));
1135 expectedOrigin = LayerSpace<SkIPoint>({0, 0});
1136 }
1137 SkASSERT(expectedImage);
1138
1139 // Apply each action and validate, from first to last action
1140 for (int i = 0; i < (int) fActions.size(); ++i) {
1141 skiatest::ReporterContext actionLabel(fRunner, SkStringPrintf("action %d", i));
1142
1143 Stats stats;
1144 auto ctx = baseContext.withNewDesiredOutput(desiredOutputs[i]);
1145 FilterResultTestAccess::TrackStats(&ctx, &stats);
1146
1147 FilterResult output = fActions[i].apply(ctx, source);
1148 // Validate consistency of the output
1149 REPORTER_ASSERT(fRunner, SkToBool(output.image()) == !output.layerBounds().isEmpty());
1150
1151 LayerSpace<SkIRect> expectedBounds = fActions[i].expectedBounds(source.layerBounds());
1152 Expect correctedExpectation = fActions[i].expectation();
1153 if (SkIRect(expectedBounds) == SkRectPriv::MakeILarge()) {
1154 // An expected image filling out to infinity should have an actual image that
1155 // fills the desired output.
1156 expectedBounds = desiredOutputs[i];
1157 if (desiredOutputs[i].isEmpty()) {
1158 correctedExpectation = Expect::kEmptyImage;
1159 }
1160 } else if (!expectedBounds.intersect(desiredOutputs[i])) {
1161 // Test cases should provide image expectations for the case where desired output
1162 // is not back-propagated. When desired output is back-propagated, it can lead to
1163 // earlier actions becoming empty actions.
1164 REPORTER_ASSERT(fRunner, fActions[i].expectation() == Expect::kEmptyImage ||
1165 backPropagateDesiredOutput);
1166 expectedBounds = LayerSpace<SkIRect>::Empty();
1167 correctedExpectation = Expect::kEmptyImage;
1168 }
1169
1170 std::vector<int> allowedOffscreenSurfaces =
1171 fActions[i].expectedOffscreenSurfaces(source);
1172
1173 int actualShaderDraws = stats.fNumShaderBasedTilingDraws + stats.fNumShaderClampedDraws;
1174 int expectedShaderTiledDraws = 0;
1175 bool actualNewImage = output.image() &&
1176 (!source.image() || output.image()->uniqueID() != source.image()->uniqueID());
1177 switch(correctedExpectation) {
1178 case Expect::kNewImage:
1179 REPORTER_ASSERT(fRunner, actualNewImage);
1180 if (source && !source.image()->isExactFit()) {
1181 // Even if we're rescaling and making multiple surfaces, shader tiling
1182 // should only ever be needed on the first step.
1183 expectedShaderTiledDraws = std::min(1, allowedOffscreenSurfaces[0]);
1184 }
1185 break;
1186 case Expect::kDeferredImage:
1187 REPORTER_ASSERT(fRunner, !actualNewImage && output.image());
1188 break;
1189 case Expect::kEmptyImage:
1190 REPORTER_ASSERT(fRunner, !actualNewImage && !output.image());
1191 break;
1192 }
1193 // Verify stats behavior for the current action
1194 REPORTER_ASSERT(fRunner,
1195 find(allowedOffscreenSurfaces.begin(),
1196 allowedOffscreenSurfaces.end(),
1197 stats.fNumOffscreenSurfaces) != allowedOffscreenSurfaces.end(),
1198 "expected %d or %d, got %d",
1199 allowedOffscreenSurfaces[0],
1200 allowedOffscreenSurfaces.size() > 1 ? allowedOffscreenSurfaces[1] : -1,
1201 stats.fNumOffscreenSurfaces);
1202 REPORTER_ASSERT(fRunner, actualShaderDraws <= expectedShaderTiledDraws,
1203 "expected %d+%d <= %d",
1204 stats.fNumShaderBasedTilingDraws, stats.fNumShaderClampedDraws,
1205 expectedShaderTiledDraws);
1206
1207 using ShaderSampleMode = FilterResultTestAccess::ShaderSampleMode;
1208 auto expectedSampleMode = fActions[i].expectedSampleMode(ctx, source);
1209 REPORTER_ASSERT(fRunner, stats.fNumShaderBasedTilingDraws == 0 ||
1210 expectedSampleMode == ShaderSampleMode::kShaderTile);
1211 REPORTER_ASSERT(fRunner, stats.fNumShaderClampedDraws == 0 ||
1212 expectedSampleMode == ShaderSampleMode::kShaderClamp);
1213
1214 // Validate layer bounds and sampling when we expect a new or deferred image
1215 if (output.image()) {
1216 auto actualBounds = output.layerBounds();
1217 // A deferred action doesn't have to crop its layer bounds to the desired output to
1218 // preserve accuracy of later bounds analysis. New images however should restrict
1219 // themselves to the desired output to minimize memory of the surface. The exception
1220 // is a new image for applyTransform() because the new transform is deferred to the
1221 // resolved image, which can make its layer bounds larger than the desired output.
1222 if (correctedExpectation == Expect::kDeferredImage ||
1223 !FilterResultTestAccess::IsIntegerTransform(output)) {
1224 // Skip the check if the desiredOutputs's SkIRect reports empty.
1225 // LayerSpace<SkIRect> won't be empty but since the W/H don't fit into 32-bit
1226 // SkIRect::intersect() will report false.
1227 REPORTER_ASSERT(fRunner, SkIRect(desiredOutputs[i]).isEmpty() ||
1228 actualBounds.intersect(desiredOutputs[i]));
1229
1230 }
1231 REPORTER_ASSERT(fRunner, !expectedBounds.isEmpty());
1232 REPORTER_ASSERT(fRunner, SkIRect(actualBounds) == SkIRect(expectedBounds));
1233 REPORTER_ASSERT(fRunner, output.sampling() == fActions[i].expectedSampling());
1234 REPORTER_ASSERT(fRunner, output.tileMode() == fActions[i].expectedTileMode());
1235 REPORTER_ASSERT(fRunner, colorfilter_equals(output.colorFilter(),
1236 fActions[i].expectedColorFilter()));
1237 if (actualShaderDraws < expectedShaderTiledDraws ||
1238 (source.tileMode() != SkTileMode::kClamp && stats.fNumShaderClampedDraws > 0)) {
1239 // Some tile draws were optimized to HW draws, or some tile draws were reduced
1240 // to shader-clamped draws, so compare the output to a non-optimized image.
1241 REPORTER_ASSERT(fRunner, fRunner.validateOptimizedImage(ctx, output));
1242 }
1243 }
1244
1245 expectedImage = fActions[i].renderExpectedImage(ctx,
1246 std::move(expectedImage),
1247 expectedOrigin,
1248 desiredOutputs[i]);
1249 expectedOrigin = desiredOutputs[i].topLeft();
1250 if (!fRunner.compareImages(ctx,
1251 expectedImage.get(),
1252 SkIPoint(expectedOrigin),
1253 output,
1254 fAllowedPercentImageDiff,
1255 fTransparentCheckBorderTolerance)) {
1256 // If one iteration is incorrect, its failures will likely cascade to further
1257 // actions so end now as the test has failed.
1258 break;
1259 }
1260 source = output;
1261 }
1262 }
1263
1264 private:
1265 // By default an action that doesn't define its own sampling options will not change sampling
1266 // unless it produces a new image. Otherwise it inherits the prior action's expectation.
getDefaultExpectedSampling(Expect expectation) const1267 SkSamplingOptions getDefaultExpectedSampling(Expect expectation) const {
1268 if (expectation != Expect::kDeferredImage || fActions.empty()) {
1269 return FilterResult::kDefaultSampling;
1270 } else {
1271 return fActions[fActions.size() - 1].expectedSampling();
1272 }
1273 }
1274 // By default an action that doesn't define its own tiling will not change the tiling, unless it
1275 // produces a new image, at which point it becomes kDecal again.
getDefaultExpectedTileMode(Expect expectation,bool cfAffectsTransparency) const1276 SkTileMode getDefaultExpectedTileMode(Expect expectation, bool cfAffectsTransparency) const {
1277 if (expectation == Expect::kNewImage && cfAffectsTransparency) {
1278 return SkTileMode::kClamp;
1279 } else if (expectation != Expect::kDeferredImage || fActions.empty()) {
1280 return SkTileMode::kDecal;
1281 } else {
1282 return fActions[fActions.size() - 1].expectedTileMode();
1283 }
1284 }
1285 // By default an action that doesn't define its own color filter will not change filtering,
1286 // unless it produces a new image. Otherwise it inherits the prior action's expectations.
getDefaultExpectedColorFilter(Expect expectation) const1287 sk_sp<SkColorFilter> getDefaultExpectedColorFilter(Expect expectation) const {
1288 if (expectation != Expect::kDeferredImage || fActions.empty()) {
1289 return nullptr;
1290 } else {
1291 return sk_ref_sp(fActions[fActions.size() - 1].expectedColorFilter());
1292 }
1293 }
1294
1295 TestRunner& fRunner;
1296 std::string fName;
1297 float fAllowedPercentImageDiff;
1298 int fTransparentCheckBorderTolerance;
1299
1300 // Used to construct an SkSpecialImage of the given size/location filled with the known pattern.
1301 LayerSpace<SkIRect> fSourceBounds;
1302
1303 // The intended area to fill with the result, controlled by outside factors (e.g. clip bounds)
1304 LayerSpace<SkIRect> fDesiredOutput;
1305
1306 std::vector<ApplyAction> fActions;
1307 };
1308
1309 // ----------------------------------------------------------------------------
1310 // Utilities to create color filters for the unit tests
1311
alpha_modulate(float v)1312 sk_sp<SkColorFilter> alpha_modulate(float v) {
1313 // dst-in blending with src = (1,1,1,v) = dst * v
1314 auto cf = SkColorFilters::Blend({1.f,1.f,1.f,v}, /*colorSpace=*/nullptr, SkBlendMode::kDstIn);
1315 SkASSERT(cf && !as_CFB(cf)->affectsTransparentBlack());
1316 return cf;
1317 }
1318
affect_transparent(SkColor4f color)1319 sk_sp<SkColorFilter> affect_transparent(SkColor4f color) {
1320 auto cf = SkColorFilters::Blend(color, /*colorSpace=*/nullptr, SkBlendMode::kPlus);
1321 SkASSERT(cf && as_CFB(cf)->affectsTransparentBlack());
1322 return cf;
1323 }
1324
1325 // ----------------------------------------------------------------------------
1326
1327 // TODO(skbug.com/14607) - Run FilterResultTests on Dawn and ANGLE backends, too
1328
1329 #if defined(SK_GANESH)
1330 #define DEF_GANESH_TEST_SUITE(name, ctsEnforcement) \
1331 DEF_GANESH_TEST_FOR_CONTEXTS(FilterResult_ganesh_##name, \
1332 skgpu::IsNativeBackend, \
1333 r, \
1334 ctxInfo, \
1335 nullptr, \
1336 ctsEnforcement) { \
1337 TestRunner runner(r, ctxInfo.directContext()); \
1338 test_suite_##name(runner); \
1339 }
1340 #else
1341 #define DEF_GANESH_TEST_SUITE(name) // do nothing
1342 #endif
1343
1344 #if defined(SK_GRAPHITE)
1345 #define DEF_GRAPHITE_TEST_SUITE(name, ctsEnforcement) \
1346 DEF_CONDITIONAL_GRAPHITE_TEST_FOR_ALL_CONTEXTS(FilterResult_graphite_##name, \
1347 skgpu::IsNativeBackend, \
1348 r, \
1349 context, \
1350 testContext, \
1351 true, \
1352 ctsEnforcement) { \
1353 using namespace skgpu::graphite; \
1354 auto recorder = context->makeRecorder(); \
1355 TestRunner runner(r, recorder.get()); \
1356 test_suite_##name(runner); \
1357 std::unique_ptr<Recording> recording = recorder->snap(); \
1358 if (!recording) { \
1359 ERRORF(r, "Failed to make recording"); \
1360 return; \
1361 } \
1362 InsertRecordingInfo insertInfo; \
1363 insertInfo.fRecording = recording.get(); \
1364 context->insertRecording(insertInfo); \
1365 testContext->syncedSubmit(context); \
1366 }
1367 #else
1368 #define DEF_GRAPHITE_TEST_SUITE(name) // do nothing
1369 #endif
1370
1371 #define DEF_TEST_SUITE(name, runner, ganeshCtsEnforcement, graphiteCtsEnforcement) \
1372 static void test_suite_##name(TestRunner&); \
1373 /* TODO(b/274901800): Uncomment to enable Graphite test execution. */ \
1374 /* DEF_GRAPHITE_TEST_SUITE(name, graphiteCtsEnforcement) */ \
1375 DEF_GANESH_TEST_SUITE(name, ganeshCtsEnforcement) \
1376 DEF_TEST(FilterResult_raster_##name, reporter) { \
1377 TestRunner runner(reporter); \
1378 test_suite_##name(runner); \
1379 } \
1380 void test_suite_##name(TestRunner& runner)
1381
1382 // ----------------------------------------------------------------------------
1383 // Empty input/output tests
1384
DEF_TEST_SUITE(EmptySource,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1385 DEF_TEST_SUITE(EmptySource, r, CtsEnforcement::kApiLevel_T, CtsEnforcement::kNextRelease) {
1386 // This is testing that an empty input image is handled by the applied actions without having
1387 // to generate new images, or that it can produce a new image from nothing when it affects
1388 // transparent black.
1389 for (SkTileMode tm : kTileModes) {
1390 TestCase(r, "applyCrop() to empty source")
1391 .source(SkIRect::MakeEmpty())
1392 .applyCrop({0, 0, 10, 10}, tm, Expect::kEmptyImage)
1393 .run(/*requestedOutput=*/{0, 0, 20, 20});
1394 }
1395
1396 TestCase(r, "applyTransform() to empty source")
1397 .source(SkIRect::MakeEmpty())
1398 .applyTransform(SkMatrix::Translate(10.f, 10.f), Expect::kEmptyImage)
1399 .run(/*requestedOutput=*/{10, 10, 20, 20});
1400
1401 TestCase(r, "applyColorFilter() to empty source")
1402 .source(SkIRect::MakeEmpty())
1403 .applyColorFilter(alpha_modulate(0.5f), Expect::kEmptyImage)
1404 .run(/*requestedOutput=*/{0, 0, 10, 10});
1405
1406 TestCase(r, "Transparency-affecting color filter overrules empty source")
1407 .source(SkIRect::MakeEmpty())
1408 .applyColorFilter(affect_transparent(SkColors::kBlue), Expect::kNewImage,
1409 /*expectedColorFilter=*/nullptr) // CF applied ASAP to make a new img
1410 .run(/*requestedOutput=*/{0, 0, 10, 10});
1411 }
1412
DEF_TEST_SUITE(EmptyDesiredOutput,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1413 DEF_TEST_SUITE(EmptyDesiredOutput, r, CtsEnforcement::kApiLevel_T, CtsEnforcement::kNextRelease) {
1414 // This is testing that an empty requested output is propagated through the applied actions so
1415 // that no actual images are generated.
1416 for (SkTileMode tm : kTileModes) {
1417 TestCase(r, "applyCrop() + empty output becomes empty")
1418 .source({0, 0, 10, 10})
1419 .applyCrop({2, 2, 8, 8}, tm, Expect::kEmptyImage)
1420 .run(/*requestedOutput=*/SkIRect::MakeEmpty());
1421 }
1422
1423 TestCase(r, "applyTransform() + empty output becomes empty")
1424 .source({0, 0, 10, 10})
1425 .applyTransform(SkMatrix::RotateDeg(10.f), Expect::kEmptyImage)
1426 .run(/*requestedOutput=*/SkIRect::MakeEmpty());
1427
1428 TestCase(r, "applyColorFilter() + empty output becomes empty")
1429 .source({0, 0, 10, 10})
1430 .applyColorFilter(alpha_modulate(0.5f), Expect::kEmptyImage)
1431 .run(/*requestedOutput=*/SkIRect::MakeEmpty());
1432
1433 TestCase(r, "Transpency-affecting color filter + empty output is empty")
1434 .source({0, 0, 10, 10})
1435 .applyColorFilter(affect_transparent(SkColors::kBlue), Expect::kEmptyImage)
1436 .run(/*requestedOutput=*/SkIRect::MakeEmpty());
1437 }
1438
1439 // ----------------------------------------------------------------------------
1440 // applyCrop() tests
1441
DEF_TEST_SUITE(Crop,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1442 DEF_TEST_SUITE(Crop, r, CtsEnforcement::kApiLevel_T, CtsEnforcement::kNextRelease) {
1443 // This is testing all the combinations of how the src, crop, and requested output rectangles
1444 // can interact while still resulting in a deferred image. The exception is non-decal tile
1445 // modes where the crop rect includes transparent pixels not filled by the source, which
1446 // requires a new image to ensure tiling matches the crop geometry.
1447 for (SkTileMode tm : kTileModes) {
1448 const Expect nonDecalExpectsNewImage = tm == SkTileMode::kDecal ? Expect::kDeferredImage
1449 : Expect::kNewImage;
1450 TestCase(r, "applyCrop() contained in source and output")
1451 .source({0, 0, 20, 20})
1452 .applyCrop({8, 8, 12, 12}, tm, Expect::kDeferredImage)
1453 .run(/*requestedOutput=*/{4, 4, 16, 16});
1454
1455 TestCase(r, "applyCrop() contained in source, intersects output")
1456 .source({0, 0, 20, 20})
1457 .applyCrop({4, 4, 12, 12}, tm, Expect::kDeferredImage)
1458 .run(/*requestedOutput=*/{8, 8, 16, 16});
1459
1460 TestCase(r, "applyCrop() intersects source, contained in output")
1461 .source({10, 10, 20, 20})
1462 .applyCrop({4, 4, 16, 16}, tm, nonDecalExpectsNewImage)
1463 .run(/*requestedOutput=*/{0, 0, 20, 20});
1464
1465 TestCase(r, "applyCrop() intersects source and output")
1466 .source({0, 0, 10, 10})
1467 .applyCrop({5, -5, 15, 5}, tm, nonDecalExpectsNewImage)
1468 .run(/*requestedOutput=*/{7, -2, 12, 8});
1469
1470 TestCase(r, "applyCrop() contains source, intersects output")
1471 .source({4, 4, 16, 16})
1472 .applyCrop({0, 0, 20, 20}, tm, nonDecalExpectsNewImage)
1473 .run(/*requestedOutput=*/{-5, -5, 18, 18});
1474
1475 // In these cases, cropping with a non-decal tile mode can be discarded because the output
1476 // bounds are entirely within the crop so no tiled edges would be visible.
1477 TestCase(r, "applyCrop() intersects source, contains output")
1478 .source({0, 0, 20, 20})
1479 .applyCrop({-5, 5, 25, 15}, tm, Expect::kDeferredImage, SkTileMode::kDecal)
1480 .run(/*requestedOutput=*/{0, 5, 20, 15});
1481
1482 TestCase(r, "applyCrop() contains source and output")
1483 .source({0, 0, 10, 10})
1484 .applyCrop({-5, -5, 15, 15}, tm, Expect::kDeferredImage, SkTileMode::kDecal)
1485 .run(/*requestedOutput=*/{1, 1, 9, 9});
1486 }
1487 }
1488
DEF_TEST_SUITE(CropDisjointFromSourceAndOutput,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1489 DEF_TEST_SUITE(CropDisjointFromSourceAndOutput, r, CtsEnforcement::kApiLevel_T,
1490 CtsEnforcement::kNextRelease) {
1491 // This tests all the combinations of src, crop, and requested output rectangles that result in
1492 // an empty image without any of the rectangles being empty themselves. The exception is for
1493 // non-decal tile modes when the source and crop still intersect. In that case the non-empty
1494 // content is tiled into the disjoint output rect, producing a non-empty image.
1495 for (SkTileMode tm : kTileModes) {
1496 TestCase(r, "applyCrop() disjoint from source, intersects output")
1497 .source({0, 0, 10, 10})
1498 .applyCrop({11, 11, 20, 20}, tm, Expect::kEmptyImage)
1499 .run(/*requestedOutput=*/{0, 0, 15, 15});
1500
1501 TestCase(r, "applyCrop() disjoint from source, intersects output disjoint from source")
1502 .source({0, 0, 10, 10})
1503 .applyCrop({11, 11, 20, 20}, tm, Expect::kEmptyImage)
1504 .run(/*requestedOutput=*/{12, 12, 18, 18});
1505
1506 TestCase(r, "applyCrop() disjoint from source and output")
1507 .source({0, 0, 10, 10})
1508 .applyCrop({12, 12, 18, 18}, tm, Expect::kEmptyImage)
1509 .run(/*requestedOutput=*/{-1, -1, 11, 11});
1510
1511 TestCase(r, "applyCrop() disjoint from source and output disjoint from source")
1512 .source({0, 0, 10, 10})
1513 .applyCrop({-10, 10, -1, -1}, tm, Expect::kEmptyImage)
1514 .run(/*requestedOutput=*/{11, 11, 20, 20});
1515
1516 // When the source and crop intersect but are disjoint from the output, the behavior depends
1517 // on the tile mode. For periodic tile modes, certain geometries can still be deferred by
1518 // conversion to a transform, but to keep expectations simple we pick bounds such that the
1519 // tiling can't be dropped. See PeriodicTileCrops for other scenarios.
1520 Expect nonDecalExpectsImage = tm == SkTileMode::kDecal ? Expect::kEmptyImage :
1521 tm == SkTileMode::kClamp ? Expect::kDeferredImage
1522 : Expect::kNewImage;
1523 TestCase(r, "applyCrop() intersects source, disjoint from output disjoint from source")
1524 .source({0, 0, 10, 10})
1525 .applyCrop({-5, -5, 5, 5}, tm, nonDecalExpectsImage)
1526 .run(/*requestedOutput=*/{12, 12, 18, 18});
1527
1528 TestCase(r, "applyCrop() intersects source, disjoint from output")
1529 .source({0, 0, 10, 10})
1530 .applyCrop({-5, -5, 5, 5}, tm, nonDecalExpectsImage)
1531 .run(/*requestedOutput=*/{6, 6, 18, 18});
1532 }
1533 }
1534
DEF_TEST_SUITE(EmptyCrop,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1535 DEF_TEST_SUITE(EmptyCrop, r, CtsEnforcement::kApiLevel_T, CtsEnforcement::kNextRelease) {
1536 for (SkTileMode tm : kTileModes) {
1537 TestCase(r, "applyCrop() is empty")
1538 .source({0, 0, 10, 10})
1539 .applyCrop(SkIRect::MakeEmpty(), tm, Expect::kEmptyImage)
1540 .run(/*requestedOutput=*/{0, 0, 10, 10});
1541
1542 TestCase(r, "applyCrop() emptiness propagates")
1543 .source({0, 0, 10, 10})
1544 .applyCrop({1, 1, 9, 9}, tm, Expect::kDeferredImage)
1545 .applyCrop(SkIRect::MakeEmpty(), tm, Expect::kEmptyImage)
1546 .run(/*requestedOutput=*/{0, 0, 10, 10});
1547 }
1548 }
1549
DEF_TEST_SUITE(DisjointCrops,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1550 DEF_TEST_SUITE(DisjointCrops, r, CtsEnforcement::kApiLevel_T, CtsEnforcement::kNextRelease) {
1551 for (SkTileMode tm : kTileModes) {
1552 TestCase(r, "Disjoint applyCrop() after kDecal become empty")
1553 .source({0, 0, 10, 10})
1554 .applyCrop({0, 0, 4, 4}, SkTileMode::kDecal, Expect::kDeferredImage)
1555 .applyCrop({6, 6, 10, 10}, tm, Expect::kEmptyImage)
1556 .run(/*requestedOutput=*/{0, 0, 10, 10});
1557
1558 if (tm != SkTileMode::kDecal) {
1559 TestCase(r, "Disjoint tiling applyCrop() before kDecal is not empty and combines")
1560 .source({0, 0, 10, 10})
1561 .applyCrop({0, 0, 4, 4}, tm, Expect::kDeferredImage)
1562 .applyCrop({6, 6, 10, 10}, SkTileMode::kDecal, Expect::kDeferredImage, tm)
1563 .run(/*requestedOutput=*/{0, 0, 10, 10});
1564
1565 TestCase(r, "Disjoint non-decal applyCrops() are not empty")
1566 .source({0, 0, 10, 10})
1567 .applyCrop({0, 0, 4, 4}, tm, Expect::kDeferredImage)
1568 .applyCrop({6, 6, 10, 10}, tm, tm == SkTileMode::kClamp ? Expect::kDeferredImage
1569 : Expect::kNewImage)
1570 .run(/*requestedOutput=*/{0, 0, 10, 10});
1571 }
1572 }
1573 }
1574
DEF_TEST_SUITE(IntersectingCrops,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1575 DEF_TEST_SUITE(IntersectingCrops, r, CtsEnforcement::kApiLevel_T, CtsEnforcement::kNextRelease) {
1576 for (SkTileMode tm : kTileModes) {
1577 TestCase(r, "Decal applyCrop() always combines with any other crop")
1578 .source({0, 0, 20, 20})
1579 .applyCrop({5, 5, 15, 15}, tm, Expect::kDeferredImage)
1580 .applyCrop({10, 10, 20, 20}, SkTileMode::kDecal, Expect::kDeferredImage, tm)
1581 .run(/*requestedOutput=*/{0, 0, 20, 20});
1582
1583 if (tm != SkTileMode::kDecal) {
1584 TestCase(r, "Decal applyCrop() before non-decal crop requires new image")
1585 .source({0, 0, 20, 20})
1586 .applyCrop({5, 5, 15, 15}, SkTileMode::kDecal, Expect::kDeferredImage)
1587 .applyCrop({10, 10, 20, 20}, tm, Expect::kNewImage)
1588 .run(/*requestedOutput=*/{0, 0, 20, 20});
1589
1590 TestCase(r, "Consecutive non-decal crops combine if both are clamp")
1591 .source({0, 0, 20, 20})
1592 .applyCrop({5, 5, 15, 15}, tm, Expect::kDeferredImage)
1593 .applyCrop({10, 10, 20, 20}, tm,
1594 tm == SkTileMode::kClamp ? Expect::kDeferredImage
1595 : Expect::kNewImage)
1596 .run(/*requestedOutput=*/{0, 0, 20, 20});
1597 }
1598 }
1599 }
1600
DEF_TEST_SUITE(PeriodicTileCrops,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1601 DEF_TEST_SUITE(PeriodicTileCrops, r, CtsEnforcement::kApiLevel_T, CtsEnforcement::kNextRelease) {
1602 for (SkTileMode tm : {SkTileMode::kRepeat, SkTileMode::kMirror}) {
1603 // In these tests, the crop periodically tiles such that it covers the desired output so
1604 // the prior image can be simply transformed.
1605 TestCase(r, "Periodic applyCrop() becomes a transform")
1606 .source({0, 0, 20, 20})
1607 .applyCrop({5, 5, 15, 15}, tm, Expect::kDeferredImage,
1608 /*expectedTileMode=*/SkTileMode::kDecal)
1609 .run(/*requestedOutput=*/{25, 25, 35, 35});
1610
1611 TestCase(r, "Periodic applyCrop() with partial transparency still becomes a transform")
1612 .source({0, 0, 20, 20})
1613 .applyCrop({-5, -5, 15, 15}, tm, Expect::kDeferredImage,
1614 /*expectedTileMode=*/SkTileMode::kDecal,
1615 /*expectedBounds=*/tm == SkTileMode::kRepeat ? SkIRect{20,20,35,35}
1616 : SkIRect{15,15,30,30})
1617 .run(/*requestedOutput*/{15, 15, 35, 35});
1618
1619 TestCase(r, "Periodic applyCrop() after complex transform can still simplify")
1620 .source({0, 0, 20, 20})
1621 .applyTransform(SkMatrix::RotateDeg(15.f, {10.f, 10.f}), Expect::kDeferredImage)
1622 .applyCrop({-5, -5, 25, 25}, tm, Expect::kDeferredImage,
1623 /*expectedTileMode=*/SkTileMode::kDecal,
1624 /*expectedBounds*/SkIRect{57,57,83,83}) // source+15 degree rotation
1625 .run(/*requestedOutput=*/{55,55,85,85});
1626
1627 // In these tests, the crop's periodic boundary intersects with the output so it should not
1628 // simplify to just a transform.
1629 TestCase(r, "Periodic applyCrop() with visible edge does not become a transform")
1630 .source({0, 0, 20, 20})
1631 .applyCrop({5, 5, 15, 15}, tm, Expect::kDeferredImage)
1632 .run(/*requestedOutput=*/{10, 10, 20, 20});
1633
1634 TestCase(r, "Periodic applyCrop() with visible edge and transparency creates new image")
1635 .source({0, 0, 20, 20})
1636 .applyCrop({-5, -5, 15, 15}, tm, Expect::kNewImage)
1637 .run(/*requestedOutput=*/{10, 10, 20, 20});
1638
1639 TestCase(r, "Periodic applyCrop() with visible edge and complex transform creates image")
1640 .source({0, 0, 20, 20})
1641 .applyTransform(SkMatrix::RotateDeg(15.f, {10.f, 10.f}), Expect::kDeferredImage)
1642 .applyCrop({-5, -5, 25, 25}, tm, Expect::kNewImage)
1643 .run(/*requestedOutput=*/{20, 20, 50, 50});
1644
1645 // oss-fuzz:70128 ensure period calculations don't overflow (but will fail image creation)
1646 TestCase(r, "Pathologically large crop rect")
1647 .source({0, 0, 10, 10})
1648 .applyCrop({0, 0, 1, 2}, tm, Expect::kDeferredImage)
1649 .applyTransform(SkMatrix::RotateDeg(1.f, {5.f, 5.f}), Expect::kEmptyImage)
1650 .run(/*requestedOutput=*/{-726713344, 7, 1464866662, 15});
1651 }
1652 }
1653
DEF_TEST_SUITE(DecalThenClamp,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1654 DEF_TEST_SUITE(DecalThenClamp, r, CtsEnforcement::kApiLevel_T, CtsEnforcement::kNextRelease) {
1655 TestCase(r, "Decal then clamp crop uses 1px buffer around intersection")
1656 .source({0, 0, 20, 20})
1657 .applyCrop({3, 3, 17, 17}, SkTileMode::kDecal, Expect::kDeferredImage)
1658 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
1659 .applyCrop({3, 3, 20, 20}, SkTileMode::kClamp, Expect::kNewImage, SkTileMode::kClamp)
1660 .run(/*requestedOutput=*/{0, 0, 20, 20});
1661
1662 TestCase(r, "Decal then clamp crop uses 1px buffer around intersection, w/ alpha color filter")
1663 .source({0, 0, 20, 20})
1664 .applyCrop({3, 3, 17, 17}, SkTileMode::kDecal, Expect::kDeferredImage)
1665 .applyColorFilter(affect_transparent(SkColors::kCyan), Expect::kDeferredImage)
1666 .applyCrop({0, 0, 17, 17}, SkTileMode::kClamp, Expect::kNewImage, SkTileMode::kClamp)
1667 .run(/*requestedOutput=*/{0, 0, 20, 20});
1668 }
1669
1670 // ----------------------------------------------------------------------------
1671 // applyTransform() tests
1672
DEF_TEST_SUITE(Transform,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1673 DEF_TEST_SUITE(Transform, r, CtsEnforcement::kApiLevel_T, CtsEnforcement::kNextRelease) {
1674 TestCase(r, "applyTransform() integer translate")
1675 .source({0, 0, 10, 10})
1676 .applyTransform(SkMatrix::Translate(5, 5), Expect::kDeferredImage)
1677 .run(/*requestedOutput=*/{0, 0, 10, 10});
1678
1679 TestCase(r, "applyTransform() fractional translate")
1680 .source({0, 0, 10, 10})
1681 .applyTransform(SkMatrix::Translate(1.5f, 3.24f), Expect::kDeferredImage)
1682 .run(/*requestedOutput=*/{0, 0, 10, 10});
1683
1684 TestCase(r, "applyTransform() scale")
1685 .source({0, 0, 24, 24})
1686 .applyTransform(SkMatrix::Scale(2.2f, 3.1f), Expect::kDeferredImage)
1687 .run(/*requestedOutput=*/{-16, -16, 96, 96});
1688
1689 // NOTE: complex is anything beyond a scale+translate. See SkImageFilter_Base::MatrixCapability.
1690 TestCase(r, "applyTransform() with complex transform")
1691 .source({0, 0, 8, 8})
1692 .applyTransform(SkMatrix::RotateDeg(10.f, {4.f, 4.f}), Expect::kDeferredImage)
1693 .run(/*requestedOutput=*/{0, 0, 16, 16});
1694 }
1695
DEF_TEST_SUITE(CompatibleSamplingConcatsTransforms,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1696 DEF_TEST_SUITE(CompatibleSamplingConcatsTransforms, r, CtsEnforcement::kApiLevel_T,
1697 CtsEnforcement::kNextRelease) {
1698 TestCase(r, "linear + linear combine")
1699 .source({0, 0, 8, 8})
1700 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1701 SkFilterMode::kLinear, Expect::kDeferredImage)
1702 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1703 SkFilterMode::kLinear, Expect::kDeferredImage)
1704 .run(/*requestedOutput=*/{0, 0, 16, 16});
1705
1706 TestCase(r, "equiv. bicubics combine")
1707 .source({0, 0, 8, 8})
1708 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1709 SkCubicResampler::Mitchell(), Expect::kDeferredImage)
1710 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1711 SkCubicResampler::Mitchell(), Expect::kDeferredImage)
1712 .run(/*requestedOutput=*/{0, 0, 16, 16});
1713
1714 TestCase(r, "linear + bicubic becomes bicubic")
1715 .source({0, 0, 8, 8})
1716 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1717 SkFilterMode::kLinear, Expect::kDeferredImage)
1718 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1719 SkCubicResampler::Mitchell(), Expect::kDeferredImage)
1720 .run(/*requestedOutput=*/{0, 0, 16, 16});
1721
1722 TestCase(r, "bicubic + linear becomes bicubic")
1723 .source({0, 0, 8, 8})
1724 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1725 SkCubicResampler::Mitchell(), Expect::kDeferredImage)
1726 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1727 SkFilterMode::kLinear, Expect::kDeferredImage,
1728 /*expectedSampling=*/SkCubicResampler::Mitchell())
1729 .run(/*requestedOutput=*/{0, 0, 16, 16});
1730
1731 TestCase(r, "aniso picks max level to combine")
1732 .source({0, 0, 8, 8})
1733 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1734 SkSamplingOptions::Aniso(4.f), Expect::kDeferredImage)
1735 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1736 SkSamplingOptions::Aniso(2.f), Expect::kDeferredImage,
1737 /*expectedSampling=*/SkSamplingOptions::Aniso(4.f))
1738 .run(/*requestedOutput=*/{0, 0, 16, 16});
1739
1740 TestCase(r, "aniso picks max level to combine (other direction)")
1741 .source({0, 0, 8, 8})
1742 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1743 SkSamplingOptions::Aniso(2.f), Expect::kDeferredImage)
1744 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1745 SkSamplingOptions::Aniso(4.f), Expect::kDeferredImage)
1746 .run(/*requestedOutput=*/{0, 0, 16, 16});
1747
1748 TestCase(r, "linear + aniso becomes aniso")
1749 .source({0, 0, 8, 8})
1750 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1751 SkFilterMode::kLinear, Expect::kDeferredImage)
1752 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1753 SkSamplingOptions::Aniso(2.f), Expect::kDeferredImage)
1754 .run(/*requestedOutput=*/{0, 0, 16, 16});
1755
1756 TestCase(r, "aniso + linear stays aniso")
1757 .source({0, 0, 8, 8})
1758 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1759 SkSamplingOptions::Aniso(4.f), Expect::kDeferredImage)
1760 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1761 SkFilterMode::kLinear, Expect::kDeferredImage,
1762 /*expectedSampling=*/SkSamplingOptions::Aniso(4.f))
1763 .run(/*requestedOutput=*/{0, 0, 16, 16});
1764
1765 // TODO: Add cases for mipmapping once that becomes relevant (SkSpecialImage does not have
1766 // mipmaps right now).
1767 }
1768
DEF_TEST_SUITE(IncompatibleSamplingResolvesImages,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1769 DEF_TEST_SUITE(IncompatibleSamplingResolvesImages, r, CtsEnforcement::kApiLevel_T,
1770 CtsEnforcement::kNextRelease) {
1771 TestCase(r, "different bicubics do not combine")
1772 .source({0, 0, 8, 8})
1773 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1774 SkCubicResampler::Mitchell(), Expect::kDeferredImage)
1775 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1776 SkCubicResampler::CatmullRom(), Expect::kNewImage)
1777 .run(/*requestedOutput=*/{0, 0, 16, 16});
1778
1779 TestCase(r, "nearest + linear do not combine")
1780 .source({0, 0, 8, 8})
1781 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1782 SkFilterMode::kNearest, Expect::kDeferredImage)
1783 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1784 SkFilterMode::kLinear, Expect::kNewImage)
1785 .run(/*requestedOutput=*/{0, 0, 16, 16});
1786
1787 TestCase(r, "linear + nearest do not combine")
1788 .source({0, 0, 8, 8})
1789 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1790 SkFilterMode::kLinear, Expect::kDeferredImage)
1791 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1792 SkFilterMode::kNearest, Expect::kNewImage)
1793 .run(/*requestedOutput=*/{0, 0, 16, 16});
1794
1795 TestCase(r, "bicubic + aniso do not combine")
1796 .source({0, 0, 8, 8})
1797 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1798 SkCubicResampler::Mitchell(), Expect::kDeferredImage)
1799 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1800 SkSamplingOptions::Aniso(4.f), Expect::kNewImage)
1801 .run(/*requestedOutput=*/{0, 0, 16, 16});
1802
1803 TestCase(r, "aniso + bicubic do not combine")
1804 .source({0, 0, 8, 8})
1805 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1806 SkSamplingOptions::Aniso(4.f), Expect::kDeferredImage)
1807 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1808 SkCubicResampler::Mitchell(), Expect::kNewImage)
1809 .run(/*requestedOutput=*/{0, 0, 16, 16});
1810
1811 TestCase(r, "nearest + nearest do not combine")
1812 .source({0, 0, 8, 8})
1813 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1814 SkFilterMode::kNearest, Expect::kDeferredImage)
1815 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1816 SkFilterMode::kNearest, Expect::kNewImage)
1817 .run(/*requestedOutput=*/{0, 0, 16, 16});
1818 }
1819
DEF_TEST_SUITE(IntegerOffsetIgnoresNearestSampling,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1820 DEF_TEST_SUITE(IntegerOffsetIgnoresNearestSampling, r, CtsEnforcement::kApiLevel_T,
1821 CtsEnforcement::kNextRelease) {
1822 // Bicubic is used here to reflect that it should use the non-NN sampling and just needs to be
1823 // something other than the default to detect that it got carried through.
1824 TestCase(r, "integer translate+NN then bicubic combines")
1825 .source({0, 0, 8, 8})
1826 .applyTransform(SkMatrix::Translate(2, 2),
1827 SkFilterMode::kNearest, Expect::kDeferredImage,
1828 FilterResult::kDefaultSampling)
1829 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1830 SkCubicResampler::Mitchell(), Expect::kDeferredImage)
1831 .run(/*requestedOutput=*/{0, 0, 16, 16});
1832
1833 TestCase(r, "bicubic then integer translate+NN combines")
1834 .source({0, 0, 8, 8})
1835 .applyTransform(SkMatrix::RotateDeg(2.f, {4.f, 4.f}),
1836 SkCubicResampler::Mitchell(), Expect::kDeferredImage)
1837 .applyTransform(SkMatrix::Translate(2, 2),
1838 SkFilterMode::kNearest, Expect::kDeferredImage,
1839 /*expectedSampling=*/SkCubicResampler::Mitchell())
1840 .run(/*requestedOutput=*/{0, 0, 16, 16});
1841 }
1842
1843 // ----------------------------------------------------------------------------
1844 // applyTransform() interacting with applyCrop()
1845
DEF_TEST_SUITE(TransformBecomesEmpty,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1846 DEF_TEST_SUITE(TransformBecomesEmpty, r, CtsEnforcement::kApiLevel_T,
1847 CtsEnforcement::kNextRelease) {
1848 TestCase(r, "Transform moves src image outside of requested output")
1849 .source({0, 0, 8, 8})
1850 .applyTransform(SkMatrix::Translate(10.f, 10.f), Expect::kEmptyImage)
1851 .run(/*requestedOutput=*/{0, 0, 8, 8});
1852
1853 TestCase(r, "Transform moves src image outside of crop")
1854 .source({0, 0, 8, 8})
1855 .applyTransform(SkMatrix::Translate(10.f, 10.f), Expect::kDeferredImage)
1856 .applyCrop({2, 2, 6, 6}, Expect::kEmptyImage)
1857 .run(/*requestedOutput=*/{0, 0, 20, 20});
1858
1859 TestCase(r, "Transform moves cropped image outside of requested output")
1860 .source({0, 0, 8, 8})
1861 .applyCrop({1, 1, 4, 4}, Expect::kDeferredImage)
1862 .applyTransform(SkMatrix::Translate(-5.f, -5.f), Expect::kEmptyImage)
1863 .run(/*requestedOutput=*/{0, 0, 8, 8});
1864 }
1865
DEF_TEST_SUITE(TransformAndCrop,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1866 DEF_TEST_SUITE(TransformAndCrop, r, CtsEnforcement::kApiLevel_T, CtsEnforcement::kNextRelease) {
1867 TestCase(r, "Crop after transform can always apply")
1868 .source({0, 0, 16, 16})
1869 .applyTransform(SkMatrix::RotateDeg(45.f, {3.f, 4.f}), Expect::kDeferredImage)
1870 .applyCrop({2, 2, 15, 15}, Expect::kDeferredImage)
1871 .run(/*requestedOutput=*/{0, 0, 16, 16});
1872
1873 // TODO: Expand this test case to be arbitrary float S+T transforms when FilterResult tracks
1874 // both a srcRect and dstRect.
1875 TestCase(r, "Crop after translate is lifted to image subset")
1876 .source({0, 0, 32, 32})
1877 .applyTransform(SkMatrix::Translate(12.f, 8.f), Expect::kDeferredImage)
1878 .applyCrop({16, 16, 24, 24}, Expect::kDeferredImage)
1879 .applyTransform(SkMatrix::RotateDeg(45.f, {16.f, 16.f}), Expect::kDeferredImage)
1880 .run(/*requestedOutput=*/{0, 0, 32, 32});
1881
1882 TestCase(r, "Transform after unlifted crop triggers new image")
1883 .source({0, 0, 16, 16})
1884 .applyTransform(SkMatrix::RotateDeg(45.f, {8.f, 8.f}), Expect::kDeferredImage)
1885 .applyCrop({1, 1, 15, 15}, Expect::kDeferredImage)
1886 .applyTransform(SkMatrix::RotateDeg(-10.f, {8.f, 4.f}), Expect::kNewImage)
1887 .run(/*requestedOutput=*/{0, 0, 16, 16});
1888
1889 TestCase(r, "Transform after unlifted crop with interior output does not trigger new image")
1890 .source({0, 0, 16, 16})
1891 .applyTransform(SkMatrix::RotateDeg(45.f, {8.f, 8.f}), Expect::kDeferredImage)
1892 .applyCrop({1, 1, 15, 15}, Expect::kDeferredImage)
1893 .applyTransform(SkMatrix::RotateDeg(-10.f, {8.f, 4.f}), Expect::kDeferredImage)
1894 .run(/*requestedOutput=*/{4, 4, 12, 12});
1895
1896 TestCase(r, "Translate after unlifted crop does not trigger new image")
1897 .source({0, 0, 16, 16})
1898 .applyTransform(SkMatrix::RotateDeg(5.f, {8.f, 8.f}), Expect::kDeferredImage)
1899 .applyCrop({2, 2, 14, 14}, Expect::kDeferredImage)
1900 .applyTransform(SkMatrix::Translate(4.f, 6.f), Expect::kDeferredImage)
1901 .run(/*requestedOutput=*/{0, 0, 16, 16});
1902
1903 TestCase(r, "Transform after large no-op crop does not trigger new image")
1904 .source({0, 0, 64, 64})
1905 .applyTransform(SkMatrix::RotateDeg(45.f, {32.f, 32.f}), Expect::kDeferredImage)
1906 .applyCrop({-64, -64, 128, 128}, Expect::kDeferredImage)
1907 .applyTransform(SkMatrix::RotateDeg(-30.f, {32.f, 32.f}), Expect::kDeferredImage)
1908 .run(/*requestedOutput=*/{0, 0, 64, 64});
1909 }
1910
DEF_TEST_SUITE(TransformAndTile,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1911 DEF_TEST_SUITE(TransformAndTile, r, CtsEnforcement::kApiLevel_T, CtsEnforcement::kNextRelease) {
1912 // Test interactions of non-decal tile modes and transforms
1913 for (SkTileMode tm : kTileModes) {
1914 if (tm == SkTileMode::kDecal) {
1915 continue;
1916 }
1917
1918 TestCase(r, "Transform after tile mode does not trigger new image")
1919 .source({0, 0, 64, 64})
1920 .applyCrop({2, 2, 32, 32}, tm, Expect::kDeferredImage)
1921 .applyTransform(SkMatrix::RotateDeg(20.f, {16.f, 8.f}), Expect::kDeferredImage)
1922 .run(/*requestedOutput=*/{0, 0, 64, 64});
1923
1924 TestCase(r, "Integer transform before tile mode does not trigger new image")
1925 .source({0, 0, 32, 32})
1926 .applyTransform(SkMatrix::Translate(16.f, 16.f), Expect::kDeferredImage)
1927 .applyCrop({20, 20, 40, 40}, tm, Expect::kDeferredImage)
1928 .run(/*requestedOutput=*/{0, 0, 64, 64});
1929
1930 TestCase(r, "Non-integer transform before tile mode triggers new image")
1931 .source({0, 0, 50, 40})
1932 .applyTransform(SkMatrix::RotateDeg(-30.f, {20.f, 10.f}), Expect::kDeferredImage)
1933 .applyCrop({10, 10, 30, 30}, tm, Expect::kNewImage)
1934 .run(/*requestedOutput=*/{0, 0, 50, 50});
1935
1936 TestCase(r, "Non-integer transform before tiling defers image if edges are hidden")
1937 .source({0, 0, 64, 64})
1938 .applyTransform(SkMatrix::RotateDeg(45.f, {32.f, 32.f}), Expect::kDeferredImage)
1939 .applyCrop({10, 10, 50, 50}, tm, Expect::kDeferredImage,
1940 /*expectedTileMode=*/SkTileMode::kDecal)
1941 .run(/*requestedOutput=*/{11, 11, 49, 49});
1942 }
1943 }
1944
1945 // ----------------------------------------------------------------------------
1946 // applyColorFilter() and interactions with transforms/crops
1947
DEF_TEST_SUITE(ColorFilter,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1948 DEF_TEST_SUITE(ColorFilter, r, CtsEnforcement::kApiLevel_T, CtsEnforcement::kNextRelease) {
1949 TestCase(r, "applyColorFilter() defers image")
1950 .source({0, 0, 24, 24})
1951 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
1952 .run(/*requestedOutput=*/{0, 0, 32, 32});
1953
1954 TestCase(r, "applyColorFilter() composes with other color filters")
1955 .source({0, 0, 24, 24})
1956 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
1957 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
1958 .run(/*requestedOutput=*/{0, 0, 32, 32});
1959
1960 TestCase(r, "Transparency-affecting color filter fills output")
1961 .source({0, 0, 24, 24})
1962 .applyColorFilter(affect_transparent(SkColors::kBlue), Expect::kDeferredImage)
1963 .run(/*requestedOutput=*/{-8, -8, 32, 32});
1964
1965 // Since there is no cropping between the composed color filters, transparency-affecting CFs
1966 // can still compose together.
1967 TestCase(r, "Transparency-affecting composition fills output (ATBx2)")
1968 .source({0, 0, 24, 24})
1969 .applyColorFilter(affect_transparent(SkColors::kBlue), Expect::kDeferredImage)
1970 .applyColorFilter(affect_transparent(SkColors::kRed), Expect::kDeferredImage)
1971 .run(/*requestedOutput=*/{-8, -8, 32, 32});
1972
1973 TestCase(r, "Transparency-affecting composition fills output (ATB,reg)")
1974 .source({0, 0, 24, 24})
1975 .applyColorFilter(affect_transparent(SkColors::kBlue), Expect::kDeferredImage)
1976 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
1977 .run(/*requestedOutput=*/{-8, -8, 32, 32});
1978
1979 TestCase(r, "Transparency-affecting composition fills output (reg,ATB)")
1980 .source({0, 0, 24, 24})
1981 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
1982 .applyColorFilter(affect_transparent(SkColors::kBlue), Expect::kDeferredImage)
1983 .run(/*requestedOutput=*/{-8, -8, 32, 32});
1984
1985 // oss-fuzz:70134 Requesting the output of an infinite image (e.g. transparency affecting
1986 // color filter) at the limit of 32-bit ints doesn't leave room for border padding pixels.
1987 // This should cleanly fail to an empty image.
1988 TestCase(r, "Pathologic output bounds with transparency-affecting color filter is empty")
1989 .source({0, 0, 16, 16})
1990 .applyColorFilter(affect_transparent(SkColors::kRed), Expect::kEmptyImage)
1991 .run(/*requestedOutput=*/{-INT32_MAX, 0, -INT32_MAX + 10, 16});
1992 }
1993
DEF_TEST_SUITE(TransformedColorFilter,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)1994 DEF_TEST_SUITE(TransformedColorFilter, r, CtsEnforcement::kApiLevel_T,
1995 CtsEnforcement::kNextRelease) {
1996 TestCase(r, "Transform composes with regular CF")
1997 .source({0, 0, 24, 24})
1998 .applyTransform(SkMatrix::RotateDeg(45.f, {12, 12}), Expect::kDeferredImage)
1999 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2000 .run(/*requestedOutput=*/{0, 0, 24, 24});
2001
2002 TestCase(r, "Regular CF composes with transform")
2003 .source({0, 0, 24, 24})
2004 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2005 .applyTransform(SkMatrix::RotateDeg(45.f, {12, 12}), Expect::kDeferredImage)
2006 .run(/*requestedOutput=*/{0, 0, 24, 24});
2007
2008 TestCase(r, "Transform composes with transparency-affecting CF")
2009 .source({0, 0, 24, 24})
2010 .applyTransform(SkMatrix::RotateDeg(45.f, {12, 12}), Expect::kDeferredImage)
2011 .applyColorFilter(affect_transparent(SkColors::kBlue), Expect::kDeferredImage)
2012 .run(/*requestedOutput=*/{0, 0, 24, 24});
2013
2014 // NOTE: Because there is no explicit crop between the color filter and the transform,
2015 // output bounds propagation means the layer bounds of the applied color filter are never
2016 // visible post transform. This is detected and allows the transform to be composed without
2017 // producing an intermediate image. See later tests for when a crop prevents this optimization.
2018 TestCase(r, "Transparency-affecting CF composes with transform")
2019 .source({0, 0, 24, 24})
2020 .applyColorFilter(affect_transparent(SkColors::kBlue), Expect::kDeferredImage)
2021 .applyTransform(SkMatrix::RotateDeg(45.f, {12, 12}), Expect::kDeferredImage)
2022 .run(/*requestedOutput=*/{-50, -50, 50, 50});
2023 }
2024
DEF_TEST_SUITE(TransformBetweenColorFilters,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)2025 DEF_TEST_SUITE(TransformBetweenColorFilters, r, CtsEnforcement::kApiLevel_T,
2026 CtsEnforcement::kNextRelease) {
2027 // NOTE: The lack of explicit crops allows all of these operations to be optimized as well.
2028 TestCase(r, "Transform between regular color filters")
2029 .source({0, 0, 24, 24})
2030 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2031 .applyTransform(SkMatrix::RotateDeg(45.f, {12, 12}), Expect::kDeferredImage)
2032 .applyColorFilter(alpha_modulate(0.75f), Expect::kDeferredImage)
2033 .run(/*requestedOutput=*/{0, 0, 24, 24});
2034
2035 TestCase(r, "Transform between transparency-affecting color filters")
2036 .source({0, 0, 24, 24})
2037 .applyColorFilter(affect_transparent(SkColors::kBlue), Expect::kDeferredImage)
2038 .applyTransform(SkMatrix::RotateDeg(45.f, {12, 12}), Expect::kDeferredImage)
2039 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2040 .run(/*requestedOutput=*/{0, 0, 24, 24});
2041
2042 TestCase(r, "Transform between ATB and regular color filters")
2043 .source({0, 0, 24, 24})
2044 .applyColorFilter(affect_transparent(SkColors::kBlue), Expect::kDeferredImage)
2045 .applyTransform(SkMatrix::RotateDeg(45.f, {12, 12}), Expect::kDeferredImage)
2046 .applyColorFilter(alpha_modulate(0.75f), Expect::kDeferredImage)
2047 .run(/*requestedOutput=*/{0, 0, 24, 24});
2048
2049 TestCase(r, "Transform between regular and ATB color filters")
2050 .source({0, 0, 24, 24})
2051 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2052 .applyTransform(SkMatrix::RotateDeg(45.f, {12, 12}), Expect::kDeferredImage)
2053 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2054 .run(/*requestedOutput=*/{0, 0, 24, 24});
2055 }
2056
DEF_TEST_SUITE(ColorFilterBetweenTransforms,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)2057 DEF_TEST_SUITE(ColorFilterBetweenTransforms, r, CtsEnforcement::kApiLevel_T,
2058 CtsEnforcement::kNextRelease) {
2059 TestCase(r, "Regular color filter between transforms")
2060 .source({0, 0, 24, 24})
2061 .applyTransform(SkMatrix::RotateDeg(20.f, {12, 12}), Expect::kDeferredImage)
2062 .applyColorFilter(alpha_modulate(0.8f), Expect::kDeferredImage)
2063 .applyTransform(SkMatrix::RotateDeg(10.f, {5.f, 8.f}), Expect::kDeferredImage)
2064 .run(/*requestedOutput=*/{0, 0, 24, 24});
2065
2066 TestCase(r, "Transparency-affecting color filter between transforms")
2067 .source({0, 0, 24, 24})
2068 .applyTransform(SkMatrix::RotateDeg(20.f, {12, 12}), Expect::kDeferredImage)
2069 .applyColorFilter(affect_transparent(SkColors::kRed), Expect::kDeferredImage)
2070 .applyTransform(SkMatrix::RotateDeg(10.f, {5.f, 8.f}), Expect::kDeferredImage)
2071 .run(/*requestedOutput=*/{0, 0, 24, 24});
2072 }
2073
DEF_TEST_SUITE(CroppedColorFilter,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)2074 DEF_TEST_SUITE(CroppedColorFilter, r, CtsEnforcement::kApiLevel_T, CtsEnforcement::kNextRelease) {
2075 for (SkTileMode tm : kTileModes) {
2076 TestCase(r, "Regular color filter after empty crop stays empty")
2077 .source({0, 0, 16, 16})
2078 .applyCrop(SkIRect::MakeEmpty(), tm, Expect::kEmptyImage)
2079 .applyColorFilter(alpha_modulate(0.2f), Expect::kEmptyImage)
2080 .run(/*requestedOutput=*/{0, 0, 16, 16});
2081
2082 TestCase(r, "Transparency-affecting color filter after empty crop creates new image")
2083 .source({0, 0, 16, 16})
2084 .applyCrop(SkIRect::MakeEmpty(), tm, Expect::kEmptyImage)
2085 .applyColorFilter(affect_transparent(SkColors::kRed), Expect::kNewImage,
2086 /*expectedColorFilter=*/nullptr) // CF applied ASAP to new img
2087 .run(/*requestedOutput=*/{0, 0, 16, 16});
2088
2089 TestCase(r, "Regular color filter composes with crop")
2090 .source({0, 0, 32, 32})
2091 .applyColorFilter(alpha_modulate(0.7f), Expect::kDeferredImage)
2092 .applyCrop({8, 8, 24, 24}, tm, Expect::kDeferredImage)
2093 .run(/*requestedOutput=*/{0, 0, 32, 32});
2094
2095 TestCase(r, "Crop composes with regular color filter")
2096 .source({0, 0, 32, 32})
2097 .applyCrop({8, 8, 24, 24}, tm, Expect::kDeferredImage)
2098 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2099 .run(/*requestedOutput=*/{0, 0, 32, 32});
2100 // FIXME need to disable the stats tracking for renderExpected() and compare()
2101
2102 TestCase(r, "Transparency-affecting color filter restricted by crop")
2103 .source({0, 0, 32, 32})
2104 .applyColorFilter(affect_transparent(SkColors::kRed), Expect::kDeferredImage)
2105 .applyCrop({8, 8, 24, 24}, tm, Expect::kDeferredImage)
2106 .run(/*requestedOutput=*/{0, 0, 32, 32});
2107
2108 TestCase(r, "Crop composes with transparency-affecting color filter")
2109 .source({0, 0, 32, 32})
2110 .applyCrop({8, 8, 24, 24}, tm, Expect::kDeferredImage)
2111 .applyColorFilter(affect_transparent(SkColors::kRed), Expect::kDeferredImage)
2112 .run(/*requestedOutput=*/{0, 0, 32, 32});
2113 }
2114 }
2115
DEF_TEST_SUITE(CropBetweenColorFilters,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)2116 DEF_TEST_SUITE(CropBetweenColorFilters, r, CtsEnforcement::kApiLevel_T,
2117 CtsEnforcement::kNextRelease) {
2118 for (SkTileMode tm : kTileModes) {
2119 TestCase(r, "Crop between regular color filters")
2120 .source({0, 0, 32, 32})
2121 .applyColorFilter(alpha_modulate(0.8f), Expect::kDeferredImage)
2122 .applyCrop({8, 8, 24, 24}, tm, Expect::kDeferredImage)
2123 .applyColorFilter(alpha_modulate(0.4f), Expect::kDeferredImage)
2124 .run(/*requestedOutput=*/{0, 0, 32, 32});
2125
2126 if (tm == SkTileMode::kDecal) {
2127 TestCase(r, "Crop between transparency-affecting color filters requires new image")
2128 .source({0, 0, 32, 32})
2129 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2130 .applyCrop({8, 8, 24, 24}, SkTileMode::kDecal, Expect::kDeferredImage)
2131 .applyColorFilter(affect_transparent(SkColors::kRed), Expect::kNewImage)
2132 .run(/*requestedOutput=*/{0, 0, 32, 32});
2133
2134 TestCase(r, "Output-constrained crop between transparency-affecting filters does not")
2135 .source({0, 0, 32, 32})
2136 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2137 .applyCrop({8, 8, 24, 24}, SkTileMode::kDecal, Expect::kDeferredImage)
2138 .applyColorFilter(affect_transparent(SkColors::kRed), Expect::kDeferredImage)
2139 .run(/*requestedOutput=*/{8, 8, 24, 24});
2140 } else {
2141 TestCase(r, "Tiling between transparency-affecting color filters defers image")
2142 .source({0, 0, 32, 32})
2143 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2144 .applyCrop({8, 8, 24, 24}, tm, Expect::kDeferredImage)
2145 .applyColorFilter(affect_transparent(SkColors::kRed), Expect::kDeferredImage)
2146 .run(/*requestedOutput=*/{0, 0, 32, 32});
2147 }
2148
2149 TestCase(r, "Crop between regular and ATB color filters")
2150 .source({0, 0, 32, 32})
2151 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2152 .applyCrop({8, 8, 24, 24}, tm, Expect::kDeferredImage)
2153 .applyColorFilter(affect_transparent(SkColors::kRed), Expect::kDeferredImage)
2154 .run(/*requestedOutput=*/{0, 0, 32, 32});
2155
2156 TestCase(r, "Crop between ATB and regular color filters")
2157 .source({0, 0, 32, 32})
2158 .applyColorFilter(affect_transparent(SkColors::kRed), Expect::kDeferredImage)
2159 .applyCrop({8, 8, 24, 24}, tm, Expect::kDeferredImage)
2160 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2161 .run(/*requestedOutput=*/{0, 0, 32, 32});
2162 }
2163 }
2164
DEF_TEST_SUITE(ColorFilterBetweenCrops,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)2165 DEF_TEST_SUITE(ColorFilterBetweenCrops, r, CtsEnforcement::kApiLevel_T,
2166 CtsEnforcement::kNextRelease) {
2167 for (SkTileMode firstTM : kTileModes) {
2168 for (SkTileMode secondTM : kTileModes) {
2169 Expect newImageIfNotDecalOrDoubleClamp =
2170 secondTM != SkTileMode::kDecal &&
2171 !(secondTM == SkTileMode::kClamp && firstTM == SkTileMode::kClamp) ?
2172 Expect::kNewImage : Expect::kDeferredImage;
2173
2174 TestCase(r, "Regular color filter between crops")
2175 .source({0, 0, 32, 32})
2176 .applyCrop({4, 4, 24, 24}, firstTM, Expect::kDeferredImage)
2177 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2178 .applyCrop({15, 15, 32, 32}, secondTM, newImageIfNotDecalOrDoubleClamp,
2179 secondTM == SkTileMode::kDecal ? firstTM : secondTM)
2180 .run(/*requestedOutput=*/{0, 0, 32, 32});
2181
2182 TestCase(r, "Transparency-affecting color filter between crops")
2183 .source({0, 0, 32, 32})
2184 .applyCrop({4, 4, 24, 24}, firstTM, Expect::kDeferredImage)
2185 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2186 .applyCrop({15, 15, 32, 32}, secondTM, newImageIfNotDecalOrDoubleClamp,
2187 secondTM == SkTileMode::kDecal ? firstTM : secondTM)
2188 .run(/*requestedOutput=*/{0, 0, 32, 32});
2189 }
2190 }
2191 }
2192
DEF_TEST_SUITE(CroppedTransformedColorFilter,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)2193 DEF_TEST_SUITE(CroppedTransformedColorFilter, r, CtsEnforcement::kApiLevel_T,
2194 CtsEnforcement::kNextRelease) {
2195 TestCase(r, "Transform -> crop -> regular color filter")
2196 .source({0, 0, 32, 32})
2197 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kDeferredImage)
2198 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2199 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2200 .run(/*requestedOutput=*/{0, 0, 32, 32});
2201
2202 TestCase(r, "Transform -> regular color filter -> crop")
2203 .source({0, 0, 32, 32})
2204 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kDeferredImage)
2205 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2206 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2207 .run(/*requestedOutput=*/{0, 0, 32, 32});
2208
2209 TestCase(r, "Crop -> transform -> regular color filter")
2210 .source({0, 0, 32, 32})
2211 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2212 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kDeferredImage)
2213 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2214 .run(/*requestedOutput=*/{0, 0, 32, 32});
2215
2216 TestCase(r, "Crop -> regular color filter -> transform")
2217 .source({0, 0, 32, 32})
2218 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2219 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2220 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kDeferredImage)
2221 .run(/*requestedOutput=*/{0, 0, 32, 32});
2222
2223 TestCase(r, "Regular color filter -> transform -> crop")
2224 .source({0, 0, 32, 32})
2225 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2226 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kDeferredImage)
2227 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2228 .run(/*requestedOutput=*/{0, 0, 32, 32});
2229
2230 TestCase(r, "Regular color filter -> crop -> transform")
2231 .source({0, 0, 32, 32})
2232 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2233 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2234 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kDeferredImage)
2235 .run(/*requestedOutput=*/{0, 0, 32, 32});
2236 }
2237
DEF_TEST_SUITE(CroppedTransformedTransparencyAffectingColorFilter,r,CtsEnforcement::kApiLevel_T,CtsEnforcement::kNextRelease)2238 DEF_TEST_SUITE(CroppedTransformedTransparencyAffectingColorFilter, r, CtsEnforcement::kApiLevel_T,
2239 CtsEnforcement::kNextRelease) {
2240 // When the crop is not between the transform and transparency-affecting color filter,
2241 // either the order of operations or the bounds propagation means that every action can be
2242 // deferred. Below, when the crop is between the two actions, new images are triggered.
2243 TestCase(r, "Transform -> transparency-affecting color filter -> crop")
2244 .source({0, 0, 32, 32})
2245 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kDeferredImage)
2246 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2247 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2248 .run(/*requestedOutput=*/{0, 0, 32, 32});
2249
2250 TestCase(r, "Crop -> transform -> transparency-affecting color filter")
2251 .source({0, 0, 32, 32})
2252 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2253 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kDeferredImage)
2254 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2255 .run(/*requestedOutput=*/{0, 0, 32, 32});
2256
2257 TestCase(r, "Crop -> transparency-affecting color filter -> transform")
2258 .source({0, 0, 32, 32})
2259 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2260 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2261 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kDeferredImage)
2262 .run(/*requestedOutput=*/{0, 0, 32, 32});
2263
2264 TestCase(r, "Transparency-affecting color filter -> transform -> crop")
2265 .source({0, 0, 32, 32})
2266 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2267 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kDeferredImage)
2268 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2269 .run(/*requestedOutput=*/{0, 0, 32, 32});
2270
2271 // Since the crop is between the transform and color filter (or vice versa), transparency
2272 // outside the crop is introduced that should not be affected by the color filter were no
2273 // new image to be created.
2274 TestCase(r, "Transform -> crop -> transparency-affecting color filter")
2275 .source({0, 0, 32, 32})
2276 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kDeferredImage)
2277 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2278 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kNewImage)
2279 .run(/*requestedOutput=*/{0, 0, 32, 32});
2280
2281 TestCase(r, "Transparency-affecting color filter -> crop -> transform")
2282 .source({0, 0, 32, 32})
2283 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2284 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2285 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kNewImage)
2286 .run(/*requestedOutput=*/{0, 0, 32, 32});
2287
2288 // However if the output is small enough to fit within the transformed interior, the
2289 // transparency is not visible.
2290 TestCase(r, "Transform -> crop -> transparency-affecting color filter")
2291 .source({0, 0, 32, 32})
2292 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kDeferredImage)
2293 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2294 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2295 .run(/*requestedOutput=*/{15, 15, 21, 21});
2296
2297 TestCase(r, "Transparency-affecting color filter -> crop -> transform")
2298 .source({0, 0, 32, 32})
2299 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2300 .applyCrop({2, 2, 30, 30}, Expect::kDeferredImage)
2301 .applyTransform(SkMatrix::RotateDeg(30.f, {16, 16}), Expect::kDeferredImage)
2302 .run(/*requestedOutput=*/{15, 15, 21, 21});
2303 }
2304
DEF_TEST_SUITE(BackdropFilterRotated,r,CtsEnforcement::kApiLevel_V,CtsEnforcement::kNextRelease)2305 DEF_TEST_SUITE(BackdropFilterRotated, r,
2306 CtsEnforcement::kApiLevel_V, CtsEnforcement::kNextRelease) {
2307 // These values are extracted from a cc_unittest that had a 200x200 image, with a 10-degree
2308 // rotated 100x200 layer over the right half of the base image, with a backdrop blur. The
2309 // rotation forces SkCanvas to crop and transform the base device's content to be aligned with
2310 // the layer space of the blur. The rotation is such that the backdrop image must be clamped
2311 // (hence the first crop) and the clamp tiling remains visible in the layer image. However,
2312 // floating point precision in the layer bounds analysis was causing FilterResult to think that
2313 // the layer decal was also visible so the first crop would be resolved before the transform was
2314 // applied.
2315 //
2316 // While it's expected that the second clamping crop (part of the blur effect), forces the
2317 // transform and first clamp to be resolved, we were incorrectly producing two new images
2318 // instead of just one.
2319 TestCase(r, "Layer decal shouldn't be visible")
2320 .source({65, 0, 199, 200})
2321 .applyCrop({65, 0, 199, 200}, SkTileMode::kClamp, Expect::kDeferredImage)
2322 .applyTransform(SkMatrix::MakeAll( 0.984808f, 0.173648f, -98.4808f,
2323 -0.173648f, 0.984808f, 17.3648f,
2324 0.000000f, 0.000000f, 1.0000f),
2325 Expect::kDeferredImage)
2326 .applyCrop({0, 0, 100, 200}, SkTileMode::kClamp, Expect::kNewImage)
2327 .run(/*requestedOutput=*/{-15, -15, 115, 215});
2328 }
2329
2330 // Nearly identity rescales are treated as the identity
2331 static constexpr SkSize kNearlyIdentity = {0.999f, 0.999f};
2332
DEF_TEST_SUITE(RescaleWithTileMode,r,CtsEnforcement::kApiLevel_V,CtsEnforcement::kNextRelease)2333 DEF_TEST_SUITE(RescaleWithTileMode, r,
2334 CtsEnforcement::kApiLevel_V, CtsEnforcement::kNextRelease) {
2335 for (SkTileMode tm : kTileModes) {
2336 TestCase(r, "Identity rescale is a no-op")
2337 .source({0, 0, 50, 50})
2338 .applyCrop({0, 0, 50, 50}, tm, Expect::kDeferredImage)
2339 .rescale({1.f, 1.f}, Expect::kDeferredImage)
2340 .run(/*requestedOutput=*/{-5, -5, 55, 55});
2341
2342 TestCase(r, "Near identity rescale is a no-op",
2343 kDefaultMaxAllowedPercentImageDiff,
2344 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2345 .source({0, 0, 50, 50})
2346 .applyCrop({0, 0, 50, 50}, tm, Expect::kDeferredImage)
2347 .rescale(kNearlyIdentity, Expect::kDeferredImage)
2348 .run(/*requestedOutput=*/{-5, -5, 55, 55});
2349
2350 // NOTE: As the scale factor decreases and more decimation steps are required, the testing
2351 // allowed tolerances increase greatly. These were chosen as "acceptable" after reviewing
2352 // the expected vs. actual images. The results diverge due to differences in the simple
2353 // expected decimation and the actual rescale() implementation, as well as how small the
2354 // final images become.
2355 //
2356 // Similarly, the allowed transparent border tolerance must be increased for kDecal tests
2357 // because the expected image's content is expanded by a larger and larger factor during its
2358 // upscale.
2359 TestCase(r, "1-step rescale preserves tile mode",
2360 kDefaultMaxAllowedPercentImageDiff,
2361 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2362 .source({16, 16, 64, 64})
2363 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2364 .rescale({0.5f, 0.5f}, Expect::kNewImage, tm)
2365 .run(/*requestedOutput=*/{0, 0, 80, 80});
2366
2367 const bool periodic = tm == SkTileMode::kRepeat || tm == SkTileMode::kMirror;
2368 TestCase(r, "2-step rescale preserves tile mode",
2369 /*allowedPercentImageDiff=*/tm == SkTileMode::kDecal ? 5.9f
2370 : periodic ? 2.5f : 1.f,
2371 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 2 : 0)
2372 .source({16, 16, 64, 64})
2373 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2374 .rescale({0.25f, 0.25f}, Expect::kNewImage, tm)
2375 .run(/*requestedOutput=*/{0, 0, 80, 80});
2376
2377 TestCase(r, "2-step rescale with near-identity elision",
2378 /*allowedPercentImageDiff=*/periodic ? 17.75f : 41.83f,
2379 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 8 : 0)
2380 .source({16, 16, 64, 64})
2381 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2382 .rescale({0.23f, 0.23f}, Expect::kNewImage, tm)
2383 .run(/*requestedOutput=*/{0, 0, 80, 80});
2384
2385 TestCase(r, "3-step rescale preserves tile mode",
2386 /*allowedPercentImageDiff=*/periodic ? 56.3f : 51.3f,
2387 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 10 : 0)
2388 .source({16, 16, 64, 64})
2389 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2390 .rescale({0.155f, 0.155f}, Expect::kNewImage, tm)
2391 .run(/*requestedOutput=*/{0, 0, 80, 80});
2392
2393 // Non-uniform scales
2394 TestCase(r, "Identity X axis, near-identity Y axis is a no-op",
2395 kDefaultMaxAllowedPercentImageDiff,
2396 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2397 .source({16, 16, 64, 64})
2398 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2399 .rescale({1.f, kNearlyIdentity.height()}, Expect::kDeferredImage)
2400 .run(/*requestedOutput=*/{0, 0, 80, 80});
2401 TestCase(r, "Near-identity X axis, identity Y axis is a no-op",
2402 kDefaultMaxAllowedPercentImageDiff,
2403 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2404 .source({16, 16, 64, 64})
2405 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2406 .rescale({kNearlyIdentity.width(), 1.f}, Expect::kDeferredImage)
2407 .run(/*requestedOutput=*/{0, 0, 80, 80});
2408
2409 TestCase(r, "Identity X axis, 1-step Y axis preserves tile mode",
2410 /*allowedPercentImageDiff=*/tm == SkTileMode::kMirror ? 1.32f : 1.f,
2411 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2412 .source({16, 16, 64, 64})
2413 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2414 .rescale({1.f, 0.5f}, Expect::kNewImage, tm)
2415 .run(/*requestedOutput=*/{0, 0, 80, 80});
2416 TestCase(r, "Near-identity X axis, 1-step Y axis preserves tile mode",
2417 /*allowedPercentImageDiff=*/tm == SkTileMode::kMirror ? 1.7f : 1.f,
2418 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2419 .source({16, 16, 64, 64})
2420 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2421 .rescale({kNearlyIdentity.width(), 0.5f}, Expect::kNewImage, tm)
2422 .run(/*requestedOutput=*/{0, 0, 80, 80});
2423 TestCase(r, "Identity X axis, 2-step Y axis preserves tile mode",
2424 /*allowedPercentImageDiff=*/3.1f,
2425 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 2 : 0)
2426 .source({16, 16, 64, 64})
2427 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2428 .rescale({1.f, 0.25f}, Expect::kNewImage, tm)
2429 .run(/*requestedOutput=*/{0, 0, 80, 80});
2430 TestCase(r, "1-step X axis, 2-step Y axis preserves tile mode",
2431 /*allowedPercentImageDiff=*/periodic ? 23.2f : 17.7f,
2432 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 5 : 0)
2433 .source({16, 16, 64, 64})
2434 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2435 .rescale({.55f, 0.27f}, Expect::kNewImage, tm)
2436 .run(/*requestedOutput=*/{0, 0, 80, 80});
2437
2438 TestCase(r, "1-step X axis, identity Y axis preserves tile mode",
2439 /*allowedPercentImageDiff=*/tm == SkTileMode::kMirror ? 1.2f : 1.f,
2440 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2441 .source({16, 16, 64, 64})
2442 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2443 .rescale({0.5f, 1.f}, Expect::kNewImage, tm)
2444 .run(/*requestedOutput=*/{0, 0, 80, 80});
2445 TestCase(r, "1-step X axis, near-identity Y axis preserves tile mode",
2446 /*allowedPercentImageDiff=*/tm == SkTileMode::kMirror ? 1.7f : 1.f,
2447 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2448 .source({16, 16, 64, 64})
2449 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2450 .rescale({0.5f, kNearlyIdentity.height()}, Expect::kNewImage, tm)
2451 .run(/*requestedOutput=*/{0, 0, 80, 80});
2452 TestCase(r, "2-step X axis, identity Y axis preserves tile mode",
2453 /*allowedPercentImageDiff=*/3.1f,
2454 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 2 : 0)
2455 .source({16, 16, 64, 64})
2456 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2457 .rescale({0.25f, 1.f}, Expect::kNewImage, tm)
2458 .run(/*requestedOutput=*/{0, 0, 80, 80});
2459 TestCase(r, "2-step X axis, 1-step Y axis preserves tile mode",
2460 /*allowedPercentImageDiff=*/periodic ? 14.9f : 14.2f,
2461 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 5 : 0)
2462 .source({16, 16, 64, 64})
2463 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2464 .rescale({.27f, 0.55f}, Expect::kNewImage, tm)
2465 .run(/*requestedOutput=*/{0, 0, 80, 80});
2466
2467 // Chained decal tile modes don't create the circumstances of interest.
2468 if (tm == SkTileMode::kDecal) {
2469 continue;
2470 }
2471 TestCase(r, "Rescale applies layer bounds",
2472 kDefaultMaxAllowedPercentImageDiff,
2473 /*transparentCheckBorderTolerance=*/1)
2474 .source({16, 16, 64, 64})
2475 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2476 .applyCrop({4, 4, 76, 76}, SkTileMode::kDecal, Expect::kDeferredImage,
2477 /*expectedTileMode=*/tm)
2478 .rescale({0.5f, 0.5f}, Expect::kNewImage, /*expectedTileMode=*/SkTileMode::kDecal)
2479 .run(/*requestedOutput=*/{0, 0, 80, 80});
2480 }
2481 }
2482
DEF_TEST_SUITE(RescaleWithTransform,r,CtsEnforcement::kApiLevel_V,CtsEnforcement::kNextRelease)2483 DEF_TEST_SUITE(RescaleWithTransform, r,
2484 CtsEnforcement::kApiLevel_V, CtsEnforcement::kNextRelease) {
2485 for (SkTileMode tm : kTileModes) {
2486 TestCase(r, "Identity rescale defers integer translation")
2487 .source({0, 0, 50, 50})
2488 .applyCrop({0, 0, 50, 50}, tm, Expect::kDeferredImage)
2489 .applyTransform(SkMatrix::Translate(-10.f, -10.f), Expect::kDeferredImage)
2490 .rescale({1.f, 1.f}, Expect::kDeferredImage)
2491 .run(/*requestedOutput=*/{-15, -15, 45, 45});
2492
2493 TestCase(r, "Identity rescale applies complex transform")
2494 .source({16, 16, 64, 64})
2495 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2496 .applyTransform(SkMatrix::RotateDeg(45.f, {16.f, 16.f}), Expect::kDeferredImage)
2497 .rescale({1.f, 1.f}, Expect::kNewImage, SkTileMode::kDecal)
2498 .run(/*requestedOutput=*/{0, 0, 80, 80});
2499
2500 TestCase(r, "Near-identity rescale defers integer translation",
2501 /*allowedPercentImageDiff=*/kDefaultMaxAllowedPercentImageDiff,
2502 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2503 .source({0, 0, 50, 50})
2504 .applyCrop({0, 0, 50, 50}, tm, Expect::kDeferredImage)
2505 .applyTransform(SkMatrix::Translate(-10.f, -10.f), Expect::kDeferredImage)
2506 .rescale(kNearlyIdentity, Expect::kDeferredImage)
2507 .run(/*requestedOutput=*/{-15, -15, 45, 45});
2508
2509 TestCase(r, "Near-identity rescale applies complex transform")
2510 .source({0, 0, 50, 50})
2511 .applyCrop({0, 0, 50, 50}, tm, Expect::kDeferredImage)
2512 .applyTransform(SkMatrix::RotateDeg(15.f, {25.f, 25.f}), Expect::kDeferredImage)
2513 .rescale(kNearlyIdentity, Expect::kNewImage, SkTileMode::kDecal)
2514 .run(/*requestedOutput=*/{-5, -5, 55, 55});
2515
2516 TestCase(r, "Identity rescale with deferred scale applies transform in first step")
2517 .source({0, 0, 50, 50})
2518 .applyCrop({0, 0, 50, 50}, tm, Expect::kDeferredImage)
2519 .applyTransform(SkMatrix::Scale(0.4f, 0.4f), Expect::kDeferredImage)
2520 .rescale({1.f, 1.f}, Expect::kNewImage, SkTileMode::kDecal)
2521 .run(/*requestedOutput=*/{-10, -10, 30, 30});
2522
2523 TestCase(r, "Near-identity rescale with deferred scale applies transform in first step",
2524 kDefaultMaxAllowedPercentImageDiff,
2525 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2526 .source({0, 0, 50, 50})
2527 .applyCrop({0, 0, 50, 50}, tm, Expect::kDeferredImage)
2528 .applyTransform(SkMatrix::Scale(0.4f, 0.4f), Expect::kDeferredImage)
2529 .rescale(kNearlyIdentity, Expect::kNewImage, SkTileMode::kDecal)
2530 .run(/*requestedOutput=*/{-10, -10, 30, 30});
2531
2532 const bool periodic = tm == SkTileMode::kRepeat || tm == SkTileMode::kMirror;
2533
2534 TestCase(r, "1-step rescale applies complex transform in first step",
2535 /*allowedPercentImageDiff=*/periodic ? 1.1f : kDefaultMaxAllowedPercentImageDiff,
2536 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2537 .source({16, 16, 64, 64})
2538 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2539 .applyTransform(SkMatrix::RotateDeg(45.f, {16.f, 16.f}), Expect::kDeferredImage)
2540 .rescale({0.5f, 0.5f}, Expect::kNewImage, SkTileMode::kDecal)
2541 .run(/*requestedOutput=*/{0, 0, 80, 80});
2542
2543 TestCase(r, "2-step rescale applies complex transform",
2544 /*allowedPercentImageDiff=*/periodic ? 10.05f: 3.7f,
2545 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 4 : 0)
2546 .source({16, 16, 64, 64})
2547 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2548 .applyTransform(SkMatrix::RotateDeg(45.f, {16.f, 16.f}), Expect::kDeferredImage)
2549 .rescale({0.25f, 0.25f}, Expect::kNewImage, /*expectedTileMode=*/SkTileMode::kDecal)
2550 .run(/*requestedOutput=*/{0, 0, 80, 80});
2551
2552 // W/o resolving the deferred transform, the first rescale step could end up with a scale
2553 // that's much less than 1/2 and sampling would miss a lot of data.
2554 TestCase(r, "Rescale with deferred downscale applies transform before first step",
2555 kDefaultMaxAllowedPercentImageDiff,
2556 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2557 .source({16, 16, 64, 64})
2558 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2559 .applyTransform(SkMatrix::Scale(0.4f, 0.4f), Expect::kDeferredImage)
2560 .rescale({0.5f, 0.5f}, Expect::kNewImage, /*expectedTileMode=*/SkTileMode::kDecal)
2561 .run(/*requestedOutput=*/{0, 0, 80, 80});
2562
2563 // But for upscaling, it doesn't contribute to such sampling errors.
2564 TestCase(r, "Rescale with deferred upscale applies transform with first step",
2565 /*allowedPercentImageDiff=*/2.55f,
2566 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 3 : 0)
2567 .source({16, 16, 64, 64})
2568 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2569 .applyTransform(SkMatrix::Scale(1.5f, 1.5f), Expect::kDeferredImage)
2570 .rescale({0.5f, 0.5f}, Expect::kNewImage, /*expectedTileMode=*/SkTileMode::kDecal)
2571 .run(/*requestedOutput=*/{0, 0, 80, 80});
2572 }
2573 }
2574
DEF_TEST_SUITE(RescaleWithColorFilter,r,CtsEnforcement::kApiLevel_V,CtsEnforcement::kNextRelease)2575 DEF_TEST_SUITE(RescaleWithColorFilter, r,
2576 CtsEnforcement::kApiLevel_V, CtsEnforcement::kNextRelease) {
2577 for (SkTileMode tm : kTileModes) {
2578 TestCase(r, "Identity rescale applies color filter but defers tile mode")
2579 .source({0, 0, 50, 50})
2580 .applyCrop({0, 0, 50, 50}, tm, Expect::kDeferredImage)
2581 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2582 .rescale({1.f, 1.f}, Expect::kNewImage, tm)
2583 .run(/*requestedOutput=*/{-5, -5, 55, 55});
2584
2585 TestCase(r, "Near-identity rescale applies color filter but defers tile mode",
2586 kDefaultMaxAllowedPercentImageDiff,
2587 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2588 .source({0, 0, 50, 50})
2589 .applyCrop({0, 0, 50, 50}, tm, Expect::kDeferredImage)
2590 .applyColorFilter(alpha_modulate(0.5f), Expect::kDeferredImage)
2591 .rescale(kNearlyIdentity, Expect::kNewImage, tm)
2592 .run(/*requestedOutput=*/{-5, -5, 55, 55});
2593
2594 TestCase(r, "Rescale applies color filter but defers tile mode",
2595 kDefaultMaxAllowedPercentImageDiff,
2596 /*transparentCheckBorderTolerance=*/tm == SkTileMode::kDecal ? 1 : 0)
2597 .source({16, 16, 64, 64})
2598 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2599 .applyColorFilter(alpha_modulate(0.75f), Expect::kDeferredImage)
2600 .rescale({0.5f, 0.5f}, Expect::kNewImage, tm)
2601 .run(/*requestedOutput=*/{0, 0, 80, 80});
2602
2603 // The color filter (simple and transparency-affecting) should be applied with a 1px
2604 // boundary around the rest of the image being rescaled when decal-tiled, so its result is
2605 // clamped tiled instead (vs. having to prepare and scale a larger, flood-filled image).
2606 SkTileMode expectedTileMode = tm == SkTileMode::kDecal ? SkTileMode::kClamp : tm;
2607 TestCase(r, "Rescale applies transparency-affecting color filter but defers tile mode")
2608 .source({16, 16, 64, 64})
2609 .applyCrop({16, 16, 64, 64}, tm, Expect::kDeferredImage)
2610 .applyColorFilter(affect_transparent(SkColors::kGreen), Expect::kDeferredImage)
2611 .rescale({0.5f, 0.5f}, Expect::kNewImage, expectedTileMode)
2612 .run(/*requestedOutput=*/{0, 0, 80, 80});
2613 }
2614 }
2615
DEF_TEST_SUITE(MakeFromImage,r,CtsEnforcement::kApiLevel_V,CtsEnforcement::kNextRelease)2616 DEF_TEST_SUITE(MakeFromImage, r, CtsEnforcement::kApiLevel_V, CtsEnforcement::kNextRelease) {
2617 static constexpr SkISize kSrcSize = {128,128};
2618 static constexpr SkIRect kIdentitySrc = {0,0,128,128};
2619 static constexpr SkIRect kSubsetSrc = {16,16,112,112};
2620 static constexpr SkIRect kOverlappingSrc = {-64, 16, 192, 112};
2621 static constexpr SkIRect kContainingSrc = {-64,-64,192,192};
2622 static constexpr SkIRect kDisjointSrc = {0,-200,128,-1};
2623
2624 // For convenience, most tests will use kIdentitySrc as the dstRect so that the result's
2625 // layer bounds can be used to validate the src->dst transform is preserved.
2626 static constexpr SkIRect kDstRect = kIdentitySrc;
2627
2628 // Sufficiently large to not affect the layer bounds of a FilterResult.
2629 static constexpr SkIRect kDesiredOutput = {-400, -400, 400, 400};
2630
2631 sk_sp<SkColorSpace> colorSpace = SkColorSpace::MakeSRGB();
2632 Context ctx{r.refBackend(),
2633 Mapping(),
2634 LayerSpace<SkIRect>(kDesiredOutput),
2635 /*source=*/{},
2636 colorSpace.get(),
2637 /*stats=*/nullptr};
2638
2639 sk_sp<SkSpecialImage> source = r.createSourceImage(kSrcSize, colorSpace);
2640 SkASSERT(source->subset() == kIdentitySrc);
2641 sk_sp<SkImage> sourceImage = source->asImage();
2642
2643
2644 auto makeImage = [&](SkIRect src, SkIRect dst) {
2645 ParameterSpace<SkRect> dstRect{SkRect::Make(dst)};
2646 return FilterResult::MakeFromImage(ctx, sourceImage, SkRect::Make(src), dstRect, {});
2647 };
2648
2649 // Failure cases should return an empty FilterResult
2650 REPORTER_ASSERT(r, !SkToBool(makeImage(kIdentitySrc, SkIRect::MakeEmpty())),
2651 "Empty dst rect returns empty FilterResult");
2652 REPORTER_ASSERT(r, !SkToBool(makeImage(SkIRect::MakeEmpty(), kDstRect)),
2653 "Empty src rect returns empty FilterResult");
2654 REPORTER_ASSERT(r, !SkToBool(makeImage(kDisjointSrc, kDstRect)),
2655 "Disjoint src rect returns empty FilterREsult");
2656
2657
2658 auto testSuccess = [&](SkIRect src, SkIRect expectedImageSubset, SkIRect expectedLayerBounds,
2659 const char* label) {
2660 auto result = makeImage(src, kDstRect);
2661 REPORTER_ASSERT(r, SkToBool(result), "Image should not be empty: %s", label);
2662 REPORTER_ASSERT(r, result.image()->subset() == expectedImageSubset,
2663 "Result subset is incorrect: %s", label);
2664 REPORTER_ASSERT(r, SkIRect(result.layerBounds()) == expectedLayerBounds,
2665 "Result layer bounds are incorrect: %s", label);
2666 };
2667
2668 testSuccess(kIdentitySrc, kIdentitySrc, kDstRect,
2669 "Identity src->dst preserves original image bounds");
2670 testSuccess(kSubsetSrc, kSubsetSrc, kDstRect,
2671 "Contained src rect is preserved, stretched to original dst bounds");
2672 testSuccess(kOverlappingSrc, {0,16,128,112}, {32,0,96,128},
2673 "Overlapping src rect is clipped and dst is scaled on clipped axis");
2674 testSuccess(kContainingSrc, kIdentitySrc, {32,32,96,96},
2675 "Containing src rect is clipped and dst is scaled on both axes");
2676 }
2677
2678 } // anonymous namespace
2679