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