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