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