xref: /aosp_15_r20/external/skia/src/effects/imagefilters/SkDisplacementMapImageFilter.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1 /*
2  * Copyright 2013 Google Inc.
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/SkColor.h"
11 #include "include/core/SkFlattenable.h"
12 #include "include/core/SkImageFilter.h"
13 #include "include/core/SkM44.h"
14 #include "include/core/SkMatrix.h"
15 #include "include/core/SkRect.h"
16 #include "include/core/SkRefCnt.h"
17 #include "include/core/SkSamplingOptions.h"
18 #include "include/core/SkScalar.h"
19 #include "include/core/SkShader.h"
20 #include "include/core/SkSize.h"
21 #include "include/core/SkTypes.h"
22 #include "include/effects/SkRuntimeEffect.h"
23 #include "include/private/base/SkSpan_impl.h"
24 #include "src/core/SkImageFilterTypes.h"
25 #include "src/core/SkImageFilter_Base.h"
26 #include "src/core/SkKnownRuntimeEffects.h"
27 #include "src/core/SkReadBuffer.h"
28 #include "src/core/SkWriteBuffer.h"
29 
30 #include <optional>
31 #include <utility>
32 
33 namespace {
34 
35 class SkDisplacementMapImageFilter final : public SkImageFilter_Base {
36     // Input image filter indices
37     static constexpr int kDisplacement = 0;
38     static constexpr int kColor = 1;
39 
40     // TODO(skbug.com/14376): Use nearest to match historical behavior, but eventually this should
41     // become a factory option.
42     static constexpr SkSamplingOptions kDisplacementSampling{SkFilterMode::kNearest};
43 
44 public:
SkDisplacementMapImageFilter(SkColorChannel xChannel,SkColorChannel yChannel,SkScalar scale,sk_sp<SkImageFilter> inputs[2])45     SkDisplacementMapImageFilter(SkColorChannel xChannel, SkColorChannel yChannel,
46                                  SkScalar scale, sk_sp<SkImageFilter> inputs[2])
47             : SkImageFilter_Base(inputs, 2)
48             , fXChannel(xChannel)
49             , fYChannel(yChannel)
50             , fScale(scale) {}
51 
52     SkRect computeFastBounds(const SkRect& src) const override;
53 
54 protected:
55     void flatten(SkWriteBuffer&) const override;
56 
57 private:
58     friend void ::SkRegisterDisplacementMapImageFilterFlattenable();
59     SK_FLATTENABLE_HOOKS(SkDisplacementMapImageFilter)
60 
61     skif::FilterResult onFilterImage(const skif::Context&) const override;
62 
63     skif::LayerSpace<SkIRect> onGetInputLayerBounds(
64             const skif::Mapping& mapping,
65             const skif::LayerSpace<SkIRect>& desiredOutput,
66             std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
67 
68     std::optional<skif::LayerSpace<SkIRect>> onGetOutputLayerBounds(
69             const skif::Mapping& mapping,
70             std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
71 
outsetByMaxDisplacement(const skif::Mapping & mapping,skif::LayerSpace<SkIRect> bounds) const72     skif::LayerSpace<SkIRect> outsetByMaxDisplacement(const skif::Mapping& mapping,
73                                                       skif::LayerSpace<SkIRect> bounds) const {
74         // For max displacement, we treat 'scale' as a size instead of a vector. The vector offset
75         // maps a [0,1] channel value to [-scale/2, scale/2], and treating it as a size
76         // automatically accounts for the absolute magnitude when transforming from param to layer.
77         skif::LayerSpace<SkSize> maxDisplacement = mapping.paramToLayer(
78             skif::ParameterSpace<SkSize>({0.5f * fScale, 0.5f * fScale}));
79         bounds.outset(maxDisplacement.ceil());
80         return bounds;
81     }
82 
83     SkColorChannel fXChannel;
84     SkColorChannel fYChannel;
85     // Scale is really a ParameterSpace<Vector> where width = height = fScale, but we store just the
86     // float here for easier serialization and convert to a size in onFilterImage().
87     SkScalar fScale;
88 };
89 
channel_selector_type_is_valid(SkColorChannel cst)90 bool channel_selector_type_is_valid(SkColorChannel cst) {
91     switch (cst) {
92         case SkColorChannel::kR:
93         case SkColorChannel::kG:
94         case SkColorChannel::kB:
95         case SkColorChannel::kA:
96             return true;
97         default:
98             break;
99     }
100     return false;
101 }
102 
make_displacement_shader(sk_sp<SkShader> displacement,sk_sp<SkShader> color,skif::LayerSpace<skif::Vector> scale,SkColorChannel xChannel,SkColorChannel yChannel)103 sk_sp<SkShader> make_displacement_shader(
104         sk_sp<SkShader> displacement,
105         sk_sp<SkShader> color,
106         skif::LayerSpace<skif::Vector> scale,
107         SkColorChannel xChannel,
108         SkColorChannel yChannel) {
109     if (!color) {
110         // Color is fully transparent, so no point in displacing it
111         return nullptr;
112     }
113     if (!displacement) {
114         // Somehow we had a valid displacement image but failed to produce a shader
115         // (e.g. an internal resolve to a new image failed). Treat the displacement as
116         // transparent, but it's too late to switch to the applyTransform() optimization.
117         displacement = SkShaders::Color(SK_ColorTRANSPARENT);
118     }
119 
120     const SkRuntimeEffect* displacementEffect =
121             GetKnownRuntimeEffect(SkKnownRuntimeEffects::StableKey::kDisplacement);
122 
123     auto channelSelector = [](SkColorChannel c) {
124         return SkV4{c == SkColorChannel::kR ? 1.f : 0.f,
125                     c == SkColorChannel::kG ? 1.f : 0.f,
126                     c == SkColorChannel::kB ? 1.f : 0.f,
127                     c == SkColorChannel::kA ? 1.f : 0.f};
128     };
129 
130     SkRuntimeShaderBuilder builder(sk_ref_sp(displacementEffect));
131     builder.child("displMap") = std::move(displacement);
132     builder.child("colorMap") = std::move(color);
133     builder.uniform("scale") = SkV2{scale.x(), scale.y()};
134     builder.uniform("xSelect") = channelSelector(xChannel);
135     builder.uniform("ySelect") = channelSelector(yChannel);
136 
137     return builder.makeShader();
138 }
139 
140 }  // anonymous namespace
141 
142 ///////////////////////////////////////////////////////////////////////////////
143 
DisplacementMap(SkColorChannel xChannelSelector,SkColorChannel yChannelSelector,SkScalar scale,sk_sp<SkImageFilter> displacement,sk_sp<SkImageFilter> color,const CropRect & cropRect)144 sk_sp<SkImageFilter> SkImageFilters::DisplacementMap(
145         SkColorChannel xChannelSelector, SkColorChannel yChannelSelector, SkScalar scale,
146         sk_sp<SkImageFilter> displacement, sk_sp<SkImageFilter> color, const CropRect& cropRect) {
147     if (!channel_selector_type_is_valid(xChannelSelector) ||
148         !channel_selector_type_is_valid(yChannelSelector)) {
149         return nullptr;
150     }
151 
152     sk_sp<SkImageFilter> inputs[2] = { std::move(displacement), std::move(color) };
153     sk_sp<SkImageFilter> filter(new SkDisplacementMapImageFilter(xChannelSelector, yChannelSelector,
154                                                                  scale, inputs));
155     if (cropRect) {
156         filter = SkImageFilters::Crop(*cropRect, std::move(filter));
157     }
158     return filter;
159 }
160 
SkRegisterDisplacementMapImageFilterFlattenable()161 void SkRegisterDisplacementMapImageFilterFlattenable() {
162     SK_REGISTER_FLATTENABLE(SkDisplacementMapImageFilter);
163     // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name
164     SkFlattenable::Register("SkDisplacementMapEffect", SkDisplacementMapImageFilter::CreateProc);
165     SkFlattenable::Register("SkDisplacementMapEffectImpl",
166                             SkDisplacementMapImageFilter::CreateProc);
167 }
168 
CreateProc(SkReadBuffer & buffer)169 sk_sp<SkFlattenable> SkDisplacementMapImageFilter::CreateProc(SkReadBuffer& buffer) {
170     SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 2);
171 
172     SkColorChannel xsel = buffer.read32LE(SkColorChannel::kLastEnum);
173     SkColorChannel ysel = buffer.read32LE(SkColorChannel::kLastEnum);
174     SkScalar      scale = buffer.readScalar();
175 
176     return SkImageFilters::DisplacementMap(xsel, ysel, scale, common.getInput(0),
177                                            common.getInput(1), common.cropRect());
178 }
179 
flatten(SkWriteBuffer & buffer) const180 void SkDisplacementMapImageFilter::flatten(SkWriteBuffer& buffer) const {
181     this->SkImageFilter_Base::flatten(buffer);
182     buffer.writeInt((int) fXChannel);
183     buffer.writeInt((int) fYChannel);
184     buffer.writeScalar(fScale);
185 }
186 
187 ///////////////////////////////////////////////////////////////////////////////
188 
onFilterImage(const skif::Context & ctx) const189 skif::FilterResult SkDisplacementMapImageFilter::onFilterImage(const skif::Context& ctx) const {
190     skif::LayerSpace<SkIRect> requiredColorInput =
191             this->outsetByMaxDisplacement(ctx.mapping(), ctx.desiredOutput());
192     skif::FilterResult colorOutput =
193             this->getChildOutput(kColor, ctx.withNewDesiredOutput(requiredColorInput));
194     if (!colorOutput) {
195         return {}; // No non-transparent black colors to displace
196     }
197 
198     // When the color image filter is unrestricted, its output will be 'maxDisplacement' larger than
199     // this filter's desired output. However, if it is cropped, we can restrict this filter's final
200     // output. However it's not simply colorOutput intersected with desiredOutput since we have to
201     // account for how the clipped colorOutput might still be displaced.
202     skif::LayerSpace<SkIRect> outputBounds =
203             this->outsetByMaxDisplacement(ctx.mapping(), colorOutput.layerBounds());
204     // 'outputBounds' has double the max displacement for edges where colorOutput had not been
205     // clipped, but that's fine since we intersect with 'desiredOutput'. For edges that were cropped
206     // the second max displacement represents how far they can be displaced, which might be inside
207     // the original 'desiredOutput'.
208     if (!outputBounds.intersect(ctx.desiredOutput())) {
209         // None of the non-transparent black colors can be displaced into the desired bounds.
210         return {};
211     }
212 
213     // Creation of the displacement map should happen in a non-colorspace aware context. This
214     // texture is a purely mathematical construct, so we want to just operate on the stored
215     // values. Consider:
216     //
217     //   User supplies an sRGB displacement map. If we're rendering to a wider gamut, then we could
218     //   end up filtering the displacement map into that gamut, which has the effect of reducing
219     //   the amount of displacement that it represents (as encoded values move away from the
220     //   primaries).
221     //
222     //   With a more complex DAG attached to this input, it's not clear that working in ANY specific
223     //   color space makes sense, so we ignore color spaces (and gamma) entirely. This may not be
224     //   ideal, but it's at least consistent and predictable.
225     skif::FilterResult displacementOutput =
226             this->getChildOutput(kDisplacement, ctx.withNewDesiredOutput(outputBounds)
227                                                    .withNewColorSpace(/*cs=*/nullptr));
228 
229     // NOTE: The scale is a "vector" not a "size" since we want to preserve negations on the final
230     // displacement vector.
231     const skif::LayerSpace<skif::Vector> scale =
232             ctx.mapping().paramToLayer(skif::ParameterSpace<skif::Vector>({fScale, fScale}));
233     if (!displacementOutput) {
234         // A null displacement map means its transparent black, but (0,0,0,0) becomes the vector
235         // (-scale/2, -scale/2) applied to the color image, so represent the displacement as a
236         // simple transform.
237         skif::LayerSpace<SkMatrix> constantDisplacement{SkMatrix::Translate(-0.5f * scale.x(),
238                                                                             -0.5f * scale.y())};
239         return colorOutput.applyTransform(ctx, constantDisplacement, kDisplacementSampling);
240     }
241 
242     // If we made it this far, then we actually have per-pixel displacement affecting the color
243     // image. We need to evaluate each pixel within 'outputBounds'.
244     using ShaderFlags = skif::FilterResult::ShaderFlags;
245 
246     skif::FilterResult::Builder builder{ctx};
247     builder.add(displacementOutput, /*sampleBounds=*/outputBounds);
248     builder.add(colorOutput,
249                 /*sampleBounds=*/requiredColorInput,
250                 ShaderFlags::kNonTrivialSampling,
251                 kDisplacementSampling);
252     return builder.eval(
253             [&](SkSpan<sk_sp<SkShader>> inputs) {
254                 return make_displacement_shader(inputs[kDisplacement], inputs[kColor],
255                                                 scale, fXChannel, fYChannel);
256             }, outputBounds);
257 }
258 
onGetInputLayerBounds(const skif::Mapping & mapping,const skif::LayerSpace<SkIRect> & desiredOutput,std::optional<skif::LayerSpace<SkIRect>> contentBounds) const259 skif::LayerSpace<SkIRect> SkDisplacementMapImageFilter::onGetInputLayerBounds(
260         const skif::Mapping& mapping,
261         const skif::LayerSpace<SkIRect>& desiredOutput,
262         std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
263     // Pixels up to the maximum displacement away from 'desiredOutput' can be moved into those
264     // bounds, depending on how the displacement map renders. To ensure those colors are defined,
265     // we require that outset buffer around 'desiredOutput' from the color map.
266     skif::LayerSpace<SkIRect> requiredInput = this->outsetByMaxDisplacement(mapping, desiredOutput);
267     requiredInput = this->getChildInputLayerBounds(kColor, mapping, requiredInput, contentBounds);
268 
269     // Accumulate the required input for the displacement filter to cover the original desired out
270     requiredInput.join(this->getChildInputLayerBounds(
271             kDisplacement, mapping, desiredOutput, contentBounds));
272     return requiredInput;
273 }
274 
onGetOutputLayerBounds(const skif::Mapping & mapping,std::optional<skif::LayerSpace<SkIRect>> contentBounds) const275 std::optional<skif::LayerSpace<SkIRect>> SkDisplacementMapImageFilter::onGetOutputLayerBounds(
276         const skif::Mapping& mapping,
277         std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
278     auto colorOutput = this->getChildOutputLayerBounds(kColor, mapping, contentBounds);
279     if (colorOutput) {
280         return this->outsetByMaxDisplacement(mapping, *colorOutput);
281     } else {
282         return skif::LayerSpace<SkIRect>::Unbounded();
283     }
284 }
285 
computeFastBounds(const SkRect & src) const286 SkRect SkDisplacementMapImageFilter::computeFastBounds(const SkRect& src) const {
287     SkRect colorBounds = this->getInput(kColor) ? this->getInput(kColor)->computeFastBounds(src)
288                                                 : src;
289     float maxDisplacement = 0.5f * SkScalarAbs(fScale);
290     return colorBounds.makeOutset(maxDisplacement, maxDisplacement);
291 }
292