Flutter Engine
The Flutter Engine
SkCropImageFilter.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2021 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
9
12#include "include/core/SkRect.h"
20#include "src/core/SkRectPriv.h"
23
24
25#include <cstdint>
26#include <optional>
27#include <utility>
28
29namespace {
30
31class SkCropImageFilter final : public SkImageFilter_Base {
32public:
33 SkCropImageFilter(const SkRect& cropRect, SkTileMode tileMode, sk_sp<SkImageFilter> input)
34 : SkImageFilter_Base(&input, 1)
35 , fCropRect(cropRect)
36 , fTileMode(tileMode) {
37 SkASSERT(cropRect.isFinite());
38 SkASSERT(cropRect.isSorted());
39 }
40
41 SkRect computeFastBounds(const SkRect& bounds) const override;
42
43protected:
44 void flatten(SkWriteBuffer&) const override;
45
46private:
48 SK_FLATTENABLE_HOOKS(SkCropImageFilter)
49 static sk_sp<SkFlattenable> LegacyTileCreateProc(SkReadBuffer&);
50
51 bool onAffectsTransparentBlack() const override { return fTileMode != SkTileMode::kDecal; }
52
53 // Disable recursing in affectsTransparentBlack() if we hit a Crop.
54 // TODO(skbug.com/14611): Automatically infer this from the output bounds being finite.
55 bool ignoreInputsAffectsTransparentBlack() const override { return true; }
56
57 skif::FilterResult onFilterImage(const skif::Context& context) const override;
58
60 const skif::Mapping& mapping,
61 const skif::LayerSpace<SkIRect>& desiredOutput,
62 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
63
64 std::optional<skif::LayerSpace<SkIRect>> onGetOutputLayerBounds(
65 const skif::Mapping& mapping,
66 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
67
68 // The crop rect is specified in floating point to allow cropping to partial local pixels,
69 // that could become whole pixels in the layer-space image if the canvas is scaled.
70 // For now it's always rounded to integer pixels as if it were non-AA.
71 //
72 // The returned rect is intersected with 'outputBounds', which is either the desired or
73 // actual bounds of the child filter.
74 skif::LayerSpace<SkIRect> cropRect(const skif::Mapping& mapping) const {
75 skif::LayerSpace<SkRect> crop = mapping.paramToLayer(fCropRect);
76 // If 'crop' has fractional values, rounding out can mean that rendering of the input image
77 // or (particularly) the source content will produce fractional coverage values in the
78 // edge pixels. With decal tiling, this is the most accurate behavior and does not produce
79 // any surprises. However, with any other mode, the fractional coverage introduces
80 // transparency that can be greatly magnified (particularly from clamping). To avoid this
81 // we round in on those modes to ensure any transparency on the edges truly came from the
82 // content and not rasterization.
83 return fTileMode == SkTileMode::kDecal ? crop.roundOut() : crop.roundIn();
84 }
85
86 // Calculates the required input to fill the crop rect, given the desired output that it will
87 // be tiled across.
88 skif::LayerSpace<SkIRect> requiredInput(const skif::Mapping& mapping,
89 const skif::LayerSpace<SkIRect>& outputBounds) const {
90 return this->cropRect(mapping).relevantSubset(outputBounds, fTileMode);
91 }
92
94 SkTileMode fTileMode;
95};
96
97} // end namespace
98
100 SkTileMode tileMode,
101 sk_sp<SkImageFilter> input) {
102 if (!SkIsValidRect(rect)) {
103 return nullptr;
104 }
105 return sk_sp<SkImageFilter>(new SkCropImageFilter(rect, tileMode, std::move(input)));
106}
107
108// While a number of filter factories could handle "empty" cases (e.g. a null SkShader or SkPicture)
109// just use a crop with an empty rect because its implementation gracefully handles empty rects.
112}
113
115 const SkRect& dst,
116 sk_sp<SkImageFilter> input) {
117 // The Tile filter is simply a crop to 'src' with a kRepeat tile mode wrapped in a crop to 'dst'
118 // with a kDecal tile mode.
120 filter = SkImageFilters::Crop(dst, SkTileMode::kDecal, std::move(filter));
121 return filter;
122}
123
125 SK_REGISTER_FLATTENABLE(SkCropImageFilter);
126 // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name
127 SkFlattenable::Register("SkTileImageFilter", SkCropImageFilter::LegacyTileCreateProc);
128 SkFlattenable::Register("SkTileImageFilterImpl", SkCropImageFilter::LegacyTileCreateProc);
129}
130
131sk_sp<SkFlattenable> SkCropImageFilter::LegacyTileCreateProc(SkReadBuffer& buffer) {
133 SkRect src, dst;
134 buffer.readRect(&src);
135 buffer.readRect(&dst);
136 return SkImageFilters::Tile(src, dst, common.getInput(0));
137}
138
139sk_sp<SkFlattenable> SkCropImageFilter::CreateProc(SkReadBuffer& buffer) {
141 SkRect cropRect = buffer.readRect();
142 if (!buffer.isValid() || !buffer.validate(SkIsValidRect(cropRect))) {
143 return nullptr;
144 }
145
148 tileMode = buffer.read32LE(SkTileMode::kLastTileMode);
149 }
150
151 return SkImageFilters::Crop(cropRect, tileMode, common.getInput(0));
152}
153
154void SkCropImageFilter::flatten(SkWriteBuffer& buffer) const {
155 this->SkImageFilter_Base::flatten(buffer);
156 buffer.writeRect(SkRect(fCropRect));
157 buffer.writeInt(static_cast<int32_t>(fTileMode));
158}
159
160///////////////////////////////////////////////////////////////////////////////////////////////////
161
162skif::FilterResult SkCropImageFilter::onFilterImage(const skif::Context& context) const {
163 skif::LayerSpace<SkIRect> cropInput = this->requiredInput(context.mapping(),
164 context.desiredOutput());
165 skif::FilterResult childOutput =
166 this->getChildOutput(0, context.withNewDesiredOutput(cropInput));
167
168 // The 'cropInput' is the optimal input to satisfy the original crop rect, but we have to pass
169 // the actual crop rect in order for the tile mode to be applied correctly to the FilterResult.
170 return childOutput.applyCrop(context, this->cropRect(context.mapping()), fTileMode);
171}
172
173// TODO(michaelludwig) - onGetInputLayerBounds() and onGetOutputLayerBounds() are tightly coupled
174// to both each other's behavior and to onFilterImage(). If onFilterImage() had a concept of a
175// dry-run (e.g. FilterResult had null images but tracked the bounds the images would be) then
176// onGetInputLayerBounds() is the union of all requested inputs at the leaf nodes of the DAG, and
177// onGetOutputLayerBounds() is the bounds of the dry-run result. This might have more overhead, but
178// would reduce the complexity of implementations by quite a bit.
179skif::LayerSpace<SkIRect> SkCropImageFilter::onGetInputLayerBounds(
180 const skif::Mapping& mapping,
181 const skif::LayerSpace<SkIRect>& desiredOutput,
182 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
183 // Assuming unbounded desired output, this filter only needs to process an image that's at most
184 // sized to our crop rect, but we can restrict the crop rect to just what's requested since
185 // anything in the crop but outside 'desiredOutput' won't be visible.
186 skif::LayerSpace<SkIRect> requiredInput = this->requiredInput(mapping, desiredOutput);
187
188 // Our required input is the desired output for our child image filter.
189 return this->getChildInputLayerBounds(0, mapping, requiredInput, contentBounds);
190}
191
192std::optional<skif::LayerSpace<SkIRect>> SkCropImageFilter::onGetOutputLayerBounds(
193 const skif::Mapping& mapping,
194 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
195 // Assuming unbounded child content, our output is an image tiled around the crop rect.
196 // But the child output image is drawn into our output surface with its own decal tiling, which
197 // may allow the output dimensions to be reduced.
198 auto childOutput = this->getChildOutputLayerBounds(0, mapping, contentBounds);
199
200 skif::LayerSpace<SkIRect> crop = this->cropRect(mapping);
201 if (childOutput && !crop.intersect(*childOutput)) {
202 // Regardless of tile mode, the content within the crop rect is fully transparent, so
203 // any tiling will maintain that transparency.
205 } else {
206 // The crop rect contains non-transparent content from the child filter; if not a decal
207 // tile mode, the actual visual output is unbounded (even if the underlying data is smaller)
208 if (fTileMode == SkTileMode::kDecal) {
209 return crop;
210 } else {
212 }
213 }
214}
215
216SkRect SkCropImageFilter::computeFastBounds(const SkRect& bounds) const {
217 // TODO(michaelludwig) - This is conceptually very similar to calling onGetOutputLayerBounds()
218 // with an identity skif::Mapping (hence why fCropRect can be used directly), but it also does
219 // not involve any rounding to pixels for both the content bounds or the output.
220 // NOTE: This relies on all image filters returning an infinite bounds when they affect
221 // transparent black.
222 SkRect inputBounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(bounds) : bounds;
223 if (!inputBounds.intersect(SkRect(fCropRect))) {
224 return SkRect::MakeEmpty();
225 }
226 return fTileMode == SkTileMode::kDecal ? inputBounds : SkRectPriv::MakeLargeS32();
227}
#define SkASSERT(cond)
Definition: SkAssert.h:116
void SkRegisterCropImageFilterFlattenable()
#define SK_FLATTENABLE_HOOKS(type)
#define SK_REGISTER_FLATTENABLE(type)
#define SK_IMAGEFILTER_UNFLATTEN_COMMON(localVar, expectedCount)
SkTileMode
Definition: SkTileMode.h:13
static bool SkIsValidRect(const SkRect &rect)
static void Register(const char name[], Factory)
virtual skif::LayerSpace< SkIRect > onGetInputLayerBounds(const skif::Mapping &mapping, const skif::LayerSpace< SkIRect > &desiredOutput, std::optional< skif::LayerSpace< SkIRect > > contentBounds) const =0
virtual bool onAffectsTransparentBlack() const
void flatten(SkWriteBuffer &) const override
virtual bool ignoreInputsAffectsTransparentBlack() const
virtual std::optional< skif::LayerSpace< SkIRect > > onGetOutputLayerBounds(const skif::Mapping &mapping, std::optional< skif::LayerSpace< SkIRect > > contentBounds) const =0
virtual skif::FilterResult onFilterImage(const skif::Context &context) const =0
virtual SkRect computeFastBounds(const SkRect &bounds) const
static sk_sp< SkImageFilter > Empty()
static sk_sp< SkImageFilter > Crop(const SkRect &rect, SkTileMode tileMode, sk_sp< SkImageFilter > input)
static sk_sp< SkImageFilter > Tile(const SkRect &src, const SkRect &dst, sk_sp< SkImageFilter > input)
@ kCropImageFilterSupportsTiling
static SkRect MakeLargeS32()
Definition: SkRectPriv.h:33
const LayerSpace< SkIRect > & desiredOutput() const
Context withNewDesiredOutput(const LayerSpace< SkIRect > &desiredOutput) const
const Mapping & mapping() const
FilterResult applyCrop(const Context &ctx, const LayerSpace< SkIRect > &crop, SkTileMode tileMode=SkTileMode::kDecal) const
bool intersect(const LayerSpace< SkIRect > &r)
LayerSpace< SkIRect > relevantSubset(const LayerSpace< SkIRect > dstRect, SkTileMode) const
LayerSpace< SkIRect > roundOut() const
LayerSpace< SkIRect > roundIn() const
LayerSpace< T > paramToLayer(const ParameterSpace< T > &paramGeometry) const
Optional< SkRect > bounds
Definition: SkRecords.h:189
sk_sp< SkBlender > blender SkRect rect
Definition: SkRecords.h:350
SK_API sk_sp< SkShader > Empty()
Definition: common.py:1
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
dst
Definition: cp.py:12
static constexpr SkRect MakeEmpty()
Definition: SkRect.h:595
bool isFinite() const
Definition: SkRect.h:711
bool intersect(const SkRect &r)
Definition: SkRect.cpp:114
bool isSorted() const
Definition: SkRect.h:705