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