• 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/SkColorSpace.h"
12 #include "include/core/SkMatrix.h"
13 #include "include/core/SkPoint.h"
14 #include "include/core/SkRect.h"
15 #include "include/core/SkSamplingOptions.h"
16 #include "include/core/SkTypes.h"
17 #include "src/core/SkSpecialImage.h"
18 #include "src/core/SkSpecialSurface.h"
19 
20 class GrRecordingContext;
21 class SkImageFilter;
22 class SkImageFilterCache;
23 class SkSpecialSurface;
24 class SkSurfaceProps;
25 
26 // The skif (SKI[mage]F[ilter]) namespace contains types that are used for filter implementations.
27 // The defined types come in two groups: users of internal Skia types, and templates to help with
28 // readability. Image filters cannot be implemented without access to key internal types, such as
29 // SkSpecialImage. It is possible to avoid the use of the readability templates, although they are
30 // strongly encouraged.
31 namespace skif {
32 
33 // Rounds in/out but with a tolerance.
34 SkIRect RoundOut(SkRect);
35 SkIRect RoundIn(SkRect);
36 
37 // skif::IVector and skif::Vector represent plain-old-data types for storing direction vectors, so
38 // that the coordinate-space templating system defined below can have a separate type id for
39 // directions vs. points, and specialize appropriately. As such, all operations with direction
40 // vectors are defined on the LayerSpace specialization, since that is the intended point of use.
41 struct IVector {
42     int32_t fX;
43     int32_t fY;
44 
45     IVector() = default;
IVectorIVector46     IVector(int32_t x, int32_t y) : fX(x), fY(y) {}
IVectorIVector47     explicit IVector(const SkIVector& v) : fX(v.fX), fY(v.fY) {}
48 };
49 
50 struct Vector {
51     SkScalar fX;
52     SkScalar fY;
53 
54     Vector() = default;
VectorVector55     Vector(SkScalar x, SkScalar y) : fX(x), fY(y) {}
VectorVector56     explicit Vector(const SkVector& v) : fX(v.fX), fY(v.fY) {}
57 };
58 
59 ///////////////////////////////////////////////////////////////////////////////////////////////////
60 // Coordinate Space Tagging
61 // - In order to enforce correct coordinate spaces in image filter implementations and use,
62 //   geometry is wrapped by templated structs to declare in the type system what coordinate space
63 //   the coordinates are defined in.
64 // - Currently there is ParameterSpace and DeviceSpace that are data-only wrappers around
65 //   coordinates, and the primary LayerSpace that provides all operative functionality for image
66 //   filters. It is intended that all logic about image bounds and access be conducted in the shared
67 //   layer space.
68 // - The LayerSpace struct has type-safe specializations for SkIRect, SkRect, SkIPoint, SkPoint,
69 //   skif::IVector (to distinguish SkIVector from SkIPoint), skif::Vector, SkISize, and SkSize.
70 // - A Mapping object provides type safe coordinate conversions between these spaces, and
71 //   automatically does the "right thing" for each geometric type.
72 ///////////////////////////////////////////////////////////////////////////////////////////////////
73 
74 // ParameterSpace is a data-only wrapper around Skia's geometric types such as SkIPoint, and SkRect.
75 // Parameter space is the same as the local coordinate space of an SkShader, or the coordinates
76 // passed into SkCanvas::drawX calls, but "local" is avoided due to the alliteration with layer
77 // space. SkImageFilters are defined in terms of ParameterSpace<T> geometry and must use the Mapping
78 // on Context to transform the parameters into LayerSpace to evaluate the filter in the shared
79 // coordinate space of the entire filter DAG.
80 //
81 // A value of ParameterSpace<SkIRect> implies that its wrapped SkIRect is defined in the local
82 // parameter space.
83 template<typename T>
84 class ParameterSpace {
85 public:
86     ParameterSpace() = default;
ParameterSpace(const T & data)87     explicit ParameterSpace(const T& data) : fData(data) {}
ParameterSpace(T && data)88     explicit ParameterSpace(T&& data) : fData(std::move(data)) {}
89 
90     explicit operator const T&() const { return fData; }
91 
Optional(const T * ptr)92     static const ParameterSpace<T>* Optional(const T* ptr) {
93         return static_cast<const ParameterSpace<T>*>(reinterpret_cast<const void*>(ptr));
94     }
95 private:
96     T fData;
97 };
98 
99 // DeviceSpace is a data-only wrapper around Skia's geometric types. It is similar to
100 // 'ParameterSpace' except that it is used to represent geometry that has been transformed or
101 // defined in the root device space (i.e. the final pixels of drawn content). Much of what SkCanvas
102 // tracks, such as its clip bounds are defined in this space and DeviceSpace provides a
103 // type-enforced mechanism for the canvas to pass that information into the image filtering system,
104 // using the Mapping of the filtering context.
105 template<typename T>
106 class DeviceSpace {
107 public:
108     DeviceSpace() = default;
DeviceSpace(const T & data)109     explicit DeviceSpace(const T& data) : fData(data) {}
DeviceSpace(T && data)110     explicit DeviceSpace(T&& data) : fData(std::move(data)) {}
111 
112     explicit operator const T&() const { return fData; }
113 
114 private:
115     T fData;
116 };
117 
118 // LayerSpace is a geometric wrapper that specifies the geometry is defined in the shared layer
119 // space where image filters are evaluated. For a given Context (and its Mapping), the image filter
120 // DAG operates in the same coordinate space. This space may be different from the local coordinate
121 // space that defined the image filter parameters (such as blur sigma), and it may be different
122 // from the total CTM of the SkCanvas.
123 //
124 // To encourage correct filter use and implementation, the bulk of filter logic should be performed
125 // in layer space (e.g. determining what portion of an input image to read, or what the output
126 // region is). LayerSpace specializations for the six common Skia math types (Sk[I]Rect, Sk[I]Point,
127 // and Sk[I]Size), and skif::[I]Vector (to allow vectors to be specialized separately from points))
128 // are provided that mimic their APIs but preserve the coordinate space and enforce type semantics.
129 template<typename T>
130 class LayerSpace {};
131 
132 // Layer-space specialization for integerized direction vectors.
133 template<>
134 class LayerSpace<IVector> {
135 public:
136     LayerSpace() = default;
LayerSpace(const IVector & geometry)137     explicit LayerSpace(const IVector& geometry) : fData(geometry) {}
LayerSpace(IVector && geometry)138     explicit LayerSpace(IVector&& geometry) : fData(std::move(geometry)) {}
139     explicit operator const IVector&() const { return fData; }
140 
SkIVector()141     explicit operator SkIVector() const { return SkIVector::Make(fData.fX, fData.fY); }
142 
x()143     int32_t x() const { return fData.fX; }
y()144     int32_t y() const { return fData.fY; }
145 
146     LayerSpace<IVector> operator-() const { return LayerSpace<IVector>({-fData.fX, -fData.fY}); }
147 
148     LayerSpace<IVector> operator+(const LayerSpace<IVector>& v) const {
149         LayerSpace<IVector> sum = *this;
150         sum += v;
151         return sum;
152     }
153     LayerSpace<IVector> operator-(const LayerSpace<IVector>& v) const {
154         LayerSpace<IVector> diff = *this;
155         diff -= v;
156         return diff;
157     }
158 
159     void operator+=(const LayerSpace<IVector>& v) {
160         fData.fX += v.fData.fX;
161         fData.fY += v.fData.fY;
162     }
163     void operator-=(const LayerSpace<IVector>& v) {
164         fData.fX -= v.fData.fX;
165         fData.fY -= v.fData.fY;
166     }
167 
168 private:
169     IVector fData;
170 };
171 
172 // Layer-space specialization for floating point direction vectors.
173 template<>
174 class LayerSpace<Vector> {
175 public:
176     LayerSpace() = default;
LayerSpace(const Vector & geometry)177     explicit LayerSpace(const Vector& geometry) : fData(geometry) {}
LayerSpace(Vector && geometry)178     explicit LayerSpace(Vector&& geometry) : fData(std::move(geometry)) {}
179     explicit operator const Vector&() const { return fData; }
180 
SkVector()181     explicit operator SkVector() const { return SkVector::Make(fData.fX, fData.fY); }
182 
x()183     SkScalar x() const { return fData.fX; }
y()184     SkScalar y() const { return fData.fY; }
185 
length()186     SkScalar length() const { return SkVector::Length(fData.fX, fData.fY); }
187 
188     LayerSpace<Vector> operator-() const { return LayerSpace<Vector>({-fData.fX, -fData.fY}); }
189 
190     LayerSpace<Vector> operator*(SkScalar s) const {
191         LayerSpace<Vector> scaled = *this;
192         scaled *= s;
193         return scaled;
194     }
195 
196     LayerSpace<Vector> operator+(const LayerSpace<Vector>& v) const {
197         LayerSpace<Vector> sum = *this;
198         sum += v;
199         return sum;
200     }
201     LayerSpace<Vector> operator-(const LayerSpace<Vector>& v) const {
202         LayerSpace<Vector> diff = *this;
203         diff -= v;
204         return diff;
205     }
206 
207     void operator*=(SkScalar s) {
208         fData.fX *= s;
209         fData.fY *= s;
210     }
211     void operator+=(const LayerSpace<Vector>& v) {
212         fData.fX += v.fData.fX;
213         fData.fY += v.fData.fY;
214     }
215     void operator-=(const LayerSpace<Vector>& v) {
216         fData.fX -= v.fData.fX;
217         fData.fY -= v.fData.fY;
218     }
219 
220     friend LayerSpace<Vector> operator*(SkScalar s, const LayerSpace<Vector>& b) {
221         return b * s;
222     }
223 
224 private:
225     Vector fData;
226 };
227 
228 // Layer-space specialization for integer 2D coordinates (treated as positions, not directions).
229 template<>
230 class LayerSpace<SkIPoint> {
231 public:
232     LayerSpace() = default;
LayerSpace(const SkIPoint & geometry)233     explicit LayerSpace(const SkIPoint& geometry)  : fData(geometry) {}
LayerSpace(SkIPoint && geometry)234     explicit LayerSpace(SkIPoint&& geometry) : fData(std::move(geometry)) {}
235     explicit operator const SkIPoint&() const { return fData; }
236 
237     // Parrot the SkIPoint API while preserving coordinate space.
x()238     int32_t x() const { return fData.fX; }
y()239     int32_t y() const { return fData.fY; }
240 
241     // Offsetting by direction vectors produce more points
242     LayerSpace<SkIPoint> operator+(const LayerSpace<IVector>& v) {
243         return LayerSpace<SkIPoint>(fData + SkIVector(v));
244     }
245     LayerSpace<SkIPoint> operator-(const LayerSpace<IVector>& v) {
246         return LayerSpace<SkIPoint>(fData - SkIVector(v));
247     }
248 
249     void operator+=(const LayerSpace<IVector>& v) {
250         fData += SkIVector(v);
251     }
252     void operator-=(const LayerSpace<IVector>& v) {
253         fData -= SkIVector(v);
254     }
255 
256     // Subtracting another point makes a direction between them
257     LayerSpace<IVector> operator-(const LayerSpace<SkIPoint>& p) {
258         return LayerSpace<IVector>(IVector(fData - p.fData));
259     }
260 
261     LayerSpace<IVector> operator-() const { return LayerSpace<IVector>({-fData.fX, -fData.fY}); }
262 
263 private:
264     SkIPoint fData;
265 };
266 
267 // Layer-space specialization for floating point 2D coordinates (treated as positions)
268 template<>
269 class LayerSpace<SkPoint> {
270 public:
271     LayerSpace() = default;
LayerSpace(const SkPoint & geometry)272     explicit LayerSpace(const SkPoint& geometry) : fData(geometry) {}
LayerSpace(SkPoint && geometry)273     explicit LayerSpace(SkPoint&& geometry) : fData(std::move(geometry)) {}
274     explicit operator const SkPoint&() const { return fData; }
275 
276     // Parrot the SkPoint API while preserving coordinate space.
x()277     SkScalar x() const { return fData.fX; }
y()278     SkScalar y() const { return fData.fY; }
279 
distanceToOrigin()280     SkScalar distanceToOrigin() const { return fData.distanceToOrigin(); }
281 
282     // Offsetting by direction vectors produce more points
283     LayerSpace<SkPoint> operator+(const LayerSpace<Vector>& v) {
284         return LayerSpace<SkPoint>(fData + SkVector(v));
285     }
286     LayerSpace<SkPoint> operator-(const LayerSpace<Vector>& v) {
287         return LayerSpace<SkPoint>(fData - SkVector(v));
288     }
289 
290     void operator+=(const LayerSpace<Vector>& v) {
291         fData += SkVector(v);
292     }
293     void operator-=(const LayerSpace<Vector>& v) {
294         fData -= SkVector(v);
295     }
296 
297     // Subtracting another point makes a direction between them
298     LayerSpace<Vector> operator-(const LayerSpace<SkPoint>& p) {
299         return LayerSpace<Vector>(Vector(fData - p.fData));
300     }
301 
302     LayerSpace<Vector> operator-() const { return LayerSpace<Vector>({-fData.fX, -fData.fY}); }
303 
304 private:
305     SkPoint fData;
306 };
307 
308 // Layer-space specialization for integer dimensions
309 template<>
310 class LayerSpace<SkISize> {
311 public:
312     LayerSpace() = default;
LayerSpace(const SkISize & geometry)313     explicit LayerSpace(const SkISize& geometry) : fData(geometry) {}
LayerSpace(SkISize && geometry)314     explicit LayerSpace(SkISize&& geometry) : fData(std::move(geometry)) {}
315     explicit operator const SkISize&() const { return fData; }
316 
width()317     int32_t width() const { return fData.width(); }
height()318     int32_t height() const { return fData.height(); }
319 
isEmpty()320     bool isEmpty() const { return fData.isEmpty(); }
321 
322 private:
323     SkISize fData;
324 };
325 
326 // Layer-space specialization for floating point dimensions
327 template<>
328 class LayerSpace<SkSize> {
329 public:
330     LayerSpace() = default;
LayerSpace(const SkSize & geometry)331     explicit LayerSpace(const SkSize& geometry) : fData(geometry) {}
LayerSpace(SkSize && geometry)332     explicit LayerSpace(SkSize&& geometry) : fData(std::move(geometry)) {}
333     explicit operator const SkSize&() const { return fData; }
334 
width()335     SkScalar width() const { return fData.width(); }
height()336     SkScalar height() const { return fData.height(); }
337 
isEmpty()338     bool isEmpty() const { return fData.isEmpty(); }
isZero()339     bool isZero() const { return fData.isZero(); }
340 
round()341     LayerSpace<SkISize> round() const { return LayerSpace<SkISize>(fData.toRound()); }
ceil()342     LayerSpace<SkISize> ceil() const { return LayerSpace<SkISize>(fData.toCeil()); }
floor()343     LayerSpace<SkISize> floor() const { return LayerSpace<SkISize>(fData.toFloor()); }
344 
345 private:
346     SkSize fData;
347 };
348 
349 // Layer-space specialization for axis-aligned integer bounding boxes.
350 template<>
351 class LayerSpace<SkIRect> {
352 public:
353     LayerSpace() = default;
LayerSpace(const SkIRect & geometry)354     explicit LayerSpace(const SkIRect& geometry) : fData(geometry) {}
LayerSpace(SkIRect && geometry)355     explicit LayerSpace(SkIRect&& geometry) : fData(std::move(geometry)) {}
LayerSpace(const SkISize & size)356     explicit LayerSpace(const SkISize& size) : fData(SkIRect::MakeSize(size)) {}
357     explicit operator const SkIRect&() const { return fData; }
358 
Empty()359     static LayerSpace<SkIRect> Empty() { return LayerSpace<SkIRect>(SkIRect::MakeEmpty()); }
360 
361     // Parrot the SkIRect API while preserving coord space
isEmpty()362     bool isEmpty() const { return fData.isEmpty(); }
contains(const LayerSpace<SkIRect> & r)363     bool contains(const LayerSpace<SkIRect>& r) const { return fData.contains(r.fData); }
364 
left()365     int32_t left() const { return fData.fLeft; }
top()366     int32_t top() const { return fData.fTop; }
right()367     int32_t right() const { return fData.fRight; }
bottom()368     int32_t bottom() const { return fData.fBottom; }
369 
width()370     int32_t width() const { return fData.width(); }
height()371     int32_t height() const { return fData.height(); }
372 
topLeft()373     LayerSpace<SkIPoint> topLeft() const { return LayerSpace<SkIPoint>(fData.topLeft()); }
size()374     LayerSpace<SkISize> size() const { return LayerSpace<SkISize>(fData.size()); }
375 
intersect(const LayerSpace<SkIRect> & r)376     bool intersect(const LayerSpace<SkIRect>& r) { return fData.intersect(r.fData); }
join(const LayerSpace<SkIRect> & r)377     void join(const LayerSpace<SkIRect>& r) { fData.join(r.fData); }
offset(const LayerSpace<IVector> & v)378     void offset(const LayerSpace<IVector>& v) { fData.offset(SkIVector(v)); }
outset(const LayerSpace<SkISize> & delta)379     void outset(const LayerSpace<SkISize>& delta) { fData.outset(delta.width(), delta.height()); }
380 
381 private:
382     SkIRect fData;
383 };
384 
385 // Layer-space specialization for axis-aligned float bounding boxes.
386 template<>
387 class LayerSpace<SkRect> {
388 public:
389     LayerSpace() = default;
LayerSpace(const SkRect & geometry)390     explicit LayerSpace(const SkRect& geometry) : fData(geometry) {}
LayerSpace(SkRect && geometry)391     explicit LayerSpace(SkRect&& geometry) : fData(std::move(geometry)) {}
LayerSpace(const LayerSpace<SkIRect> & rect)392     explicit LayerSpace(const LayerSpace<SkIRect>& rect) : fData(SkRect::Make(SkIRect(rect))) {}
393     explicit operator const SkRect&() const { return fData; }
394 
Empty()395     static LayerSpace<SkRect> Empty() { return LayerSpace<SkRect>(SkRect::MakeEmpty()); }
396 
397     // Parrot the SkRect API while preserving coord space and usage
isEmpty()398     bool isEmpty() const { return fData.isEmpty(); }
contains(const LayerSpace<SkRect> & r)399     bool contains(const LayerSpace<SkRect>& r) const { return fData.contains(r.fData); }
400 
left()401     SkScalar left() const { return fData.fLeft; }
top()402     SkScalar top() const { return fData.fTop; }
right()403     SkScalar right() const { return fData.fRight; }
bottom()404     SkScalar bottom() const { return fData.fBottom; }
405 
width()406     SkScalar width() const { return fData.width(); }
height()407     SkScalar height() const { return fData.height(); }
408 
topLeft()409     LayerSpace<SkPoint> topLeft() const {
410         return LayerSpace<SkPoint>(SkPoint::Make(fData.fLeft, fData.fTop));
411     }
size()412     LayerSpace<SkSize> size() const {
413         return LayerSpace<SkSize>(SkSize::Make(fData.width(), fData.height()));
414     }
415 
round()416     LayerSpace<SkIRect> round() const { return LayerSpace<SkIRect>(fData.round()); }
roundIn()417     LayerSpace<SkIRect> roundIn() const { return LayerSpace<SkIRect>(RoundIn(fData)); }
roundOut()418     LayerSpace<SkIRect> roundOut() const { return LayerSpace<SkIRect>(RoundOut(fData)); }
419 
intersect(const LayerSpace<SkRect> & r)420     bool intersect(const LayerSpace<SkRect>& r) { return fData.intersect(r.fData); }
join(const LayerSpace<SkRect> & r)421     void join(const LayerSpace<SkRect>& r) { fData.join(r.fData); }
offset(const LayerSpace<Vector> & v)422     void offset(const LayerSpace<Vector>& v) { fData.offset(SkVector(v)); }
outset(const LayerSpace<SkSize> & delta)423     void outset(const LayerSpace<SkSize>& delta) { fData.outset(delta.width(), delta.height()); }
424 
425 private:
426     SkRect fData;
427 };
428 
429 // A transformation that manipulates geometry in the layer-space coordinate system. Mathematically
430 // there's little difference from these matrices compared to what's stored in a skif::Mapping, but
431 // the intent differs. skif::Mapping's matrices map geometry from one coordinate space to another
432 // while these transforms move geometry w/o changing the coordinate space semantics.
433 // TODO(michaelludwig): Will be replaced with an SkM44 version when skif::Mapping works with SkM44.
434 template<>
435 class LayerSpace<SkMatrix> {
436 public:
437     LayerSpace() = default;
LayerSpace(const SkMatrix & m)438     explicit LayerSpace(const SkMatrix& m) : fData(m) {}
LayerSpace(SkMatrix && m)439     explicit LayerSpace(SkMatrix&& m) : fData(std::move(m)) {}
440     explicit operator const SkMatrix&() const { return fData; }
441 
442     // Parrot a limited selection of the SkMatrix API while preserving coordinate space.
443     LayerSpace<SkRect> mapRect(const LayerSpace<SkRect>& r) const;
444 
445     // Effectively mapRect(SkRect).roundOut() but more accurate when the underlying matrix or
446     // SkIRect has large floating point values.
447     LayerSpace<SkIRect> mapRect(const LayerSpace<SkIRect>& r) const;
448 
mapPoint(const LayerSpace<SkPoint> & p)449     LayerSpace<SkPoint> mapPoint(const LayerSpace<SkPoint>& p) const {
450         return LayerSpace<SkPoint>(fData.mapPoint(SkPoint(p)));
451     }
452 
mapVector(const LayerSpace<Vector> & v)453     LayerSpace<Vector> mapVector(const LayerSpace<Vector>& v) const {
454         return LayerSpace<Vector>(Vector(fData.mapVector(v.x(), v.y())));
455     }
456 
preConcat(const LayerSpace<SkMatrix> & m)457     LayerSpace<SkMatrix>& preConcat(const LayerSpace<SkMatrix>& m) {
458         fData = SkMatrix::Concat(fData, m.fData);
459         return *this;
460     }
461 
postConcat(const LayerSpace<SkMatrix> & m)462     LayerSpace<SkMatrix>& postConcat(const LayerSpace<SkMatrix>& m) {
463         fData = SkMatrix::Concat(m.fData, fData);
464         return *this;
465     }
466 
invert(LayerSpace<SkMatrix> * inverse)467     bool invert(LayerSpace<SkMatrix>* inverse) const {
468         return fData.invert(&inverse->fData);
469     }
470 
rc(int row,int col)471     float rc(int row, int col) const { return fData.rc(row, col); }
get(int i)472     float get(int i) const { return fData.get(i); }
473 
474 private:
475     SkMatrix fData;
476 };
477 
478 // Mapping is the primary definition of the shared layer space used when evaluating an image filter
479 // DAG. It encapsulates any needed decomposition of the total CTM into the parameter-to-layer matrix
480 // (that filters use to map their parameters to the layer space), and the layer-to-device matrix
481 // (that canvas uses to map the output layer-space image into its root device space). Mapping
482 // defines functions to transform ParameterSpace and DeviceSpace types to and from their LayerSpace
483 // variants, which can then be used and reasoned about by SkImageFilter implementations.
484 class Mapping {
485 public:
486     Mapping() = default;
487 
488     // Helper constructor that equates device and layer space to the same coordinate space.
Mapping(const SkMatrix & paramToLayer)489     explicit Mapping(const SkMatrix& paramToLayer)
490             : fLayerToDevMatrix(SkMatrix::I())
491             , fParamToLayerMatrix(paramToLayer)
492             , fDevToLayerMatrix(SkMatrix::I()) {}
493 
494     // This constructor allows the decomposition to be explicitly provided, assumes that
495     // 'layerToDev's inverse has already been calculated in 'devToLayer'
Mapping(const SkMatrix & layerToDev,const SkMatrix & devToLayer,const SkMatrix & paramToLayer)496     Mapping(const SkMatrix& layerToDev, const SkMatrix& devToLayer, const SkMatrix& paramToLayer)
497             : fLayerToDevMatrix(layerToDev)
498             , fParamToLayerMatrix(paramToLayer)
499             , fDevToLayerMatrix(devToLayer) {}
500 
501     // Sets this Mapping to the default decomposition of the canvas's total transform, given the
502     // requirements of the 'filter'. Returns false if the decomposition failed or would produce an
503     // invalid device matrix. Assumes 'ctm' is invertible.
504     bool SK_WARN_UNUSED_RESULT decomposeCTM(const SkMatrix& ctm,
505                                             const SkImageFilter* filter,
506                                             const skif::ParameterSpace<SkPoint>& representativePt);
507 
508     // Update the mapping's parameter-to-layer matrix to be pre-concatenated with the specified
509     // local space transformation. This changes the definition of parameter space, any
510     // skif::ParameterSpace<> values are interpreted anew. Layer space and device space are
511     // unchanged.
concatLocal(const SkMatrix & local)512     void concatLocal(const SkMatrix& local) { fParamToLayerMatrix.preConcat(local); }
513 
514     // Update the mapping's layer space coordinate system by post-concatenating the given matrix
515     // to it's parameter-to-layer transform, and pre-concatenating the inverse of the matrix with
516     // it's layer-to-device transform. The net effect is that neither the parameter nor device
517     // coordinate systems are changed, but skif::LayerSpace is adjusted.
518     //
519     // Returns false if the layer matrix cannot be inverted, and this mapping is left unmodified.
520     bool adjustLayerSpace(const SkMatrix& layer);
521 
522     // Update the mapping's layer space so that the point 'origin' in the current layer coordinate
523     // space maps to (0, 0) in the adjusted coordinate space.
applyOrigin(const LayerSpace<SkIPoint> & origin)524     void applyOrigin(const LayerSpace<SkIPoint>& origin) {
525         SkAssertResult(this->adjustLayerSpace(SkMatrix::Translate(-origin.x(), -origin.y())));
526     }
527 
layerToDevice()528     const SkMatrix& layerToDevice() const { return fLayerToDevMatrix; }
deviceToLayer()529     const SkMatrix& deviceToLayer() const { return fDevToLayerMatrix; }
layerMatrix()530     const SkMatrix& layerMatrix() const { return fParamToLayerMatrix; }
totalMatrix()531     SkMatrix totalMatrix() const {
532         return SkMatrix::Concat(fLayerToDevMatrix, fParamToLayerMatrix);
533     }
534 
535     template<typename T>
paramToLayer(const ParameterSpace<T> & paramGeometry)536     LayerSpace<T> paramToLayer(const ParameterSpace<T>& paramGeometry) const {
537         return LayerSpace<T>(map(static_cast<const T&>(paramGeometry), fParamToLayerMatrix));
538     }
539 
540     template<typename T>
deviceToLayer(const DeviceSpace<T> & devGeometry)541     LayerSpace<T> deviceToLayer(const DeviceSpace<T>& devGeometry) const {
542         return LayerSpace<T>(map(static_cast<const T&>(devGeometry), fDevToLayerMatrix));
543     }
544 
545     template<typename T>
layerToDevice(const LayerSpace<T> & layerGeometry)546     DeviceSpace<T> layerToDevice(const LayerSpace<T>& layerGeometry) const {
547         return DeviceSpace<T>(map(static_cast<const T&>(layerGeometry), fLayerToDevMatrix));
548     }
549 
550 private:
551     // The image filter process decomposes the total CTM into layerToDev * paramToLayer and uses the
552     // param-to-layer matrix to define the layer-space coordinate system. Depending on how it's
553     // decomposed, either the layer matrix or the device matrix could be the identity matrix (but
554     // sometimes neither).
555     SkMatrix fLayerToDevMatrix;
556     SkMatrix fParamToLayerMatrix;
557 
558     // Cached inverse of fLayerToDevMatrix
559     SkMatrix fDevToLayerMatrix;
560 
561     // Actual geometric mapping operations that work on coordinates and matrices w/o the type
562     // safety of the coordinate space wrappers (hence these are private).
563     template<typename T>
564     static T map(const T& geom, const SkMatrix& matrix);
565 };
566 
567 class Context; // Forward declare for FilterResult
568 
569 // Wraps an SkSpecialImage and metadata needed to rasterize it to a shared layer coordinate space.
570 // This includes a transform matrix, sampling options, and clip. Frequently, the transform is an
571 // integer translation that effectively places the origin of the image within the layer space. When
572 // this is the case, the FilterResult's layerBounds have the same width and height as the subset
573 // of the special image and translated to that origin. However, the transform of a FilterResult can
574 // be arbitrary, in which case its layer bounds is the bounding box that would contain the sampled
575 // image's contents.
576 //
577 // In order to collapse image filter nodes dynamically, FilterResult provides utilities to apply
578 // operations (like transform or crop) that attempt to modify the metadata without producing an
579 // intermediate image. Internally it tracks when a new image is needed and rasterizes as needed.
580 //
581 // When filter implementations are processing intermediate FilterResult results, it can be assumed
582 // that all FilterResult' layerBounds are in the same coordinate space defined by the shared
583 // skif::Context.
584 //
585 // NOTE: This is named FilterResult since most instances will represent the output of an image
586 // filter (even if that is then used as an input to the next filter). The main exception is the
587 // source input used when an input filter is null, but from a data-standpoint it is the same since
588 // it is equivalent to the result of an identity filter.
589 class FilterResult {
590     // Bilinear is used as the default because it can be downgraded to nearest-neighbor when the
591     // final transform is pixel-aligned, and chaining multiple bilinear samples and transforms is
592     // assumed to be visually close enough to sampling once at highest quality and final transform.
593     static constexpr SkSamplingOptions kDefaultSampling{SkFilterMode::kLinear};
594 public:
FilterResult()595     FilterResult() : FilterResult(nullptr) {}
596 
FilterResult(sk_sp<SkSpecialImage> image)597     explicit FilterResult(sk_sp<SkSpecialImage> image)
598             : FilterResult(std::move(image), LayerSpace<SkIPoint>({0, 0})) {}
599 
FilterResult(std::pair<sk_sp<SkSpecialImage>,LayerSpace<SkIPoint>> imageAndOrigin)600     FilterResult(std::pair<sk_sp<SkSpecialImage>, LayerSpace<SkIPoint>> imageAndOrigin)
601             : FilterResult(std::move(std::get<0>(imageAndOrigin)), std::get<1>(imageAndOrigin)) {}
602 
FilterResult(sk_sp<SkSpecialImage> image,const LayerSpace<SkIPoint> & origin)603     FilterResult(sk_sp<SkSpecialImage> image, const LayerSpace<SkIPoint>& origin)
604             : fImage(std::move(image))
605             , fSamplingOptions(kDefaultSampling)
606             , fTransform(SkMatrix::Translate(origin.x(), origin.y()))
607             , fLayerBounds(
608                     fTransform.mapRect(LayerSpace<SkIRect>(fImage ? fImage->dimensions()
609                                                                   : SkISize{0, 0}))) {}
610 
611     explicit operator bool() const { return SkToBool(fImage); }
612 
613     // TODO(michaelludwig): Given the planned expansion of FilterResult state, it might be nice to
614     // pull this back and not expose anything other than its bounding box. This will be possible if
615     // all rendering can be handled by functions defined on FilterResult.
image()616     const SkSpecialImage* image() const { return fImage.get(); }
refImage()617     sk_sp<SkSpecialImage> refImage() const { return fImage; }
618 
619     // Get the layer-space bounds of the result. This will incorporate any layer-space transform.
layerBounds()620     LayerSpace<SkIRect> layerBounds() const {
621         return fLayerBounds;
622     }
623 
624     // Produce a new FilterResult that has been cropped to 'crop', taking into account the context's
625     // desired output. When possible, the returned FilterResult will reuse the underlying image and
626     // adjust its metadata. This will depend on the current transform and tile mode as well as how
627     // the crop rect intersects this result's layer bounds.
628     // TODO (michaelludwig): All FilterResults are decal mode and there are no current usages that
629     // require force-padding a decal FilterResult so these arguments aren't implemented yet.
630     FilterResult applyCrop(const Context& ctx,
631                            const LayerSpace<SkIRect>& crop) const;
632                            //  SkTileMode newTileMode=SkTileMode::kDecal,
633                            //  bool forcePad=false) const;
634 
635     // Produce a new FilterResult that is the transformation of this FilterResult. When this
636     // result's sampling and transform are compatible with the new transformation, the returned
637     // FilterResult can reuse the same image data and adjust just the metadata.
638     FilterResult applyTransform(const Context& ctx,
639                                 const LayerSpace<SkMatrix>& transform,
640                                 const SkSamplingOptions& sampling) const;
641 
642     // Extract image and origin, safely when the image is null. If there are deferred operations
643     // on FilterResult (such as tiling or transforms) not representable as an image+origin pair,
644     // the returned image will be the resolution resulting from that metadata and not necessarily
645     // equal to the original 'image()'.
646     // TODO (michaelludwig) - This is intended for convenience until all call sites of
647     // SkImageFilter_Base::filterImage() have been updated to work in the new type system
648     // (which comes later as SkDevice, SkCanvas, etc. need to be modified, and coordinate space
649     // tagging needs to be added).
650     sk_sp<SkSpecialImage> imageAndOffset(SkIPoint* offset) const;
651 
652 private:
653     // Renders this FilterResult into a new, but visually equivalent, image that fills 'dstBounds',
654     // has nearest-neighbor sampling, and a transform that just translates by 'dstBounds' TL corner.
655     std::pair<sk_sp<SkSpecialImage>, LayerSpace<SkIPoint>>
656     resolve(LayerSpace<SkIRect> dstBounds) const;
657 
658     // Update metadata to concat the given transform directly.
659     void concatTransform(const LayerSpace<SkMatrix>& transform,
660                          const SkSamplingOptions& newSampling,
661                          const LayerSpace<SkIRect>& outputBounds);
662 
663     // The effective image of a FilterResult is 'fImage' sampled by 'fSamplingOptions' and
664     // respecting 'fTileMode' (on the SkSpecialImage's subset), transformed by 'fTransform', clipped
665     // to 'fLayerBounds'.
666     sk_sp<SkSpecialImage> fImage;
667     SkSamplingOptions     fSamplingOptions;
668     // SkTileMode         fTileMode = SkTileMode::kDecal;
669     // Typically this will be an integer translation that encodes the origin of the top left corner,
670     // but can become more complex when combined with applyTransform().
671     LayerSpace<SkMatrix>  fTransform;
672 
673     // The layer bounds are initially fImage's dimensions mapped by fTransform. As the filter result
674     // is processed by the image filter DAG, it can be further restricted by crop rects or the
675     // implicit desired output at each node.
676     LayerSpace<SkIRect>   fLayerBounds;
677 };
678 
679 // The context contains all necessary information to describe how the image filter should be
680 // computed (i.e. the current layer matrix and clip), and the color information of the output of a
681 // filter DAG. For now, this is just the color space (of the original requesting device). This is
682 // used when constructing intermediate rendering surfaces, so that we ensure we land in a surface
683 // that's similar/compatible to the final consumer of the DAG's output.
684 class Context {
685 public:
686     // Creates a context with the given layer matrix and destination clip, reading from 'source'
687     // with an origin of (0,0).
Context(const SkMatrix & layerMatrix,const SkIRect & clipBounds,SkImageFilterCache * cache,SkColorType colorType,SkColorSpace * colorSpace,const SkSpecialImage * source)688     Context(const SkMatrix& layerMatrix, const SkIRect& clipBounds, SkImageFilterCache* cache,
689             SkColorType colorType, SkColorSpace* colorSpace, const SkSpecialImage* source)
690         : fMapping(layerMatrix)
691         , fDesiredOutput(clipBounds)
692         , fCache(cache)
693         , fColorType(colorType)
694         , fColorSpace(colorSpace)
695         , fSource(sk_ref_sp(source), LayerSpace<SkIPoint>({0, 0})) {}
696 
Context(const Mapping & mapping,const LayerSpace<SkIRect> & desiredOutput,SkImageFilterCache * cache,SkColorType colorType,SkColorSpace * colorSpace,const FilterResult & source)697     Context(const Mapping& mapping, const LayerSpace<SkIRect>& desiredOutput,
698             SkImageFilterCache* cache, SkColorType colorType, SkColorSpace* colorSpace,
699             const FilterResult& source)
700         : fMapping(mapping)
701         , fDesiredOutput(desiredOutput)
702         , fCache(cache)
703         , fColorType(colorType)
704         , fColorSpace(colorSpace)
705         , fSource(source) {}
706 
707     // The mapping that defines the transformation from local parameter space of the filters to the
708     // layer space where the image filters are evaluated, as well as the remaining transformation
709     // from the layer space to the final device space. The layer space defined by the returned
710     // Mapping may be the same as the root device space, or be an intermediate space that is
711     // supported by the image filter DAG (depending on what it returns from getCTMCapability()).
712     // If a node returns something other than kComplex from getCTMCapability(), the layer matrix of
713     // the mapping will respect that return value, and the remaining matrix will be appropriately
714     // set to transform the layer space to the final device space (applied by the SkCanvas when
715     // filtering is finished).
mapping()716     const Mapping& mapping() const { return fMapping; }
717     // DEPRECATED: Use mapping() and its coordinate-space types instead
ctm()718     const SkMatrix& ctm() const { return fMapping.layerMatrix(); }
719     // The bounds, in the layer space, that the filtered image will be clipped to. The output
720     // from filterImage() must cover these clip bounds, except in areas where it will just be
721     // transparent black, in which case a smaller output image can be returned.
desiredOutput()722     const LayerSpace<SkIRect>& desiredOutput() const { return fDesiredOutput; }
723     // DEPRECATED: Use desiredOutput() instead
clipBounds()724     const SkIRect& clipBounds() const { return static_cast<const SkIRect&>(fDesiredOutput); }
725     // The cache to use when recursing through the filter DAG, in order to avoid repeated
726     // calculations of the same image.
cache()727     SkImageFilterCache* cache() const { return fCache; }
728     // The output device's color type, which can be used for intermediate images to be
729     // compatible with the eventual target of the filtered result.
colorType()730     SkColorType colorType() const { return fColorType; }
731 #if defined(SK_GANESH)
grColorType()732     GrColorType grColorType() const { return SkColorTypeToGrColorType(fColorType); }
733 #endif
734     // The output device's color space, so intermediate images can match, and so filtering can
735     // be performed in the destination color space.
colorSpace()736     SkColorSpace* colorSpace() const { return fColorSpace; }
refColorSpace()737     sk_sp<SkColorSpace> refColorSpace() const { return sk_ref_sp(fColorSpace); }
738     // The default surface properties to use when making transient surfaces during filtering.
surfaceProps()739     const SkSurfaceProps& surfaceProps() const { return fSource.image()->props(); }
740 
741     // This is the image to use whenever an expected input filter has been set to null. In the
742     // majority of cases, this is the original source image for the image filter DAG so it comes
743     // from the SkDevice that holds either the saveLayer or the temporary rendered result. The
744     // exception is composing two image filters (via SkImageFilters::Compose), which must use
745     // the output of the inner DAG as the "source" for the outer DAG.
source()746     const FilterResult& source() const { return fSource; }
747     // DEPRECATED: Use source() instead to get both the image and its origin.
sourceImage()748     const SkSpecialImage* sourceImage() const { return fSource.image(); }
749 
750     // True if image filtering should occur on the GPU if possible.
gpuBacked()751     bool gpuBacked() const { return fSource.image()->isTextureBacked(); }
752     // The recording context to use when computing the filter with the GPU.
getContext()753     GrRecordingContext* getContext() const { return fSource.image()->getContext(); }
754 
755     /**
756      *  Since a context can be built directly, its constructor has no chance to "return null" if
757      *  it's given invalid or unsupported inputs. Call this to know of the the context can be
758      *  used.
759      *
760      *  The SkImageFilterCache Key, for example, requires a finite ctm (no infinities or NaN),
761      *  so that test is part of isValid.
762      */
isValid()763     bool isValid() const { return fSource.image() != nullptr && fMapping.layerMatrix().isFinite(); }
764 
765     // Create a surface of the given size, that matches the context's color type and color space
766     // as closely as possible, and uses the same backend of the device that produced the source
767     // image.
768     sk_sp<SkSpecialSurface> makeSurface(const SkISize& size,
769                                         const SkSurfaceProps* props = nullptr) const {
770         if (!props) {
771              props = &this->surfaceProps();
772         }
773         return fSource.image()->makeSurface(fColorType, fColorSpace, size,
774                                             kPremul_SkAlphaType, *props);
775     }
776 
777     // Create a new context that matches this context, but with an overridden layer space.
withNewMapping(const Mapping & mapping)778     Context withNewMapping(const Mapping& mapping) const {
779         return Context(mapping, fDesiredOutput, fCache, fColorType, fColorSpace, fSource);
780     }
781     // Create a new context that matches this context, but with an overridden desired output rect.
withNewDesiredOutput(const LayerSpace<SkIRect> & desiredOutput)782     Context withNewDesiredOutput(const LayerSpace<SkIRect>& desiredOutput) const {
783         return Context(fMapping, desiredOutput, fCache, fColorType, fColorSpace, fSource);
784     }
785 
786 private:
787     Mapping             fMapping;
788     LayerSpace<SkIRect> fDesiredOutput;
789     SkImageFilterCache* fCache;
790     SkColorType         fColorType;
791     // The pointed-to object is owned by the device controlling the filter process, and our lifetime
792     // is bounded by the device, so this can be a bare pointer.
793     SkColorSpace*       fColorSpace;
794     FilterResult        fSource;
795 };
796 
797 } // end namespace skif
798 
799 #endif // SkImageFilterTypes_DEFINED
800