• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 Google LLC
3  *
4  * Use of this source code is governed by a BSD-style license that can be
5  * found in the LICENSE file.
6  */
7 
8 #ifndef SkImageFilterTypes_DEFINED
9 #define SkImageFilterTypes_DEFINED
10 
11 #include "include/core/SkColorFilter.h"
12 #include "include/core/SkColorSpace.h"
13 #include "include/core/SkM44.h"
14 #include "include/core/SkMatrix.h"
15 #include "include/core/SkPoint.h"
16 #include "include/core/SkRect.h"
17 #include "include/core/SkRefCnt.h"
18 #include "include/core/SkSamplingOptions.h"
19 #include "include/core/SkScalar.h"
20 #include "include/core/SkSize.h"
21 #include "include/core/SkSpan.h"
22 #include "include/core/SkSurfaceProps.h"
23 #include "include/core/SkTileMode.h"
24 #include "include/core/SkTypes.h"
25 #include "include/private/base/SkFloatingPoint.h"
26 #include "include/private/base/SkTArray.h"
27 #include "include/private/base/SkTPin.h"
28 #include "include/private/base/SkTo.h"
29 #include "src/base/SkEnumBitMask.h"
30 #include "src/core/SkSpecialImage.h"
31 
32 #include <cstdint>
33 #include <optional>
34 #include <utility>
35 
36 class FilterResultTestAccess;  // for testing
37 class SkBitmap;
38 class SkBlender;
39 class SkBlurEngine;
40 class SkDevice;
41 class SkImage;
42 class SkImageFilter;
43 class SkImageFilterCache;
44 class SkPicture;
45 class SkShader;
46 enum SkColorType : int;
47 
48 // The skif (SKI[mage]F[ilter]) namespace contains types that are used for filter implementations.
49 // The defined types come in two groups: users of internal Skia types, and templates to help with
50 // readability. Image filters cannot be implemented without access to key internal types, such as
51 // SkSpecialImage. It is possible to avoid the use of the readability templates, although they are
52 // strongly encouraged.
53 namespace skif {
54 
55 // Rounds in/out but with a tolerance.
56 SkIRect RoundOut(SkRect);
57 SkIRect RoundIn(SkRect);
58 
59 // skif::IVector and skif::Vector represent plain-old-data types for storing direction vectors, so
60 // that the coordinate-space templating system defined below can have a separate type id for
61 // directions vs. points, and specialize appropriately. As such, all operations with direction
62 // vectors are defined on the LayerSpace specialization, since that is the intended point of use.
63 struct IVector {
64     int32_t fX;
65     int32_t fY;
66 
67     IVector() = default;
IVectorIVector68     IVector(int32_t x, int32_t y) : fX(x), fY(y) {}
IVectorIVector69     explicit IVector(const SkIVector& v) : fX(v.fX), fY(v.fY) {}
70 };
71 
72 struct Vector {
73     SkScalar fX;
74     SkScalar fY;
75 
76     Vector() = default;
VectorVector77     Vector(SkScalar x, SkScalar y) : fX(x), fY(y) {}
VectorVector78     explicit Vector(const SkVector& v) : fX(v.fX), fY(v.fY) {}
79 
isFiniteVector80     bool isFinite() const { return SkIsFinite(fX, fY); }
81 };
82 
83 ///////////////////////////////////////////////////////////////////////////////////////////////////
84 // Coordinate Space Tagging
85 // - In order to enforce correct coordinate spaces in image filter implementations and use,
86 //   geometry is wrapped by templated structs to declare in the type system what coordinate space
87 //   the coordinates are defined in.
88 // - Currently there is ParameterSpace and DeviceSpace that are data-only wrappers around
89 //   coordinates, and the primary LayerSpace that provides all operative functionality for image
90 //   filters. It is intended that all logic about image bounds and access be conducted in the shared
91 //   layer space.
92 // - The LayerSpace struct has type-safe specializations for SkIRect, SkRect, SkIPoint, SkPoint,
93 //   skif::IVector (to distinguish SkIVector from SkIPoint), skif::Vector, SkISize, and SkSize.
94 // - A Mapping object provides type safe coordinate conversions between these spaces, and
95 //   automatically does the "right thing" for each geometric type.
96 ///////////////////////////////////////////////////////////////////////////////////////////////////
97 
98 // ParameterSpace is a data-only wrapper around Skia's geometric types such as SkIPoint, and SkRect.
99 // Parameter space is the same as the local coordinate space of an SkShader, or the coordinates
100 // passed into SkCanvas::drawX calls, but "local" is avoided due to the alliteration with layer
101 // space. SkImageFilters are defined in terms of ParameterSpace<T> geometry and must use the Mapping
102 // on Context to transform the parameters into LayerSpace to evaluate the filter in the shared
103 // coordinate space of the entire filter DAG.
104 //
105 // A value of ParameterSpace<SkIRect> implies that its wrapped SkIRect is defined in the local
106 // parameter space.
107 template<typename T>
108 class ParameterSpace {
109 public:
110     ParameterSpace() = default;
ParameterSpace(const T & data)111     explicit ParameterSpace(const T& data) : fData(data) {}
ParameterSpace(T && data)112     explicit ParameterSpace(T&& data) : fData(std::move(data)) {}
113 
114     explicit operator const T&() const { return fData; }
115 
116 private:
117     T fData;
118 };
119 
120 // DeviceSpace is a data-only wrapper around Skia's geometric types. It is similar to
121 // 'ParameterSpace' except that it is used to represent geometry that has been transformed or
122 // defined in the root device space (i.e. the final pixels of drawn content). Much of what SkCanvas
123 // tracks, such as its clip bounds are defined in this space and DeviceSpace provides a
124 // type-enforced mechanism for the canvas to pass that information into the image filtering system,
125 // using the Mapping of the filtering context.
126 template<typename T>
127 class DeviceSpace {
128 public:
129     DeviceSpace() = default;
DeviceSpace(const T & data)130     explicit DeviceSpace(const T& data) : fData(data) {}
DeviceSpace(T && data)131     explicit DeviceSpace(T&& data) : fData(std::move(data)) {}
132 
133     explicit operator const T&() const { return fData; }
134 
135 private:
136     T fData;
137 };
138 
139 // LayerSpace is a geometric wrapper that specifies the geometry is defined in the shared layer
140 // space where image filters are evaluated. For a given Context (and its Mapping), the image filter
141 // DAG operates in the same coordinate space. This space may be different from the local coordinate
142 // space that defined the image filter parameters (such as blur sigma), and it may be different
143 // from the total CTM of the SkCanvas.
144 //
145 // To encourage correct filter use and implementation, the bulk of filter logic should be performed
146 // in layer space (e.g. determining what portion of an input image to read, or what the output
147 // region is). LayerSpace specializations for the six common Skia math types (Sk[I]Rect, Sk[I]Point,
148 // and Sk[I]Size), and skif::[I]Vector (to allow vectors to be specialized separately from points))
149 // are provided that mimic their APIs but preserve the coordinate space and enforce type semantics.
150 template<typename T>
151 class LayerSpace {};
152 
153 // Layer-space specialization for integerized direction vectors.
154 template<>
155 class LayerSpace<IVector> {
156 public:
157     LayerSpace() = default;
LayerSpace(const IVector & geometry)158     explicit LayerSpace(const IVector& geometry) : fData(geometry) {}
LayerSpace(IVector && geometry)159     explicit LayerSpace(IVector&& geometry) : fData(std::move(geometry)) {}
160     explicit operator const IVector&() const { return fData; }
161 
SkIVector()162     explicit operator SkIVector() const { return SkIVector::Make(fData.fX, fData.fY); }
163 
x()164     int32_t x() const { return fData.fX; }
y()165     int32_t y() const { return fData.fY; }
166 
167     LayerSpace<IVector> operator-() const { return LayerSpace<IVector>({-fData.fX, -fData.fY}); }
168 
169     LayerSpace<IVector> operator+(const LayerSpace<IVector>& v) const {
170         LayerSpace<IVector> sum = *this;
171         sum += v;
172         return sum;
173     }
174     LayerSpace<IVector> operator-(const LayerSpace<IVector>& v) const {
175         LayerSpace<IVector> diff = *this;
176         diff -= v;
177         return diff;
178     }
179 
180     void operator+=(const LayerSpace<IVector>& v) {
181         fData.fX += v.fData.fX;
182         fData.fY += v.fData.fY;
183     }
184     void operator-=(const LayerSpace<IVector>& v) {
185         fData.fX -= v.fData.fX;
186         fData.fY -= v.fData.fY;
187     }
188 
189 private:
190     IVector fData;
191 };
192 
193 // Layer-space specialization for floating point direction vectors.
194 template<>
195 class LayerSpace<Vector> {
196 public:
197     LayerSpace() = default;
LayerSpace(const Vector & geometry)198     explicit LayerSpace(const Vector& geometry) : fData(geometry) {}
LayerSpace(Vector && geometry)199     explicit LayerSpace(Vector&& geometry) : fData(std::move(geometry)) {}
200     explicit operator const Vector&() const { return fData; }
201 
SkVector()202     explicit operator SkVector() const { return SkVector::Make(fData.fX, fData.fY); }
203 
x()204     SkScalar x() const { return fData.fX; }
y()205     SkScalar y() const { return fData.fY; }
206 
length()207     SkScalar length() const { return SkVector::Length(fData.fX, fData.fY); }
208 
209     LayerSpace<Vector> operator-() const { return LayerSpace<Vector>({-fData.fX, -fData.fY}); }
210 
211     LayerSpace<Vector> operator*(SkScalar s) const {
212         LayerSpace<Vector> scaled = *this;
213         scaled *= s;
214         return scaled;
215     }
216 
217     LayerSpace<Vector> operator+(const LayerSpace<Vector>& v) const {
218         LayerSpace<Vector> sum = *this;
219         sum += v;
220         return sum;
221     }
222     LayerSpace<Vector> operator-(const LayerSpace<Vector>& v) const {
223         LayerSpace<Vector> diff = *this;
224         diff -= v;
225         return diff;
226     }
227 
228     void operator*=(SkScalar s) {
229         fData.fX *= s;
230         fData.fY *= s;
231     }
232     void operator+=(const LayerSpace<Vector>& v) {
233         fData.fX += v.fData.fX;
234         fData.fY += v.fData.fY;
235     }
236     void operator-=(const LayerSpace<Vector>& v) {
237         fData.fX -= v.fData.fX;
238         fData.fY -= v.fData.fY;
239     }
240 
241     friend LayerSpace<Vector> operator*(SkScalar s, const LayerSpace<Vector>& b) {
242         return b * s;
243     }
244 
245 private:
246     Vector fData;
247 };
248 
249 // Layer-space specialization for integer 2D coordinates (treated as positions, not directions).
250 template<>
251 class LayerSpace<SkIPoint> {
252 public:
253     LayerSpace() = default;
LayerSpace(const SkIPoint & geometry)254     explicit LayerSpace(const SkIPoint& geometry)  : fData(geometry) {}
LayerSpace(SkIPoint && geometry)255     explicit LayerSpace(SkIPoint&& geometry) : fData(std::move(geometry)) {}
256     explicit operator const SkIPoint&() const { return fData; }
257 
258     // Parrot the SkIPoint API while preserving coordinate space.
x()259     int32_t x() const { return fData.fX; }
y()260     int32_t y() const { return fData.fY; }
261 
262     // Offsetting by direction vectors produce more points
263     LayerSpace<SkIPoint> operator+(const LayerSpace<IVector>& v) {
264         return LayerSpace<SkIPoint>(fData + SkIVector(v));
265     }
266     LayerSpace<SkIPoint> operator-(const LayerSpace<IVector>& v) {
267         return LayerSpace<SkIPoint>(fData - SkIVector(v));
268     }
269 
270     void operator+=(const LayerSpace<IVector>& v) {
271         fData += SkIVector(v);
272     }
273     void operator-=(const LayerSpace<IVector>& v) {
274         fData -= SkIVector(v);
275     }
276 
277     // Subtracting another point makes a direction between them
278     LayerSpace<IVector> operator-(const LayerSpace<SkIPoint>& p) {
279         return LayerSpace<IVector>(IVector(fData - p.fData));
280     }
281 
282     LayerSpace<IVector> operator-() const { return LayerSpace<IVector>({-fData.fX, -fData.fY}); }
283 
284 private:
285     SkIPoint fData;
286 };
287 
288 // Layer-space specialization for floating point 2D coordinates (treated as positions)
289 template<>
290 class LayerSpace<SkPoint> {
291 public:
292     LayerSpace() = default;
LayerSpace(const SkPoint & geometry)293     explicit LayerSpace(const SkPoint& geometry) : fData(geometry) {}
LayerSpace(SkPoint && geometry)294     explicit LayerSpace(SkPoint&& geometry) : fData(std::move(geometry)) {}
295     explicit operator const SkPoint&() const { return fData; }
296 
297     // Parrot the SkPoint API while preserving coordinate space.
x()298     SkScalar x() const { return fData.fX; }
y()299     SkScalar y() const { return fData.fY; }
300 
distanceToOrigin()301     SkScalar distanceToOrigin() const { return fData.distanceToOrigin(); }
302 
303     // Offsetting by direction vectors produce more points
304     LayerSpace<SkPoint> operator+(const LayerSpace<Vector>& v) {
305         return LayerSpace<SkPoint>(fData + SkVector(v));
306     }
307     LayerSpace<SkPoint> operator-(const LayerSpace<Vector>& v) {
308         return LayerSpace<SkPoint>(fData - SkVector(v));
309     }
310 
311     void operator+=(const LayerSpace<Vector>& v) {
312         fData += SkVector(v);
313     }
314     void operator-=(const LayerSpace<Vector>& v) {
315         fData -= SkVector(v);
316     }
317 
318     // Subtracting another point makes a direction between them
319     LayerSpace<Vector> operator-(const LayerSpace<SkPoint>& p) {
320         return LayerSpace<Vector>(Vector(fData - p.fData));
321     }
322 
323     LayerSpace<Vector> operator-() const { return LayerSpace<Vector>({-fData.fX, -fData.fY}); }
324 
325 private:
326     SkPoint fData;
327 };
328 
329 // Layer-space specialization for integer dimensions
330 template<>
331 class LayerSpace<SkISize> {
332 public:
333     LayerSpace() = default;
LayerSpace(const SkISize & geometry)334     explicit LayerSpace(const SkISize& geometry) : fData(geometry) {}
LayerSpace(SkISize && geometry)335     explicit LayerSpace(SkISize&& geometry) : fData(std::move(geometry)) {}
336     explicit operator const SkISize&() const { return fData; }
337 
width()338     int32_t width() const { return fData.width(); }
height()339     int32_t height() const { return fData.height(); }
340 
isEmpty()341     bool isEmpty() const { return fData.isEmpty(); }
342 
343 private:
344     SkISize fData;
345 };
346 
347 // Layer-space specialization for floating point dimensions
348 template<>
349 class LayerSpace<SkSize> {
350 public:
351     LayerSpace() = default;
LayerSpace(const SkSize & geometry)352     explicit LayerSpace(const SkSize& geometry) : fData(geometry) {}
LayerSpace(SkSize && geometry)353     explicit LayerSpace(SkSize&& geometry) : fData(std::move(geometry)) {}
354     explicit operator const SkSize&() const { return fData; }
355 
width()356     SkScalar width() const { return fData.width(); }
height()357     SkScalar height() const { return fData.height(); }
358 
isEmpty()359     bool isEmpty() const { return fData.isEmpty(); }
isZero()360     bool isZero() const { return fData.isZero(); }
361 
362     LayerSpace<SkISize> round() const;
363     LayerSpace<SkISize> ceil() const;
364     LayerSpace<SkISize> floor() const;
365 
366 private:
367     SkSize fData;
368 };
369 
370 // Layer-space specialization for axis-aligned integer bounding boxes.
371 template<>
372 class LayerSpace<SkIRect> {
373 public:
374     LayerSpace() = default;
LayerSpace(const SkIRect & geometry)375     explicit LayerSpace(const SkIRect& geometry) : fData(geometry) {}
LayerSpace(SkIRect && geometry)376     explicit LayerSpace(SkIRect&& geometry) : fData(std::move(geometry)) {}
LayerSpace(const SkISize & size)377     explicit LayerSpace(const SkISize& size) : fData(SkIRect::MakeSize(size)) {}
378     explicit operator const SkIRect&() const { return fData; }
379 
Empty()380     static LayerSpace<SkIRect> Empty() { return LayerSpace<SkIRect>(SkIRect::MakeEmpty()); }
381 
Unbounded()382     static constexpr std::optional<LayerSpace<SkIRect>> Unbounded() { return {}; }
383 
384     // Utility function to iterate a collection of items that can map to LayerSpace<SkIRect> bounds
385     // and returns the union of those bounding boxes. 'boundsFn' will be invoked with i = 0 to
386     // boundsCount-1.
387     template<typename BoundsFn>
Union(int boundsCount,BoundsFn boundsFn)388     static LayerSpace<SkIRect> Union(int boundsCount, BoundsFn boundsFn) {
389         if (boundsCount <= 0) {
390             return LayerSpace<SkIRect>::Empty();
391         }
392         LayerSpace<SkIRect> output = boundsFn(0);
393         for (int i = 1; i < boundsCount; ++i) {
394             output.join(boundsFn(i));
395         }
396         return output;
397     }
398 
399     // Utility function to calculate the smallest relevant subset of this rect to fill `dstRect`
400     // given the provided tile mode.
401     LayerSpace<SkIRect> relevantSubset(const LayerSpace<SkIRect> dstRect, SkTileMode) const;
402 
403     // Parrot the SkIRect API while preserving coord space
isEmpty()404     bool isEmpty() const { return fData.isEmpty64(); }
contains(const LayerSpace<SkIRect> & r)405     bool contains(const LayerSpace<SkIRect>& r) const { return fData.contains(r.fData); }
406 
left()407     int32_t left() const { return fData.fLeft; }
top()408     int32_t top() const { return fData.fTop; }
right()409     int32_t right() const { return fData.fRight; }
bottom()410     int32_t bottom() const { return fData.fBottom; }
411 
width()412     int32_t width() const { return fData.width(); }
height()413     int32_t height() const { return fData.height(); }
414 
topLeft()415     LayerSpace<SkIPoint> topLeft() const { return LayerSpace<SkIPoint>(fData.topLeft()); }
size()416     LayerSpace<SkISize> size() const { return LayerSpace<SkISize>(fData.size()); }
417 
Intersects(const LayerSpace<SkIRect> & a,const LayerSpace<SkIRect> & b)418     static bool Intersects(const LayerSpace<SkIRect>& a, const LayerSpace<SkIRect>& b) {
419         return SkIRect::Intersects(a.fData, b.fData);
420     }
421 
intersect(const LayerSpace<SkIRect> & r)422     bool intersect(const LayerSpace<SkIRect>& r) { return fData.intersect(r.fData); }
join(const LayerSpace<SkIRect> & r)423     void join(const LayerSpace<SkIRect>& r) { fData.join(r.fData); }
offset(const LayerSpace<IVector> & v)424     void offset(const LayerSpace<IVector>& v) { fData.offset(SkIVector(v)); }
outset(const LayerSpace<SkISize> & delta)425     void outset(const LayerSpace<SkISize>& delta) { fData.outset(delta.width(), delta.height()); }
inset(const LayerSpace<SkISize> & delta)426     void inset(const LayerSpace<SkISize>& delta) { fData.inset(delta.width(), delta.height()); }
427 
428 private:
429     SkIRect fData;
430 };
431 
432 // Layer-space specialization for axis-aligned float bounding boxes.
433 template<>
434 class LayerSpace<SkRect> {
435 public:
436     LayerSpace() = default;
LayerSpace(const SkRect & geometry)437     explicit LayerSpace(const SkRect& geometry) : fData(geometry) {}
LayerSpace(SkRect && geometry)438     explicit LayerSpace(SkRect&& geometry) : fData(std::move(geometry)) {}
LayerSpace(const LayerSpace<SkIRect> & rect)439     explicit LayerSpace(const LayerSpace<SkIRect>& rect) : fData(SkRect::Make(SkIRect(rect))) {}
440     explicit operator const SkRect&() const { return fData; }
441 
Empty()442     static LayerSpace<SkRect> Empty() { return LayerSpace<SkRect>(SkRect::MakeEmpty()); }
443 
444     // Parrot the SkRect API while preserving coord space and usage
isEmpty()445     bool isEmpty() const { return fData.isEmpty(); }
contains(const LayerSpace<SkRect> & r)446     bool contains(const LayerSpace<SkRect>& r) const { return fData.contains(r.fData); }
447 
left()448     SkScalar left() const { return fData.fLeft; }
top()449     SkScalar top() const { return fData.fTop; }
right()450     SkScalar right() const { return fData.fRight; }
bottom()451     SkScalar bottom() const { return fData.fBottom; }
452 
width()453     SkScalar width() const { return fData.width(); }
height()454     SkScalar height() const { return fData.height(); }
455 
topLeft()456     LayerSpace<SkPoint> topLeft() const {
457         return LayerSpace<SkPoint>(SkPoint::Make(fData.fLeft, fData.fTop));
458     }
center()459     LayerSpace<SkPoint> center() const {
460         return LayerSpace<SkPoint>(fData.center());
461     }
size()462     LayerSpace<SkSize> size() const {
463         return LayerSpace<SkSize>(SkSize::Make(fData.width(), fData.height()));
464     }
465 
round()466     LayerSpace<SkIRect> round() const { return LayerSpace<SkIRect>(fData.round()); }
roundIn()467     LayerSpace<SkIRect> roundIn() const { return LayerSpace<SkIRect>(RoundIn(fData)); }
roundOut()468     LayerSpace<SkIRect> roundOut() const { return LayerSpace<SkIRect>(RoundOut(fData)); }
469 
intersect(const LayerSpace<SkRect> & r)470     bool intersect(const LayerSpace<SkRect>& r) { return fData.intersect(r.fData); }
join(const LayerSpace<SkRect> & r)471     void join(const LayerSpace<SkRect>& r) { fData.join(r.fData); }
offset(const LayerSpace<Vector> & v)472     void offset(const LayerSpace<Vector>& v) { fData.offset(SkVector(v)); }
outset(const LayerSpace<SkSize> & delta)473     void outset(const LayerSpace<SkSize>& delta) { fData.outset(delta.width(), delta.height()); }
inset(const LayerSpace<SkSize> & delta)474     void inset(const LayerSpace<SkSize>& delta) { fData.inset(delta.width(), delta.height()); }
475 
clamp(LayerSpace<SkPoint> pt)476     LayerSpace<SkPoint> clamp(LayerSpace<SkPoint> pt) const {
477         return LayerSpace<SkPoint>(SkPoint::Make(SkTPin(pt.x(), fData.fLeft, fData.fRight),
478                                                  SkTPin(pt.y(), fData.fTop, fData.fBottom)));
479     }
480 
481 private:
482     SkRect fData;
483 };
484 
485 // A transformation that manipulates geometry in the layer-space coordinate system. Mathematically
486 // there's little difference from these matrices compared to what's stored in a skif::Mapping, but
487 // the intent differs. skif::Mapping's matrices map geometry from one coordinate space to another
488 // while these transforms move geometry w/o changing the coordinate space semantics.
489 // TODO(michaelludwig): Will be replaced with an SkM44 version when skif::Mapping works with SkM44.
490 template<>
491 class LayerSpace<SkMatrix> {
492 public:
493     LayerSpace() = default;
LayerSpace(const SkMatrix & m)494     explicit LayerSpace(const SkMatrix& m) : fData(m) {}
LayerSpace(SkMatrix && m)495     explicit LayerSpace(SkMatrix&& m) : fData(std::move(m)) {}
496     explicit operator const SkMatrix&() const { return fData; }
497 
RectToRect(const LayerSpace<SkRect> & from,const LayerSpace<SkRect> & to)498     static LayerSpace<SkMatrix> RectToRect(const LayerSpace<SkRect>& from,
499                                            const LayerSpace<SkRect>& to) {
500         return LayerSpace<SkMatrix>(SkMatrix::RectToRect(SkRect(from), SkRect(to)));
501     }
502 
503     // Parrot a limited selection of the SkMatrix API while preserving coordinate space.
504     LayerSpace<SkRect> mapRect(const LayerSpace<SkRect>& r) const;
505 
506     // Effectively mapRect(SkRect).roundOut() but more accurate when the underlying matrix or
507     // SkIRect has large floating point values.
508     LayerSpace<SkIRect> mapRect(const LayerSpace<SkIRect>& r) const;
509 
510     LayerSpace<SkPoint> mapPoint(const LayerSpace<SkPoint>& p) const;
511 
512     LayerSpace<Vector> mapVector(const LayerSpace<Vector>& v) const;
513 
514     LayerSpace<SkSize> mapSize(const LayerSpace<SkSize>& s) const;
515 
preConcat(const LayerSpace<SkMatrix> & m)516     LayerSpace<SkMatrix>& preConcat(const LayerSpace<SkMatrix>& m) {
517         fData = SkMatrix::Concat(fData, m.fData);
518         return *this;
519     }
520 
postConcat(const LayerSpace<SkMatrix> & m)521     LayerSpace<SkMatrix>& postConcat(const LayerSpace<SkMatrix>& m) {
522         fData = SkMatrix::Concat(m.fData, fData);
523         return *this;
524     }
525 
invert(LayerSpace<SkMatrix> * inverse)526     bool invert(LayerSpace<SkMatrix>* inverse) const {
527         return fData.invert(inverse ? &inverse->fData : nullptr);
528     }
529 
530     // Transforms 'r' by the inverse of this matrix if it is invertible and stores it in 'out'.
531     // Returns false if not invertible, in which case 'out' is undefined.
532     bool inverseMapRect(const LayerSpace<SkRect>& r, LayerSpace<SkRect>* out) const;
533     bool inverseMapRect(const LayerSpace<SkIRect>& r, LayerSpace<SkIRect>* out) const;
534 
rc(int row,int col)535     float rc(int row, int col) const { return fData.rc(row, col); }
get(int i)536     float get(int i) const { return fData.get(i); }
537 
538 private:
539     SkMatrix fData;
540 };
541 
542 /**
543  * Most ImageFilters can natively handle scaling and translate components in the CTM. Only some of
544  * them can handle affine (or more complex) matrices. Some may only handle translation.
545  */
546 enum class MatrixCapability {
547     kTranslate,
548     kScaleTranslate,
549     kComplex,
550 };
551 
552 // Mapping is the primary definition of the shared layer space used when evaluating an image filter
553 // DAG. It encapsulates any needed decomposition of the total CTM into the parameter-to-layer matrix
554 // (that filters use to map their parameters to the layer space), and the layer-to-device matrix
555 // (that canvas uses to map the output layer-space image into its root device space). Mapping
556 // defines functions to transform ParameterSpace and DeviceSpace types to and from their LayerSpace
557 // variants, which can then be used and reasoned about by SkImageFilter implementations.
558 class Mapping {
559 public:
560     Mapping() = default;
561 
562     // Helper constructor that equates device and layer space to the same coordinate space.
Mapping(const SkM44 & paramToLayer)563     explicit Mapping(const SkM44& paramToLayer)
564             : fLayerToDevMatrix(SkM44())
565             , fParamToLayerMatrix(paramToLayer)
566             , fDevToLayerMatrix(SkM44()) {}
567 
568     // This constructor allows the decomposition to be explicitly provided, assumes that
569     // 'layerToDev's inverse has already been calculated in 'devToLayer'
Mapping(const SkM44 & layerToDev,const SkM44 & devToLayer,const SkM44 & paramToLayer)570     Mapping(const SkM44& layerToDev, const SkM44& devToLayer, const SkM44& paramToLayer)
571             : fLayerToDevMatrix(layerToDev)
572             , fParamToLayerMatrix(paramToLayer)
573             , fDevToLayerMatrix(devToLayer) {}
574 
575     // Sets this Mapping to the default decomposition of the canvas's total transform, given the
576     // requirements of the 'filter'. Returns false if the decomposition failed or would produce an
577     // invalid device matrix. Assumes 'ctm' is invertible.
578     [[nodiscard]] bool decomposeCTM(const SkM44& ctm,
579                                     const SkImageFilter* filter,
580                                     const skif::ParameterSpace<SkPoint>& representativePt);
581     [[nodiscard]] bool decomposeCTM(const SkM44& ctm,
582                                     MatrixCapability,
583                                     const skif::ParameterSpace<SkPoint>& representativePt);
584 
585     // Update the mapping's parameter-to-layer matrix to be pre-concatenated with the specified
586     // local space transformation. This changes the definition of parameter space, any
587     // skif::ParameterSpace<> values are interpreted anew. Layer space and device space are
588     // unchanged.
concatLocal(const SkMatrix & local)589     void concatLocal(const SkMatrix& local) { fParamToLayerMatrix.preConcat(local); }
590 
591     // Update the mapping's layer space coordinate system by post-concatenating the given matrix
592     // to it's parameter-to-layer transform, and pre-concatenating the inverse of the matrix with
593     // it's layer-to-device transform. The net effect is that neither the parameter nor device
594     // coordinate systems are changed, but skif::LayerSpace is adjusted.
595     //
596     // Returns false if the layer matrix cannot be inverted, and this mapping is left unmodified.
597     bool adjustLayerSpace(const SkM44& layer);
598 
599     // Update the mapping's layer space so that the point 'origin' in the current layer coordinate
600     // space maps to (0, 0) in the adjusted coordinate space.
applyOrigin(const LayerSpace<SkIPoint> & origin)601     void applyOrigin(const LayerSpace<SkIPoint>& origin) {
602         SkAssertResult(this->adjustLayerSpace(SkM44::Translate(-origin.x(), -origin.y())));
603     }
604 
layerToDevice()605     const SkM44& layerToDevice() const { return fLayerToDevMatrix; }
deviceToLayer()606     const SkM44& deviceToLayer() const { return fDevToLayerMatrix; }
layerMatrix()607     const SkM44& layerMatrix() const { return fParamToLayerMatrix; }
totalMatrix()608     SkM44 totalMatrix() const {
609         return fLayerToDevMatrix * fParamToLayerMatrix;
610     }
611 
612     template<typename T>
paramToLayer(const ParameterSpace<T> & paramGeometry)613     LayerSpace<T> paramToLayer(const ParameterSpace<T>& paramGeometry) const {
614         return LayerSpace<T>(map(static_cast<const T&>(paramGeometry),
615                                  fParamToLayerMatrix.asM33()));
616     }
617 
618     template<typename T>
deviceToLayer(const DeviceSpace<T> & devGeometry)619     LayerSpace<T> deviceToLayer(const DeviceSpace<T>& devGeometry) const {
620         // For inverse mapping back to layer space, we may be undoing perspective projection.
621         // Using fDevToLayerMatrix for this would require knowing the device-space Z values,
622         // which are discarded. fDevToLayerMatrix.asM33() would operate as if all those
623         // Z values were 0 (this is true for local 2D geometry, not device space). Instead,
624         // derive the 3x3 inverse of the flattened layer-to-device matrix, returning empty
625         // if numerical stability meant its 4x4 was invertible but somehow the 3x3 wasn't.
626         SkMatrix devToLayer33;
627         if (!fLayerToDevMatrix.asM33().invert(&devToLayer33)) {
628             return LayerSpace<T>::Empty();
629         }
630         return LayerSpace<T>(map(static_cast<const T&>(devGeometry), devToLayer33));
631     }
632 
633     template<typename T>
layerToDevice(const LayerSpace<T> & layerGeometry)634     DeviceSpace<T> layerToDevice(const LayerSpace<T>& layerGeometry) const {
635         return DeviceSpace<T>(map(static_cast<const T&>(layerGeometry), fLayerToDevMatrix.asM33()));
636     }
637 
638 private:
639     friend class LayerSpace<SkMatrix>; // for map()
640     friend class FilterResult;         // ""
641 
642     // The image filter process decomposes the total CTM into layerToDev * paramToLayer and uses the
643     // param-to-layer matrix to define the layer-space coordinate system. Depending on how it's
644     // decomposed, either the layer matrix or the device matrix could be the identity matrix (but
645     // sometimes neither).
646     SkM44 fLayerToDevMatrix;
647     SkM44 fParamToLayerMatrix;
648 
649     // Cached inverse of fLayerToDevMatrix. We keep this as 4x4 so that conversion between different
650     // SkDevice coordinate spaces and coord space reconstruction is lossless.
651     SkM44 fDevToLayerMatrix;
652 
653     // Actual geometric mapping operations that work on coordinates and matrices w/o the type
654     // safety of the coordinate space wrappers (hence these are private).
655     // TODO(b/40042800): Finish moving skif::Mapping operations to use the SkM44 directly.
656     template<typename T>
657     static T map(const T& geom, const SkMatrix& matrix);
658 };
659 
660 class Context; // Forward declare for FilterResult
661 
662 // A FilterResult represents a lazy image anchored in the "layer" coordinate space of the current
663 // image filtering context. It's named Filter*Result* since most instances represent the output of
664 // a specific image filter (even if that is then used as an input to the next filter). FilterResults
665 // are lazy to allow certain operations to combine analytically instead of producing an offscreen
666 // image for every node in a filter graph. Helper functions are provided to modify FilterResults
667 // that manage this internally.
668 //
669 // Even though FilterResult represents a lazy image, it is always backed by a non-lazy source image
670 // that is then transformed, sampled, cropped, tiled, and/or color-filtered to produce the resolved
671 // image of the FilterResult. It is these actions applied to the source image that can be combined
672 // without producing a new intermediate "source" if it's determined that the combined actions
673 // rendered once would create an image close enough to the canonical output of rendering each action
674 // separately. Eliding offscreen renders in this way can introduce visually imperceptible pixel
675 // differences due to avoiding casting down to a lower precision pixel format or performing fewer
676 // image sampling sequences.
677 //
678 // The resolved image of a FilterResult is the output of rendering:
679 //
680 //   SkMatrix netTransform = RectToRect(fSrcRect, fDstRect);
681 //   netTransform.postConcat(fTransform);
682 //
683 //   SkPaint paint;
684 //   paint.setShader(fImage->makeShader(fTileMode, fSamplingOptions, &netTransform));
685 //   paint.setColorFilter(fColorFilter);
686 //   paint.setBlendMode(kSrc);
687 //
688 //   canvas->drawRect(fLayerBounds, paint);
689 //
690 // A FilterResult may represent the output of multiple operations affecting the different meta
691 // properties defined above. The operations are applied in order:
692 //   1. Tile the image using configured SkTileMode on the source rect.
693 //   2. Transform and sample (with configured SkSamplingOptions) from source rect up to the dest
694 //      rect and then any additional transform.
695 //   3. Apply any SkColorFilter to all pixels from #2 (including transparent black pixels resulting
696 //      from decal sampling).
697 //   4. Restrict the result to the layer bounds.
698 //
699 // If a new operation applied to a FilterResult does not respect this order, or cannot be modified
700 // to be re-ordered in place (e.g. modify fSrcRect/fDstRect instead of fLayerBounds for a crop),
701 // then the FilterResult must be resolved and the new operation applied to a clean slate. If it can
702 // be applied while respecting the order of operations than the action is free and no new
703 // intermediate image is produced.
704 //
705 // NOTE: The above comment reflects the end goal of the in-progress FilterResult. Currently
706 // SkSpecialImage is used, which internally has a subset property (its fSrcRect) and always has an
707 // fDstRect equal to (0,0,subset WH). Tile modes haven't been implemented yet and kDecal
708 // is always assumed; Color filters have also not been implemented yet.
709 class FilterResult {
710 public:
FilterResult()711     FilterResult() : FilterResult(nullptr) {}
712 
FilterResult(sk_sp<SkSpecialImage> image)713     explicit FilterResult(sk_sp<SkSpecialImage> image)
714             : FilterResult(std::move(image), LayerSpace<SkIPoint>({0, 0})) {}
715 
FilterResult(sk_sp<SkSpecialImage> image,const LayerSpace<SkIPoint> & origin)716     FilterResult(sk_sp<SkSpecialImage> image, const LayerSpace<SkIPoint>& origin)
717             : FilterResult(std::move(image), origin, PixelBoundary::kUnknown) {}
718 
719     // Renders the 'pic', clipped by 'cullRect', into an optimally sized surface (depending on
720     // picture bounds and 'ctx's desired output). The picture is transformed by the context's
721     // layer matrix. 'pic' must not be null.
722     static FilterResult MakeFromPicture(const Context& ctx,
723                                         sk_sp<SkPicture> pic,
724                                         ParameterSpace<SkRect> cullRect);
725 
726     // Renders 'shader' into a surface that fills the context's desired output bounds, 'shader' must
727     // not be null.
728     // TODO: Update 'dither' to SkImageFilters::Dither, but that cannot be forward declared at the
729     // moment because SkImageFilters is a class and not a namespace.
730     static FilterResult MakeFromShader(const Context& ctx,
731                                        sk_sp<SkShader> shader,
732                                        bool dither);
733 
734     // Converts image to a FilterResult. If 'srcRect' is pixel-aligned it does so without rendering.
735     // Otherwise it draws the src->dst sampling of 'image' into an optimally sized surface based
736     // on the context's desired output. 'image' must not be null.
737     static FilterResult MakeFromImage(const Context& ctx,
738                                       sk_sp<SkImage> image,
739                                       SkRect srcRect,
740                                       ParameterSpace<SkRect> dstRect,
741                                       const SkSamplingOptions& sampling);
742 
743     // Bilinear is used as the default because it can be downgraded to nearest-neighbor when the
744     // final transform is pixel-aligned, and chaining multiple bilinear samples and transforms is
745     // assumed to be visually close enough to sampling once at highest quality and final transform.
746     static constexpr SkSamplingOptions kDefaultSampling{SkFilterMode::kLinear};
747 
748     explicit operator bool() const { return SkToBool(fImage); }
749 
750     // TODO(michaelludwig): Given the planned expansion of FilterResult state, it might be nice to
751     // pull this back and not expose anything other than its bounding box. This will be possible if
752     // all rendering can be handled by functions defined on FilterResult.
image()753     const SkSpecialImage* image() const { return fImage.get(); }
refImage()754     sk_sp<SkSpecialImage> refImage() const { return fImage; }
755 
756     // Get the layer-space bounds of the result. This will incorporate any layer-space transform.
layerBounds()757     LayerSpace<SkIRect> layerBounds() const { return fLayerBounds; }
tileMode()758     SkTileMode tileMode() const { return fTileMode; }
sampling()759     SkSamplingOptions sampling() const { return fSamplingOptions; }
760 
colorFilter()761     const SkColorFilter* colorFilter() const { return fColorFilter.get(); }
762 
763     // Produce a new FilterResult that has been cropped to 'crop', taking into account the context's
764     // desired output. When possible, the returned FilterResult will reuse the underlying image and
765     // adjust its metadata. This will depend on the current transform and tile mode as well as how
766     // the crop rect intersects this result's layer bounds.
767     FilterResult applyCrop(const Context& ctx,
768                            const LayerSpace<SkIRect>& crop,
769                            SkTileMode tileMode=SkTileMode::kDecal) const;
770 
771     // Produce a new FilterResult that is the transformation of this FilterResult. When this
772     // result's sampling and transform are compatible with the new transformation, the returned
773     // FilterResult can reuse the same image data and adjust just the metadata.
774     FilterResult applyTransform(const Context& ctx,
775                                 const LayerSpace<SkMatrix>& transform,
776                                 const SkSamplingOptions& sampling) const;
777 
778     // Produce a new FilterResult that is visually equivalent to the output of the SkColorFilter
779     // evaluating this FilterResult. If the color filter affects transparent black, the returned
780     // FilterResult can become non-empty even if the input were empty.
781     FilterResult applyColorFilter(const Context& ctx,
782                                   sk_sp<SkColorFilter> colorFilter) const;
783 
784     // Extract image and origin, safely when the image is null. If there are deferred operations
785     // on FilterResult (such as tiling or transforms) not representable as an image+origin pair,
786     // the returned image will be the resolution resulting from that metadata and not necessarily
787     // equal to the original 'image()'.
788     // TODO (michaelludwig) - This is intended for convenience until all call sites of
789     // SkImageFilter_Base::filterImage() have been updated to work in the new type system
790     // (which comes later as SkDevice, SkCanvas, etc. need to be modified, and coordinate space
791     // tagging needs to be added).
792     sk_sp<SkSpecialImage> imageAndOffset(const Context& ctx, SkIPoint* offset) const;
793     // TODO (michaelludwig) - This is a more type-safe version of the above imageAndOffset() and
794     // may need to remain to support SkBlurImageFilter calling out to the SkBlurEngine. An alternate
795     // option would be for FilterResult::Builder to have a blur() function that internally can
796     // resolve the input and pass to the skif::Context's blur engine. Then imageAndOffset() can go
797     // away entirely.
798     std::pair<sk_sp<SkSpecialImage>, LayerSpace<SkIPoint>> imageAndOffset(const Context& ctx) const;
799 
800      // Draw this FilterResult into 'target' by applying the remaining layer-to-device transform of
801      // 'mapping', using the provided 'blender' to composite the effective image on top of 'target'.
802      // If 'blender' is null, it's equivalent to kSrcOver blending.
803     void draw(const Context& ctx, SkDevice* target, const SkBlender* blender) const;
804 
805     // SkCanvas can prepare layer source images with transparent padding, similarly to AutoSurface.
806     // This adjusts the FilterResult metadata to be aware of that padding. This should only be
807     // called when it's externally known that the FilterResult has a 1px buffer of transparent
808     // black pixels and has had no further modifications.
809     FilterResult insetForSaveLayer() const;
810 
811     class Builder;
812 
813     enum class ShaderFlags : int {
814         kNone = 0,
815         // A hint that the input FilterResult will be sampled repeatedly per pixel. If there's
816         // colorspace conversions or deferred color filtering, it's worth resolving to a temporary
817         // image so that those calculations are performed once per pixel instead of N times.
818         kSampledRepeatedly = 1 << 0,
819         // Specifies that the shader performs non-trivial operations on its coordinates to determine
820         // how to sample any input FilterResults, so their sampling options should not be converted
821         // to nearest-neighbor even if they appeared pixel-aligned with the output surface.
822         kNonTrivialSampling = 1 << 1,
823         // TODO: Add option to convey that the output can carry input tiling forward to make a
824         // smaller backing surface somehow. May not be a flag and just args passed to eval().
825     };
826     SK_DECL_BITMASK_OPS_FRIENDS(ShaderFlags)
827 
828 private:
829     friend class ::FilterResultTestAccess; // For testing draw() and asShader()
830 
831     class AutoSurface;
832 
833     enum class PixelBoundary : int {
834         kUnknown,     // Pixels outside the image subset are of unknown value, possibly unitialized
835         kTransparent, // Pixels bordering the image subset are transparent black
836         kInitialized, // Pixels bordering the image are known to be initialized
837     };
838 
FilterResult(sk_sp<SkSpecialImage> image,const LayerSpace<SkIPoint> & origin,PixelBoundary boundary)839     FilterResult(sk_sp<SkSpecialImage> image,
840                  const LayerSpace<SkIPoint>& origin,
841                  PixelBoundary boundary)
842             : fImage(std::move(image))
843             , fBoundary(boundary)
844             , fSamplingOptions(kDefaultSampling)
845             , fTileMode(SkTileMode::kDecal)
846             , fTransform(SkMatrix::Translate(origin.x(), origin.y()))
847             , fColorFilter(nullptr)
848             , fLayerBounds(
849                     fTransform.mapRect(LayerSpace<SkIRect>(fImage ? fImage->dimensions()
850                                                                   : SkISize{0, 0}))) {}
851 
852     // Renders this FilterResult into a new, but visually equivalent, image that fills 'dstBounds',
853     // has default sampling, no color filter, and a transform that translates by only 'dstBounds's
854     // top-left corner. 'dstBounds' is intersected with 'fLayerBounds' unless 'preserveDstBounds'
855     // is true.
856     FilterResult resolve(const Context& ctx, LayerSpace<SkIRect> dstBounds,
857                          bool preserveDstBounds=false) const;
858     // Returns a decal-tiled subset view of this FilterResult, requiring that this has an integer
859     // translation equivalent to 'knownOrigin'. If 'clampSrcIfDisjoint' is true and the image bounds
860     // do not overlap with dstBounds, the closest edge/corner pixels of the image will be extracted,
861     // assuming it will be tiled with kClamp.
862     FilterResult subset(const LayerSpace<SkIPoint>& knownOrigin,
863                         const LayerSpace<SkIRect>& subsetBounds,
864                         bool clampSrcIfDisjoint=false) const;
865     // Convenient version of subset() that insets a single pixel.
866     FilterResult insetByPixel() const;
867 
868     enum class BoundsAnalysis : int {
869         // The image can be drawn directly, without needing to apply tiling, or handling how any
870         // color filter might affect transparent black.
871         kSimple = 0,
872         // The image does not directly cover the intersection of 'dstBounds' and the layer bounds.
873         // (ignoring tiling or color filters).
874         kDstBoundsNotCovered = 1 << 0,
875         // Added when kDstBoundsNotCovered is true, *and* there are non-decal tiling or transparency
876         // affecting color filters that would fill to the layer bounds, not covered by the image
877         // itself.
878         kHasLayerFillingEffect = 1 << 1,
879         // The crop boundary induced by `fLayerBounds` is visible when rendering to the 'dstBounds',
880         // although this could be either because it intersects the image's content or because
881         // kHasLayerFillingEffect is true.
882         kRequiresLayerCrop = 1 << 2,
883         // The image's sampling would access pixel data outside of its valid subset so shader-based
884         // tiling is necessary. This can be true even if kHasLayerFillingEffect is false due to the
885         // filter sampling radius; it can also be false when kHasLayerFillingEffect is true if the
886         // image can use HW tiling.
887         kRequiresShaderTiling = 1 << 3,
888         // The image's decal tiling/sampling would operate at the wrong resolution (e.g. drawImage
889         // vs. image-shader look different), so it has to be applied with a wrapping shader effect
890         kRequiresDecalInLayerSpace = 1 << 4,
891     };
892     SK_DECL_BITMASK_OPS_FRIENDS(BoundsAnalysis)
893 
894     enum class BoundsScope : int {
895         kDeferred,        // The bounds analysis won't be used for any rendering yet
896         kCanDrawDirectly, // The rendering may draw the image directly if analysis allows it
897         kShaderOnly,      // The rendering will always use a filling shader, e.g. drawPaint()
898         kRescale          // The rendering is controlled by rescaling logic, so ignores decal size
899     };
900 
901     // Determine what effects are visible based on the target 'dstBounds' and extra transform that
902     // will be applied when this FilterResult is drawn. These are not LayerSpace because the
903     // 'xtraTransform' may be either a within-layer transform, or a layer-to-device space transform.
904     // The 'dstBounds' should be in the same coordinate space that 'xtraTransform' maps to. When
905     // that is the identity matrix, 'dstBounds' is in layer space.
906     SkEnumBitMask<BoundsAnalysis> analyzeBounds(const SkMatrix& xtraTransform,
907                                                 const SkIRect& dstBounds,
908                                                 BoundsScope scope = BoundsScope::kDeferred) const;
909     SkEnumBitMask<BoundsAnalysis> analyzeBounds(const LayerSpace<SkIRect>& dstBounds,
910                                                 BoundsScope scope = BoundsScope::kDeferred) const {
911         return this->analyzeBounds(SkMatrix::I(), SkIRect(dstBounds), scope);
912     }
913 
914     // If true, the tile mode can be changed to kClamp to sample the transparent black pixels in
915     // the boundary. This will be visually equivalent to the decal tiling or anti-aliasing of a
916     // drawn image.
canClampToTransparentBoundary(SkEnumBitMask<BoundsAnalysis> analysis)917     bool canClampToTransparentBoundary(SkEnumBitMask<BoundsAnalysis> analysis) const {
918         return fTileMode == SkTileMode::kDecal &&
919                fBoundary == PixelBoundary::kTransparent &&
920                !(analysis & BoundsAnalysis::kRequiresDecalInLayerSpace);
921     }
922 
923     // Return an equivalent FilterResult such that its backing image dimensions have been reduced
924     // by the X and Y scale factors in 'scale' (assumed to be in [0, 1]). The returned FilterResult
925     // will have a transform that aligns it with the original FilterResult (i.e. a deferred upscale)
926     // and may also have a deferred tilemode. If 'enforceDecal' is true, the returned
927     // FilterResult will be kDecal sampled and any tiling will already be applied.
928     //
929     // All deferred effects, other than potentially tile mode, will be applied. The FilterResult
930     // will also be converted to the color type and color space of 'ctx' so the result is suitable
931     // to pass to the blur engine.
932     FilterResult rescale(const Context& ctx,
933                          const LayerSpace<SkSize>& scale,
934                          bool enforceDecal) const;
935     // Draw directly to the device, which draws the same image as produced by resolve() but can be
936     // useful if multiple operations need to be performed on the canvas.
937     //
938     // This assumes that the device's transform is set to match the current layer space coordinate
939     // system. This will concat any internal extra transform and apply clipping as necessary. If
940     // 'preserveDeviceState' is true it will undo any modifications. This can be set to false if the
941     // device is a one-off that will be snapped to an image after this returns.
942     //
943     // If 'blender' is null, the filter result is drawn with src-over blending. If it's not, it will
944     // be drawn using the given 'blender', filling the device's current clip when the blend
945     // modifies transparent black.
946     void draw(const Context& ctx,
947               SkDevice* device,
948               bool preserveDeviceState,
949               const SkBlender* blender=nullptr) const;
950 
951     // Returns the FilterResult as a shader, ideally without resolving to an axis-aligned image.
952     // 'xtraSampling' is the sampling that any parent shader applies to the FilterResult.
953     // 'sampleBounds' is the bounding box of coords the shader will be evaluated at by any parent.
954     //
955     // This variant may resolve to an intermediate image if needed. The returned shader encapsulates
956     // all deferred effects of the FilterResult.
957     sk_sp<SkShader> asShader(const Context& ctx,
958                              const SkSamplingOptions& xtraSampling,
959                              SkEnumBitMask<ShaderFlags> flags,
960                              const LayerSpace<SkIRect>& sampleBounds) const;
961 
962     // This variant should only be called after analysis and final sampling has been determined, and
963     // there's no need to resolve the FilterResult to an intermediate image. This version will
964     // never introduce a new image pass but is unable to handle the layer crop. If (analysis &
965     // kRequiresLayerCrop) is true, it must be accounted for outside of this shader.
966     sk_sp<SkShader> getAnalyzedShaderView(const Context& ctx,
967                                           const SkSamplingOptions& finalSampling,
968                                           SkEnumBitMask<BoundsAnalysis> analysis) const;
969 
970     // Safely updates fTileMode, doing nothing if the FilterResult is empty. Updates the layer
971     // bounds to the context's desired output if the tilemode is not decal.
972     void updateTileMode(const Context& ctx, SkTileMode tileMode);
973 
974     // The effective image of a FilterResult is 'fImage' sampled by 'fSamplingOptions' and
975     // respecting 'fTileMode' (on the SkSpecialImage's subset), transformed by 'fTransform',
976     // filtered by 'fColorFilter', and then clipped to 'fLayerBounds'.
977     sk_sp<SkSpecialImage> fImage;
978     PixelBoundary         fBoundary;
979 
980     SkSamplingOptions     fSamplingOptions;
981     SkTileMode            fTileMode;
982     // Typically this will be an integer translation that encodes the origin of the top left corner,
983     // but can become more complex when combined with applyTransform().
984     LayerSpace<SkMatrix>  fTransform;
985 
986     // A null color filter is the identity function. Since the output is clipped to fLayerBounds
987     // after color filtering, SkColorFilters that affect transparent black are not unbounded.
988     sk_sp<SkColorFilter>  fColorFilter;
989 
990     // The layer bounds are initially fImage's dimensions mapped by fTransform. As the filter result
991     // is processed by the image filter DAG, it can be further restricted by crop rects or the
992     // implicit desired output at each node.
993     LayerSpace<SkIRect>   fLayerBounds;
994 };
995 SK_MAKE_BITMASK_OPS(FilterResult::ShaderFlags)
SK_MAKE_BITMASK_OPS(FilterResult::BoundsAnalysis)996 SK_MAKE_BITMASK_OPS(FilterResult::BoundsAnalysis)
997 
998 // A FilterResult::Builder is used to render one or more FilterResults or other sources into
999 // a new FilterResult. It automatically aggregates the incoming bounds to minimize the output's
1000 // layer bounds.
1001 class FilterResult::Builder {
1002 public:
1003     Builder(const Context& context);
1004     ~Builder();
1005 
1006     // If 'sampleBounds' is not provided, it defaults to the output bounds calculated for eval()
1007     // (generally the Context's desired output but could be restricted based on the ShaderFlags).
1008     //
1009     // If it is provided, it represents the bounding box of possible coords 'input' will be sampled
1010     // at by the shader created from eval(). This can be useful to provide when the shader does non
1011     // trivial sampling since it may avoid having to resolve a FilterResult to an image.
1012     //
1013     // The 'inputFlags' are per-input flags that are OR'ed with the ShaderFlag mask passed to
1014     // eval() to control how 'input' is converted to an SkShader. 'inputSampling' specifies the
1015     // sampling options to use on the input's image when sampled by the final shader created in eval
1016     //
1017     // 'sampleBounds', 'inputFlags' and 'inputSampling' must not be used with merge() or blur().
1018     Builder& add(const FilterResult& input,
1019                  std::optional<LayerSpace<SkIRect>> sampleBounds = {},
1020                  SkEnumBitMask<ShaderFlags> inputFlags = ShaderFlags::kNone,
1021                  const SkSamplingOptions& inputSampling = kDefaultSampling) {
1022         fInputs.push_back({input, sampleBounds, inputFlags, inputSampling});
1023         return *this;
1024     }
1025 
1026     // Combine all added inputs by merging them with src-over blending into a single output.
1027     FilterResult merge();
1028 
1029     // Blur the single input with a Gaussian blur. The exact blur implementation is chosen based on
1030     // the skif::Context's backend. The sample bounds of the input and the final output bounds are
1031     // automatically derived from the sigma, input layer bounds, and desired output bounds of the
1032     // Builder's Context.
1033     FilterResult blur(const LayerSpace<SkSize>& sigma);
1034 
1035     // Combine all added inputs by transforming them into equivalent SkShaders and invoking the
1036     // shader factory that binds them together into a single shader that fills the output surface.
1037     //
1038     // 'ShaderFn' should be an invokable type with the signature
1039     //     (SkSpan<sk_sp<SkShader>>)->sk_sp<SkShader>
1040     // The length of the span will equal the number of FilterResults added to the builder. If an
1041     // input FilterResult was fully transparent, its corresponding shader will be null. 'ShaderFn'
1042     // should return a null shader its output would be fully transparent.
1043     //
1044     // By default, the returned FilterResult will fill the Context's desired image. If
1045     // 'explicitOutput' has a value, it is intersected with the Context's desired output bounds to
1046     // produce a possibly restricted output surface that the evaluated shader is rendered into.
1047     //
1048     // The shader created by `ShaderFn` will by default be invoked with coordinates in the layer
1049     // space of the Context. If `evaluateInParameterSpace` is true, the drawing matrix will be
1050     // adjusted so that the shader processes coordinates mapped back into parameter space (the
1051     // underlying output is still in layer space). In this case, it's assumed that the shaders for
1052     // the added FilterResult inputs will be evaluated with coordinates also in parameter space,
1053     // so they will be adjusted to map back to layer space before sampling their underlying images.
1054     template <typename ShaderFn>
1055     FilterResult eval(ShaderFn shaderFn,
1056                       std::optional<LayerSpace<SkIRect>> explicitOutput = {},
1057                       bool evaluateInParameterSpace=false) {
1058         auto outputBounds = this->outputBounds(explicitOutput);
1059         if (outputBounds.isEmpty()) {
1060             return {};
1061         }
1062 
1063         auto inputShaders = this->createInputShaders(outputBounds, evaluateInParameterSpace);
1064         return this->drawShader(shaderFn(inputShaders), outputBounds, evaluateInParameterSpace);
1065     }
1066 
1067 private:
1068     struct SampledFilterResult {
1069         FilterResult fImage;
1070         std::optional<LayerSpace<SkIRect>> fSampleBounds;
1071         SkEnumBitMask<ShaderFlags> fFlags;
1072         SkSamplingOptions fSampling;
1073     };
1074 
1075     SkSpan<sk_sp<SkShader>> createInputShaders(const LayerSpace<SkIRect>& outputBounds,
1076                                                bool evaluateInParameterSpace);
1077 
1078     LayerSpace<SkIRect> outputBounds(std::optional<LayerSpace<SkIRect>> explicitOutput) const;
1079 
1080     FilterResult drawShader(sk_sp<SkShader> shader,
1081                             const LayerSpace<SkIRect>& outputBounds,
1082                             bool evaluateInParameterSpace) const;
1083 
1084     const Context& fContext; // Must outlive the builder
1085     skia_private::STArray<1, SampledFilterResult> fInputs;
1086     // Lazily created once all inputs are collected, but parallels fInputs.
1087     skia_private::STArray<1, sk_sp<SkShader>> fInputShaders;
1088 };
1089 
1090 // The backend provides key functionality to the image filtering pipeline that must be implemented
1091 // by the Skia backend (e.g. raster or GPU). While a Context's state may change as the image filter
1092 // DAG is evaluated, a given filter invocation will always use one Backend.
1093 class Backend : public SkRefCnt {
1094 public:
1095     ~Backend() override;
1096 
1097     // For creating offscreen intermediate renderable images
1098     virtual sk_sp<SkDevice> makeDevice(SkISize size,
1099                                        sk_sp<SkColorSpace>,
1100                                        const SkSurfaceProps* props=nullptr) const = 0;
1101 
1102     // For input images to be processed by image filters
1103     virtual sk_sp<SkSpecialImage> makeImage(const SkIRect& subset, sk_sp<SkImage> image) const = 0;
1104 
1105     // For internal data to be accessed by filter implementations
1106     virtual sk_sp<SkImage> getCachedBitmap(const SkBitmap& data) const = 0;
1107 
1108     // TODO: Once all Backends provide a blur engine, maybe just have Backend extend it.
1109     virtual const SkBlurEngine* getBlurEngine() const = 0;
1110 
1111     // TODO: Can be removed once all blur engines rely on FilterResult::rescale and not their own
1112     // rescale implementations.
useLegacyFilterResultBlur()1113     virtual bool useLegacyFilterResultBlur() const { return true; }
1114 
1115     // Properties controlling the pixel data for offscreen surfaces rendered to during filtering.
surfaceProps()1116     const SkSurfaceProps& surfaceProps() const { return fSurfaceProps; }
colorType()1117     SkColorType colorType() const { return fColorType; }
1118 
cache()1119     SkImageFilterCache* cache() const { return fCache.get(); }
1120 
1121 protected:
1122     Backend(sk_sp<SkImageFilterCache> cache,
1123             const SkSurfaceProps& surfaceProps,
1124             const SkColorType colorType);
1125 
1126 private:
1127     sk_sp<SkImageFilterCache> fCache;
1128     SkSurfaceProps fSurfaceProps;
1129     SkColorType fColorType;
1130 };
1131 
1132 sk_sp<Backend> MakeRasterBackend(const SkSurfaceProps& surfaceProps, SkColorType colorType);
1133 
1134 // Stats for a single image filter evaluation
1135 struct Stats {
1136     int fNumVisitedImageFilters = 0; // size of the filter dag
1137     int fNumCacheHits = 0; // amount of reuse within the dag
1138     int fNumOffscreenSurfaces = 0; // difference to the # of visited filters shows deferred steps
1139     int fNumShaderClampedDraws = 0; // shader-emulated clamp is fairly cheap but HW tiling is best
1140     int fNumShaderBasedTilingDraws = 0; // shader-emulated decal, mirror, repeat are expensive
1141 
1142     void dumpStats() const;   // log to std out
1143     void reportStats() const; // trace event counters
1144 };
1145 
1146 // The context contains all necessary information to describe how the image filter should be
1147 // computed (i.e. the current layer matrix and clip), and the color information of the output of a
1148 // filter DAG. For now, this is just the color space (of the original requesting device). This is
1149 // used when constructing intermediate rendering surfaces, so that we ensure we land in a surface
1150 // that's similar/compatible to the final consumer of the DAG's output.
1151 class Context {
1152 public:
Context(sk_sp<Backend> backend,const Mapping & mapping,const LayerSpace<SkIRect> & desiredOutput,const FilterResult & source,const SkColorSpace * colorSpace,Stats * stats)1153     Context(sk_sp<Backend> backend,
1154             const Mapping& mapping,
1155             const LayerSpace<SkIRect>& desiredOutput,
1156             const FilterResult& source,
1157             const SkColorSpace* colorSpace,
1158             Stats* stats)
1159         : fBackend(std::move(backend))
1160         , fMapping(mapping)
1161         , fDesiredOutput(desiredOutput)
1162         , fSource(source)
1163         , fColorSpace(sk_ref_sp(colorSpace))
1164         , fStats(stats) {}
1165 
backend()1166     const Backend* backend() const { return fBackend.get(); }
1167 
1168     // The mapping that defines the transformation from local parameter space of the filters to the
1169     // layer space where the image filters are evaluated, as well as the remaining transformation
1170     // from the layer space to the final device space. The layer space defined by the returned
1171     // Mapping may be the same as the root device space, or be an intermediate space that is
1172     // supported by the image filter DAG (depending on what it returns from getCTMCapability()).
1173     // If a node returns something other than kComplex from getCTMCapability(), the layer matrix of
1174     // the mapping will respect that return value, and the remaining matrix will be appropriately
1175     // set to transform the layer space to the final device space (applied by the SkCanvas when
1176     // filtering is finished).
mapping()1177     const Mapping& mapping() const { return fMapping; }
1178 
1179     // The bounds, in the layer space, that the filtered image will be clipped to. The output
1180     // from filterImage() must cover these clip bounds, except in areas where it will just be
1181     // transparent black, in which case a smaller output image can be returned.
desiredOutput()1182     const LayerSpace<SkIRect>& desiredOutput() const { return fDesiredOutput; }
1183 
1184     // The output device's color space, so intermediate images can match, and so filtering can
1185     // be performed in the destination color space.
colorSpace()1186     SkColorSpace* colorSpace() const { return fColorSpace.get(); }
refColorSpace()1187     sk_sp<SkColorSpace> refColorSpace() const { return fColorSpace; }
1188 
1189     // This is the image to use whenever an expected input filter has been set to null. In the
1190     // majority of cases, this is the original source image for the image filter DAG so it comes
1191     // from the SkDevice that holds either the saveLayer or the temporary rendered result. The
1192     // exception is composing two image filters (via SkImageFilters::Compose), which must use
1193     // the output of the inner DAG as the "source" for the outer DAG.
source()1194     const FilterResult& source() const { return fSource; }
1195 
1196 
1197     // Create a new context that matches this context, but with an overridden layer space.
withNewMapping(const Mapping & mapping)1198     Context withNewMapping(const Mapping& mapping) const {
1199         Context c = *this;
1200         c.fMapping = mapping;
1201         return c;
1202     }
1203     // Create a new context that matches this context, but with an overridden desired output rect.
withNewDesiredOutput(const LayerSpace<SkIRect> & desiredOutput)1204     Context withNewDesiredOutput(const LayerSpace<SkIRect>& desiredOutput) const {
1205         Context c = *this;
1206         c.fDesiredOutput = desiredOutput;
1207         return c;
1208     }
1209     // Create a new context that matches this context, but with an overridden color space.
withNewColorSpace(SkColorSpace * cs)1210     Context withNewColorSpace(SkColorSpace* cs) const {
1211         Context c = *this;
1212         c.fColorSpace = sk_ref_sp(cs);
1213         return c;
1214     }
1215 
1216     // Create a new context that matches this context, but with an overridden source.
withNewSource(const FilterResult & source)1217     Context withNewSource(const FilterResult& source) const {
1218         Context c = *this;
1219         c.fSource = source;
1220         return c;
1221     }
1222 
1223 
1224     // Stats tracking
markVisitedImageFilter()1225     void markVisitedImageFilter() const {
1226         if (fStats) {
1227             fStats->fNumVisitedImageFilters++;
1228         }
1229     }
markCacheHit()1230     void markCacheHit() const {
1231         if (fStats) {
1232             fStats->fNumCacheHits++;
1233         }
1234     }
markNewSurface()1235     void markNewSurface() const {
1236         if (fStats) {
1237             fStats->fNumOffscreenSurfaces++;
1238         }
1239     }
markShaderBasedTilingRequired(SkTileMode tileMode)1240     void markShaderBasedTilingRequired(SkTileMode tileMode) const {
1241         if (fStats) {
1242             if (tileMode == SkTileMode::kClamp) {
1243                 fStats->fNumShaderClampedDraws++;
1244             } else {
1245                 fStats->fNumShaderBasedTilingDraws++;
1246             }
1247         }
1248     }
1249 
1250 private:
1251     friend class ::FilterResultTestAccess; // For controlling Stats
1252 
1253     sk_sp<Backend> fBackend;
1254 
1255     // Properties controlling the size and coordinate space of image filtering
1256     Mapping             fMapping;
1257     LayerSpace<SkIRect> fDesiredOutput;
1258     // Can contain a null image if the image filter DAG has no late-bound null inputs.
1259     FilterResult        fSource;
1260     // The color space the filters are evaluated in
1261     sk_sp<SkColorSpace> fColorSpace;
1262 
1263     Stats* fStats;
1264 };
1265 
1266 } // end namespace skif
1267 
1268 #endif // SkImageFilterTypes_DEFINED
1269