• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 Google LLC
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #include "src/core/SkImageFilterTypes.h"
9 
10 #include "include/core/SkAlphaType.h"
11 #include "include/core/SkBlendMode.h"
12 #include "include/core/SkBlender.h"
13 #include "include/core/SkCanvas.h"
14 #include "include/core/SkClipOp.h"
15 #include "include/core/SkColor.h"
16 #include "include/core/SkColorType.h"
17 #include "include/core/SkImage.h"
18 #include "include/core/SkImageInfo.h"
19 #include "include/core/SkM44.h"
20 #include "include/core/SkPaint.h"
21 #include "include/core/SkPicture.h"  // IWYU pragma: keep
22 #include "include/core/SkShader.h"
23 #include "include/effects/SkRuntimeEffect.h"
24 #include "include/private/base/SkDebug.h"
25 #include "include/private/base/SkFloatingPoint.h"
26 #include "src/base/SkMathPriv.h"
27 #include "src/base/SkVx.h"
28 #include "src/core/SkBitmapDevice.h"
29 #include "src/core/SkBlenderBase.h"
30 #include "src/core/SkBlurEngine.h"
31 #include "src/core/SkCanvasPriv.h"
32 #include "src/core/SkColorSpacePriv.h"
33 #include "src/core/SkDevice.h"
34 #include "src/core/SkImageFilterCache.h"
35 #include "src/core/SkImageFilter_Base.h"
36 #include "src/core/SkKnownRuntimeEffects.h"
37 #include "src/core/SkMatrixPriv.h"
38 #include "src/core/SkRectPriv.h"
39 #include "src/core/SkTraceEvent.h"
40 #include "src/effects/colorfilters/SkColorFilterBase.h"
41 
42 #include <algorithm>
43 #include <cmath>
44 #include <limits>
45 
46 namespace skif {
47 
48 namespace {
49 
50 // This exists to cover up issues where infinite precision would produce integers but float
51 // math produces values just larger/smaller than an int and roundOut/In on bounds would produce
52 // nearly a full pixel error. One such case is crbug.com/1313579 where the caller has produced
53 // near integer CTM and uses integer crop rects that would grab an extra row/column of the
54 // input image when using a strict roundOut.
55 static constexpr float kRoundEpsilon = 1e-3f;
56 
are_axes_nearly_integer_aligned(const LayerSpace<SkMatrix> & m,LayerSpace<SkIPoint> * out=nullptr)57 std::pair<bool, bool> are_axes_nearly_integer_aligned(const LayerSpace<SkMatrix>& m,
58                                                       LayerSpace<SkIPoint>* out=nullptr) {
59     float invW  = sk_ieee_float_divide(1.f, m.rc(2,2));
60     float tx = SkScalarRoundToScalar(m.rc(0,2)*invW);
61     float ty = SkScalarRoundToScalar(m.rc(1,2)*invW);
62     // expected = [1 0 tx] after normalizing perspective (divide by m[2,2])
63     //            [0 1 ty]
64     //            [0 0  1]
65     bool affine = SkScalarNearlyEqual(m.rc(2,0)*invW, 0.f, kRoundEpsilon) &&
66                   SkScalarNearlyEqual(m.rc(2,1)*invW, 0.f, kRoundEpsilon);
67     if (!affine) {
68         return {false, false};
69     }
70 
71     bool xAxis = SkScalarNearlyEqual(1.f, m.rc(0,0)*invW, kRoundEpsilon) &&
72                  SkScalarNearlyEqual(0.f, m.rc(0,1)*invW, kRoundEpsilon) &&
73                  SkScalarNearlyEqual(tx,  m.rc(0,2)*invW, kRoundEpsilon);
74     bool yAxis = SkScalarNearlyEqual(0.f, m.rc(1,0)*invW, kRoundEpsilon) &&
75                  SkScalarNearlyEqual(1.f, m.rc(1,1)*invW, kRoundEpsilon) &&
76                  SkScalarNearlyEqual(ty,  m.rc(1,2)*invW, kRoundEpsilon);
77     if (out && xAxis && yAxis) {
78         *out = LayerSpace<SkIPoint>({(int) tx, (int) ty});
79     }
80     return {xAxis, yAxis};
81 }
82 
83 // If m is epsilon within the form [1 0 tx], this returns true and sets out to [tx, ty]
84 //                                 [0 1 ty]
85 //                                 [0 0 1 ]
86 // TODO: Use this in decomposeCTM() (and possibly extend it to support is_nearly_scale_translate)
87 // to be a little more forgiving on matrix types during layer configuration.
is_nearly_integer_translation(const LayerSpace<SkMatrix> & m,LayerSpace<SkIPoint> * out=nullptr)88 bool is_nearly_integer_translation(const LayerSpace<SkMatrix>& m,
89                                    LayerSpace<SkIPoint>* out=nullptr) {
90     auto [axisX, axisY] = are_axes_nearly_integer_aligned(m, out);
91     return axisX && axisY;
92 }
93 
decompose_transform(const SkMatrix & transform,SkPoint representativePoint,SkMatrix * postScaling,SkMatrix * scaling)94 void decompose_transform(const SkMatrix& transform, SkPoint representativePoint,
95                          SkMatrix* postScaling, SkMatrix* scaling) {
96     SkSize scale;
97     if (transform.decomposeScale(&scale, postScaling)) {
98         *scaling = SkMatrix::Scale(scale.fWidth, scale.fHeight);
99     } else {
100         // Perspective, which has a non-uniform scaling effect on the filter. Pick a single scale
101         // factor that best matches where the filter will be evaluated.
102         float approxScale = SkMatrixPriv::DifferentialAreaScale(transform, representativePoint);
103         if (SkIsFinite(approxScale) && !SkScalarNearlyZero(approxScale)) {
104             // Now take the sqrt to go from an area scale factor to a scaling per X and Y
105             approxScale = SkScalarSqrt(approxScale);
106         } else {
107             // The point was behind the W = 0 plane, so don't factor out any scale.
108             approxScale = 1.f;
109         }
110         if (postScaling) {
111             *postScaling = transform;
112             float invScale = SkScalarInvert(approxScale);
113             postScaling->preScale(invScale, invScale);
114         }
115         *scaling = SkMatrix::Scale(approxScale, approxScale);
116     }
117 }
118 
periodic_axis_transform(SkTileMode tileMode,const LayerSpace<SkIRect> & crop,const LayerSpace<SkIRect> & output)119 std::optional<LayerSpace<SkMatrix>> periodic_axis_transform(
120         SkTileMode tileMode,
121         const LayerSpace<SkIRect>& crop,
122         const LayerSpace<SkIRect>& output) {
123     if (tileMode == SkTileMode::kClamp || tileMode == SkTileMode::kDecal) {
124         // Not periodic
125         return {};
126     }
127 
128     // Lift crop dimensions into 64 bit so that we can combine with 'output' without worrying about
129     // overflowing 32 bits.
130     double cropL = (double) crop.left();
131     double cropT = (double) crop.top();
132     double cropWidth = crop.right() - cropL;
133     double cropHeight = crop.bottom() - cropT;
134 
135     // Calculate normalized periodic coordinates of 'output' relative to the 'crop' being tiled.
136     double periodL = std::floor((output.left() - cropL) / cropWidth);
137     double periodT = std::floor((output.top() - cropT) / cropHeight);
138     double periodR = std::ceil((output.right() - cropL) / cropWidth);
139     double periodB = std::ceil((output.bottom() - cropT) / cropHeight);
140 
141     if (periodR - periodL <= 1.0 && periodB - periodT <= 1.0) {
142         // The tiling pattern won't be visible, so we can draw the image without tiling and an
143         // adjusted transform. We calculate the final translation in double to be exact and then
144         // verify that it can round-trip as a float.
145         float sx = 1.f;
146         float sy = 1.f;
147         double tx = -cropL;
148         double ty = -cropT;
149 
150         if (tileMode == SkTileMode::kMirror) {
151             // Flip image when in odd periods on each axis. The periods are stored as doubles but
152             // hold integer values since they came from floor or ceil.
153             if (std::fmod(periodL, 2.f) > SK_ScalarNearlyZero) {
154                 sx = -1.f;
155                 tx = cropWidth - tx;
156             }
157             if (std::fmod(periodT, 2.f) > SK_ScalarNearlyZero) {
158                 sy = -1.f;
159                 ty = cropHeight - ty;
160             }
161         }
162         // Now translate by periods and make relative to crop's top left again. Given 32-bit inputs,
163         // the period * dimension shouldn't overflow 64-bits.
164         tx += periodL * cropWidth + cropL;
165         ty += periodT * cropHeight + cropT;
166 
167         // Representing the periodic tiling as a float SkMatrix would lose the pixel precision
168         // required to represent it, so don't apply this optimization.
169         if (sk_double_saturate2int(tx) != (float) tx ||
170             sk_double_saturate2int(ty) != (float) ty) {
171             return {};
172         }
173 
174         SkMatrix periodicTransform;
175         periodicTransform.setScaleTranslate(sx, sy, (float) tx, (float) ty);
176         return LayerSpace<SkMatrix>(periodicTransform);
177     } else {
178         // Both low and high edges of the crop would be visible in 'output', or a mirrored
179         // boundary is visible in 'output'. Just keep the periodic tiling.
180         return {};
181     }
182 }
183 
184 class RasterBackend : public Backend {
185 public:
186 
RasterBackend(const SkSurfaceProps & surfaceProps,SkColorType colorType)187     RasterBackend(const SkSurfaceProps& surfaceProps, SkColorType colorType)
188             : Backend(SkImageFilterCache::Get(), surfaceProps, colorType) {}
189 
makeDevice(SkISize size,sk_sp<SkColorSpace> colorSpace,const SkSurfaceProps * props) const190     sk_sp<SkDevice> makeDevice(SkISize size,
191                                sk_sp<SkColorSpace> colorSpace,
192                                const SkSurfaceProps* props) const override {
193         SkImageInfo imageInfo = SkImageInfo::Make(size,
194                                                   this->colorType(),
195                                                   kPremul_SkAlphaType,
196                                                   std::move(colorSpace));
197         return SkBitmapDevice::Create(imageInfo, props ? *props : this->surfaceProps());
198     }
199 
makeImage(const SkIRect & subset,sk_sp<SkImage> image) const200     sk_sp<SkSpecialImage> makeImage(const SkIRect& subset, sk_sp<SkImage> image) const override {
201         return SkSpecialImages::MakeFromRaster(subset, image, this->surfaceProps());
202     }
203 
getCachedBitmap(const SkBitmap & data) const204     sk_sp<SkImage> getCachedBitmap(const SkBitmap& data) const override {
205         return SkImages::RasterFromBitmap(data);
206     }
207 
208 #if defined(SK_USE_LEGACY_BLUR_RASTER)
getBlurEngine() const209     const SkBlurEngine* getBlurEngine() const override { return nullptr; }
210 #else
useLegacyFilterResultBlur() const211     bool useLegacyFilterResultBlur() const override { return false; }
212 
getBlurEngine() const213     const SkBlurEngine* getBlurEngine() const override {
214         return SkBlurEngine::GetRasterBlurEngine();
215     }
216 #endif
217 
218 };
219 
220 } // anonymous namespace
221 
222 ///////////////////////////////////////////////////////////////////////////////////////////////////
223 
Backend(sk_sp<SkImageFilterCache> cache,const SkSurfaceProps & surfaceProps,const SkColorType colorType)224 Backend::Backend(sk_sp<SkImageFilterCache> cache,
225                  const SkSurfaceProps& surfaceProps,
226                  const SkColorType colorType)
227         : fCache(std::move(cache))
228         , fSurfaceProps(surfaceProps)
229         , fColorType(colorType) {}
230 
231 Backend::~Backend() = default;
232 
MakeRasterBackend(const SkSurfaceProps & surfaceProps,SkColorType colorType)233 sk_sp<Backend> MakeRasterBackend(const SkSurfaceProps& surfaceProps, SkColorType colorType) {
234     // TODO (skbug:14286): Remove this forcing to 8888. Many legacy image filters only support
235     // N32 on CPU, but once they are implemented in terms of draws and SkSL they will support
236     // all color types, like the GPU backends.
237     colorType = kN32_SkColorType;
238 
239     return sk_make_sp<RasterBackend>(surfaceProps, colorType);
240 }
241 
dumpStats() const242 void Stats::dumpStats() const {
243     SkDebugf("ImageFilter Stats:\n"
244              "      # visited filters: %d\n"
245              "           # cache hits: %d\n"
246              "   # offscreen surfaces: %d\n"
247              " # shader-clamped draws: %d\n"
248              "   # shader-tiled draws: %d\n",
249              fNumVisitedImageFilters,
250              fNumCacheHits,
251              fNumOffscreenSurfaces,
252              fNumShaderClampedDraws,
253              fNumShaderBasedTilingDraws);
254 }
255 
reportStats() const256 void Stats::reportStats() const {
257     TRACE_EVENT_INSTANT2("skia", "ImageFilter Graph Size", TRACE_EVENT_SCOPE_THREAD,
258                          "count", fNumVisitedImageFilters, "cache hits", fNumCacheHits);
259     TRACE_EVENT_INSTANT1("skia", "ImageFilter Surfaces", TRACE_EVENT_SCOPE_THREAD,
260                          "count", fNumOffscreenSurfaces);
261     TRACE_EVENT_INSTANT2("skia", "ImageFilter Shader Tiling", TRACE_EVENT_SCOPE_THREAD,
262                          "clamp", fNumShaderClampedDraws, "other", fNumShaderBasedTilingDraws);
263 }
264 
265 ///////////////////////////////////////////////////////////////////////////////////////////////////
266 // Mapping
267 
RoundOut(SkRect r)268 SkIRect RoundOut(SkRect r) { return r.makeInset(kRoundEpsilon, kRoundEpsilon).roundOut(); }
269 
RoundIn(SkRect r)270 SkIRect RoundIn(SkRect r) { return r.makeOutset(kRoundEpsilon, kRoundEpsilon).roundIn(); }
271 
decomposeCTM(const SkM44 & ctm,MatrixCapability capability,const skif::ParameterSpace<SkPoint> & representativePt)272 bool Mapping::decomposeCTM(const SkM44& ctm, MatrixCapability capability,
273                            const skif::ParameterSpace<SkPoint>& representativePt) {
274     SkM44 remainder{SkM44::kUninitialized_Constructor};
275     SkM44 layer{SkM44::kUninitialized_Constructor};
276     if (capability == MatrixCapability::kTranslate) {
277         // Apply the entire CTM post-filtering
278         remainder = ctm;
279         layer = SkM44();
280     } else if (SkMatrixPriv::IsScaleTranslateAsM33(ctm) ||
281                capability == MatrixCapability::kComplex) {
282         // Either layer space can be anything (kComplex) - or - it can be scale+translate, and the
283         // ctm is. In both cases, the layer space can be equivalent to device space.
284         remainder = SkM44();
285         layer = ctm;
286     } else {
287         // This case implies some amount of sampling post-filtering, either due to skew or rotation
288         // in the original matrix. As such, keep the layer matrix as simple as possible.
289         SkMatrix layer33;
290         decompose_transform(ctm.asM33(), SkPoint(representativePt),
291                             /*postScaling=*/nullptr, &layer33);
292         layer = SkM44(layer33);
293         // Reconstruct full 4x4 remainder matrix so the mapping doesn't lose the 3rd row/column.
294         remainder = ctm;
295         remainder.preScale(1.f / layer.rc(0,0), 1.f / layer.rc(1,1));
296     }
297 
298     SkM44 invRemainder;
299     if (!remainder.invert(&invRemainder)) {
300         // Under floating point arithmetic, it's possible to decompose an invertible matrix into
301         // a scaling matrix and a remainder and have the remainder be non-invertible. Generally
302         // when this happens the scale factors are so large and the matrix so ill-conditioned that
303         // it's unlikely that any drawing would be reasonable, so failing to make a layer is okay.
304         return false;
305     } else {
306         fParamToLayerMatrix = layer;
307         fLayerToDevMatrix = remainder;
308         fDevToLayerMatrix = invRemainder;
309         return true;
310     }
311 }
312 
decomposeCTM(const SkM44 & ctm,const SkImageFilter * filter,const skif::ParameterSpace<SkPoint> & representativePt)313 bool Mapping::decomposeCTM(const SkM44& ctm,
314                            const SkImageFilter* filter,
315                            const skif::ParameterSpace<SkPoint>& representativePt) {
316     return this->decomposeCTM(
317             ctm,
318             filter ? as_IFB(filter)->getCTMCapability() : MatrixCapability::kComplex,
319             representativePt);
320 }
321 
adjustLayerSpace(const SkM44 & layer)322 bool Mapping::adjustLayerSpace(const SkM44& layer) {
323     SkM44 invLayer;
324     if (!layer.invert(&invLayer)) {
325         return false;
326     }
327     fParamToLayerMatrix.postConcat(layer);
328     fDevToLayerMatrix.postConcat(layer);
329     fLayerToDevMatrix.preConcat(invLayer);
330     return true;
331 }
332 
333 // Instantiate map specializations for the 6 geometric types used during filtering
334 template<>
map(const SkRect & geom,const SkMatrix & matrix)335 SkRect Mapping::map<SkRect>(const SkRect& geom, const SkMatrix& matrix) {
336     return geom.isEmpty() ? SkRect::MakeEmpty() : matrix.mapRect(geom);
337 }
338 
339 template<>
map(const SkIRect & geom,const SkMatrix & matrix)340 SkIRect Mapping::map<SkIRect>(const SkIRect& geom, const SkMatrix& matrix) {
341     if (geom.isEmpty()) {
342         return SkIRect::MakeEmpty();
343     }
344     // Unfortunately, there is a range of integer values such that we have 1px precision as an int,
345     // but less precision as a float. This can lead to non-empty SkIRects becoming empty simply
346     // because of float casting. If we're already dealing with a float rect or having a float
347     // output, that's what we're stuck with; but if we are starting form an irect and desiring an
348     // SkIRect output, we go through efforts to preserve the 1px precision for simple transforms.
349     if (matrix.isScaleTranslate()) {
350         double l = (double)matrix.getScaleX()*geom.fLeft   + (double)matrix.getTranslateX();
351         double r = (double)matrix.getScaleX()*geom.fRight  + (double)matrix.getTranslateX();
352         double t = (double)matrix.getScaleY()*geom.fTop    + (double)matrix.getTranslateY();
353         double b = (double)matrix.getScaleY()*geom.fBottom + (double)matrix.getTranslateY();
354         return {sk_double_saturate2int(std::floor(std::min(l, r) + kRoundEpsilon)),
355                 sk_double_saturate2int(std::floor(std::min(t, b) + kRoundEpsilon)),
356                 sk_double_saturate2int(std::ceil(std::max(l, r)  - kRoundEpsilon)),
357                 sk_double_saturate2int(std::ceil(std::max(t, b)  - kRoundEpsilon))};
358     } else {
359         return RoundOut(matrix.mapRect(SkRect::Make(geom)));
360     }
361 }
362 
363 template<>
map(const SkIPoint & geom,const SkMatrix & matrix)364 SkIPoint Mapping::map<SkIPoint>(const SkIPoint& geom, const SkMatrix& matrix) {
365     SkPoint p = SkPoint::Make(SkIntToScalar(geom.fX), SkIntToScalar(geom.fY));
366     matrix.mapPoints(&p, 1);
367     return SkIPoint::Make(SkScalarRoundToInt(p.fX), SkScalarRoundToInt(p.fY));
368 }
369 
370 template<>
map(const SkPoint & geom,const SkMatrix & matrix)371 SkPoint Mapping::map<SkPoint>(const SkPoint& geom, const SkMatrix& matrix) {
372     SkPoint p;
373     matrix.mapPoints(&p, &geom, 1);
374     return p;
375 }
376 
377 template<>
map(const Vector & geom,const SkMatrix & matrix)378 Vector Mapping::map<Vector>(const Vector& geom, const SkMatrix& matrix) {
379     SkVector v = SkVector::Make(geom.fX, geom.fY);
380     matrix.mapVectors(&v, 1);
381     return Vector{v};
382 }
383 
384 template<>
map(const IVector & geom,const SkMatrix & matrix)385 IVector Mapping::map<IVector>(const IVector& geom, const SkMatrix& matrix) {
386     SkVector v = SkVector::Make(SkIntToScalar(geom.fX), SkIntToScalar(geom.fY));
387     matrix.mapVectors(&v, 1);
388     return IVector(SkScalarRoundToInt(v.fX), SkScalarRoundToInt(v.fY));
389 }
390 
391 // Sizes are also treated as non-positioned values (although this assumption breaks down if there's
392 // perspective). Unlike vectors, we treat input sizes as specifying lengths of the local X and Y
393 // axes and return the lengths of those mapped axes.
394 template<>
map(const SkSize & geom,const SkMatrix & matrix)395 SkSize Mapping::map<SkSize>(const SkSize& geom, const SkMatrix& matrix) {
396     if (matrix.isScaleTranslate()) {
397         // This is equivalent to mapping the two basis vectors and calculating their lengths.
398         SkVector sizes = matrix.mapVector(geom.fWidth, geom.fHeight);
399         return {SkScalarAbs(sizes.fX), SkScalarAbs(sizes.fY)};
400     }
401 
402     SkVector xAxis = matrix.mapVector(geom.fWidth, 0.f);
403     SkVector yAxis = matrix.mapVector(0.f, geom.fHeight);
404     return {xAxis.length(), yAxis.length()};
405 }
406 
407 template<>
map(const SkISize & geom,const SkMatrix & matrix)408 SkISize Mapping::map<SkISize>(const SkISize& geom, const SkMatrix& matrix) {
409     SkSize size = map(SkSize::Make(geom), matrix);
410     return SkISize::Make(SkScalarCeilToInt(size.fWidth - kRoundEpsilon),
411                          SkScalarCeilToInt(size.fHeight - kRoundEpsilon));
412 }
413 
414 template<>
map(const SkMatrix & m,const SkMatrix & matrix)415 SkMatrix Mapping::map<SkMatrix>(const SkMatrix& m, const SkMatrix& matrix) {
416     // If 'matrix' maps from the C1 coord space to the C2 coord space, and 'm' is a transform that
417     // operates on, and outputs to, the C1 coord space, we want to return a new matrix that is
418     // equivalent to 'm' that operates on and outputs to C2. This is the same as mapping the input
419     // from C2 to C1 (matrix^-1), then transforming by 'm', and then mapping from C1 to C2 (matrix).
420     SkMatrix inv;
421     SkAssertResult(matrix.invert(&inv));
422     inv.postConcat(m);
423     inv.postConcat(matrix);
424     return inv;
425 }
426 
427 ///////////////////////////////////////////////////////////////////////////////////////////////////
428 // LayerSpace<T>
429 
relevantSubset(const LayerSpace<SkIRect> dstRect,SkTileMode tileMode) const430 LayerSpace<SkIRect> LayerSpace<SkIRect>::relevantSubset(const LayerSpace<SkIRect> dstRect,
431                                                         SkTileMode tileMode) const {
432     LayerSpace<SkIRect> fittedSrc = *this;
433     if (tileMode == SkTileMode::kDecal || tileMode == SkTileMode::kClamp) {
434         // For both decal/clamp, we only care about the region that is in dstRect, unless we are
435         // clamping and have to preserve edge pixels when there's no overlap.
436         if (!fittedSrc.intersect(dstRect)) {
437             if (tileMode == SkTileMode::kDecal) {
438                 // The dstRect would be filled with transparent black.
439                 fittedSrc = LayerSpace<SkIRect>::Empty();
440             } else {
441                 // We just need the closest row/column/corner of this rect to dstRect.
442                 auto edge = SkRectPriv::ClosestDisjointEdge(SkIRect(fittedSrc),  SkIRect(dstRect));
443                 fittedSrc = LayerSpace<SkIRect>(edge);
444             }
445         }
446     } // else assume the entire source is needed for periodic tile modes, so leave fittedSrc alone
447 
448     return fittedSrc;
449 }
450 
451 // Match rounding tolerances of SkRects to SkIRects
round() const452 LayerSpace<SkISize> LayerSpace<SkSize>::round() const {
453     return LayerSpace<SkISize>(fData.toRound());
454 }
ceil() const455 LayerSpace<SkISize> LayerSpace<SkSize>::ceil() const {
456     return LayerSpace<SkISize>({SkScalarCeilToInt(fData.fWidth - kRoundEpsilon),
457                                 SkScalarCeilToInt(fData.fHeight - kRoundEpsilon)});
458 }
floor() const459 LayerSpace<SkISize> LayerSpace<SkSize>::floor() const {
460     return LayerSpace<SkISize>({SkScalarFloorToInt(fData.fWidth + kRoundEpsilon),
461                                 SkScalarFloorToInt(fData.fHeight + kRoundEpsilon)});
462 }
463 
mapRect(const LayerSpace<SkRect> & r) const464 LayerSpace<SkRect> LayerSpace<SkMatrix>::mapRect(const LayerSpace<SkRect>& r) const {
465     return LayerSpace<SkRect>(Mapping::map(SkRect(r), fData));
466 }
467 
468 // Effectively mapRect(SkRect).roundOut() but more accurate when the underlying matrix or
469 // SkIRect has large floating point values.
mapRect(const LayerSpace<SkIRect> & r) const470 LayerSpace<SkIRect> LayerSpace<SkMatrix>::mapRect(const LayerSpace<SkIRect>& r) const {
471     return LayerSpace<SkIRect>(Mapping::map(SkIRect(r), fData));
472 }
473 
mapPoint(const LayerSpace<SkPoint> & p) const474 LayerSpace<SkPoint> LayerSpace<SkMatrix>::mapPoint(const LayerSpace<SkPoint>& p) const {
475     return LayerSpace<SkPoint>(Mapping::map(SkPoint(p), fData));
476 }
477 
mapVector(const LayerSpace<Vector> & v) const478 LayerSpace<Vector> LayerSpace<SkMatrix>::mapVector(const LayerSpace<Vector>& v) const {
479     return LayerSpace<Vector>(Mapping::map(Vector(v), fData));
480 }
481 
mapSize(const LayerSpace<SkSize> & s) const482 LayerSpace<SkSize> LayerSpace<SkMatrix>::mapSize(const LayerSpace<SkSize>& s) const {
483     return LayerSpace<SkSize>(Mapping::map(SkSize(s), fData));
484 }
485 
inverseMapRect(const LayerSpace<SkRect> & r,LayerSpace<SkRect> * out) const486 bool LayerSpace<SkMatrix>::inverseMapRect(const LayerSpace<SkRect>& r,
487                                           LayerSpace<SkRect>* out) const {
488     SkRect mapped;
489     if (r.isEmpty()) {
490         // An empty input always inverse maps to an empty rect "successfully"
491         *out = LayerSpace<SkRect>::Empty();
492         return true;
493     } else if (SkMatrixPriv::InverseMapRect(fData, &mapped, SkRect(r))) {
494         *out = LayerSpace<SkRect>(mapped);
495         return true;
496     } else {
497         return false;
498     }
499 }
500 
inverseMapRect(const LayerSpace<SkIRect> & rect,LayerSpace<SkIRect> * out) const501 bool LayerSpace<SkMatrix>::inverseMapRect(const LayerSpace<SkIRect>& rect,
502                                           LayerSpace<SkIRect>* out) const {
503     if (rect.isEmpty()) {
504         // An empty input always inverse maps to an empty rect "successfully"
505         *out = LayerSpace<SkIRect>::Empty();
506         return true;
507     } else if (fData.isScaleTranslate()) { // Specialized inverse of 1px-preserving map<SkIRect>
508         // A scale-translate matrix with a 0 scale factor is not invertible.
509         if (fData.getScaleX() == 0.f || fData.getScaleY() == 0.f) {
510             return false;
511         }
512         double l = (rect.left()   - (double)fData.getTranslateX()) / (double)fData.getScaleX();
513         double r = (rect.right()  - (double)fData.getTranslateX()) / (double)fData.getScaleX();
514         double t = (rect.top()    - (double)fData.getTranslateY()) / (double)fData.getScaleY();
515         double b = (rect.bottom() - (double)fData.getTranslateY()) / (double)fData.getScaleY();
516 
517         SkIRect mapped{sk_double_saturate2int(std::floor(std::min(l, r) + kRoundEpsilon)),
518                        sk_double_saturate2int(std::floor(std::min(t, b) + kRoundEpsilon)),
519                        sk_double_saturate2int(std::ceil(std::max(l, r)  - kRoundEpsilon)),
520                        sk_double_saturate2int(std::ceil(std::max(t, b)  - kRoundEpsilon))};
521         *out = LayerSpace<SkIRect>(mapped);
522         return true;
523     } else {
524         SkRect mapped;
525         if (SkMatrixPriv::InverseMapRect(fData, &mapped, SkRect::Make(SkIRect(rect)))) {
526             *out = LayerSpace<SkRect>(mapped).roundOut();
527             return true;
528         }
529     }
530 
531     return false;
532 }
533 
534 ///////////////////////////////////////////////////////////////////////////////////////////////////
535 // FilterResult::AutoSurface
536 //
537 // AutoSurface manages an SkCanvas and device state to draw to a layer-space bounding box,
538 // and then snap it into a FilterResult. It provides operators to be used directly as an SkDevice,
539 // assuming surface creation succeeded. It can also be viewed as an SkCanvas (for when an operation
540 // is unavailable on SkDevice). A given AutoSurface should only rely on one access API.
541 // Usage:
542 //
543 //     AutoSurface surface{ctx, dstBounds, renderInParameterSpace}; // if true, concats layer matrix
544 //     if (surface) {
545 //         surface->drawFoo(...);
546 //     }
547 //     return surface.snap(); // Automatically handles failed allocations
548 class FilterResult::AutoSurface {
549 public:
AutoSurface(const Context & ctx,const LayerSpace<SkIRect> & dstBounds,PixelBoundary boundary,bool renderInParameterSpace,const SkSurfaceProps * props=nullptr)550     AutoSurface(const Context& ctx,
551                 const LayerSpace<SkIRect>& dstBounds,
552                 PixelBoundary boundary,
553                 bool renderInParameterSpace,
554                 const SkSurfaceProps* props = nullptr)
555             : fDstBounds(dstBounds)
556             , fBoundary(boundary) {
557         // We don't intersect by ctx.desiredOutput() and only use the Context to make the surface.
558         // It is assumed the caller has already accounted for the desired output, or it's a
559         // situation where the desired output shouldn't apply (e.g. this surface will be transformed
560         // to align with the actual desired output via FilterResult metadata).
561         sk_sp<SkDevice> device = nullptr;
562         if (!dstBounds.isEmpty()) {
563             int padding = this->padding();
564             if (padding) {
565                 fDstBounds.outset(LayerSpace<SkISize>({padding, padding}));
566                 // If we are dealing with pathological inputs, the bounds may be near the maximum
567                 // represented by an int, in which case the outset gets saturated and we don't end
568                 // up with the expected padding pixels. We could downgrade the boundary value in
569                 // this case, but given that these values are going to be causing problems for any
570                 // of the floating point math during rendering we just fail.
571                 if (fDstBounds.left() >= dstBounds.left() ||
572                     fDstBounds.right() <= dstBounds.right() ||
573                     fDstBounds.top() >= dstBounds.top() ||
574                     fDstBounds.bottom() <= dstBounds.bottom()) {
575                     return;
576                 }
577             }
578             device = ctx.backend()->makeDevice(SkISize(fDstBounds.size()),
579                                                ctx.refColorSpace(),
580                                                props);
581         }
582 
583         if (!device) {
584             return;
585         }
586 
587         // Wrap the device in a canvas and use that to configure its origin and clip. This ensures
588         // the device and the canvas are in sync regardless of how the AutoSurface user intends
589         // to render.
590         ctx.markNewSurface();
591         fCanvas.emplace(std::move(device));
592         fCanvas->translate(-fDstBounds.left(), -fDstBounds.top());
593         fCanvas->clear(SkColors::kTransparent);
594         if (fBoundary == PixelBoundary::kTransparent) {
595             // Clip to the original un-padded dst bounds, ensuring that the border pixels remain
596             // fully transparent.
597             fCanvas->clipIRect(SkIRect(dstBounds));
598         } else {
599             // Otherwise clip to the possibly padded fDstBounds, if the backend made an approx-fit
600             // surface. If the bounds were padded for PixelBoundary::kInitialized, this will allow
601             // the border pixels to be rendered naturally.
602             fCanvas->clipIRect(SkIRect(fDstBounds));
603         }
604 
605         if (renderInParameterSpace) {
606             fCanvas->concat(ctx.mapping().layerMatrix());
607         }
608     }
609 
operator bool() const610     explicit operator bool() const { return fCanvas.has_value(); }
611 
canvas()612     SkCanvas* canvas() { SkASSERT(fCanvas.has_value()); return &*fCanvas; }
device()613     SkDevice* device() { return SkCanvasPriv::TopDevice(this->canvas()); }
operator ->()614     SkCanvas* operator->() { return this->canvas(); }
615 
snap()616     FilterResult snap() {
617         if (fCanvas.has_value()) {
618             // Finish everything and mark the device as immutable so that snapSpecial() can avoid
619             // copying data.
620             fCanvas->restoreToCount(0);
621             this->device()->setImmutable();
622 
623             // Snap a subset of the device with the padded dst bounds
624             SkIRect subset = SkIRect::MakeWH(fDstBounds.width(), fDstBounds.height());
625             sk_sp<SkSpecialImage> image = this->device()->snapSpecial(subset);
626             fCanvas.reset(); // Only use the AutoSurface once
627 
628             if (image && fBoundary != PixelBoundary::kUnknown) {
629                 // Inset subset relative to 'image' reported size
630                 const int padding = this->padding();
631                 subset = SkIRect::MakeSize(image->dimensions()).makeInset(padding, padding);
632                 LayerSpace<SkIPoint> origin{{fDstBounds.left() + padding,
633                                              fDstBounds.top() + padding}};
634                 return {image->makeSubset(subset), origin, fBoundary};
635             } else {
636                 // No adjustment to make
637                 return {image, fDstBounds.topLeft(), PixelBoundary::kUnknown};
638             }
639         } else {
640             return {};
641         }
642     }
643 
644 private:
padding() const645     int padding() const { return fBoundary == PixelBoundary::kUnknown ? 0 : 1; }
646 
647     std::optional<SkCanvas> fCanvas;
648     LayerSpace<SkIRect> fDstBounds; // includes padding, if any
649     PixelBoundary fBoundary;
650 };
651 
652 ///////////////////////////////////////////////////////////////////////////////////////////////////
653 // FilterResult
654 
imageAndOffset(const Context & ctx,SkIPoint * offset) const655 sk_sp<SkSpecialImage> FilterResult::imageAndOffset(const Context& ctx, SkIPoint* offset) const {
656     auto [image, origin] = this->imageAndOffset(ctx);
657     *offset = SkIPoint(origin);
658     return image;
659 }
660 
imageAndOffset(const Context & ctx) const661 std::pair<sk_sp<SkSpecialImage>, LayerSpace<SkIPoint>>FilterResult::imageAndOffset(
662         const Context& ctx) const {
663     FilterResult resolved = this->resolve(ctx, ctx.desiredOutput());
664     return {resolved.fImage, resolved.layerBounds().topLeft()};
665 }
666 
insetForSaveLayer() const667 FilterResult FilterResult::insetForSaveLayer() const {
668     if (!fImage) {
669         return {};
670     }
671 
672     // SkCanvas processing should have prepared a decal-tiled image before calling this.
673     SkASSERT(fTileMode == SkTileMode::kDecal);
674 
675     // PixelBoundary tracking assumes the special image's subset does not include the padding, so
676     // inset by a single pixel.
677     FilterResult inset = this->insetByPixel();
678     // Trust that SkCanvas configured the layer's SkDevice to ensure the padding remained
679     // transparent. Upgrading this pixel boundary knowledge allows the source image to use the
680     // simpler clamp math (vs. decal math) when used in a shader context.
681     SkASSERT(inset.fBoundary == PixelBoundary::kInitialized &&
682              inset.fTileMode == SkTileMode::kDecal);
683     inset.fBoundary = PixelBoundary::kTransparent;
684     return inset;
685 }
686 
insetByPixel() const687 FilterResult FilterResult::insetByPixel() const {
688     // This assumes that the image is pixel aligned with its layer bounds, which is validated in
689     // the call to subset().
690     auto insetBounds = fLayerBounds;
691     insetBounds.inset(LayerSpace<SkISize>({1, 1}));
692      // Shouldn't be calling this except in situations where padding was explicitly added before.
693     SkASSERT(!insetBounds.isEmpty());
694     return this->subset(fLayerBounds.topLeft(), insetBounds);
695 }
696 
analyzeBounds(const SkMatrix & xtraTransform,const SkIRect & dstBounds,BoundsScope scope) const697 SkEnumBitMask<FilterResult::BoundsAnalysis> FilterResult::analyzeBounds(
698         const SkMatrix& xtraTransform,
699         const SkIRect& dstBounds,
700         BoundsScope scope) const {
701     static constexpr SkSamplingOptions kNearestNeighbor = {};
702     static constexpr float kHalfPixel = 0.5f;
703     static constexpr float kCubicRadius = 1.5f;
704 
705     SkEnumBitMask<BoundsAnalysis> analysis = BoundsAnalysis::kSimple;
706     const bool fillsLayerBounds = fTileMode != SkTileMode::kDecal ||
707                                   (fColorFilter && as_CFB(fColorFilter)->affectsTransparentBlack());
708 
709     // 1. Is the layer geometry visible in the dstBounds (ignoring whether or not there are shading
710     //    effects that highlight that boundary).
711     SkRect pixelCenterBounds = SkRect::Make(dstBounds);
712     if (!SkRectPriv::QuadContainsRect(xtraTransform,
713                                       SkIRect(fLayerBounds),
714                                       dstBounds,
715                                       kRoundEpsilon)) {
716         // 1a. If an effect doesn't fill out to the layer bounds, is the image content itself
717         //     clipped by the layer bounds?
718         bool requireLayerCrop = fillsLayerBounds;
719         if (!fillsLayerBounds) {
720             LayerSpace<SkIRect> imageBounds =
721                     fTransform.mapRect(LayerSpace<SkIRect>{fImage->dimensions()});
722             requireLayerCrop = !fLayerBounds.contains(imageBounds);
723         }
724 
725         if (requireLayerCrop) {
726             analysis |= BoundsAnalysis::kRequiresLayerCrop;
727             // And since the layer crop will have to be applied externally, we can restrict the
728             // sample bounds to the intersection of dstBounds and layerBounds
729             SkIRect layerBoundsInDst = Mapping::map(SkIRect(fLayerBounds), xtraTransform);
730             // In some cases these won't intersect, usually in a complex graph where the input is
731             // a bitmap or the dynamic source, in which case it hasn't been clipped or dropped by
732             // earlier image filter processing for that particular node. We could return a flag here
733             // to signal that the operation should be treated as transparent black, but that would
734             // create more shader combinations and image sampling will still do the right thing by
735             // leaving 'pixelCenterBounds' as the original 'dstBounds'.
736             (void) pixelCenterBounds.intersect(SkRect::Make(layerBoundsInDst));
737         }
738         // else this is a decal-tiled, non-transparent affecting FilterResult that doesn't have
739         // its pixel data clipped by the layer bounds, so the layer crop doesn't have to be applied
740         // separately. But this means that the image will be sampled over all of 'dstBounds'.
741     }
742     // else the layer bounds geometry isn't visible, so 'dstBounds' is already a tighter bounding
743     // box for how the image will be sampled.
744 
745     // 2. Are the tiling and deferred color filter effects visible in the sampled bounds
746     SkRect imageBounds = SkRect::Make(fImage->dimensions());
747     LayerSpace<SkMatrix> netTransform = fTransform;
748     netTransform.postConcat(LayerSpace<SkMatrix>(xtraTransform));
749     SkM44 netM44{SkMatrix(netTransform)};
750 
751     const auto [xAxisAligned, yAxisAligned] = are_axes_nearly_integer_aligned(netTransform);
752     const bool isPixelAligned = xAxisAligned && yAxisAligned;
753     // When decal sampling, we use an inset image bounds for checking if the dst is covered. If not,
754     // an image that exactly filled the dst bounds could still sample transparent black, in which
755     // case the transform's scale factor needs to be taken into account.
756     const bool decalLeaks = scope != BoundsScope::kRescale &&
757                             fTileMode == SkTileMode::kDecal &&
758                             fSamplingOptions != kNearestNeighbor &&
759                             !isPixelAligned;
760 
761     const float sampleRadius = fSamplingOptions.useCubic ? kCubicRadius : kHalfPixel;
762     SkRect safeImageBounds = imageBounds.makeInset(sampleRadius, sampleRadius);
763     if (fSamplingOptions == kDefaultSampling && !isPixelAligned) {
764         // When using default sampling, integer translations are eventually downgraded to nearest
765         // neighbor, so the 1/2px inset clamping is sufficient to safely access within the subset.
766         // When staying with linear filtering, a sample at 1/2px inset exactly will end up accessing
767         // one external pixel with a weight of 0 (but MSAN will complain and not all GPUs actually
768         // seem to get that correct). To be safe we have to clamp to epsilon inside the 1/2px.
769         safeImageBounds.inset(xAxisAligned ? 0.f : kRoundEpsilon,
770                               yAxisAligned ? 0.f : kRoundEpsilon);
771     }
772     bool hasPixelPadding = fBoundary != PixelBoundary::kUnknown;
773 
774     if (!SkRectPriv::QuadContainsRect(netM44,
775                                       decalLeaks ? safeImageBounds : imageBounds,
776                                       pixelCenterBounds,
777                                       kRoundEpsilon)) {
778         analysis |= BoundsAnalysis::kDstBoundsNotCovered;
779         if (fillsLayerBounds) {
780             analysis |= BoundsAnalysis::kHasLayerFillingEffect;
781         }
782         if (decalLeaks) {
783             // Some amount of decal tiling will be visible in the output so check the relative size
784             // of the decal interpolation from texel to dst space; if it's not close to 1 it needs
785             // to be handled specially to keep rendering methods visually consistent.
786             float scaleFactors[2];
787             if (!(SkMatrix(netTransform).getMinMaxScales(scaleFactors) &&
788                     SkScalarNearlyEqual(scaleFactors[0], 1.f, 0.2f) &&
789                     SkScalarNearlyEqual(scaleFactors[1], 1.f, 0.2f))) {
790                 analysis |= BoundsAnalysis::kRequiresDecalInLayerSpace;
791                 if (fBoundary == PixelBoundary::kTransparent) {
792                     // Turn off considering the transparent padding as safe to prevent that
793                     // transparency from multiplying with the layer-space decal effect.
794                     hasPixelPadding = false;
795                 }
796             }
797         }
798     }
799 
800     if (scope == BoundsScope::kDeferred) {
801         return analysis; // skip sampling analysis
802     } else if (scope == BoundsScope::kCanDrawDirectly &&
803                !(analysis & BoundsAnalysis::kHasLayerFillingEffect)) {
804         // When drawing the image directly, the geometry is limited to the image. If the texels
805         // are pixel aligned, then it is safe to skip shader-based tiling.
806         const bool nnOrBilerp = fSamplingOptions == kDefaultSampling ||
807                                 fSamplingOptions == kNearestNeighbor;
808         if (nnOrBilerp && (hasPixelPadding || isPixelAligned)) {
809             return analysis;
810         }
811     }
812 
813     // 3. Would image pixels outside of its subset be sampled if shader-clamping is skipped?
814 
815     // Include the padding for sampling analysis and inset the dst by 1/2 px to represent where the
816     // sampling is evaluated at.
817     if (hasPixelPadding) {
818         safeImageBounds.outset(1.f, 1.f);
819     }
820     pixelCenterBounds.inset(kHalfPixel, kHalfPixel);
821 
822     // True if all corners of 'pixelCenterBounds' are on the inside of each edge of
823     // 'safeImageBounds', ordered T,R,B,L.
824     skvx::int4 edgeMask = SkRectPriv::QuadContainsRectMask(netM44,
825                                                            safeImageBounds,
826                                                            pixelCenterBounds,
827                                                            kRoundEpsilon);
828     if (!all(edgeMask)) {
829         // Sampling outside the image subset occurs, but if the edges that are exceeded are HW
830         // edges, then we can avoid using shader-based tiling.
831         skvx::int4 hwEdge{fImage->subset().fTop == 0,
832                           fImage->subset().fRight == fImage->backingStoreDimensions().fWidth,
833                           fImage->subset().fBottom == fImage->backingStoreDimensions().fHeight,
834                           fImage->subset().fLeft == 0};
835         if (fTileMode == SkTileMode::kRepeat || fTileMode == SkTileMode::kMirror) {
836             // For periodic tile modes, we require both edges on an axis to be HW edges
837             hwEdge = hwEdge & skvx::shuffle<2,3,0,1>(hwEdge); // TRBL & BLTR
838         }
839         if (!all(edgeMask | hwEdge)) {
840             analysis |= BoundsAnalysis::kRequiresShaderTiling;
841         }
842     }
843 
844     return analysis;
845 }
846 
updateTileMode(const Context & ctx,SkTileMode tileMode)847 void FilterResult::updateTileMode(const Context& ctx, SkTileMode tileMode) {
848     if (fImage) {
849         fTileMode = tileMode;
850         if (tileMode != SkTileMode::kDecal) {
851             fLayerBounds = ctx.desiredOutput();
852         }
853     }
854 }
855 
applyCrop(const Context & ctx,const LayerSpace<SkIRect> & crop,SkTileMode tileMode) const856 FilterResult FilterResult::applyCrop(const Context& ctx,
857                                      const LayerSpace<SkIRect>& crop,
858                                      SkTileMode tileMode) const {
859     static const LayerSpace<SkMatrix> kIdentity{SkMatrix::I()};
860 
861     if (crop.isEmpty() || ctx.desiredOutput().isEmpty()) {
862         // An empty crop cannot be anything other than fully transparent
863         return {};
864     }
865 
866     // First, determine how this image's layer bounds interact with the crop rect, which determines
867     // the portion of 'crop' that could have non-transparent content.
868     LayerSpace<SkIRect> cropContent = crop;
869     if (!fImage ||
870         !cropContent.intersect(fLayerBounds)) {
871         // The pixels within 'crop' would be fully transparent, and tiling won't change that.
872         return {};
873     }
874 
875     // Second, determine the subset of 'crop' that is relevant to ctx.desiredOutput().
876     LayerSpace<SkIRect> fittedCrop = crop.relevantSubset(ctx.desiredOutput(), tileMode);
877 
878     // Third, check if there's overlap with the known non-transparent cropped content and what's
879     // used to tile the desired output. If not, the image is known to be empty. This modifies
880     // 'cropContent' and not 'fittedCrop' so that any transparent padding remains if we have to
881     // apply repeat/mirror tiling to the original geometry.
882     if (!cropContent.intersect(fittedCrop)) {
883         return {};
884     }
885 
886     // Fourth, a periodic tiling that covers the output with a single instance of the image can be
887     // simplified to just a transform.
888     auto periodicTransform = periodic_axis_transform(tileMode, fittedCrop, ctx.desiredOutput());
889     if (periodicTransform) {
890         return this->applyTransform(ctx, *periodicTransform, FilterResult::kDefaultSampling);
891     }
892 
893     bool preserveTransparencyInCrop = false;
894     if (tileMode == SkTileMode::kDecal) {
895         // We can reduce the crop dimensions to what's non-transparent
896         fittedCrop = cropContent;
897     } else if (fittedCrop.contains(ctx.desiredOutput())) {
898         tileMode = SkTileMode::kDecal;
899         fittedCrop = ctx.desiredOutput();
900     } else if (!cropContent.contains(fittedCrop)) {
901         // There is transparency in fittedCrop that must be resolved in order to maintain the new
902         // tiling geometry.
903         preserveTransparencyInCrop = true;
904         if (fTileMode == SkTileMode::kDecal && tileMode == SkTileMode::kClamp) {
905             // include 1px buffer for transparency from original kDecal tiling
906             cropContent.outset(skif::LayerSpace<SkISize>({1, 1}));
907             SkAssertResult(fittedCrop.intersect(cropContent));
908         }
909     } // Otherwise cropContent == fittedCrop
910 
911     // Fifth, when the transform is an integer translation, any prior tiling and the new tiling
912     // can sometimes be addressed analytically without producing a new image. Moving the crop into
913     // the image dimensions allows future operations like applying a transform or color filter to
914     // be composed without rendering a new image since there will not be an intervening crop.
915     const bool doubleClamp = fTileMode == SkTileMode::kClamp && tileMode == SkTileMode::kClamp;
916     LayerSpace<SkIPoint> origin;
917     if (!preserveTransparencyInCrop &&
918         is_nearly_integer_translation(fTransform, &origin) &&
919         (doubleClamp ||
920          !(this->analyzeBounds(fittedCrop) & BoundsAnalysis::kHasLayerFillingEffect))) {
921         // Since the transform is axis-aligned, the tile mode can be applied to the original
922         // image pre-transformation and still be consistent with the 'crop' geometry. When the
923         // original tile mode is decal, extract_subset is always valid. When the original mode is
924         // mirror/repeat, !kHasLayerFillingEffect ensures that 'fittedCrop' is contained within
925         // the base image bounds, so extract_subset is valid. When the original mode is clamp
926         // and the new mode is not clamp, that is also the case. When both modes are clamp, we have
927         // to consider how 'fittedCrop' intersects (or doesn't) with the base image bounds.
928         FilterResult restrictedOutput = this->subset(origin, fittedCrop, doubleClamp);
929         restrictedOutput.updateTileMode(ctx, tileMode);
930         if (restrictedOutput.fBoundary == PixelBoundary::kInitialized ||
931             tileMode != SkTileMode::kDecal) {
932             // Discard kInitialized since a crop is a strict constraint on sampling outside of it.
933             // But preserve (kTransparent+kDecal) if this is a no-op crop.
934             restrictedOutput.fBoundary = PixelBoundary::kUnknown;
935         }
936         return restrictedOutput;
937     } else if (tileMode == SkTileMode::kDecal) {
938         // A decal crop can always be applied as the final operation by adjusting layer bounds, and
939         // does not modify any prior tile mode.
940         SkASSERT(!preserveTransparencyInCrop);
941         FilterResult restrictedOutput = *this;
942         restrictedOutput.fLayerBounds = fittedCrop;
943         return restrictedOutput;
944     } else {
945         // There is a non-trivial transform to the image data that must be applied before the
946         // non-decal tilemode is meant to be applied to the axis-aligned 'crop'.
947         FilterResult tiled = this->resolve(ctx, fittedCrop, /*preserveDstBounds=*/true);
948         tiled.updateTileMode(ctx, tileMode);
949         return tiled;
950     }
951 }
952 
applyColorFilter(const Context & ctx,sk_sp<SkColorFilter> colorFilter) const953 FilterResult FilterResult::applyColorFilter(const Context& ctx,
954                                             sk_sp<SkColorFilter> colorFilter) const {
955     // A null filter is the identity, so it should have been caught during image filter DAG creation
956     SkASSERT(colorFilter);
957 
958     if (ctx.desiredOutput().isEmpty()) {
959         return {};
960     }
961 
962     // Color filters are applied after the transform and image sampling, but before the fLayerBounds
963     // crop. We can compose 'colorFilter' with any previously applied color filter regardless
964     // of the transform/sample state, so long as it respects the effect of the current crop.
965     LayerSpace<SkIRect> newLayerBounds = fLayerBounds;
966     if (as_CFB(colorFilter)->affectsTransparentBlack()) {
967         if (!fImage || !newLayerBounds.intersect(ctx.desiredOutput())) {
968             // The current image's intersection with the desired output is fully transparent, but
969             // the new color filter converts that into a non-transparent color. The desired output
970             // is filled with this color, but use a 1x1 surface and clamp tiling.
971             AutoSurface surface{ctx,
972                                 LayerSpace<SkIRect>{SkIRect::MakeXYWH(ctx.desiredOutput().left(),
973                                                                       ctx.desiredOutput().top(),
974                                                                       1, 1)},
975                                 PixelBoundary::kInitialized,
976                                 /*renderInParameterSpace=*/false};
977             if (surface) {
978                 SkPaint paint;
979                 paint.setColor4f(SkColors::kTransparent, /*colorSpace=*/nullptr);
980                 paint.setColorFilter(std::move(colorFilter));
981 #if !defined(SK_USE_SRCOVER_FOR_FILTERS)
982                 paint.setBlendMode(SkBlendMode::kSrc);
983 #endif
984                 surface->drawPaint(paint);
985             }
986             FilterResult solidColor = surface.snap();
987             solidColor.updateTileMode(ctx, SkTileMode::kClamp);
988             return solidColor;
989         }
990 
991         if (this->analyzeBounds(ctx.desiredOutput()) & BoundsAnalysis::kRequiresLayerCrop) {
992             // Since 'colorFilter' modifies transparent black, the new result's layer bounds must
993             // be the desired output. But if the current image is cropped we need to resolve the
994             // image to avoid losing the effect of the current 'fLayerBounds'.
995             newLayerBounds.outset(LayerSpace<SkISize>({1, 1}));
996             SkAssertResult(newLayerBounds.intersect(ctx.desiredOutput()));
997             FilterResult filtered = this->resolve(ctx, newLayerBounds, /*preserveDstBounds=*/true);
998             filtered.fColorFilter = std::move(colorFilter);
999             filtered.updateTileMode(ctx, SkTileMode::kClamp);
1000             return filtered;
1001         }
1002 
1003         // otherwise we can fill out to the desired output without worrying about losing the crop.
1004         newLayerBounds = ctx.desiredOutput();
1005     } else {
1006         if (!fImage || !LayerSpace<SkIRect>::Intersects(newLayerBounds, ctx.desiredOutput())) {
1007             // The color filter does not modify transparent black, so it remains transparent
1008             return {};
1009         }
1010         // otherwise a non-transparent affecting color filter can always be lifted before any crop
1011         // because it does not change the "shape" of the prior FilterResult.
1012     }
1013 
1014     // If we got here we can compose the new color filter with the previous filter and the prior
1015     // layer bounds are either soft-cropped to the desired output, or we fill out the desired output
1016     // when the new color filter affects transparent black. We don't check if the entire composed
1017     // filter affects transparent black because earlier floods are restricted by the layer bounds.
1018     FilterResult filtered = *this;
1019     filtered.fLayerBounds = newLayerBounds;
1020     filtered.fColorFilter = SkColorFilters::Compose(std::move(colorFilter), fColorFilter);
1021     return filtered;
1022 }
1023 
compatible_sampling(const SkSamplingOptions & currentSampling,bool currentXformWontAffectNearest,SkSamplingOptions * nextSampling,bool nextXformWontAffectNearest)1024 static bool compatible_sampling(const SkSamplingOptions& currentSampling,
1025                                 bool currentXformWontAffectNearest,
1026                                 SkSamplingOptions* nextSampling,
1027                                 bool nextXformWontAffectNearest) {
1028     // Both transforms could perform non-trivial sampling, but if they are similar enough we
1029     // assume performing one non-trivial sampling operation with the concatenated transform will
1030     // not be visually distinguishable from sampling twice.
1031     // TODO(michaelludwig): For now ignore mipmap policy, SkSpecialImages are not supposed to be
1032     // drawn with mipmapping, and the majority of filter steps produce images that are at the
1033     // proper scale and do not define mip levels. The main exception is the ::Image() filter
1034     // leaf but that doesn't use this system yet.
1035     if (currentSampling.isAniso() && nextSampling->isAniso()) {
1036         // Assume we can get away with one sampling at the highest anisotropy level
1037         *nextSampling =  SkSamplingOptions::Aniso(std::max(currentSampling.maxAniso,
1038                                                            nextSampling->maxAniso));
1039         return true;
1040     } else if (currentSampling.isAniso() && nextSampling->filter == SkFilterMode::kLinear) {
1041         // Assume we can get away with the current anisotropic filter since the next is linear
1042         *nextSampling = currentSampling;
1043         return true;
1044     } else if (nextSampling->isAniso() && currentSampling.filter == SkFilterMode::kLinear) {
1045         // Mirror of the above, assume we can just get away with next's anisotropic filter
1046         return true;
1047     } else if (currentSampling.useCubic && (nextSampling->filter == SkFilterMode::kLinear ||
1048                                             (nextSampling->useCubic &&
1049                                              currentSampling.cubic.B == nextSampling->cubic.B &&
1050                                              currentSampling.cubic.C == nextSampling->cubic.C))) {
1051         // Assume we can get away with the current bicubic filter, since the next is the same
1052         // or a bilerp that can be upgraded.
1053         *nextSampling = currentSampling;
1054         return true;
1055     } else if (nextSampling->useCubic && currentSampling.filter == SkFilterMode::kLinear) {
1056         // Mirror of the above, assume we can just get away with next's cubic resampler
1057         return true;
1058     } else if (currentSampling.filter == SkFilterMode::kLinear &&
1059                nextSampling->filter == SkFilterMode::kLinear) {
1060         // Assume we can get away with a single bilerp vs. the two
1061         return true;
1062     } else if (nextSampling->filter == SkFilterMode::kNearest && currentXformWontAffectNearest) {
1063         // The next transform and nearest-neighbor filtering isn't impacted by the current transform
1064         SkASSERT(currentSampling.filter == SkFilterMode::kLinear);
1065         return true;
1066     } else if (currentSampling.filter == SkFilterMode::kNearest && nextXformWontAffectNearest) {
1067         // The next transform doesn't change the nearest-neighbor filtering of the current transform
1068         SkASSERT(nextSampling->filter == SkFilterMode::kLinear);
1069         *nextSampling = currentSampling;
1070         return true;
1071     } else {
1072         // The current or next sampling is nearest neighbor, and will produce visible texels
1073         // oriented with the current transform; assume this is a desired effect and preserve it.
1074         return false;
1075     }
1076 }
1077 
applyTransform(const Context & ctx,const LayerSpace<SkMatrix> & transform,const SkSamplingOptions & sampling) const1078 FilterResult FilterResult::applyTransform(const Context& ctx,
1079                                           const LayerSpace<SkMatrix>& transform,
1080                                           const SkSamplingOptions &sampling) const {
1081     if (!fImage || ctx.desiredOutput().isEmpty()) {
1082         // Transformed transparent black remains transparent black.
1083         SkASSERT(!fColorFilter);
1084         return {};
1085     }
1086 
1087     if (!transform.invert(nullptr)) {
1088         return {};
1089     }
1090 
1091     // Extract the sampling options that matter based on the current and next transforms.
1092     // We make sure the new sampling is bilerp (default) if the new transform doesn't matter
1093     // (and assert that the current is bilerp if its transform didn't matter). Bilerp can be
1094     // maximally combined, so simplifies the logic in compatible_sampling().
1095     const bool currentXformIsInteger = is_nearly_integer_translation(fTransform);
1096     const bool nextXformIsInteger = is_nearly_integer_translation(transform);
1097 
1098     SkASSERT(!currentXformIsInteger || fSamplingOptions == kDefaultSampling);
1099     SkSamplingOptions nextSampling = nextXformIsInteger ? kDefaultSampling : sampling;
1100 
1101     // Determine if the image is being visibly cropped by the layer bounds, in which case we can't
1102     // merge this transform with any previous transform (unless the new transform is an integer
1103     // translation in which case any visible edge is aligned with the desired output and can be
1104     // resolved by intersecting the transformed layer bounds and the output bounds).
1105     bool isCropped = !nextXformIsInteger &&
1106                      (this->analyzeBounds(SkMatrix(transform), SkIRect(ctx.desiredOutput()))
1107                             & BoundsAnalysis::kRequiresLayerCrop);
1108 
1109     FilterResult transformed;
1110     if (!isCropped && compatible_sampling(fSamplingOptions, currentXformIsInteger,
1111                                           &nextSampling, nextXformIsInteger)) {
1112         // We can concat transforms and 'nextSampling' will be either fSamplingOptions,
1113         // sampling, or a merged combination depending on the two transforms in play.
1114         transformed = *this;
1115     } else {
1116         // We'll have to resolve this FilterResult first before 'transform' and 'sampling' can be
1117         // correctly evaluated. 'nextSampling' will always be 'sampling'.
1118         LayerSpace<SkIRect> tightBounds;
1119         if (transform.inverseMapRect(ctx.desiredOutput(), &tightBounds)) {
1120             transformed = this->resolve(ctx, tightBounds);
1121         }
1122 
1123         if (!transformed.fImage) {
1124             // Transform not invertible or resolve failed to create an image
1125             return {};
1126         }
1127     }
1128 
1129     transformed.fSamplingOptions = nextSampling;
1130     transformed.fTransform.postConcat(transform);
1131     // Rebuild the layer bounds and then restrict to the current desired output. The original value
1132     // of fLayerBounds includes the image mapped by the original fTransform as well as any
1133     // accumulated soft crops from desired outputs of prior stages. To prevent discarding that info,
1134     // we map fLayerBounds by the additional transform, instead of re-mapping the image bounds.
1135     transformed.fLayerBounds = transform.mapRect(transformed.fLayerBounds);
1136     if (!LayerSpace<SkIRect>::Intersects(transformed.fLayerBounds, ctx.desiredOutput())) {
1137         // The transformed output doesn't touch the desired, so it would just be transparent black.
1138         return {};
1139     }
1140 
1141     return transformed;
1142 }
1143 
resolve(const Context & ctx,LayerSpace<SkIRect> dstBounds,bool preserveDstBounds) const1144 FilterResult FilterResult::resolve(const Context& ctx,
1145                                    LayerSpace<SkIRect> dstBounds,
1146                                    bool preserveDstBounds) const {
1147     // The layer bounds is the final clip, so it can always be used to restrict 'dstBounds'. Even
1148     // if there's a non-decal tile mode or transparent-black affecting color filter, those floods
1149     // are restricted to fLayerBounds.
1150     if (!fImage || (!preserveDstBounds && !dstBounds.intersect(fLayerBounds))) {
1151         return {nullptr, {}};
1152     }
1153 
1154     // If we have any extra effect to apply, there's no point in trying to extract a subset.
1155     const bool subsetCompatible = !fColorFilter &&
1156                                   fTileMode == SkTileMode::kDecal &&
1157                                   !preserveDstBounds;
1158 
1159     // TODO(michaelludwig): If we get to the point where all filter results track bounds in
1160     // floating point, then we can extend this case to any S+T transform.
1161     LayerSpace<SkIPoint> origin;
1162     if (subsetCompatible && is_nearly_integer_translation(fTransform, &origin)) {
1163         return this->subset(origin, dstBounds);
1164     } // else fall through and attempt a draw
1165 
1166     // Don't use context properties to avoid DMSAA on internal stages of filter evaluation.
1167     SkSurfaceProps props = {};
1168     PixelBoundary boundary = preserveDstBounds ? PixelBoundary::kUnknown
1169                                                : PixelBoundary::kTransparent;
1170     AutoSurface surface{ctx, dstBounds, boundary, /*renderInParameterSpace=*/false, &props};
1171     if (surface) {
1172         this->draw(ctx, surface.device(), /*preserveDeviceState=*/false);
1173     }
1174     return surface.snap();
1175 }
1176 
subset(const LayerSpace<SkIPoint> & knownOrigin,const LayerSpace<SkIRect> & subsetBounds,bool clampSrcIfDisjoint) const1177 FilterResult FilterResult::subset(const LayerSpace<SkIPoint>& knownOrigin,
1178                                   const LayerSpace<SkIRect>& subsetBounds,
1179                                   bool clampSrcIfDisjoint) const {
1180     SkDEBUGCODE(LayerSpace<SkIPoint> actualOrigin;)
1181     SkASSERT(is_nearly_integer_translation(fTransform, &actualOrigin) &&
1182              SkIPoint(actualOrigin) == SkIPoint(knownOrigin));
1183 
1184 
1185     LayerSpace<SkIRect> imageBounds(SkIRect::MakeXYWH(knownOrigin.x(), knownOrigin.y(),
1186                                                       fImage->width(), fImage->height()));
1187     imageBounds = imageBounds.relevantSubset(subsetBounds, clampSrcIfDisjoint ? SkTileMode::kClamp
1188                                                                               : SkTileMode::kDecal);
1189     if (imageBounds.isEmpty()) {
1190         return {};
1191     }
1192 
1193     // Offset the image subset directly to avoid issues negating (origin). With the prior
1194     // intersection (bounds - origin) will be >= 0, but (bounds + (-origin)) may not, (e.g.
1195     // origin is INT_MIN).
1196     SkIRect subset = { imageBounds.left() - knownOrigin.x(),
1197                        imageBounds.top() - knownOrigin.y(),
1198                        imageBounds.right() - knownOrigin.x(),
1199                        imageBounds.bottom() - knownOrigin.y() };
1200     SkASSERT(subset.fLeft >= 0 && subset.fTop >= 0 &&
1201              subset.fRight <= fImage->width() && subset.fBottom <= fImage->height());
1202 
1203     FilterResult result{fImage->makeSubset(subset), imageBounds.topLeft()};
1204     result.fColorFilter = fColorFilter;
1205 
1206     // Update what's known about PixelBoundary based on how the subset aligns.
1207     SkASSERT(result.fBoundary == PixelBoundary::kUnknown);
1208     // If the pixel bounds didn't change, preserve the original boundary value
1209     if (fImage->subset() == result.fImage->subset()) {
1210         result.fBoundary = fBoundary;
1211     } else {
1212         // If the new pixel bounds are bordered by valid data, upgrade to kInitialized
1213         SkIRect safeSubset = fImage->subset();
1214         if (fBoundary == PixelBoundary::kUnknown) {
1215             safeSubset.inset(1, 1);
1216         }
1217         if (safeSubset.contains(result.fImage->subset())) {
1218             result.fBoundary = PixelBoundary::kInitialized;
1219         }
1220     }
1221     return result;
1222 }
1223 
draw(const Context & ctx,SkDevice * target,const SkBlender * blender) const1224 void FilterResult::draw(const Context& ctx, SkDevice* target, const SkBlender* blender) const {
1225     SkAutoDeviceTransformRestore adtr{target, ctx.mapping().layerToDevice()};
1226     this->draw(ctx, target, /*preserveDeviceState=*/true, blender);
1227 }
1228 
draw(const Context & ctx,SkDevice * device,bool preserveDeviceState,const SkBlender * blender) const1229 void FilterResult::draw(const Context& ctx,
1230                         SkDevice* device,
1231                         bool preserveDeviceState,
1232                         const SkBlender* blender) const {
1233     const bool blendAffectsTransparentBlack = blender && as_BB(blender)->affectsTransparentBlack();
1234     if (!fImage) {
1235         // The image is transparent black, this is a no-op unless we need to apply the blend mode
1236         if (blendAffectsTransparentBlack) {
1237             SkPaint clear;
1238             clear.setColor4f(SkColors::kTransparent);
1239             clear.setBlender(sk_ref_sp(blender));
1240             device->drawPaint(clear);
1241         }
1242         return;
1243     }
1244 
1245     BoundsScope scope = blendAffectsTransparentBlack ? BoundsScope::kShaderOnly
1246                                                      : BoundsScope::kCanDrawDirectly;
1247     SkEnumBitMask<BoundsAnalysis> analysis = this->analyzeBounds(device->localToDevice(),
1248                                                                  device->devClipBounds(),
1249                                                                  scope);
1250 
1251     if (analysis & BoundsAnalysis::kRequiresLayerCrop) {
1252         if (blendAffectsTransparentBlack) {
1253             // This is similar to the resolve() path in applyColorFilter() when the filter affects
1254             // transparent black but must be applied after the prior visible layer bounds clip.
1255             // NOTE: We map devClipBounds() by the local-to-device matrix instead of the Context
1256             // mapping because that works for both use cases: drawing to the final device (where
1257             // the transforms are the same), or drawing to intermediate layer images (where they
1258             // are not the same).
1259             LayerSpace<SkIRect> dstBounds;
1260             if (!LayerSpace<SkMatrix>(device->localToDevice()).inverseMapRect(
1261                         LayerSpace<SkIRect>(device->devClipBounds()), &dstBounds)) {
1262                 return;
1263             }
1264             // Regardless of the scenario, the end result is that it's in layer space.
1265             FilterResult clipped = this->resolve(ctx, dstBounds);
1266             clipped.draw(ctx, device, preserveDeviceState, blender);
1267             return;
1268         }
1269         // Otherwise we can apply the layer bounds as a clip to avoid an intermediate render pass
1270         if (preserveDeviceState) {
1271             device->pushClipStack();
1272         }
1273         device->clipRect(SkRect::Make(SkIRect(fLayerBounds)), SkClipOp::kIntersect, /*aa=*/true);
1274     }
1275 
1276     // If we are an integer translate, the default bilinear sampling *should* be equivalent to
1277     // nearest-neighbor. Going through the direct image-drawing path tends to detect this
1278     // and reduce sampling automatically. When we have to use an image shader, this isn't
1279     // detected and some GPUs' linear filtering doesn't exactly match nearest-neighbor and can
1280     // lead to leaks beyond the image's subset. Detect and reduce sampling explicitly.
1281     const bool pixelAligned =
1282             is_nearly_integer_translation(fTransform) &&
1283             is_nearly_integer_translation(skif::LayerSpace<SkMatrix>(device->localToDevice()));
1284     SkSamplingOptions sampling = fSamplingOptions;
1285     if (sampling == kDefaultSampling && pixelAligned) {
1286         sampling = {};
1287     }
1288 
1289     if (analysis & BoundsAnalysis::kHasLayerFillingEffect ||
1290         (blendAffectsTransparentBlack && (analysis & BoundsAnalysis::kDstBoundsNotCovered))) {
1291         // Fill the canvas with the shader, so that the pixels beyond the image dimensions are still
1292         // covered by the draw and either resolve tiling into the image, color filter transparent
1293         // black, apply the blend mode to the dst, or any combination thereof.
1294         SkPaint paint;
1295         if (!preserveDeviceState && !blender) {
1296             // When we don't care about the device's prior contents, the default blender can be kSrc
1297 #if !defined(SK_USE_SRCOVER_FOR_FILTERS)
1298             paint.setBlendMode(SkBlendMode::kSrc);
1299 #endif
1300         } else {
1301             paint.setBlender(sk_ref_sp(blender));
1302         }
1303         paint.setShader(this->getAnalyzedShaderView(ctx, sampling, analysis));
1304         device->drawPaint(paint);
1305     } else {
1306         SkPaint paint;
1307         paint.setBlender(sk_ref_sp(blender));
1308         paint.setColorFilter(fColorFilter);
1309 
1310         // src's origin is embedded in fTransform. For historical reasons, drawSpecial() does
1311         // not automatically use the device's current local-to-device matrix, but that's what preps
1312         // it to match the expected layer coordinate system.
1313         SkMatrix netTransform = SkMatrix::Concat(device->localToDevice(), SkMatrix(fTransform));
1314 
1315         // Check fSamplingOptions for linear filtering, not 'sampling' since it may have been
1316         // reduced to nearest neighbor.
1317         if (this->canClampToTransparentBoundary(analysis) && fSamplingOptions == kDefaultSampling) {
1318             SkASSERT(!(analysis & BoundsAnalysis::kRequiresShaderTiling));
1319             // Draw non-AA with a 1px outset image so that the transparent boundary filtering is
1320             // not multiplied with the AA (which creates a harsher AA transition).
1321             if (!preserveDeviceState && !blender) {
1322                 // Since this is a non-AA draw, kSrc can be more efficient if we are the default
1323                 // blend mode and can assume the prior dst pixels were transparent black.
1324 #if !defined(SK_USE_SRCOVER_FOR_FILTERS)
1325                 paint.setBlendMode(SkBlendMode::kSrc);
1326 #endif
1327             }
1328             netTransform.preTranslate(-1.f, -1.f);
1329             device->drawSpecial(fImage->makePixelOutset().get(), netTransform, sampling, paint,
1330                                 SkCanvas::kFast_SrcRectConstraint);
1331         } else {
1332             paint.setAntiAlias(true);
1333             SkCanvas::SrcRectConstraint constraint = SkCanvas::kFast_SrcRectConstraint;
1334             if (analysis & BoundsAnalysis::kRequiresShaderTiling) {
1335                 constraint = SkCanvas::kStrict_SrcRectConstraint;
1336                 ctx.markShaderBasedTilingRequired(SkTileMode::kClamp);
1337             }
1338             device->drawSpecial(fImage.get(), netTransform, sampling, paint, constraint);
1339         }
1340     }
1341 
1342     if (preserveDeviceState && (analysis & BoundsAnalysis::kRequiresLayerCrop)) {
1343         device->popClipStack();
1344     }
1345 }
1346 
asShader(const Context & ctx,const SkSamplingOptions & xtraSampling,SkEnumBitMask<ShaderFlags> flags,const LayerSpace<SkIRect> & sampleBounds) const1347 sk_sp<SkShader> FilterResult::asShader(const Context& ctx,
1348                                        const SkSamplingOptions& xtraSampling,
1349                                        SkEnumBitMask<ShaderFlags> flags,
1350                                        const LayerSpace<SkIRect>& sampleBounds) const {
1351     if (!fImage) {
1352         return nullptr;
1353     }
1354     // Even if flags don't force resolving the filter result to an axis-aligned image, if the
1355     // extra sampling to be applied is not compatible with the accumulated transform and sampling,
1356     // or if the logical image is cropped by the layer bounds, the FilterResult will need to be
1357     // resolved to an image before we wrap it as an SkShader. When checking if cropped, we use the
1358     // FilterResult's layer bounds instead of the context's desired output, assuming that the layer
1359     // bounds reflect the bounds of the coords a parent shader will pass to eval().
1360     const bool currentXformIsInteger = is_nearly_integer_translation(fTransform);
1361     const bool nextXformIsInteger = !(flags & ShaderFlags::kNonTrivialSampling);
1362 
1363     SkBlendMode colorFilterMode;
1364     SkEnumBitMask<BoundsAnalysis> analysis = this->analyzeBounds(sampleBounds,
1365                                                                  BoundsScope::kShaderOnly);
1366 
1367     SkSamplingOptions sampling = xtraSampling;
1368     const bool needsResolve =
1369             // Deferred calculations on the input would be repeated with each sample, but we allow
1370             // simple color filters to skip resolving since their repeated math should be cheap.
1371             (flags & ShaderFlags::kSampledRepeatedly &&
1372                     ((fColorFilter && (!fColorFilter->asAColorMode(nullptr, &colorFilterMode) ||
1373                                        colorFilterMode > SkBlendMode::kLastCoeffMode)) ||
1374                      !SkColorSpace::Equals(fImage->getColorSpace(), ctx.colorSpace()))) ||
1375             // The deferred sampling options can't be merged with the one requested
1376             !compatible_sampling(fSamplingOptions, currentXformIsInteger,
1377                                  &sampling, nextXformIsInteger) ||
1378             // The deferred edge of the layer bounds is visible to sampling
1379             (analysis & BoundsAnalysis::kRequiresLayerCrop);
1380 
1381     // Downgrade to nearest-neighbor if the sequence of sampling doesn't do anything
1382     if (sampling == kDefaultSampling && nextXformIsInteger &&
1383         (needsResolve || currentXformIsInteger)) {
1384         sampling = {};
1385     }
1386 
1387     sk_sp<SkShader> shader;
1388     if (needsResolve) {
1389         // The resolve takes care of fTransform (sans origin), fTileMode, fColorFilter, and
1390         // fLayerBounds.
1391         FilterResult resolved = this->resolve(ctx, sampleBounds);
1392         if (resolved) {
1393             // Redo the analysis, however, because it's hard to predict HW edge tiling. Since the
1394             // original layer crop was visible, that implies that the now-resolved image won't cover
1395             // dst bounds. Since we are using this as a shader to fill the dst bounds, we may have
1396             // to still do shader-clamping (to a transparent boundary) if the resolved image doesn't
1397             // have HW-tileable boundaries.
1398             [[maybe_unused]] static constexpr SkEnumBitMask<BoundsAnalysis> kExpectedAnalysis =
1399                     BoundsAnalysis::kDstBoundsNotCovered | BoundsAnalysis::kRequiresShaderTiling;
1400             analysis = resolved.analyzeBounds(sampleBounds, BoundsScope::kShaderOnly);
1401             SkASSERT(!(analysis & ~kExpectedAnalysis));
1402             return resolved.getAnalyzedShaderView(ctx, sampling, analysis);
1403         }
1404     } else {
1405         shader = this->getAnalyzedShaderView(ctx, sampling, analysis);
1406     }
1407 
1408     return shader;
1409 }
1410 
getAnalyzedShaderView(const Context & ctx,const SkSamplingOptions & finalSampling,SkEnumBitMask<BoundsAnalysis> analysis) const1411 sk_sp<SkShader> FilterResult::getAnalyzedShaderView(
1412         const Context& ctx,
1413         const SkSamplingOptions& finalSampling,
1414         SkEnumBitMask<BoundsAnalysis> analysis) const {
1415     const SkMatrix& localMatrix(fTransform);
1416     const SkRect imageBounds = SkRect::Make(fImage->dimensions());
1417     // We need to apply the decal in a coordinate space that matches the resolution of the layer
1418     // space. If the transform preserves rectangles, map the image bounds by the transform so we
1419     // can apply it before we evaluate the shader. Otherwise decompose the transform into a
1420     // non-scaling post-decal transform and a scaling pre-decal transform.
1421     SkMatrix postDecal, preDecal;
1422     if (localMatrix.rectStaysRect() ||
1423         !(analysis & BoundsAnalysis::kRequiresDecalInLayerSpace)) {
1424         postDecal = SkMatrix::I();
1425         preDecal = localMatrix;
1426     } else {
1427         decompose_transform(localMatrix, imageBounds.center(), &postDecal, &preDecal);
1428     }
1429 
1430     // If the image covers the dst bounds, then its tiling won't be visible, so we can switch
1431     // to the faster kClamp for either HW or shader-based tiling. If we are applying the decal
1432     // in layer space, then that extra shader implements the tiling, so we can switch to clamp
1433     // for the image shader itself.
1434     SkTileMode effectiveTileMode = fTileMode;
1435     const bool decalClampToTransparent = this->canClampToTransparentBoundary(analysis);
1436     const bool strict = SkToBool(analysis & BoundsAnalysis::kRequiresShaderTiling);
1437 
1438     sk_sp<SkShader> imageShader;
1439     if (strict && decalClampToTransparent) {
1440         // Make the image shader apply to the 1px outset so that the strict subset includes the
1441         // transparent pixels.
1442         preDecal.preTranslate(-1.f, -1.f);
1443         imageShader = fImage->makePixelOutset()->asShader(SkTileMode::kClamp, finalSampling,
1444                                                           preDecal, strict);
1445         effectiveTileMode = SkTileMode::kClamp;
1446     } else {
1447         if (!(analysis & BoundsAnalysis::kDstBoundsNotCovered) ||
1448             (analysis & BoundsAnalysis::kRequiresDecalInLayerSpace)) {
1449             effectiveTileMode = SkTileMode::kClamp;
1450         }
1451         imageShader = fImage->asShader(effectiveTileMode, finalSampling, preDecal, strict);
1452     }
1453     if (strict) {
1454         ctx.markShaderBasedTilingRequired(effectiveTileMode);
1455     }
1456 
1457     if (analysis & BoundsAnalysis::kRequiresDecalInLayerSpace) {
1458         SkASSERT(fTileMode == SkTileMode::kDecal);
1459         // TODO(skbug:12784) - As part of fully supporting subsets in image shaders, it probably
1460         // makes sense to share the subset tiling logic that's in GrTextureEffect as dedicated
1461         // SkShaders. Graphite can then add those to its program as-needed vs. always doing
1462         // shader-based tiling, and CPU can have raster-pipeline tiling applied more flexibly than
1463         // at the bitmap level. At that point, this effect is redundant and can be replaced with the
1464         // decal-subset shader.
1465         const SkRuntimeEffect* decalEffect =
1466                 GetKnownRuntimeEffect(SkKnownRuntimeEffects::StableKey::kDecal);
1467 
1468         SkRuntimeShaderBuilder builder(sk_ref_sp(decalEffect));
1469         builder.child("image") = std::move(imageShader);
1470         builder.uniform("decalBounds") = preDecal.mapRect(imageBounds);
1471 
1472         imageShader = builder.makeShader();
1473     }
1474 
1475     if (imageShader && (analysis & BoundsAnalysis::kRequiresDecalInLayerSpace)) {
1476         imageShader = imageShader->makeWithLocalMatrix(postDecal);
1477     }
1478 
1479     if (imageShader && fColorFilter) {
1480         imageShader = imageShader->makeWithColorFilter(fColorFilter);
1481     }
1482 
1483     // Shader now includes the image, the sampling, the tile mode, the transform, and the color
1484     // filter, skipping deferred effects that aren't present or aren't visible given 'analysis'.
1485     // The last "effect", layer bounds cropping, must be handled externally by either resolving
1486     // the image before hand or clipping the device that's drawing the returned shader.
1487     return imageShader;
1488 }
1489 
1490 // FilterResult::rescale() implementation
1491 
1492 namespace {
1493 
1494 // The following code uses "PixelSpace" as an alias to refer to the LayerSpace of the low-res
1495 // input image and blurred output to differentiate values for the original and final layer space
1496 template <typename T>
1497 using PixelSpace = LayerSpace<T>;
1498 
downscale_step_count(float netScaleFactor)1499 int downscale_step_count(float netScaleFactor) {
1500     int steps = SkNextLog2(sk_float_ceil2int(1.f / netScaleFactor));
1501     // There are (steps-1) 1/2x steps and then one step that will be between 1/2-1x. If the
1502     // final step is practically the identity scale, we can save a render pass and not incur too
1503     // much sampling error by reducing the step count and using a final scale that's slightly less
1504     // than 1/2.
1505     if (steps > 0) {
1506         // For a multipass rescale, we allow for a lot of tolerance when deciding to collapse the
1507         // final step. If there's only a single pass, we require the scale factor to be very close
1508         // to the identity since it causes the step count to go to 0.
1509         static constexpr float kMultiPassLimit = 0.9f;
1510         static constexpr float kNearIdentityLimit = 1.f - kRoundEpsilon; // 1px error in 1000px img
1511 
1512         float finalStepScale = netScaleFactor * (1 << (steps - 1));
1513         float limit = steps == 1 ? kNearIdentityLimit : kMultiPassLimit;
1514         if (finalStepScale >= limit) {
1515             steps--;
1516         }
1517     }
1518 
1519     return steps;
1520 }
1521 
scale_about_center(const PixelSpace<SkRect> src,float sx,float sy)1522 PixelSpace<SkRect> scale_about_center(const PixelSpace<SkRect> src, float sx, float sy) {
1523     float cx = sx == 1.f ? 0.f : (0.5f * src.left() + 0.5f * src.right());
1524     float cy = sy == 1.f ? 0.f : (0.5f * src.top()  + 0.5f * src.bottom());
1525     return LayerSpace<SkRect>({(src.left()  - cx) * sx, (src.top()    - cy) * sy,
1526                                (src.right() - cx) * sx, (src.bottom() - cy) * sy});
1527 }
1528 
draw_color_filtered_border(SkCanvas * canvas,PixelSpace<SkIRect> border,sk_sp<SkColorFilter> colorFilter)1529 void draw_color_filtered_border(SkCanvas* canvas,
1530                                 PixelSpace<SkIRect> border,
1531                                 sk_sp<SkColorFilter> colorFilter) {
1532     SkPaint cfOnly;
1533     cfOnly.setColor4f(SkColors::kTransparent);
1534     cfOnly.setColorFilter(std::move(colorFilter));
1535 #if !defined(SK_USE_SRCOVER_FOR_FILTERS)
1536     cfOnly.setBlendMode(SkBlendMode::kSrc);
1537 #endif
1538 
1539     canvas->drawIRect({border.left(),      border.top(),
1540                        border.right(),     border.top() + 1},
1541                        cfOnly); // Top (with corners)
1542     canvas->drawIRect({border.left(),      border.bottom() - 1,
1543                        border.right(),     border.bottom()},
1544                        cfOnly); // Bottom (with corners)
1545     canvas->drawIRect({border.left(),      border.top() + 1,
1546                        border.left() + 1,  border.bottom() - 1},
1547                        cfOnly); // Left (no corners)
1548     canvas->drawIRect({border.right() - 1, border.top() + 1,
1549                        border.right(),     border.bottom() - 1},
1550                        cfOnly); // Right (no corners)
1551 }
1552 
draw_tiled_border(SkCanvas * canvas,SkTileMode tileMode,const SkPaint & paint,const PixelSpace<SkMatrix> & srcToDst,PixelSpace<SkRect> srcBorder,PixelSpace<SkRect> dstBorder)1553 void draw_tiled_border(SkCanvas* canvas,
1554                        SkTileMode tileMode,
1555                        const SkPaint& paint,
1556                        const PixelSpace<SkMatrix>& srcToDst,
1557                        PixelSpace<SkRect> srcBorder,
1558                        PixelSpace<SkRect> dstBorder) {
1559     SkASSERT(tileMode != SkTileMode::kDecal); // There are faster ways for just transparent black
1560 
1561     // Sample the border pixels directly, scaling only on an axis at a time for
1562     // edges, and with no scaling for corners. Since only the CTM is adjusted, these
1563     // 8 draws should be batchable with the primary fill that had used `paint`.
1564     auto drawEdge = [&](const SkRect& src, const SkRect& dst) {
1565         canvas->save();
1566         canvas->concat(SkMatrix::RectToRect(src, dst));
1567         canvas->drawRect(src, paint);
1568         canvas->restore();
1569     };
1570     auto drawCorner = [&](const SkPoint& src, const SkPoint& dst) {
1571         drawEdge(SkRect::MakeXYWH(src.fX, src.fY, 1.f, 1.f),
1572                  SkRect::MakeXYWH(dst.fX, dst.fY, 1.f, 1.f));
1573     };
1574 
1575     // 'dstBorder' includes the 1px padding that we are filling in. Inset to reconstruct the
1576     // original sampled dst.
1577     PixelSpace<SkRect> dstSampleBounds{dstBorder};
1578     dstSampleBounds.inset(PixelSpace<SkSize>({1.f, 1.f}));
1579 
1580     // Reconstruct the original source coordinate bounds
1581     PixelSpace<SkRect> srcSampleBounds;
1582     SkAssertResult(srcToDst.inverseMapRect(dstSampleBounds, &srcSampleBounds));
1583 
1584     if (tileMode == SkTileMode::kMirror || tileMode == SkTileMode::kRepeat) {
1585         // Adjust 'srcBorder' to instead match the 1px rectangle centered over srcSampleBounds
1586         // in order to calculate the average of the two outermost sampled pixels.
1587         // Inset by an extra 1/2 so that the eventual sample coordinates average the outermost two
1588         // rows/columns of src pixels.
1589         srcBorder = dstSampleBounds;
1590         srcBorder.inset(PixelSpace<SkSize>({0.5f, 0.5f}));
1591         SkAssertResult(srcToDst.inverseMapRect(srcBorder, &srcBorder));
1592         srcBorder.outset(PixelSpace<SkSize>({0.5f, 0.5f}));
1593     }
1594 
1595     // Invert the dst coordinates for repeat so that the left edge is mapped to the
1596     // right edge of the output, etc.
1597     if (tileMode == SkTileMode::kRepeat) {
1598         dstBorder = PixelSpace<SkRect>({dstBorder.right() - 1.f, dstBorder.bottom() - 1.f,
1599                                         dstBorder.left()  + 1.f, dstBorder.top()    + 1.f});
1600     }
1601 
1602     // Edges (excluding corners)
1603     drawEdge({srcBorder.left(),        srcSampleBounds.top(),
1604               srcBorder.left() + 1.f,  srcSampleBounds.bottom()},
1605              {dstBorder.left(),        dstSampleBounds.top(),
1606               dstBorder.left() + 1.f,  dstSampleBounds.bottom()}); // Left
1607 
1608     drawEdge({srcBorder.right() - 1.f, srcSampleBounds.top(),
1609               srcBorder.right(),       srcSampleBounds.bottom()},
1610              {dstBorder.right() - 1.f, dstSampleBounds.top(),
1611               dstBorder.right(),       dstSampleBounds.bottom()}); // Right
1612 
1613     drawEdge({srcSampleBounds.left(),  srcBorder.top(),
1614               srcSampleBounds.right(), srcBorder.top() + 1.f},
1615              {dstSampleBounds.left(),  dstBorder.top(),
1616               dstSampleBounds.right(), dstBorder.top() + 1.f});    // Top
1617 
1618     drawEdge({srcSampleBounds.left(),  srcBorder.bottom() - 1.f,
1619               srcSampleBounds.right(), srcBorder.bottom()},
1620              {dstSampleBounds.left(),  dstBorder.bottom() - 1.f,
1621               dstSampleBounds.right(), dstBorder.bottom()});       // Bottom
1622 
1623     // Corners (sampled directly to preserve their value since they can dominate the
1624     // output of a clamped blur with a large radius).
1625     drawCorner({srcBorder.left(),        srcBorder.top()},
1626                {dstBorder.left(),        dstBorder.top()});          // TL
1627     drawCorner({srcBorder.right() - 1.f, srcBorder.top()},
1628                {dstBorder.right() - 1.f, dstBorder.top()});          // TR
1629     drawCorner({srcBorder.right() - 1.f, srcBorder.bottom() - 1.f},
1630                {dstBorder.right() - 1.f, dstBorder.bottom() - 1.f}); // BR
1631     drawCorner({srcBorder.left(),        srcBorder.bottom() - 1.f},
1632                {dstBorder.left(),        dstBorder.bottom() - 1.f}); // BL
1633 }
1634 
1635 } // anonymous namespace
1636 
rescale(const Context & ctx,const LayerSpace<SkSize> & scale,bool enforceDecal) const1637 FilterResult FilterResult::rescale(const Context& ctx,
1638                                    const LayerSpace<SkSize>& scale,
1639                                    bool enforceDecal) const {
1640     LayerSpace<SkIRect> visibleLayerBounds = fLayerBounds;
1641     if (!fImage || !visibleLayerBounds.intersect(ctx.desiredOutput()) ||
1642         scale.width() <= 0.f || scale.height() <= 0.f) {
1643         return {};
1644     }
1645 
1646     // NOTE: For the first pass, PixelSpace and LayerSpace are equivalent
1647     PixelSpace<SkIPoint> origin;
1648     const bool pixelAligned = is_nearly_integer_translation(fTransform, &origin);
1649     SkEnumBitMask<BoundsAnalysis> analysis = this->analyzeBounds(ctx.desiredOutput(),
1650                                                                  BoundsScope::kRescale);
1651 
1652     // If there's no actual scaling, and no other effects that have to be resolved for blur(),
1653     // then just extract the necessary subset. Otherwise fall through and apply the effects with
1654     // scale factor (possibly identity).
1655     const bool canDeferTiling =
1656             pixelAligned &&
1657             !(analysis & BoundsAnalysis::kRequiresLayerCrop) &&
1658             !(enforceDecal && (analysis & BoundsAnalysis::kHasLayerFillingEffect));
1659 
1660     // To match legacy color space conversion logic, treat a null src as sRGB and a null dst as
1661     // as the src CS.
1662     const SkColorSpace* srcCS = fImage->getColorSpace() ? fImage->getColorSpace()
1663                                                         : sk_srgb_singleton();
1664     const SkColorSpace* dstCS = ctx.colorSpace() ? ctx.colorSpace() : srcCS;
1665     const bool hasEffectsToApply =
1666             !canDeferTiling ||
1667             SkToBool(fColorFilter) ||
1668             fImage->colorType() != ctx.backend()->colorType() ||
1669             !SkColorSpace::Equals(srcCS, dstCS);
1670 
1671     int xSteps = downscale_step_count(scale.width());
1672     int ySteps = downscale_step_count(scale.height());
1673     if (xSteps == 0 && ySteps == 0 && !hasEffectsToApply) {
1674         if (analysis & BoundsAnalysis::kHasLayerFillingEffect) {
1675             // At this point, the only effects that could be visible is a non-decal mode, so just
1676             // return the image with adjusted layer bounds to match desired output.
1677             FilterResult noop = *this;
1678             noop.fLayerBounds = visibleLayerBounds;
1679             return noop;
1680         } else {
1681             // The visible layer bounds represents a tighter bounds than the image itself
1682             return this->subset(origin, visibleLayerBounds);
1683         }
1684     }
1685 
1686     PixelSpace<SkIRect> srcRect;
1687     SkTileMode tileMode;
1688     bool cfBorder = false;
1689     bool deferPeriodicTiling = false;
1690     if (canDeferTiling && (analysis & BoundsAnalysis::kHasLayerFillingEffect)) {
1691         // When we can defer tiling, and said tiling is visible, rescaling the original image
1692         // uses smaller textures.
1693         srcRect = LayerSpace<SkIRect>(SkIRect::MakeXYWH(origin.x(), origin.y(),
1694                                                         fImage->width(), fImage->height()));
1695         if (fTileMode == SkTileMode::kDecal &&
1696             (analysis & BoundsAnalysis::kHasLayerFillingEffect)) {
1697             // Like in applyColorFilter() evaluate the transparent CF'ed border and clamp to it.
1698             tileMode = SkTileMode::kClamp;
1699             cfBorder = true;
1700         } else {
1701             tileMode = fTileMode;
1702             deferPeriodicTiling = tileMode == SkTileMode::kRepeat ||
1703                                   tileMode == SkTileMode::kMirror;
1704         }
1705     } else {
1706         // Otherwise we either have to rescale the layer-bounds-sized image (!canDeferTiling)
1707         // or the tiling isn't visible so the layer bounds represents a smaller effective
1708         // image than the original image data.
1709         srcRect = visibleLayerBounds;
1710         tileMode = SkTileMode::kDecal;
1711     }
1712 
1713     srcRect = srcRect.relevantSubset(ctx.desiredOutput(), tileMode);
1714     // To avoid incurring error from rounding up the dimensions at every step, the logical size of
1715     // the image is tracked in floats through the whole process; rounding to integers is only done
1716     // to produce a conservative pixel buffer and clamp-tiling is used so that partially covered
1717     // pixels are filled with the un-weighted color.
1718     PixelSpace<SkRect> stepBoundsF{srcRect};
1719     if (stepBoundsF.isEmpty()) {
1720         return {};
1721     }
1722     // stepPixelBounds holds integer pixel values (as floats) and includes any padded outsetting
1723     // that was rendered by the previous step, while stepBoundsF does not have any padding.
1724     PixelSpace<SkRect> stepPixelBounds{srcRect};
1725 
1726     // If we made it here, at least one iteration is required, even if xSteps and ySteps are 0.
1727     FilterResult image = *this;
1728     if (!pixelAligned && (xSteps > 0 || ySteps > 0)) {
1729         // If the source image has a deferred transform with a downscaling factor, we don't want to
1730         // necessarily compose the first rescale step's transform with it because we will then be
1731         // missing pixels in the bilinear filtering and create sampling artifacts during animations.
1732         // NOTE: Force nextSteps counts to the max integer value when the accumulated scale factor
1733         // is not finite, to force the input image to be resolved.
1734         LayerSpace<SkSize> netScale = image.fTransform.mapSize(scale);
1735         int nextXSteps = std::isfinite(netScale.width()) ? downscale_step_count(netScale.width())
1736                                                          : std::numeric_limits<int>::max();
1737         int nextYSteps = std::isfinite(netScale.height()) ? downscale_step_count(netScale.height())
1738                                                           : std::numeric_limits<int>::max();
1739         // We only need to resolve the deferred transform if the rescaling along an axis is not
1740         // near identity (steps > 0). If it's near identity, there's no real difference in sampling
1741         // between resolving here and deferring it to the first rescale iteration.
1742         if ((xSteps > 0 && nextXSteps > xSteps) || (ySteps > 0 && nextYSteps > ySteps)) {
1743             // Resolve the deferred transform. We don't just fold the deferred scale factor into
1744             // the rescaling steps because, for better or worse, the deferred transform does not
1745             // otherwise participate in progressive scaling so we should be consistent.
1746             image = image.resolve(ctx, srcRect);
1747             if (!image) {
1748                 // Early out if the resolve failed
1749                 return {};
1750             }
1751             if (!cfBorder) {
1752                 // This sets the resolved image to match either kDecal or the deferred tile mode.
1753                 image.fTileMode = tileMode;
1754             } // else leave it as kDecal when cfBorder is true
1755         }
1756     }
1757 
1758     // For now, if we are deferring periodic tiling, we need to ensure that the low-res image bounds
1759     // are pixel aligned. This is because the tiling is applied at the pixel level in SkImageShader,
1760     // and we need the period of the low-res image to align with the original high-resolution period
1761     // If/when SkImageShader supports shader-tiling over fractional bounds, this can relax.
1762     float finalScaleX = xSteps > 0 ? scale.width() : 1.f;
1763     float finalScaleY = ySteps > 0 ? scale.height() : 1.f;
1764     if (deferPeriodicTiling) {
1765         PixelSpace<SkRect> dstBoundsF = scale_about_center(stepBoundsF, finalScaleX, finalScaleY);
1766         // Use a pixel bounds that's smaller than what was requested to ensure any post-blur amount
1767         // is lower than the max supported. In the event that roundIn() would collapse to an empty
1768         // rect, use a 1x1 bounds that contains the center point.
1769         PixelSpace<SkIRect> innerDstPixels = dstBoundsF.roundIn();
1770         int dstCenterX = sk_float_floor2int(0.5f * dstBoundsF.right()  + 0.5f * dstBoundsF.left());
1771         int dstCenterY = sk_float_floor2int(0.5f * dstBoundsF.bottom() + 0.5f * dstBoundsF.top());
1772         dstBoundsF = PixelSpace<SkRect>({(float) std::min(dstCenterX,   innerDstPixels.left()),
1773                                          (float) std::min(dstCenterY,   innerDstPixels.top()),
1774                                          (float) std::max(dstCenterX+1, innerDstPixels.right()),
1775                                          (float) std::max(dstCenterY+1, innerDstPixels.bottom())});
1776 
1777         finalScaleX = dstBoundsF.width() / srcRect.width();
1778         finalScaleY = dstBoundsF.height() / srcRect.height();
1779 
1780         // Recompute how many steps are needed, as we may need to do one more step from the round-in
1781         xSteps = downscale_step_count(finalScaleX);
1782         ySteps = downscale_step_count(finalScaleY);
1783 
1784         // The periodic tiling effect will be manually rendered into the lower resolution image so
1785         // that clamp tiling can be used at each decimation.
1786         image.fTileMode = SkTileMode::kClamp;
1787     }
1788 
1789     do {
1790         float sx = 1.f;
1791         if (xSteps > 0) {
1792             sx = xSteps > 1 ? 0.5f : srcRect.width()*finalScaleX / stepBoundsF.width();
1793             xSteps--;
1794         }
1795 
1796         float sy = 1.f;
1797         if (ySteps > 0) {
1798             sy = ySteps > 1 ? 0.5f : srcRect.height()*finalScaleY / stepBoundsF.height();
1799             ySteps--;
1800         }
1801 
1802         // Downscale relative to the center of the image, which better distributes any sort of
1803         // sampling errors across the image (vs. emphasizing the bottom right edges).
1804         PixelSpace<SkRect> dstBoundsF = scale_about_center(stepBoundsF, sx, sy);
1805 
1806         // NOTE: Rounding out is overly conservative when dstBoundsF has an odd integer width/height
1807         // but with coordinates at 1/2. In this case, we could create a pixel grid that has a
1808         // fractional translation in the final FilterResult but that will best be done when
1809         // FilterResult tracks floating bounds.
1810         PixelSpace<SkIRect> dstPixelBounds = dstBoundsF.roundOut();
1811 
1812         PixelBoundary boundary = PixelBoundary::kUnknown;
1813         PixelSpace<SkIRect> sampleBounds = dstPixelBounds;
1814         if (tileMode == SkTileMode::kDecal) {
1815             boundary = PixelBoundary::kTransparent;
1816         } else {
1817             // This is roughly equivalent to using PixelBoundary::kInitialized, but keeps some of
1818             // the later logic simpler.
1819             dstPixelBounds.outset(LayerSpace<SkISize>({1,1}));
1820         }
1821 
1822         AutoSurface surface{ctx, dstPixelBounds, boundary, /*renderInParameterSpace=*/false};
1823         if (surface) {
1824             const auto scaleXform = PixelSpace<SkMatrix>::RectToRect(stepBoundsF, dstBoundsF);
1825 
1826             // Redo analysis with the actual scale transform and padded low res bounds.
1827             // With the padding added to dstPixelBounds, intermediate steps should not require
1828             // shader tiling. Unfortunately, when the last step requires a scale factor other than
1829             // 1/2, shader based clamping may still be necessary with just a single pixel of padding
1830             // TODO: Given that the final step may often require shader-based tiling, it may make
1831             // sense to tile into a large enough texture that the subsequent blurs will not require
1832             // any shader-based tiling.
1833             analysis = image.analyzeBounds(SkMatrix(scaleXform),
1834                                            SkIRect(sampleBounds),
1835                                            BoundsScope::kRescale);
1836 
1837             // Primary fill that will cover all of 'sampleBounds'
1838             SkPaint paint;
1839             paint.setShader(image.getAnalyzedShaderView(ctx, image.sampling(), analysis));
1840 #if !defined(SK_USE_SRCOVER_FOR_FILTERS)
1841             paint.setBlendMode(SkBlendMode::kSrc);
1842 #endif
1843 
1844             PixelSpace<SkRect> srcSampled;
1845             SkAssertResult(scaleXform.inverseMapRect(PixelSpace<SkRect>(sampleBounds),
1846                                                      &srcSampled));
1847 
1848             surface->save();
1849                 surface->concat(SkMatrix(scaleXform));
1850                 surface->drawRect(SkRect(srcSampled), paint);
1851             surface->restore();
1852 
1853             if (cfBorder) {
1854                 // Fill in the border with the transparency-affecting color filter, which is
1855                 // what the image shader's tile mode would have produced anyways but this avoids
1856                 // triggering shader-based tiling.
1857                 SkASSERT(fColorFilter && as_CFB(fColorFilter)->affectsTransparentBlack());
1858                 SkASSERT(tileMode == SkTileMode::kClamp);
1859 
1860                 draw_color_filtered_border(surface.canvas(), dstPixelBounds, fColorFilter);
1861                 // Clamping logic will preserve its values on subsequent rescale steps.
1862                 cfBorder = false;
1863             } else if (tileMode != SkTileMode::kDecal) {
1864                 // Draw the edges of the shader into the padded border, respecting the tile mode
1865                 draw_tiled_border(surface.canvas(), tileMode, paint, scaleXform,
1866                                   stepPixelBounds, PixelSpace<SkRect>(dstPixelBounds));
1867             }
1868         } else {
1869             // Rescaling can't complete, no sense in downscaling non-existent data
1870             return {};
1871         }
1872 
1873         image = surface.snap();
1874         // If we are deferring periodic tiling, use kClamp on subsequent steps to preserve the
1875         // border pixels. The original tile mode will be restored at the end.
1876         image.fTileMode = deferPeriodicTiling ? SkTileMode::kClamp : tileMode;
1877 
1878         stepBoundsF = dstBoundsF;
1879         stepPixelBounds = PixelSpace<SkRect>(dstPixelBounds);
1880     } while(xSteps > 0 || ySteps > 0);
1881 
1882 
1883     // Rebuild the downscaled image, including a transform back to the original layer-space
1884     // resolution, restoring the layer bounds it should fill, and setting tile mode.
1885     if (deferPeriodicTiling) {
1886         // Inset the image to undo the manually added border of pixels, which will allow the result
1887         // to have the kInitialized boundary state.
1888         image = image.insetByPixel();
1889     } else {
1890         SkASSERT(tileMode == SkTileMode::kDecal || tileMode == SkTileMode::kClamp);
1891         // Leave the image as-is. If it's decal tiled, this preserves the known transparent
1892         // boundary. If it's clamp tiled, we want to clamp to the carefully maintained boundary
1893         // pixels that better preserved the original boundary. Taking a subset like we did for
1894         // periodic tiles would effectively clamp to the interior of the image.
1895     }
1896     image.fTileMode = tileMode;
1897     image.fTransform.postConcat(
1898             LayerSpace<SkMatrix>::RectToRect(stepBoundsF, LayerSpace<SkRect>{srcRect}));
1899     image.fLayerBounds = visibleLayerBounds;
1900 
1901     SkASSERT(!enforceDecal || image.fTileMode == SkTileMode::kDecal);
1902     SkASSERT(image.fTileMode != SkTileMode::kDecal ||
1903              image.fBoundary == PixelBoundary::kTransparent);
1904     SkASSERT(!deferPeriodicTiling || image.fBoundary == PixelBoundary::kInitialized);
1905     return image;
1906 }
1907 
MakeFromPicture(const Context & ctx,sk_sp<SkPicture> pic,ParameterSpace<SkRect> cullRect)1908 FilterResult FilterResult::MakeFromPicture(const Context& ctx,
1909                                            sk_sp<SkPicture> pic,
1910                                            ParameterSpace<SkRect> cullRect) {
1911     SkASSERT(pic);
1912     LayerSpace<SkIRect> dstBounds = ctx.mapping().paramToLayer(cullRect).roundOut();
1913     if (!dstBounds.intersect(ctx.desiredOutput())) {
1914         return {};
1915     }
1916 
1917     // Given the standard usage of the picture image filter (i.e., to render content at a fixed
1918     // resolution that, most likely, differs from the screen's) disable LCD text by removing any
1919     // knowledge of the pixel geometry.
1920     // TODO: Should we just generally do this for layers with image filters? Or can we preserve it
1921     // for layers that are still axis-aligned?
1922     SkSurfaceProps props = ctx.backend()->surfaceProps()
1923                                          .cloneWithPixelGeometry(kUnknown_SkPixelGeometry);
1924     // TODO(b/329700315): The SkPicture may contain dithered content, which would be affected by any
1925     // boundary padding. Until we can control the dither origin, force it to have no padding.
1926     AutoSurface surface{ctx, dstBounds, PixelBoundary::kUnknown,
1927                         /*renderInParameterSpace=*/true, &props};
1928     if (surface) {
1929         surface->clipRect(SkRect(cullRect));
1930         surface->drawPicture(std::move(pic));
1931     }
1932     return surface.snap();
1933 }
1934 
MakeFromShader(const Context & ctx,sk_sp<SkShader> shader,bool dither)1935 FilterResult FilterResult::MakeFromShader(const Context& ctx,
1936                                           sk_sp<SkShader> shader,
1937                                           bool dither) {
1938     SkASSERT(shader);
1939 
1940     // TODO(b/329700315): Using a boundary other than unknown shifts the origin of dithering, which
1941     // complicates layout test validation in chrome. Until we can control the dither origin,
1942     // force dithered shader FilterResults to have no padding.
1943     PixelBoundary boundary = dither ? PixelBoundary::kUnknown : PixelBoundary::kTransparent;
1944     AutoSurface surface{ctx, ctx.desiredOutput(), boundary, /*renderInParameterSpace=*/true};
1945     if (surface) {
1946         SkPaint paint;
1947         paint.setShader(shader);
1948         paint.setDither(dither);
1949 #if !defined(SK_USE_SRCOVER_FOR_FILTERS)
1950         paint.setBlendMode(SkBlendMode::kSrc);
1951 #endif
1952         surface->drawPaint(paint);
1953     }
1954     return surface.snap();
1955 }
1956 
MakeFromImage(const Context & ctx,sk_sp<SkImage> image,SkRect srcRect,ParameterSpace<SkRect> dstRect,const SkSamplingOptions & sampling)1957 FilterResult FilterResult::MakeFromImage(const Context& ctx,
1958                                          sk_sp<SkImage> image,
1959                                          SkRect srcRect,
1960                                          ParameterSpace<SkRect> dstRect,
1961                                          const SkSamplingOptions& sampling) {
1962     SkASSERT(image);
1963 
1964     SkRect imageBounds = SkRect::Make(image->dimensions());
1965     if (!imageBounds.contains(srcRect)) {
1966         SkMatrix srcToDst = SkMatrix::RectToRect(srcRect, SkRect(dstRect));
1967         if (!srcRect.intersect(imageBounds)) {
1968             return {}; // No overlap, so return an empty/transparent image
1969         }
1970         // Adjust dstRect to match the updated srcRect
1971         dstRect = ParameterSpace<SkRect>{srcToDst.mapRect(srcRect)};
1972     }
1973 
1974     if (SkRect(dstRect).isEmpty()) {
1975         return {}; // Output collapses to empty
1976     }
1977 
1978     // Check for direct conversion to an SkSpecialImage and then FilterResult. Eventually this
1979     // whole function should be replaceable with:
1980     //    FilterResult(fImage, fSrcRect, fDstRect).applyTransform(mapping.layerMatrix(), fSampling);
1981     SkIRect srcSubset = RoundOut(srcRect);
1982     if (SkRect::Make(srcSubset) == srcRect) {
1983         // Construct an SkSpecialImage from the subset directly instead of drawing.
1984         sk_sp<SkSpecialImage> specialImage = ctx.backend()->makeImage(srcSubset, std::move(image));
1985 
1986         // Treat the srcRect's top left as "layer" space since we are folding the src->dst transform
1987         // and the param->layer transform into a single transform step. We don't override the
1988         // PixelBoundary from kUnknown even if srcRect is contained within the 'image' because the
1989         // client could be doing their own external approximate-fit texturing.
1990         skif::FilterResult subset{std::move(specialImage),
1991                                   skif::LayerSpace<SkIPoint>(srcSubset.topLeft())};
1992         SkM44 transform = ctx.mapping().layerMatrix() * SkM44::RectToRect(srcRect, SkRect(dstRect));
1993         return subset.applyTransform(ctx, skif::LayerSpace<SkMatrix>(transform.asM33()), sampling);
1994     }
1995 
1996     // For now, draw the src->dst subset of image into a new image.
1997     LayerSpace<SkIRect> dstBounds = ctx.mapping().paramToLayer(dstRect).roundOut();
1998     if (!dstBounds.intersect(ctx.desiredOutput())) {
1999         return {};
2000     }
2001 
2002     AutoSurface surface{ctx, dstBounds, PixelBoundary::kTransparent,
2003                         /*renderInParameterSpace=*/true};
2004     if (surface) {
2005         SkPaint paint;
2006         paint.setAntiAlias(true);
2007         surface->drawImageRect(std::move(image), srcRect, SkRect(dstRect), sampling, &paint,
2008                                SkCanvas::kStrict_SrcRectConstraint);
2009     }
2010     return surface.snap();
2011 }
2012 
2013 ///////////////////////////////////////////////////////////////////////////////////////////////////
2014 // FilterResult::Builder
2015 
Builder(const Context & context)2016 FilterResult::Builder::Builder(const Context& context) : fContext(context) {}
2017 FilterResult::Builder::~Builder() = default;
2018 
createInputShaders(const LayerSpace<SkIRect> & outputBounds,bool evaluateInParameterSpace)2019 SkSpan<sk_sp<SkShader>> FilterResult::Builder::createInputShaders(
2020         const LayerSpace<SkIRect>& outputBounds,
2021         bool evaluateInParameterSpace) {
2022     SkEnumBitMask<ShaderFlags> xtraFlags = ShaderFlags::kNone;
2023     SkMatrix layerToParam;
2024     if (evaluateInParameterSpace) {
2025         // The FilterResult is meant to be sampled in layer space, but the shader this is feeding
2026         // into is being sampled in parameter space. Add the inverse of the layerMatrix() (i.e.
2027         // layer to parameter space) as a local matrix to convert from the parameter-space coords
2028         // of the outer shader to the layer-space coords of the FilterResult).
2029         SkAssertResult(fContext.mapping().layerMatrix().asM33().invert(&layerToParam));
2030         // Automatically add nonTrivial sampling if the layer-to-parameter space mapping isn't
2031         // also pixel aligned.
2032         if (!is_nearly_integer_translation(LayerSpace<SkMatrix>(layerToParam))) {
2033             xtraFlags |= ShaderFlags::kNonTrivialSampling;
2034         }
2035     }
2036 
2037     fInputShaders.reserve(fInputs.size());
2038     for (const SampledFilterResult& input : fInputs) {
2039         // Assume the input shader will be evaluated once per pixel in the output unless otherwise
2040         // specified when the FilterResult was added to the builder.
2041         auto sampleBounds = input.fSampleBounds ? *input.fSampleBounds : outputBounds;
2042         auto shader = input.fImage.asShader(fContext,
2043                                             input.fSampling,
2044                                             input.fFlags | xtraFlags,
2045                                             sampleBounds);
2046         if (evaluateInParameterSpace && shader) {
2047             shader = shader->makeWithLocalMatrix(layerToParam);
2048         }
2049         fInputShaders.push_back(std::move(shader));
2050     }
2051     return SkSpan<sk_sp<SkShader>>(fInputShaders);
2052 }
2053 
outputBounds(std::optional<LayerSpace<SkIRect>> explicitOutput) const2054 LayerSpace<SkIRect> FilterResult::Builder::outputBounds(
2055         std::optional<LayerSpace<SkIRect>> explicitOutput) const {
2056     // Pessimistically assume output fills the full desired bounds
2057     LayerSpace<SkIRect> output = fContext.desiredOutput();
2058     if (explicitOutput.has_value()) {
2059         // Intersect with the provided explicit bounds
2060         if (!output.intersect(*explicitOutput)) {
2061             return LayerSpace<SkIRect>::Empty();
2062         }
2063     }
2064     return output;
2065 }
2066 
drawShader(sk_sp<SkShader> shader,const LayerSpace<SkIRect> & outputBounds,bool evaluateInParameterSpace) const2067 FilterResult FilterResult::Builder::drawShader(sk_sp<SkShader> shader,
2068                                                const LayerSpace<SkIRect>& outputBounds,
2069                                                bool evaluateInParameterSpace) const {
2070     SkASSERT(!outputBounds.isEmpty()); // Should have been rejected before we created shaders
2071     if (!shader) {
2072         return {};
2073     }
2074 
2075     AutoSurface surface{fContext, outputBounds, PixelBoundary::kTransparent,
2076                         evaluateInParameterSpace};
2077     if (surface) {
2078         SkPaint paint;
2079         paint.setShader(std::move(shader));
2080 #if !defined(SK_USE_SRCOVER_FOR_FILTERS)
2081         paint.setBlendMode(SkBlendMode::kSrc);
2082 #endif
2083         surface->drawPaint(paint);
2084     }
2085     return surface.snap();
2086 }
2087 
merge()2088 FilterResult FilterResult::Builder::merge() {
2089     // merge() could return an empty image on 0 added inputs, but this should have been caught
2090     // earlier and routed to SkImageFilters::Empty() instead.
2091     SkASSERT(!fInputs.empty());
2092     if (fInputs.size() == 1) {
2093         SkASSERT(!fInputs[0].fSampleBounds.has_value() &&
2094                  fInputs[0].fSampling == kDefaultSampling &&
2095                  fInputs[0].fFlags == ShaderFlags::kNone);
2096         return fInputs[0].fImage;
2097     }
2098 
2099     const auto mergedBounds = LayerSpace<SkIRect>::Union(
2100             (int) fInputs.size(),
2101             [this](int i) { return fInputs[i].fImage.layerBounds(); });
2102     const auto outputBounds = this->outputBounds(mergedBounds);
2103 
2104     AutoSurface surface{fContext, outputBounds, PixelBoundary::kTransparent,
2105                         /*renderInParameterSpace=*/false};
2106     if (surface) {
2107         for (const SampledFilterResult& input : fInputs) {
2108             SkASSERT(!input.fSampleBounds.has_value() &&
2109                      input.fSampling == kDefaultSampling &&
2110                      input.fFlags == ShaderFlags::kNone);
2111             input.fImage.draw(fContext, surface.device(), /*preserveDeviceState=*/true);
2112         }
2113     }
2114     return surface.snap();
2115 }
2116 
blur(const LayerSpace<SkSize> & sigma)2117 FilterResult FilterResult::Builder::blur(const LayerSpace<SkSize>& sigma) {
2118     SkASSERT(fInputs.size() == 1);
2119 
2120     // TODO: The blur functor is only supported for GPU contexts; SkBlurImageFilter should have
2121     // detected this.
2122     const SkBlurEngine* blurEngine = fContext.backend()->getBlurEngine();
2123     SkASSERT(blurEngine);
2124 
2125     const SkBlurEngine::Algorithm* algorithm = blurEngine->findAlgorithm(
2126             SkSize(sigma), fContext.backend()->colorType());
2127     if (!algorithm) {
2128         return {};
2129     }
2130 
2131     // TODO: De-duplicate this logic between SkBlurImageFilter, here, and skgpu::BlurUtils.
2132     LayerSpace<SkISize> radii =
2133             LayerSpace<SkSize>({3.f*sigma.width(), 3.f*sigma.height()}).ceil();
2134     auto maxOutput = fInputs[0].fImage.layerBounds();
2135     maxOutput.outset(radii);
2136 
2137     auto outputBounds = this->outputBounds(maxOutput);
2138     if (outputBounds.isEmpty()) {
2139         return {};
2140     }
2141 
2142     // These are the source pixels that will be read from the input image, which can be calculated
2143     // internally because the blur's access pattern is well defined (vs. needing it to be provided
2144     // in Builder::add()).
2145     auto sampleBounds = outputBounds;
2146     sampleBounds.outset(radii);
2147 
2148     if (fContext.backend()->useLegacyFilterResultBlur()) {
2149         SkASSERT(sigma.width() <= algorithm->maxSigma() && sigma.height() <= algorithm->maxSigma());
2150 
2151         FilterResult resolved = fInputs[0].fImage.resolve(fContext, sampleBounds);
2152         if (!resolved) {
2153             return {};
2154         }
2155         auto srcRelativeOutput = outputBounds;
2156         srcRelativeOutput.offset(-resolved.layerBounds().topLeft());
2157         resolved = {algorithm->blur(SkSize(sigma),
2158                                     resolved.fImage,
2159                                     SkIRect::MakeSize(resolved.fImage->dimensions()),
2160                                     SkTileMode::kDecal,
2161                                     SkIRect(srcRelativeOutput)),
2162                     outputBounds.topLeft()};
2163         return resolved;
2164     }
2165 
2166     float sx = sigma.width()  > algorithm->maxSigma() ? algorithm->maxSigma()/sigma.width()  : 1.f;
2167     float sy = sigma.height() > algorithm->maxSigma() ? algorithm->maxSigma()/sigma.height() : 1.f;
2168     // For identity scale factors, this rescale() is a no-op when possible, but otherwise it will
2169     // also handle resolving any color filters or transform similar to a resolve() except that it
2170     // can defer the tile mode.
2171     FilterResult lowResImage = fInputs[0].fImage.rescale(
2172             fContext.withNewDesiredOutput(sampleBounds),
2173             LayerSpace<SkSize>({sx, sy}),
2174             algorithm->supportsOnlyDecalTiling());
2175     if (!lowResImage) {
2176         return {};
2177     }
2178     SkASSERT(lowResImage.tileMode() == SkTileMode::kDecal ||
2179              !algorithm->supportsOnlyDecalTiling());
2180 
2181     // Map 'sigma' into the low-res image's pixel space to determine the low-res blur params to pass
2182     // into the blur engine. This relies on rescale() producing an image with a scale+translate
2183     // transform, so it's possible to derive the inverse scale factors directly. We also clamp to
2184     // be <= maxSigma just in case floating point error made it slightly higher.
2185     const float invScaleX = sk_ieee_float_divide(1.f, lowResImage.fTransform.rc(0,0));
2186     const float invScaleY = sk_ieee_float_divide(1.f, lowResImage.fTransform.rc(1,1));
2187     PixelSpace<SkSize> lowResSigma{{std::min(sigma.width() * invScaleX, algorithm->maxSigma()),
2188                                     std::min(sigma.height()* invScaleY, algorithm->maxSigma())}};
2189     PixelSpace<SkIRect> lowResMaxOutput{SkISize{lowResImage.fImage->width(),
2190                                                 lowResImage.fImage->height()}};
2191 
2192     PixelSpace<SkIRect> srcRelativeOutput;
2193     if (lowResImage.tileMode() == SkTileMode::kRepeat ||
2194         lowResImage.tileMode() == SkTileMode::kMirror) {
2195         // The periodic tiling was deferred when down-sampling; we can further defer it to after the
2196         // blur. The low-res output is 1-to-1 with the low res image.
2197         srcRelativeOutput = lowResMaxOutput;
2198     } else {
2199         // For decal and clamp tiling, the blurred image stops being interesting outside the radii
2200         // outset, so redo the max output analysis with the 'outputBounds' mapped into pixel space.
2201         SkAssertResult(lowResImage.fTransform.inverseMapRect(outputBounds, &srcRelativeOutput));
2202 
2203         // NOTE: Since 'lowResMaxOutput' is based on the actual image and deferred tiling, this can
2204         // be smaller than the pessimistic filling for a clamp-tiled blur.
2205         lowResMaxOutput.outset(PixelSpace<SkSize>({3.f * lowResSigma.width(),
2206                                                    3.f * lowResSigma.height()}).ceil());
2207         srcRelativeOutput = lowResMaxOutput.relevantSubset(srcRelativeOutput,
2208                                                            lowResImage.tileMode());
2209 
2210         // Clamp won't return empty from relevantSubset() and a non-intersecting decal should have
2211         // been caught earlier.
2212         // TODO(40042624): However, with some pathological inputs and the current mix of float vs.
2213         // int representations, the definition of emptiness can change. Once everything is floating
2214         // point, this check can be removed.
2215         if (srcRelativeOutput.isEmpty()) {
2216             return {};
2217         }
2218 
2219         // Include 1px of blur output so that it can be sampled during the upscale, which is needed
2220         // to correctly seam large blurs across crop/raster tiles (crbug.com/1500021).
2221         srcRelativeOutput.outset(PixelSpace<SkISize>({1, 1}));
2222     }
2223 
2224     sk_sp<SkSpecialImage> lowResBlur = lowResImage.refImage();
2225     SkIRect blurOutputBounds = SkIRect(srcRelativeOutput);
2226     SkTileMode tileMode = lowResImage.tileMode();
2227     if (!algorithm->supportsOnlyDecalTiling() &&
2228         lowResImage.canClampToTransparentBoundary(BoundsAnalysis::kSimple)) {
2229         // Have to manage this manually since the BlurEngine isn't aware of the known pixel padding.
2230         lowResBlur = lowResBlur->makePixelOutset();
2231         // This offset() is intentional; `blurOutputBounds` already includes an outset from an
2232         // earlier modification of `srcRelativeOutput`. This offset is to align the SkBlurAlgorithm
2233         // output bounds with the adjusted source image.
2234         blurOutputBounds.offset(1, 1);
2235         tileMode = SkTileMode::kClamp;
2236     }
2237 
2238     lowResBlur = algorithm->blur(SkSize(lowResSigma),
2239                                  lowResBlur,
2240                                  SkIRect::MakeSize(lowResBlur->dimensions()),
2241                                  tileMode,
2242                                  blurOutputBounds);
2243     if (!lowResBlur) {
2244         // The blur output bounds may exceed max texture size even if the source image did not.
2245         // TODO(b/377932106): Can we handle this more gracefully by rendering a smaller image and
2246         // then transforming it to fill the large space?
2247         return {};
2248     }
2249 
2250     FilterResult result{std::move(lowResBlur), srcRelativeOutput.topLeft()};
2251     if (lowResImage.tileMode() == SkTileMode::kClamp ||
2252         lowResImage.tileMode() == SkTileMode::kDecal) {
2253         // Undo the outset padding that was added to srcRelativeOutput before invoking the blur
2254         result = result.insetByPixel();
2255     }
2256 
2257     result.fTransform.postConcat(lowResImage.fTransform);
2258     if (lowResImage.tileMode() == SkTileMode::kDecal) {
2259         // Recalculate the output bounds based on the blur output; with rounding the final image may
2260         // be slightly larger than the original, which would unnecessarily add cropping to the layer
2261         // bounds. But so long as the `outputBounds` had been constrained by the input's own layer,
2262         // that crop is unnecessary. The result is still restricted to the desired output bounds,
2263         // which will induce clipping as needed for a rounded-out image.
2264         outputBounds = this->outputBounds(
2265                 result.fTransform.mapRect(LayerSpace<SkIRect>(result.fImage->dimensions())));
2266     }
2267     result.fLayerBounds = outputBounds;
2268     result.fTileMode = lowResImage.tileMode();
2269     return result;
2270 }
2271 
2272 } // end namespace skif
2273