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