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