Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkDisplacementMapImageFilter.cpp
Go to the documentation of this file.
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
9
13#include "include/core/SkM44.h"
15#include "include/core/SkRect.h"
20#include "include/core/SkSize.h"
29
30#include <optional>
31#include <utility>
32
33namespace {
34
35class 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
44public:
45 SkDisplacementMapImageFilter(SkColorChannel xChannel, SkColorChannel yChannel,
48 , fXChannel(xChannel)
49 , fYChannel(yChannel)
50 , fScale(scale) {}
51
52 SkRect computeFastBounds(const SkRect& src) const override;
53
54protected:
55 void flatten(SkWriteBuffer&) const override;
56
57private:
58 friend void ::SkRegisterDisplacementMapImageFilterFlattenable();
59 SK_FLATTENABLE_HOOKS(SkDisplacementMapImageFilter)
60
61 skif::FilterResult onFilterImage(const skif::Context&) const override;
62
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
72 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
90bool channel_selector_type_is_valid(SkColorChannel cst) {
91 switch (cst) {
96 return true;
97 default:
98 break;
99 }
100 return false;
101}
102
103sk_sp<SkShader> make_displacement_shader(
104 sk_sp<SkShader> displacement,
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
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
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
169sk_sp<SkFlattenable> SkDisplacementMapImageFilter::CreateProc(SkReadBuffer& buffer) {
171
174 SkScalar scale = buffer.readScalar();
175
176 return SkImageFilters::DisplacementMap(xsel, ysel, scale, common.getInput(0),
177 common.getInput(1), common.cropRect());
178}
179
180void 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
189skif::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.
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
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
259skif::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
275std::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 {
283 }
284}
285
286SkRect 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}
static constexpr SkColor kColor
SkColor4f color
constexpr SkColor SK_ColorTRANSPARENT
Definition SkColor.h:99
SkColorChannel
Definition SkColor.h:228
void SkRegisterDisplacementMapImageFilterFlattenable()
#define SK_FLATTENABLE_HOOKS(type)
#define SK_REGISTER_FLATTENABLE(type)
#define SK_IMAGEFILTER_UNFLATTEN_COMMON(localVar, expectedCount)
sk_sp< T > sk_ref_sp(T *obj)
Definition SkRefCnt.h:381
#define SkScalarAbs(x)
Definition SkScalar.h:39
static void Register(const char name[], Factory)
virtual skif::LayerSpace< SkIRect > onGetInputLayerBounds(const skif::Mapping &mapping, const skif::LayerSpace< SkIRect > &desiredOutput, std::optional< skif::LayerSpace< SkIRect > > contentBounds) const =0
void flatten(SkWriteBuffer &) const override
virtual std::optional< skif::LayerSpace< SkIRect > > onGetOutputLayerBounds(const skif::Mapping &mapping, std::optional< skif::LayerSpace< SkIRect > > contentBounds) const =0
virtual skif::FilterResult onFilterImage(const skif::Context &context) const =0
virtual SkRect computeFastBounds(const SkRect &bounds) const
static sk_sp< SkImageFilter > DisplacementMap(SkColorChannel xChannelSelector, SkColorChannel yChannelSelector, SkScalar scale, sk_sp< SkImageFilter > displacement, sk_sp< SkImageFilter > color, const CropRect &cropRect={})
static sk_sp< SkImageFilter > Crop(const SkRect &rect, SkTileMode tileMode, sk_sp< SkImageFilter > input)
static SkMatrix Translate(SkScalar dx, SkScalar dy)
Definition SkMatrix.h:91
Context withNewColorSpace(SkColorSpace *cs) const
const LayerSpace< SkIRect > & desiredOutput() const
Context withNewDesiredOutput(const LayerSpace< SkIRect > &desiredOutput) const
const Mapping & mapping() const
LayerSpace< SkIRect > layerBounds() const
FilterResult applyTransform(const Context &ctx, const LayerSpace< SkMatrix > &transform, const SkSamplingOptions &sampling) const
LayerSpace< T > paramToLayer(const ParameterSpace< T > &paramGeometry) const
float SkScalar
Definition extension.cpp:12
static const uint8_t buffer[]
const SkRuntimeEffect * GetKnownRuntimeEffect(StableKey stableKey)
Optional< SkRect > bounds
Definition SkRecords.h:189
@ kColor
hue and saturation of source with luminosity of destination
const Scalar scale
SkRect makeOutset(float dx, float dy) const
Definition SkRect.h:1002
Definition SkM44.h:19
float x
Definition SkM44.h:20
Definition SkM44.h:98