Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
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:
47 friend void ::SkRegisterCropImageFilterFlattenable();
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.
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.
119 sk_sp<SkImageFilter> filter = SkImageFilters::Crop(src, SkTileMode::kRepeat, std::move(input));
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
LayerSpace< T > paramToLayer(const ParameterSpace< T > &paramGeometry) const
static const uint8_t buffer[]
Optional< SkRect > bounds
Definition SkRecords.h:189
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