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