xref: /aosp_15_r20/external/skia/src/effects/imagefilters/SkCropImageFilter.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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