Flutter Engine
The Flutter Engine
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Modules Pages
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:
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,
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.
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(
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
Definition: CanvasTest.cpp:265
@ kColor
hue and saturation of source with luminosity of destination
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
bool intersect(const LayerSpace< SkIRect > &r)
void join(const LayerSpace< SkIRect > &r)
LayerSpace< SkISize > ceil() const
LayerSpace< T > paramToLayer(const ParameterSpace< T > &paramGeometry) const
DlColor color
float SkScalar
Definition: extension.cpp:12
const SkRuntimeEffect * GetKnownRuntimeEffect(StableKey stableKey)
Optional< SkRect > bounds
Definition: SkRecords.h:189
SK_API sk_sp< SkShader > Color(SkColor)
Definition: common.py:1
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir Path to the cache directory This is different from the persistent_cache_path in embedder which is used for Skia shader cache icu native lib Path to the library file that exports the ICU data vm service The hostname IP address on which the Dart VM Service should be served If not defaults to or::depending on whether ipv6 is specified vm service A custom Dart VM Service port The default is to pick a randomly available open port disable vm Disable the Dart VM Service The Dart VM Service is never available in release mode disable vm service Disable mDNS Dart VM Service publication Bind to the IPv6 localhost address for the Dart VM Service Ignored if vm service host is set endless trace buffer
Definition: switches.h:126
const Scalar scale
SkRect makeOutset(float dx, float dy) const
Definition: SkRect.h:1002
Definition: SkM44.h:19
Definition: SkM44.h:98