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/SkMatrix.h" |
12 | #include "src/core/SkSpecialImage.h" |
13 | #include "src/core/SkSpecialSurface.h" |
14 | |
15 | class GrRecordingContext; |
16 | class SkImageFilter; |
17 | class SkImageFilterCache; |
18 | class SkSpecialSurface; |
19 | class SkSurfaceProps; |
20 | |
21 | // The skif (SKI[mage]F[ilter]) namespace contains types that are used for filter implementations. |
22 | // The defined types come in two groups: users of internal Skia types, and templates to help with |
23 | // readability. Image filters cannot be implemented without access to key internal types, such as |
24 | // SkSpecialImage. It is possible to avoid the use of the readability templates, although they are |
25 | // strongly encouraged. |
26 | namespace skif { |
27 | |
28 | // skif::IVector and skif::Vector represent plain-old-data types for storing direction vectors, so |
29 | // that the coordinate-space templating system defined below can have a separate type id for |
30 | // directions vs. points, and specialize appropriately. As such, all operations with direction |
31 | // vectors are defined on the LayerSpace specialization, since that is the intended point of use. |
32 | struct IVector { |
33 | int32_t fX; |
34 | int32_t fY; |
35 | |
36 | IVector() = default; |
37 | IVector(int32_t x, int32_t y) : fX(x), fY(y) {} |
38 | explicit IVector(const SkIVector& v) : fX(v.fX), fY(v.fY) {} |
39 | }; |
40 | |
41 | struct Vector { |
42 | SkScalar fX; |
43 | SkScalar fY; |
44 | |
45 | Vector() = default; |
46 | Vector(SkScalar x, SkScalar y) : fX(x), fY(y) {} |
47 | explicit Vector(const SkVector& v) : fX(v.fX), fY(v.fY) {} |
48 | }; |
49 | |
50 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
51 | // Coordinate Space Tagging |
52 | // - In order to enforce correct coordinate spaces in image filter implementations and use, |
53 | // geometry is wrapped by templated structs to declare in the type system what coordinate space |
54 | // the coordinates are defined in. |
55 | // - Currently there is ParameterSpace and DeviceSpace that are data-only wrappers around |
56 | // coordinates, and the primary LayerSpace that provides all operative functionality for image |
57 | // filters. It is intended that all logic about image bounds and access be conducted in the shared |
58 | // layer space. |
59 | // - The LayerSpace struct has type-safe specializations for SkIRect, SkRect, SkIPoint, SkPoint, |
60 | // skif::IVector (to distinguish SkIVector from SkIPoint), skif::Vector, SkISize, and SkSize. |
61 | // - A Mapping object provides type safe coordinate conversions between these spaces, and |
62 | // automatically does the "right thing" for each geometric type. |
63 | /////////////////////////////////////////////////////////////////////////////////////////////////// |
64 | |
65 | // ParameterSpace is a data-only wrapper around Skia's geometric types such as SkIPoint, and SkRect. |
66 | // Parameter space is the same as the local coordinate space of an SkShader, or the coordinates |
67 | // passed into SkCanvas::drawX calls, but "local" is avoided due to the alliteration with layer |
68 | // space. SkImageFilters are defined in terms of ParameterSpace<T> geometry and must use the Mapping |
69 | // on Context to transform the parameters into LayerSpace to evaluate the filter in the shared |
70 | // coordinate space of the entire filter DAG. |
71 | // |
72 | // A value of ParameterSpace<SkIRect> implies that its wrapped SkIRect is defined in the local |
73 | // parameter space. |
74 | template<typename T> |
75 | class ParameterSpace { |
76 | public: |
77 | explicit ParameterSpace(const T& data) : fData(data) {} |
78 | explicit ParameterSpace(T&& data) : fData(std::move(data)) {} |
79 | |
80 | explicit operator const T&() const { return fData; } |
81 | |
82 | private: |
83 | T fData; |
84 | }; |
85 | |
86 | // DeviceSpace is a data-only wrapper around Skia's geometric types. It is similar to |
87 | // 'ParameterSpace' except that it is used to represent geometry that has been transformed or |
88 | // defined in the root device space (i.e. the final pixels of drawn content). Much of what SkCanvas |
89 | // tracks, such as its clip bounds are defined in this space and DeviceSpace provides a |
90 | // type-enforced mechanism for the canvas to pass that information into the image filtering system, |
91 | // using the Mapping of the filtering context. |
92 | template<typename T> |
93 | class DeviceSpace { |
94 | public: |
95 | explicit DeviceSpace(const T& data) : fData(data) {} |
96 | explicit DeviceSpace(T&& data) : fData(std::move(data)) {} |
97 | |
98 | explicit operator const T&() const { return fData; } |
99 | |
100 | private: |
101 | T fData; |
102 | }; |
103 | |
104 | // LayerSpace is a geometric wrapper that specifies the geometry is defined in the shared layer |
105 | // space where image filters are evaluated. For a given Context (and its Mapping), the image filter |
106 | // DAG operates in the same coordinate space. This space may be different from the local coordinate |
107 | // space that defined the image filter parameters (such as blur sigma), and it may be different |
108 | // from the total CTM of the SkCanvas. |
109 | // |
110 | // To encourage correct filter use and implementation, the bulk of filter logic should be performed |
111 | // in layer space (e.g. determining what portion of an input image to read, or what the output |
112 | // region is). LayerSpace specializations for the six common Skia math types (Sk[I]Rect, Sk[I]Point, |
113 | // and Sk[I]Size), and skif::[I]Vector (to allow vectors to be specialized separately from points)) |
114 | // are provided that mimic their APIs but preserve the coordinate space and enforce type semantics. |
115 | template<typename T> |
116 | class LayerSpace {}; |
117 | |
118 | // Layer-space specialization for integerized direction vectors. |
119 | template<> |
120 | class LayerSpace<IVector> { |
121 | public: |
122 | LayerSpace() = default; |
123 | explicit LayerSpace(const IVector& geometry) : fData(geometry) {} |
124 | explicit LayerSpace(IVector&& geometry) : fData(std::move(geometry)) {} |
125 | explicit operator const IVector&() const { return fData; } |
126 | |
127 | explicit operator SkIVector() const { return SkIVector::Make(fData.fX, fData.fY); } |
128 | |
129 | int32_t x() const { return fData.fX; } |
130 | int32_t y() const { return fData.fY; } |
131 | |
132 | LayerSpace<IVector> operator-() const { return LayerSpace<IVector>({-fData.fX, -fData.fY}); } |
133 | |
134 | LayerSpace<IVector> operator+(const LayerSpace<IVector>& v) const { |
135 | LayerSpace<IVector> sum = *this; |
136 | sum += v; |
137 | return sum; |
138 | } |
139 | LayerSpace<IVector> operator-(const LayerSpace<IVector>& v) const { |
140 | LayerSpace<IVector> diff = *this; |
141 | diff -= v; |
142 | return diff; |
143 | } |
144 | |
145 | void operator+=(const LayerSpace<IVector>& v) { |
146 | fData.fX += v.fData.fX; |
147 | fData.fY += v.fData.fY; |
148 | } |
149 | void operator-=(const LayerSpace<IVector>& v) { |
150 | fData.fX -= v.fData.fX; |
151 | fData.fY -= v.fData.fY; |
152 | } |
153 | |
154 | private: |
155 | IVector fData; |
156 | }; |
157 | |
158 | // Layer-space specialization for floating point direction vectors. |
159 | template<> |
160 | class LayerSpace<Vector> { |
161 | public: |
162 | LayerSpace() = default; |
163 | explicit LayerSpace(const Vector& geometry) : fData(geometry) {} |
164 | explicit LayerSpace(Vector&& geometry) : fData(std::move(geometry)) {} |
165 | explicit operator const Vector&() const { return fData; } |
166 | |
167 | explicit operator SkVector() const { return SkVector::Make(fData.fX, fData.fY); } |
168 | |
169 | SkScalar x() const { return fData.fX; } |
170 | SkScalar y() const { return fData.fY; } |
171 | |
172 | SkScalar length() const { return SkVector::Length(fData.fX, fData.fY); } |
173 | |
174 | LayerSpace<Vector> operator-() const { return LayerSpace<Vector>({-fData.fX, -fData.fY}); } |
175 | |
176 | LayerSpace<Vector> operator*(SkScalar s) const { |
177 | LayerSpace<Vector> scaled = *this; |
178 | scaled *= s; |
179 | return scaled; |
180 | } |
181 | |
182 | LayerSpace<Vector> operator+(const LayerSpace<Vector>& v) const { |
183 | LayerSpace<Vector> sum = *this; |
184 | sum += v; |
185 | return sum; |
186 | } |
187 | LayerSpace<Vector> operator-(const LayerSpace<Vector>& v) const { |
188 | LayerSpace<Vector> diff = *this; |
189 | diff -= v; |
190 | return diff; |
191 | } |
192 | |
193 | void operator*=(SkScalar s) { |
194 | fData.fX *= s; |
195 | fData.fY *= s; |
196 | } |
197 | void operator+=(const LayerSpace<Vector>& v) { |
198 | fData.fX += v.fData.fX; |
199 | fData.fY += v.fData.fY; |
200 | } |
201 | void operator-=(const LayerSpace<Vector>& v) { |
202 | fData.fX -= v.fData.fX; |
203 | fData.fY -= v.fData.fY; |
204 | } |
205 | |
206 | friend LayerSpace<Vector> operator*(SkScalar s, const LayerSpace<Vector>& b) { |
207 | return b * s; |
208 | } |
209 | |
210 | private: |
211 | Vector fData; |
212 | }; |
213 | |
214 | // Layer-space specialization for integer 2D coordinates (treated as positions, not directions). |
215 | template<> |
216 | class LayerSpace<SkIPoint> { |
217 | public: |
218 | LayerSpace() = default; |
219 | explicit LayerSpace(const SkIPoint& geometry) : fData(geometry) {} |
220 | explicit LayerSpace(SkIPoint&& geometry) : fData(std::move(geometry)) {} |
221 | explicit operator const SkIPoint&() const { return fData; } |
222 | |
223 | // Parrot the SkIPoint API while preserving coordinate space. |
224 | int32_t x() const { return fData.fX; } |
225 | int32_t y() const { return fData.fY; } |
226 | |
227 | // Offsetting by direction vectors produce more points |
228 | LayerSpace<SkIPoint> operator+(const LayerSpace<IVector>& v) { |
229 | return LayerSpace<SkIPoint>(fData + SkIVector(v)); |
230 | } |
231 | LayerSpace<SkIPoint> operator-(const LayerSpace<IVector>& v) { |
232 | return LayerSpace<SkIPoint>(fData - SkIVector(v)); |
233 | } |
234 | |
235 | void operator+=(const LayerSpace<IVector>& v) { |
236 | fData += SkIVector(v); |
237 | } |
238 | void operator-=(const LayerSpace<IVector>& v) { |
239 | fData -= SkIVector(v); |
240 | } |
241 | |
242 | // Subtracting another point makes a direction between them |
243 | LayerSpace<IVector> operator-(const LayerSpace<SkIPoint>& p) { |
244 | return LayerSpace<IVector>(IVector(fData - p.fData)); |
245 | } |
246 | |
247 | private: |
248 | SkIPoint fData; |
249 | }; |
250 | |
251 | // Layer-space specialization for floating point 2D coordinates (treated as positions) |
252 | template<> |
253 | class LayerSpace<SkPoint> { |
254 | public: |
255 | LayerSpace() = default; |
256 | explicit LayerSpace(const SkPoint& geometry) : fData(geometry) {} |
257 | explicit LayerSpace(SkPoint&& geometry) : fData(std::move(geometry)) {} |
258 | explicit operator const SkPoint&() const { return fData; } |
259 | |
260 | // Parrot the SkPoint API while preserving coordinate space. |
261 | SkScalar x() const { return fData.fX; } |
262 | SkScalar y() const { return fData.fY; } |
263 | |
264 | SkScalar distanceToOrigin() const { return fData.distanceToOrigin(); } |
265 | |
266 | // Offsetting by direction vectors produce more points |
267 | LayerSpace<SkPoint> operator+(const LayerSpace<Vector>& v) { |
268 | return LayerSpace<SkPoint>(fData + SkVector(v)); |
269 | } |
270 | LayerSpace<SkPoint> operator-(const LayerSpace<Vector>& v) { |
271 | return LayerSpace<SkPoint>(fData - SkVector(v)); |
272 | } |
273 | |
274 | void operator+=(const LayerSpace<Vector>& v) { |
275 | fData += SkVector(v); |
276 | } |
277 | void operator-=(const LayerSpace<Vector>& v) { |
278 | fData -= SkVector(v); |
279 | } |
280 | |
281 | // Subtracting another point makes a direction between them |
282 | LayerSpace<Vector> operator-(const LayerSpace<SkPoint>& p) { |
283 | return LayerSpace<Vector>(Vector(fData - p.fData)); |
284 | } |
285 | |
286 | private: |
287 | SkPoint fData; |
288 | }; |
289 | |
290 | // Layer-space specialization for integer dimensions |
291 | template<> |
292 | class LayerSpace<SkISize> { |
293 | public: |
294 | LayerSpace() = default; |
295 | explicit LayerSpace(const SkISize& geometry) : fData(geometry) {} |
296 | explicit LayerSpace(SkISize&& geometry) : fData(std::move(geometry)) {} |
297 | explicit operator const SkISize&() const { return fData; } |
298 | |
299 | int32_t width() const { return fData.width(); } |
300 | int32_t height() const { return fData.height(); } |
301 | |
302 | bool isEmpty() const { return fData.isEmpty(); } |
303 | |
304 | private: |
305 | SkISize fData; |
306 | }; |
307 | |
308 | // Layer-space specialization for floating point dimensions |
309 | template<> |
310 | class LayerSpace<SkSize> { |
311 | public: |
312 | LayerSpace() = default; |
313 | explicit LayerSpace(const SkSize& geometry) : fData(geometry) {} |
314 | explicit LayerSpace(SkSize&& geometry) : fData(std::move(geometry)) {} |
315 | explicit operator const SkSize&() const { return fData; } |
316 | |
317 | SkScalar width() const { return fData.width(); } |
318 | SkScalar height() const { return fData.height(); } |
319 | |
320 | bool isEmpty() const { return fData.isEmpty(); } |
321 | bool isZero() const { return fData.isZero(); } |
322 | |
323 | LayerSpace<SkISize> round() const { return LayerSpace<SkISize>(fData.toRound()); } |
324 | LayerSpace<SkISize> ceil() const { return LayerSpace<SkISize>(fData.toCeil()); } |
325 | LayerSpace<SkISize> floor() const { return LayerSpace<SkISize>(fData.toFloor()); } |
326 | |
327 | private: |
328 | SkSize fData; |
329 | }; |
330 | |
331 | // Layer-space specialization for axis-aligned integer bounding boxes. |
332 | template<> |
333 | class LayerSpace<SkIRect> { |
334 | public: |
335 | LayerSpace() = default; |
336 | explicit LayerSpace(const SkIRect& geometry) : fData(geometry) {} |
337 | explicit LayerSpace(SkIRect&& geometry) : fData(std::move(geometry)) {} |
338 | explicit operator const SkIRect&() const { return fData; } |
339 | |
340 | // Parrot the SkIRect API while preserving coord space |
341 | int32_t left() const { return fData.fLeft; } |
342 | int32_t top() const { return fData.fTop; } |
343 | int32_t right() const { return fData.fRight; } |
344 | int32_t bottom() const { return fData.fBottom; } |
345 | |
346 | int32_t width() const { return fData.width(); } |
347 | int32_t height() const { return fData.height(); } |
348 | |
349 | LayerSpace<SkIPoint> topLeft() const { return LayerSpace<SkIPoint>(fData.topLeft()); } |
350 | LayerSpace<SkISize> size() const { return LayerSpace<SkISize>(fData.size()); } |
351 | |
352 | bool intersect(const LayerSpace<SkIRect>& r) { return fData.intersect(r.fData); } |
353 | void join(const LayerSpace<SkIRect>& r) { fData.join(r.fData); } |
354 | void offset(const LayerSpace<IVector>& v) { fData.offset(SkIVector(v)); } |
355 | void outset(const LayerSpace<SkISize>& delta) { fData.outset(delta.width(), delta.height()); } |
356 | |
357 | private: |
358 | SkIRect fData; |
359 | }; |
360 | |
361 | // Layer-space specialization for axis-aligned float bounding boxes. |
362 | template<> |
363 | class LayerSpace<SkRect> { |
364 | public: |
365 | LayerSpace() = default; |
366 | explicit LayerSpace(const SkRect& geometry) : fData(geometry) {} |
367 | explicit LayerSpace(SkRect&& geometry) : fData(std::move(geometry)) {} |
368 | explicit operator const SkRect&() const { return fData; } |
369 | |
370 | // Parrot the SkRect API while preserving coord space and usage |
371 | SkScalar left() const { return fData.fLeft; } |
372 | SkScalar top() const { return fData.fTop; } |
373 | SkScalar right() const { return fData.fRight; } |
374 | SkScalar bottom() const { return fData.fBottom; } |
375 | |
376 | SkScalar width() const { return fData.width(); } |
377 | SkScalar height() const { return fData.height(); } |
378 | |
379 | LayerSpace<SkPoint> topLeft() const { |
380 | return LayerSpace<SkPoint>(SkPoint::Make(fData.fLeft, fData.fTop)); |
381 | } |
382 | LayerSpace<SkSize> size() const { |
383 | return LayerSpace<SkSize>(SkSize::Make(fData.width(), fData.height())); |
384 | } |
385 | LayerSpace<SkIRect> roundOut() const { return LayerSpace<SkIRect>(fData.roundOut()); } |
386 | |
387 | bool intersect(const LayerSpace<SkRect>& r) { return fData.intersect(r.fData); } |
388 | void join(const LayerSpace<SkRect>& r) { fData.join(r.fData); } |
389 | void offset(const LayerSpace<Vector>& v) { fData.offset(SkVector(v)); } |
390 | void outset(const LayerSpace<SkSize>& delta) { fData.outset(delta.width(), delta.height()); } |
391 | |
392 | private: |
393 | SkRect fData; |
394 | }; |
395 | |
396 | // Mapping is the primary definition of the shared layer space used when evaluating an image filter |
397 | // DAG. It encapsulates any needed decomposition of the total CTM into the parameter-to-layer matrix |
398 | // (that filters use to map their parameters to the layer space), and the layer-to-device matrix |
399 | // (that canvas uses to map the output layer-space image into its root device space). Mapping |
400 | // defines functions to transform ParameterSpace and DeviceSpace types to and from their LayerSpace |
401 | // variants, which can then be used and reasoned about by SkImageFilter implementations. |
402 | class Mapping { |
403 | public: |
404 | // This constructor allows the decomposition to be explicitly provided |
405 | Mapping(const SkMatrix& layerToDev, const SkMatrix& paramToLayer) |
406 | : fLayerToDevMatrix(layerToDev) |
407 | , fParamToLayerMatrix(paramToLayer) {} |
408 | |
409 | // Make the default decomposition Mapping, given the total CTM and the root image filter. |
410 | static Mapping Make(const SkMatrix& ctm, const SkImageFilter* filter); |
411 | |
412 | // Return a new Mapping object whose parameter-to-layer matrix is equal to this->layerMatrix() * |
413 | // local, but both share the same layer-to-device matrix. |
414 | Mapping concatLocal(const SkMatrix& local) const { |
415 | return Mapping(fLayerToDevMatrix, SkMatrix::Concat(fParamToLayerMatrix, local)); |
416 | } |
417 | |
418 | const SkMatrix& deviceMatrix() const { return fLayerToDevMatrix; } |
419 | const SkMatrix& layerMatrix() const { return fParamToLayerMatrix; } |
420 | SkMatrix totalMatrix() const { |
421 | return SkMatrix::Concat(fLayerToDevMatrix, fParamToLayerMatrix); |
422 | } |
423 | |
424 | template<typename T> |
425 | LayerSpace<T> paramToLayer(const ParameterSpace<T>& paramGeometry) const { |
426 | return LayerSpace<T>(map(static_cast<const T&>(paramGeometry), fParamToLayerMatrix)); |
427 | } |
428 | |
429 | template<typename T> |
430 | LayerSpace<T> deviceToLayer(const DeviceSpace<T>& devGeometry) const { |
431 | // The mapping from device space to layer space is defined by the inverse of the |
432 | // layer-to-device matrix |
433 | SkMatrix devToLayerMatrix; |
434 | if (!fLayerToDevMatrix.invert(&devToLayerMatrix)) { |
435 | // Punt and just pass through the geometry unmodified... |
436 | return LayerSpace<T>(static_cast<const T&>(devGeometry)); |
437 | } else { |
438 | return LayerSpace<T>(map(static_cast<const T&>(devGeometry), devToLayerMatrix)); |
439 | } |
440 | } |
441 | |
442 | template<typename T> |
443 | DeviceSpace<T> layerToDevice(const LayerSpace<T>& layerGeometry) const { |
444 | return DeviceSpace<T>(map(static_cast<const T&>(layerGeometry), fLayerToDevMatrix)); |
445 | } |
446 | |
447 | private: |
448 | // The image filter process decomposes the total CTM into layerToDev * paramToLayer and uses the |
449 | // param-to-layer matrix to define the layer-space coordinate system. Depending on how it's |
450 | // decomposed, either the layer matrix or the device matrix could be the identity matrix (but |
451 | // sometimes neither). |
452 | SkMatrix fLayerToDevMatrix; |
453 | SkMatrix fParamToLayerMatrix; |
454 | |
455 | // Actual geometric mapping operations that work on coordinates and matrices w/o the type |
456 | // safety of the coordinate space wrappers (hence these are private). |
457 | template<typename T> |
458 | static T map(const T& geom, const SkMatrix& matrix); |
459 | }; |
460 | |
461 | // Usage is a template tag to improve the readability of filter implementations. It is attached to |
462 | // images and geometry to group a collection of related variables and ensure that moving from one |
463 | // use case to another is explicit. |
464 | // NOTE: This can be aliased as 'For' when using the fluent type names. |
465 | // TODO (michaelludwig) - If the primary motivation for Usage--enforcing layer to image space |
466 | // transformations safely when multiple images are involved--can be handled entirely by helper |
467 | // functions on FilterResult, then Usage can go away and FilterResult will not need to be templated |
468 | enum class Usage { |
469 | // Designates the semantic purpose of the bounds, coordinate, or image as being an input |
470 | // to the image filter calculations. When this usage is used, it denotes a generic input, |
471 | // such as the current input in a dynamic loop, or some aggregate of all inputs. Because |
472 | // most image filters consume 1 or 2 filters only, the related kInput0 and kInput1 are |
473 | // also defined. |
474 | kInput, |
475 | // A more specific version of kInput, this marks the tagged variable as attached to the |
476 | // image filter of SkImageFilter_Base::getInput(0). |
477 | kInput0, |
478 | // A more specific version of kInput, this marks the tagged variable as attached to the |
479 | // image filter of SkImageFilter_Base::getInput(1). |
480 | kInput1, |
481 | // Designates the purpose of the bounds, coordinate, or image as being the output of the |
482 | // current image filter calculation. There is only ever one output for an image filter. |
483 | kOutput, |
484 | }; |
485 | |
486 | // Convenience macros to add 'using' declarations that rename the above enums to provide a more |
487 | // fluent and readable API. This should only be used in a private or local scope to prevent leakage |
488 | // of the names. Use the IN_CLASS variant at the start of a class declaration in those scenarios. |
489 | // These macros enable the following simpler type names: |
490 | // skif::Image<skif::Usage::kInput> -> Image<For::kInput> |
491 | #define SK_USE_FLUENT_IMAGE_FILTER_TYPES \ |
492 | using For = skif::Usage; |
493 | |
494 | #define SK_USE_FLUENT_IMAGE_FILTER_TYPES_IN_CLASS \ |
495 | protected: SK_USE_FLUENT_IMAGE_FILTER_TYPES public: |
496 | |
497 | // Wraps an SkSpecialImage and tags it with a corresponding usage, either as generic input (e.g. the |
498 | // source image), or a specific input image from a filter's connected inputs. It also includes the |
499 | // origin of the image in the layer space. This origin is used to draw the image in the correct |
500 | // location. The 'layerBounds' rectangle of the filtered image is the layer-space bounding box of |
501 | // the image. It has its top left corner at 'origin' and has the same dimensions as the underlying |
502 | // special image subset. Transforming 'layerBounds' by the Context's layer matrix and painting it |
503 | // with the subset rectangle will display the filtered results in the appropriate device-space |
504 | // region. |
505 | // |
506 | // When filter implementations are processing intermediate FilterResult results, it can be assumed |
507 | // that all FilterResult' layerBounds are in the same layer coordinate space defined by the shared |
508 | // skif::Context. |
509 | // |
510 | // NOTE: This is named FilterResult since most instances will represent the output of an image |
511 | // filter (even if that is then cast to be the input to the next filter). The main exception is the |
512 | // source input used when an input filter is null, but from a data-standpoint it is the same since |
513 | // it is equivalent to the result of an identity filter. |
514 | template<Usage kU> |
515 | class FilterResult { |
516 | public: |
517 | FilterResult() : fImage(nullptr), fOrigin(SkIPoint::Make(0, 0)) {} |
518 | |
519 | FilterResult(sk_sp<SkSpecialImage> image, const LayerSpace<SkIPoint>& origin) |
520 | : fImage(std::move(image)) |
521 | , fOrigin(origin) {} |
522 | |
523 | // Allow explicit moves/copies in order to cast from one use type to another, except kInput0 |
524 | // and kInput1 can only be cast to kOutput (e.g. as part of a noop image filter). |
525 | template<Usage kI> |
526 | explicit FilterResult(FilterResult<kI>&& image) |
527 | : fImage(std::move(image.fImage)) |
528 | , fOrigin(image.fOrigin) { |
529 | static_assert((kU != Usage::kInput) || (kI != Usage::kInput0 && kI != Usage::kInput1), |
530 | "kInput0 and kInput1 cannot be moved to more generic kInput usage." ); |
531 | static_assert((kU != Usage::kInput0 && kU != Usage::kInput1) || |
532 | (kI == kU || kI == Usage::kInput || kI == Usage::kOutput), |
533 | "Can only move to specific input from the generic kInput or kOutput usage." ); |
534 | } |
535 | |
536 | template<Usage kI> |
537 | explicit FilterResult(const FilterResult<kI>& image) |
538 | : fImage(image.fImage) |
539 | , fOrigin(image.fOrigin) { |
540 | static_assert((kU != Usage::kInput) || (kI != Usage::kInput0 && kI != Usage::kInput1), |
541 | "kInput0 and kInput1 cannot be copied to more generic kInput usage." ); |
542 | static_assert((kU != Usage::kInput0 && kU != Usage::kInput1) || |
543 | (kI == kU || kI == Usage::kInput || kI == Usage::kOutput), |
544 | "Can only copy to specific input from the generic kInput usage." ); |
545 | } |
546 | |
547 | const SkSpecialImage* image() const { return fImage.get(); } |
548 | sk_sp<SkSpecialImage> refImage() const { return fImage; } |
549 | |
550 | // Get the layer-space bounds of the result. This will have the same dimensions as the |
551 | // image and its top left corner will be 'origin()'. |
552 | LayerSpace<SkIRect> layerBounds() const { |
553 | return LayerSpace<SkIRect>(SkIRect::MakeXYWH(fOrigin.x(), fOrigin.y(), |
554 | fImage->width(), fImage->height())); |
555 | } |
556 | |
557 | // Get the layer-space coordinate of this image's top left pixel. |
558 | const LayerSpace<SkIPoint>& layerOrigin() const { return fOrigin; } |
559 | |
560 | // Extract image and origin, safely when the image is null. |
561 | // TODO (michaelludwig) - This is intended for convenience until all call sites of |
562 | // SkImageFilter_Base::filterImage() have been updated to work in the new type system |
563 | // (which comes later as SkDevice, SkCanvas, etc. need to be modified, and coordinate space |
564 | // tagging needs to be added). |
565 | sk_sp<SkSpecialImage> imageAndOffset(SkIPoint* offset) const { |
566 | if (fImage) { |
567 | *offset = SkIPoint(fOrigin); |
568 | return fImage; |
569 | } else { |
570 | *offset = {0, 0}; |
571 | return nullptr; |
572 | } |
573 | } |
574 | |
575 | private: |
576 | // Allow all FilterResult templates access to each others members |
577 | template<Usage kO> |
578 | friend class FilterResult; |
579 | |
580 | sk_sp<SkSpecialImage> fImage; |
581 | LayerSpace<SkIPoint> fOrigin; |
582 | }; |
583 | |
584 | // The context contains all necessary information to describe how the image filter should be |
585 | // computed (i.e. the current layer matrix and clip), and the color information of the output of a |
586 | // filter DAG. For now, this is just the color space (of the original requesting device). This is |
587 | // used when constructing intermediate rendering surfaces, so that we ensure we land in a surface |
588 | // that's similar/compatible to the final consumer of the DAG's output. |
589 | class Context { |
590 | public: |
591 | SK_USE_FLUENT_IMAGE_FILTER_TYPES_IN_CLASS |
592 | |
593 | // Creates a context with the given layer matrix and destination clip, reading from 'source' |
594 | // with an origin of (0,0). |
595 | Context(const SkMatrix& layerMatrix, const SkIRect& clipBounds, SkImageFilterCache* cache, |
596 | SkColorType colorType, SkColorSpace* colorSpace, const SkSpecialImage* source) |
597 | : fMapping(SkMatrix::I(), layerMatrix) |
598 | , fDesiredOutput(clipBounds) |
599 | , fCache(cache) |
600 | , fColorType(colorType) |
601 | , fColorSpace(colorSpace) |
602 | , fSource(sk_ref_sp(source), LayerSpace<SkIPoint>({0, 0})) {} |
603 | |
604 | Context(const Mapping& mapping, const LayerSpace<SkIRect>& desiredOutput, |
605 | SkImageFilterCache* cache, SkColorType colorType, SkColorSpace* colorSpace, |
606 | const FilterResult<For::kInput>& source) |
607 | : fMapping(mapping) |
608 | , fDesiredOutput(desiredOutput) |
609 | , fCache(cache) |
610 | , fColorType(colorType) |
611 | , fColorSpace(colorSpace) |
612 | , fSource(source) {} |
613 | |
614 | // The mapping that defines the transformation from local parameter space of the filters to the |
615 | // layer space where the image filters are evaluated, as well as the remaining transformation |
616 | // from the layer space to the final device space. The layer space defined by the returned |
617 | // Mapping may be the same as the root device space, or be an intermediate space that is |
618 | // supported by the image filter DAG (depending on what it returns from canHandleComplexCTM()). |
619 | // If a node returns false from canHandleComplexCTM(), the layer matrix of the mapping will be |
620 | // at most a scale + translate, and the remaining matrix will be appropriately set to transform |
621 | // the layer space to the final device space (applied by the SkCanvas when filtering is |
622 | // finished). |
623 | const Mapping& mapping() const { return fMapping; } |
624 | // DEPRECATED: Use mapping() and its coordinate-space types instead |
625 | const SkMatrix& ctm() const { return fMapping.layerMatrix(); } |
626 | // The bounds, in the layer space, that the filtered image will be clipped to. The output |
627 | // from filterImage() must cover these clip bounds, except in areas where it will just be |
628 | // transparent black, in which case a smaller output image can be returned. |
629 | const LayerSpace<SkIRect>& desiredOutput() const { return fDesiredOutput; } |
630 | // DEPRECATED: Use desiredOutput() instead |
631 | const SkIRect& clipBounds() const { return static_cast<const SkIRect&>(fDesiredOutput); } |
632 | // The cache to use when recursing through the filter DAG, in order to avoid repeated |
633 | // calculations of the same image. |
634 | SkImageFilterCache* cache() const { return fCache; } |
635 | // The output device's color type, which can be used for intermediate images to be |
636 | // compatible with the eventual target of the filtered result. |
637 | SkColorType colorType() const { return fColorType; } |
638 | #if SK_SUPPORT_GPU |
639 | GrColorType grColorType() const { return SkColorTypeToGrColorType(fColorType); } |
640 | #endif |
641 | // The output device's color space, so intermediate images can match, and so filtering can |
642 | // be performed in the destination color space. |
643 | SkColorSpace* colorSpace() const { return fColorSpace; } |
644 | sk_sp<SkColorSpace> refColorSpace() const { return sk_ref_sp(fColorSpace); } |
645 | // The default surface properties to use when making transient surfaces during filtering. |
646 | const SkSurfaceProps* surfaceProps() const { return &fSource.image()->props(); } |
647 | |
648 | // This is the image to use whenever an expected input filter has been set to null. In the |
649 | // majority of cases, this is the original source image for the image filter DAG so it comes |
650 | // from the SkDevice that holds either the saveLayer or the temporary rendered result. The |
651 | // exception is composing two image filters (via SkImageFilters::Compose), which must use |
652 | // the output of the inner DAG as the "source" for the outer DAG. |
653 | const FilterResult<For::kInput>& source() const { return fSource; } |
654 | // DEPRECATED: Use source() instead to get both the image and its origin. |
655 | const SkSpecialImage* sourceImage() const { return fSource.image(); } |
656 | |
657 | // True if image filtering should occur on the GPU if possible. |
658 | bool gpuBacked() const { return fSource.image()->isTextureBacked(); } |
659 | // The recording context to use when computing the filter with the GPU. |
660 | GrRecordingContext* getContext() const { return fSource.image()->getContext(); } |
661 | |
662 | /** |
663 | * Since a context can be built directly, its constructor has no chance to "return null" if |
664 | * it's given invalid or unsupported inputs. Call this to know of the the context can be |
665 | * used. |
666 | * |
667 | * The SkImageFilterCache Key, for example, requires a finite ctm (no infinities or NaN), |
668 | * so that test is part of isValid. |
669 | */ |
670 | bool isValid() const { return fSource.image() != nullptr && fMapping.layerMatrix().isFinite(); } |
671 | |
672 | // Create a surface of the given size, that matches the context's color type and color space |
673 | // as closely as possible, and uses the same backend of the device that produced the source |
674 | // image. |
675 | sk_sp<SkSpecialSurface> makeSurface(const SkISize& size, |
676 | const SkSurfaceProps* props = nullptr) const { |
677 | return fSource.image()->makeSurface(fColorType, fColorSpace, size, |
678 | kPremul_SkAlphaType, props); |
679 | } |
680 | |
681 | // Create a new context that matches this context, but with an overridden layer space. |
682 | Context withNewMapping(const Mapping& mapping) const { |
683 | return Context(mapping, fDesiredOutput, fCache, fColorType, fColorSpace, fSource); |
684 | } |
685 | // Create a new context that matches this context, but with an overridden desired output rect. |
686 | Context withNewDesiredOutput(const LayerSpace<SkIRect>& desiredOutput) const { |
687 | return Context(fMapping, desiredOutput, fCache, fColorType, fColorSpace, fSource); |
688 | } |
689 | |
690 | private: |
691 | Mapping fMapping; |
692 | LayerSpace<SkIRect> fDesiredOutput; |
693 | SkImageFilterCache* fCache; |
694 | SkColorType fColorType; |
695 | // The pointed-to object is owned by the device controlling the filter process, and our lifetime |
696 | // is bounded by the device, so this can be a bare pointer. |
697 | SkColorSpace* fColorSpace; |
698 | FilterResult<For::kInput> fSource; |
699 | }; |
700 | |
701 | } // end namespace skif |
702 | |
703 | #endif // SkImageFilterTypes_DEFINED |
704 | |