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