xref: /aosp_15_r20/external/skia/src/effects/imagefilters/SkMorphologyImageFilter.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2012 The Android Open Source Project
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/SkM44.h"
13 #include "include/core/SkRect.h"
14 #include "include/core/SkRefCnt.h"
15 #include "include/core/SkScalar.h"
16 #include "include/core/SkShader.h"
17 #include "include/core/SkSize.h"
18 #include "include/core/SkTypes.h"
19 #include "include/effects/SkRuntimeEffect.h"
20 #include "include/private/base/SkSpan_impl.h"
21 #include "src/core/SkImageFilterTypes.h"
22 #include "src/core/SkImageFilter_Base.h"
23 #include "src/core/SkKnownRuntimeEffects.h"
24 #include "src/core/SkReadBuffer.h"
25 #include "src/core/SkWriteBuffer.h"
26 
27 #include <algorithm>
28 #include <cstdint>
29 #include <optional>
30 #include <utility>
31 
32 namespace {
33 
34 enum class MorphType {
35     kErode,
36     kDilate,
37     kLastType = kDilate
38 };
39 
40 enum class MorphDirection { kX, kY };
41 
42 class SkMorphologyImageFilter final : public SkImageFilter_Base {
43 public:
SkMorphologyImageFilter(MorphType type,SkSize radii,sk_sp<SkImageFilter> input)44     SkMorphologyImageFilter(MorphType type, SkSize radii, sk_sp<SkImageFilter> input)
45             : SkImageFilter_Base(&input, 1)
46             , fType(type)
47             , fRadii(radii) {}
48 
49     SkRect computeFastBounds(const SkRect& src) const override;
50 
51 
52 protected:
53     void flatten(SkWriteBuffer&) const override;
54 
55 private:
56     friend void ::SkRegisterMorphologyImageFilterFlattenables();
57     SK_FLATTENABLE_HOOKS(SkMorphologyImageFilter)
58 
59     skif::FilterResult onFilterImage(const skif::Context&) const override;
60 
61     skif::LayerSpace<SkIRect> onGetInputLayerBounds(
62             const skif::Mapping& mapping,
63             const skif::LayerSpace<SkIRect>& desiredOutput,
64             std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
65 
66     std::optional<skif::LayerSpace<SkIRect>> onGetOutputLayerBounds(
67             const skif::Mapping& mapping,
68             std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
69 
radii(const skif::Mapping & mapping) const70     skif::LayerSpace<SkISize> radii(const skif::Mapping& mapping) const {
71         skif::LayerSpace<SkISize> radii = mapping.paramToLayer(fRadii).round();
72         SkASSERT(radii.width() >= 0 && radii.height() >= 0);
73 
74         // We limit the radius to something small, to avoid slow draw calls: crbug.com/1123035
75         static constexpr int kMaxRadii = 256;
76         return skif::LayerSpace<SkISize>({std::min(radii.width(), kMaxRadii),
77                                           std::min(radii.height(), kMaxRadii)});
78     }
79 
requiredInput(const skif::Mapping & mapping,skif::LayerSpace<SkIRect> bounds) const80     skif::LayerSpace<SkIRect> requiredInput(const skif::Mapping& mapping,
81                                             skif::LayerSpace<SkIRect> bounds) const {
82         // The input for a morphology filter is always the kernel outset, regardless of morph type.
83         bounds.outset(this->radii(mapping));
84         return bounds;
85     }
86 
kernelOutputBounds(const skif::Mapping & mapping,skif::LayerSpace<SkIRect> bounds) const87     skif::LayerSpace<SkIRect> kernelOutputBounds(const skif::Mapping& mapping,
88                                                  skif::LayerSpace<SkIRect> bounds) const {
89         skif::LayerSpace<SkISize> radii = this->radii(mapping);
90         if (fType == MorphType::kDilate) {
91             // Transparent pixels up to the kernel radius away will be overridden by kDilate's "max"
92             // function and be set to the input's boundary pixel colors, thus expanding the output.
93             bounds.outset(radii);
94         } else {
95             // Pixels closer than the kernel radius to the input image's edges are overridden by
96             // kErode's "min" function and will be set to transparent black, contracting the output.
97             bounds.inset(radii);
98         }
99         return bounds;
100     }
101 
102     MorphType fType;
103     skif::ParameterSpace<SkSize> fRadii;
104 };
105 
make_morphology(MorphType type,SkSize radii,sk_sp<SkImageFilter> input,const SkImageFilters::CropRect & cropRect)106 sk_sp<SkImageFilter> make_morphology(MorphType type,
107                                      SkSize radii,
108                                      sk_sp<SkImageFilter> input,
109                                      const SkImageFilters::CropRect& cropRect) {
110     if (radii.width() < 0.f || radii.height() < 0.f) {
111         return nullptr; // invalid
112     }
113     sk_sp<SkImageFilter> filter = std::move(input);
114     if (radii.width() > 0.f || radii.height() > 0.f) {
115         filter = sk_sp<SkImageFilter>(new SkMorphologyImageFilter(type, radii, std::move(filter)));
116     }
117     // otherwise both radii are 0, so the kernel is always the identity function, in which case
118     // we just need to apply the 'cropRect' to the 'input'.
119 
120     if (cropRect) {
121         filter = SkImageFilters::Crop(*cropRect, std::move(filter));
122     }
123     return filter;
124 }
125 
126 // The linear morphology kernel does (2R+1) texture samples per pixel, which we want to keep less
127 // than the maximum fragment samples allowed in DX9SM2 (32), so we choose R=14 to have some head
128 // room. The other tradeoff is that for R > kMaxLinearRadius, the sparse morphology kernel only
129 // requires 2 samples to double the accumulated kernel size, but at the cost of another render
130 // target.
131 static constexpr int kMaxLinearRadius = 14; // KEEP IN SYNC WITH CONSTANT IN `sksl_rt_shader.sksl`
make_linear_morphology(sk_sp<SkShader> input,MorphType type,MorphDirection direction,int radius)132 sk_sp<SkShader> make_linear_morphology(sk_sp<SkShader> input,
133                                        MorphType type,
134                                        MorphDirection direction,
135                                        int radius) {
136     SkASSERT(radius <= kMaxLinearRadius);
137 
138     const SkRuntimeEffect* linearMorphologyEffect =
139             GetKnownRuntimeEffect(SkKnownRuntimeEffects::StableKey::kLinearMorphology);
140 
141     SkRuntimeShaderBuilder builder(sk_ref_sp(linearMorphologyEffect));
142     builder.child("child") = std::move(input);
143     builder.uniform("offset") = direction == MorphDirection::kX ? SkV2{1.f, 0.f} : SkV2{0.f, 1.f};
144     builder.uniform("flip") = (type == MorphType::kDilate) ? 1.f : -1.f;
145     builder.uniform("radius") = (int32_t)radius;
146 
147     return builder.makeShader();
148 }
149 
150 // Assuming 'input' was created by a series of morphology passes, each texel holds the aggregate
151 // (min or max depending on type) of (i-R) to (i+R) for some R. If 'radius' <= R, then the returned
152 // shader produces a new aggregate at each texel, i, of (i-R-radius) to (i+R+radius) with only two
153 // texture samples, which can be used to double the kernel size of the morphology effect.
make_sparse_morphology(sk_sp<SkShader> input,MorphType type,MorphDirection direction,int radius)154 sk_sp<SkShader> make_sparse_morphology(sk_sp<SkShader> input,
155                                        MorphType type,
156                                        MorphDirection direction,
157                                        int radius) {
158 
159     const SkRuntimeEffect* sparseMorphologyEffect =
160             GetKnownRuntimeEffect(SkKnownRuntimeEffects::StableKey::kSparseMorphology);
161 
162     SkRuntimeShaderBuilder builder(sk_ref_sp(sparseMorphologyEffect));
163     builder.child("child") = std::move(input);
164     builder.uniform("offset") = direction == MorphDirection::kX ? SkV2{(float)radius, 0.f}
165                                                                 : SkV2{0.f, (float)radius};
166     builder.uniform("flip") = (type == MorphType::kDilate) ? 1.f : -1.f;
167 
168     return builder.makeShader();
169 }
170 
morphology_pass(const skif::Context & ctx,const skif::FilterResult & input,MorphType type,MorphDirection dir,int radius)171 skif::FilterResult morphology_pass(const skif::Context& ctx, const skif::FilterResult& input,
172                                    MorphType type, MorphDirection dir, int radius) {
173     using ShaderFlags = skif::FilterResult::ShaderFlags;
174 
175     auto axisDelta = [dir](int step) {
176         return skif::LayerSpace<SkISize>({
177                 dir == MorphDirection::kX ? step : 0,
178                 dir == MorphDirection::kY ? step : 0});
179     };
180 
181     // The first iteration will sample a full kernel outset from the final output.
182     skif::LayerSpace<SkIRect> sampleBounds = ctx.desiredOutput();
183     sampleBounds.outset(axisDelta(radius));
184 
185     skif::FilterResult childOutput = input;
186     int appliedRadius = 0;
187     while (radius > appliedRadius) {
188         if (!childOutput) {
189             return {}; // Eroded or dilated transparent black is still transparent black
190         }
191 
192         // The first iteration uses up to kMaxLinearRadius with a linear accumulation pass.
193         // After that we double the radius each step until we can finish with the target radius.
194         int stepRadius =
195                 appliedRadius == 0 ? std::min(kMaxLinearRadius, radius)
196                                    : std::min(radius - appliedRadius, appliedRadius);
197 
198         skif::Context stepCtx = ctx;
199         if (appliedRadius + stepRadius < radius) {
200             // Intermediate steps need to output what will be sampled on the next iteration
201             auto outputBounds = sampleBounds;
202             outputBounds.inset(axisDelta(stepRadius));
203             stepCtx = ctx.withNewDesiredOutput(outputBounds);
204         } // else the last iteration should output what was originally requested
205 
206         skif::FilterResult::Builder builder{stepCtx};
207         builder.add(childOutput, sampleBounds, ShaderFlags::kSampledRepeatedly);
208         childOutput = builder.eval(
209                 [&](SkSpan<sk_sp<SkShader>> inputs) {
210                     if (appliedRadius == 0) {
211                         return make_linear_morphology(inputs[0], type, dir, stepRadius);
212                     } else {
213                         return make_sparse_morphology(inputs[0], type, dir, stepRadius);
214                     }
215                 });
216 
217         sampleBounds = stepCtx.desiredOutput();
218         appliedRadius += stepRadius;
219         SkASSERT(appliedRadius <= radius); // Our last iteration should hit 'radius' exactly.
220     }
221 
222     return childOutput;
223 }
224 
225 } // end namespace
226 
Dilate(SkScalar radiusX,SkScalar radiusY,sk_sp<SkImageFilter> input,const CropRect & cropRect)227 sk_sp<SkImageFilter> SkImageFilters::Dilate(SkScalar radiusX, SkScalar radiusY,
228                                             sk_sp<SkImageFilter> input,
229                                             const CropRect& cropRect) {
230     return make_morphology(MorphType::kDilate, {radiusX, radiusY}, std::move(input), cropRect);
231 }
232 
Erode(SkScalar radiusX,SkScalar radiusY,sk_sp<SkImageFilter> input,const CropRect & cropRect)233 sk_sp<SkImageFilter> SkImageFilters::Erode(SkScalar radiusX, SkScalar radiusY,
234                                            sk_sp<SkImageFilter> input,
235                                            const CropRect& cropRect) {
236     return make_morphology(MorphType::kErode, {radiusX, radiusY}, std::move(input), cropRect);
237 }
238 
SkRegisterMorphologyImageFilterFlattenables()239 void SkRegisterMorphologyImageFilterFlattenables() {
240     SK_REGISTER_FLATTENABLE(SkMorphologyImageFilter);
241     // TODO (michaelludwig): Remove after grace period for SKPs to stop using old name
242     SkFlattenable::Register("SkMorphologyImageFilterImpl", SkMorphologyImageFilter::CreateProc);
243 }
244 
CreateProc(SkReadBuffer & buffer)245 sk_sp<SkFlattenable> SkMorphologyImageFilter::CreateProc(SkReadBuffer& buffer) {
246     SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 1);
247 
248     SkScalar width = buffer.readScalar();
249     SkScalar height = buffer.readScalar();
250     MorphType filterType = buffer.read32LE(MorphType::kLastType);
251 
252     if (filterType == MorphType::kDilate) {
253         return SkImageFilters::Dilate(width, height, common.getInput(0), common.cropRect());
254     } else if (filterType == MorphType::kErode) {
255         return SkImageFilters::Erode(width, height, common.getInput(0), common.cropRect());
256     } else {
257         return nullptr;
258     }
259 }
260 
flatten(SkWriteBuffer & buffer) const261 void SkMorphologyImageFilter::flatten(SkWriteBuffer& buffer) const {
262     this->SkImageFilter_Base::flatten(buffer);
263     buffer.writeScalar(SkSize(fRadii).width());
264     buffer.writeScalar(SkSize(fRadii).height());
265     buffer.writeInt(static_cast<int>(fType));
266 }
267 
268 ///////////////////////////////////////////////////////////////////////////////
269 
onFilterImage(const skif::Context & ctx) const270 skif::FilterResult SkMorphologyImageFilter::onFilterImage(const skif::Context& ctx) const {
271     skif::LayerSpace<SkIRect> requiredInput =
272             this->requiredInput(ctx.mapping(), ctx.desiredOutput());
273     skif::FilterResult childOutput =
274             this->getChildOutput(0, ctx.withNewDesiredOutput(requiredInput));
275 
276     // If childOutput completely fulfilled requiredInput, maxOutput will match the context's
277     // desired output, but if the output image is smaller, this will restrict the morphology output
278     // to what is actual produceable.
279     skif::LayerSpace<SkIRect> maxOutput =
280         this->kernelOutputBounds(ctx.mapping(), childOutput.layerBounds());
281     if (!maxOutput.intersect(ctx.desiredOutput())) {
282         return {};
283     }
284 
285     // The X pass has to preserve the extra rows to later be consumed by the Y pass.
286     skif::LayerSpace<SkISize> radii = this->radii(ctx.mapping());
287     skif::LayerSpace<SkIRect> maxOutputX = maxOutput;
288     maxOutputX.outset(skif::LayerSpace<SkISize>({0, radii.height()}));
289     childOutput = morphology_pass(ctx.withNewDesiredOutput(maxOutputX), childOutput, fType,
290                                   MorphDirection::kX, radii.width());
291     childOutput = morphology_pass(ctx.withNewDesiredOutput(maxOutput), childOutput, fType,
292                                   MorphDirection::kY, radii.height());
293     return childOutput;
294 }
295 
onGetInputLayerBounds(const skif::Mapping & mapping,const skif::LayerSpace<SkIRect> & desiredOutput,std::optional<skif::LayerSpace<SkIRect>> contentBounds) const296 skif::LayerSpace<SkIRect> SkMorphologyImageFilter::onGetInputLayerBounds(
297         const skif::Mapping& mapping,
298         const skif::LayerSpace<SkIRect>& desiredOutput,
299         std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
300     skif::LayerSpace<SkIRect> requiredInput = this->requiredInput(mapping, desiredOutput);
301     return this->getChildInputLayerBounds(0, mapping, requiredInput, contentBounds);
302 }
303 
onGetOutputLayerBounds(const skif::Mapping & mapping,std::optional<skif::LayerSpace<SkIRect>> contentBounds) const304 std::optional<skif::LayerSpace<SkIRect>> SkMorphologyImageFilter::onGetOutputLayerBounds(
305         const skif::Mapping& mapping,
306         std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
307     auto childOutput = this->getChildOutputLayerBounds(0, mapping, contentBounds);
308     if (childOutput) {
309         return this->kernelOutputBounds(mapping, *childOutput);
310     } else {
311         return skif::LayerSpace<SkIRect>::Unbounded();
312     }
313 }
314 
computeFastBounds(const SkRect & src) const315 SkRect SkMorphologyImageFilter::computeFastBounds(const SkRect& src) const {
316     // See kernelOutputBounds() for rationale
317     SkRect bounds = this->getInput(0) ? this->getInput(0)->computeFastBounds(src) : src;
318     if (fType == MorphType::kDilate) {
319         bounds.outset(SkSize(fRadii).width(), SkSize(fRadii).height());
320     } else {
321         bounds.inset(SkSize(fRadii).width(), SkSize(fRadii).height());
322     }
323     return bounds;
324 }
325