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
8 #include "include/effects/SkImageFilters.h"
9
10 #include "include/core/SkFlattenable.h"
11 #include "include/core/SkImageFilter.h"
12 #include "include/core/SkRect.h"
13 #include "include/core/SkRefCnt.h"
14 #include "include/core/SkTileMode.h"
15 #include "include/private/base/SkAssert.h"
16 #include "src/core/SkImageFilterTypes.h"
17 #include "src/core/SkImageFilter_Base.h"
18 #include "src/core/SkPicturePriv.h"
19 #include "src/core/SkReadBuffer.h"
20 #include "src/core/SkRectPriv.h"
21 #include "src/core/SkValidationUtils.h"
22 #include "src/core/SkWriteBuffer.h"
23
24
25 #include <cstdint>
26 #include <optional>
27 #include <utility>
28
29 namespace {
30
31 class SkCropImageFilter final : public SkImageFilter_Base {
32 public:
SkCropImageFilter(const SkRect & cropRect,SkTileMode tileMode,sk_sp<SkImageFilter> input)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
43 protected:
44 void flatten(SkWriteBuffer&) const override;
45
46 private:
47 friend void ::SkRegisterCropImageFilterFlattenable();
48 SK_FLATTENABLE_HOOKS(SkCropImageFilter)
49 static sk_sp<SkFlattenable> LegacyTileCreateProc(SkReadBuffer&);
50
onAffectsTransparentBlack() const51 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.
ignoreInputsAffectsTransparentBlack() const55 bool ignoreInputsAffectsTransparentBlack() const override { return true; }
56
57 skif::FilterResult onFilterImage(const skif::Context& context) const override;
58
59 skif::LayerSpace<SkIRect> onGetInputLayerBounds(
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.
cropRect(const skif::Mapping & mapping) const74 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.
requiredInput(const skif::Mapping & mapping,const skif::LayerSpace<SkIRect> & outputBounds) const88 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
93 skif::ParameterSpace<SkRect> fCropRect;
94 SkTileMode fTileMode;
95 };
96
97 } // end namespace
98
Crop(const SkRect & rect,SkTileMode tileMode,sk_sp<SkImageFilter> input)99 sk_sp<SkImageFilter> SkImageFilters::Crop(const SkRect& rect,
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.
Empty()110 sk_sp<SkImageFilter> SkImageFilters::Empty() {
111 return SkImageFilters::Crop(SkRect::MakeEmpty(), SkTileMode::kDecal, nullptr);
112 }
113
Tile(const SkRect & src,const SkRect & dst,sk_sp<SkImageFilter> input)114 sk_sp<SkImageFilter> SkImageFilters::Tile(const SkRect& src,
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
SkRegisterCropImageFilterFlattenable()124 void SkRegisterCropImageFilterFlattenable() {
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
LegacyTileCreateProc(SkReadBuffer & buffer)131 sk_sp<SkFlattenable> SkCropImageFilter::LegacyTileCreateProc(SkReadBuffer& buffer) {
132 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
133 SkRect src, dst;
134 buffer.readRect(&src);
135 buffer.readRect(&dst);
136 return SkImageFilters::Tile(src, dst, common.getInput(0));
137 }
138
CreateProc(SkReadBuffer & buffer)139 sk_sp<SkFlattenable> SkCropImageFilter::CreateProc(SkReadBuffer& buffer) {
140 SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
141 SkRect cropRect = buffer.readRect();
142 if (!buffer.isValid() || !buffer.validate(SkIsValidRect(cropRect))) {
143 return nullptr;
144 }
145
146 SkTileMode tileMode = SkTileMode::kDecal;
147 if (!buffer.isVersionLT(SkPicturePriv::kCropImageFilterSupportsTiling)) {
148 tileMode = buffer.read32LE(SkTileMode::kLastTileMode);
149 }
150
151 return SkImageFilters::Crop(cropRect, tileMode, common.getInput(0));
152 }
153
flatten(SkWriteBuffer & buffer) const154 void 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
onFilterImage(const skif::Context & context) const162 skif::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.
onGetInputLayerBounds(const skif::Mapping & mapping,const skif::LayerSpace<SkIRect> & desiredOutput,std::optional<skif::LayerSpace<SkIRect>> contentBounds) const179 skif::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
onGetOutputLayerBounds(const skif::Mapping & mapping,std::optional<skif::LayerSpace<SkIRect>> contentBounds) const192 std::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.
204 return skif::LayerSpace<SkIRect>::Empty();
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 {
211 return skif::LayerSpace<SkIRect>::Unbounded();
212 }
213 }
214 }
215
computeFastBounds(const SkRect & bounds) const216 SkRect 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 }
228