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