Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
SkBlendImageFilter.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2013 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
9
15#include "include/core/SkM44.h"
16#include "include/core/SkRect.h"
30#include "src/core/SkRectPriv.h"
32
33#include <cstdint>
34#include <optional>
35#include <utility>
36
37namespace {
38
39class SkBlendImageFilter : public SkImageFilter_Base {
40 // Input image filter indices
41 static constexpr int kBackground = 0;
42 static constexpr int kForeground = 1;
43
44public:
45 SkBlendImageFilter(sk_sp<SkBlender> blender,
46 const std::optional<SkV4>& coefficients,
47 bool enforcePremul,
48 sk_sp<SkImageFilter> inputs[2])
50 , fBlender(std::move(blender))
51 , fArithmeticCoefficients(coefficients)
52 , fEnforcePremul(enforcePremul) {
53 // A null blender represents src-over, which should have been filled in by the factory
54 SkASSERT(fBlender);
55 }
56
57 SkRect computeFastBounds(const SkRect& bounds) const override;
58
59protected:
60 void flatten(SkWriteBuffer&) const override;
61
62private:
63 static constexpr uint32_t kArithmetic_SkBlendMode = kCustom_SkBlendMode + 1;
64
65 friend void ::SkRegisterBlendImageFilterFlattenable();
66 SK_FLATTENABLE_HOOKS(SkBlendImageFilter)
67 static sk_sp<SkFlattenable> LegacyArithmeticCreateProc(SkReadBuffer& buffer);
68
69 MatrixCapability onGetCTMCapability() const override { return MatrixCapability::kComplex; }
70
71 bool onAffectsTransparentBlack() const override {
72 // An arbitrary runtime blender or an arithmetic runtime blender with k3 != 0 affects
73 // transparent black.
74 return !as_BB(fBlender)->asBlendMode().has_value() &&
75 (!fArithmeticCoefficients.has_value() || (*fArithmeticCoefficients)[3] != 0.f);
76 }
77
78 skif::FilterResult onFilterImage(const skif::Context&) const override;
79
81 const skif::Mapping& mapping,
82 const skif::LayerSpace<SkIRect>& desiredOutput,
83 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
84
85 std::optional<skif::LayerSpace<SkIRect>> onGetOutputLayerBounds(
86 const skif::Mapping& mapping,
87 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
88
89 sk_sp<SkShader> makeBlendShader(sk_sp<SkShader> bg, sk_sp<SkShader> fg) const;
90
91 sk_sp<SkBlender> fBlender;
92
93 // Normally runtime SkBlenders are pessimistic about the bounds they affect. For Arithmetic,
94 // we remember the coefficients so that bounds can be reasoned about.
95 std::optional<SkV4> fArithmeticCoefficients;
96 bool fEnforcePremul; // Remembered to serialize the Arithmetic variant correctly
97};
98
99sk_sp<SkImageFilter> make_blend(sk_sp<SkBlender> blender,
100 sk_sp<SkImageFilter> background,
101 sk_sp<SkImageFilter> foreground,
102 const SkImageFilters::CropRect& cropRect,
103 std::optional<SkV4> coefficients = {},
104 bool enforcePremul = false) {
105 if (!blender) {
107 }
108
109 auto cropped = [cropRect](sk_sp<SkImageFilter> filter) {
110 if (cropRect) {
111 filter = SkImageFilters::Crop(*cropRect, std::move(filter));
112 }
113 return filter;
114 };
115
116 if (auto bm = as_BB(blender)->asBlendMode()) {
117 if (bm == SkBlendMode::kSrc) {
118 return cropped(std::move(foreground));
119 } else if (bm == SkBlendMode::kDst) {
120 return cropped(std::move(background));
121 } else if (bm == SkBlendMode::kClear) {
122 return SkImageFilters::Empty();
123 }
124 }
125
126 sk_sp<SkImageFilter> inputs[2] = { std::move(background), std::move(foreground) };
127 sk_sp<SkImageFilter> filter{new SkBlendImageFilter(blender, coefficients,
128 enforcePremul, inputs)};
129 return cropped(std::move(filter));
130}
131
132} // anonymous namespace
133
135 sk_sp<SkImageFilter> background,
136 sk_sp<SkImageFilter> foreground,
137 const CropRect& cropRect) {
138 return make_blend(SkBlender::Mode(mode),
139 std::move(background),
140 std::move(foreground),
141 cropRect);
142}
143
145 sk_sp<SkImageFilter> background,
146 sk_sp<SkImageFilter> foreground,
147 const CropRect& cropRect) {
148 return make_blend(std::move(blender), std::move(background), std::move(foreground), cropRect);
149}
150
152 SkScalar k2,
153 SkScalar k3,
154 SkScalar k4,
155 bool enforcePMColor,
156 sk_sp<SkImageFilter> background,
157 sk_sp<SkImageFilter> foreground,
158 const CropRect& cropRect) {
159 auto blender = SkBlenders::Arithmetic(k1, k2, k3, k4, enforcePMColor);
160 if (!blender) {
161 // Arithmetic() returns null on an error, not to optimize src-over
162 return nullptr;
163 }
164 return make_blend(std::move(blender),
165 std::move(background),
166 std::move(foreground),
167 cropRect,
168 // Carry arithmetic coefficients and premul behavior into image filter for
169 // serialization and bounds analysis
170 SkV4{k1, k2, k3, k4},
171 enforcePMColor);
172}
173
175 SK_REGISTER_FLATTENABLE(SkBlendImageFilter);
176 // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name
177 SkFlattenable::Register("SkXfermodeImageFilter_Base", SkBlendImageFilter::CreateProc);
178 SkFlattenable::Register("SkXfermodeImageFilterImpl", SkBlendImageFilter::CreateProc);
179 SkFlattenable::Register("ArithmeticImageFilterImpl",
180 SkBlendImageFilter::LegacyArithmeticCreateProc);
181 SkFlattenable::Register("SkArithmeticImageFilter",
182 SkBlendImageFilter::LegacyArithmeticCreateProc);
183}
184
185sk_sp<SkFlattenable> SkBlendImageFilter::LegacyArithmeticCreateProc(SkReadBuffer& buffer) {
186 // Newer SKPs should be using the updated Blend CreateProc.
187 if (!buffer.validate(buffer.isVersionLT(SkPicturePriv::kCombineBlendArithmeticFilters))) {
188 SkASSERT(false); // debug-only, so release will just see a failed deserialization
189 return nullptr;
190 }
191
193 float k[4];
194 for (int i = 0; i < 4; ++i) {
195 k[i] = buffer.readScalar();
196 }
197 const bool enforcePremul = buffer.readBool();
198 return SkImageFilters::Arithmetic(k[0], k[1], k[2], k[3], enforcePremul,
199 common.getInput(0), common.getInput(1), common.cropRect());
200}
201
202sk_sp<SkFlattenable> SkBlendImageFilter::CreateProc(SkReadBuffer& buffer) {
204
205 sk_sp<SkBlender> blender;
206 std::optional<SkV4> coefficients;
207 bool enforcePremul = false;
208
209 const uint32_t mode = buffer.read32();
210 if (mode == kArithmetic_SkBlendMode) {
211 // Should only see this sentinel value in newer SKPs
212 if (buffer.validate(!buffer.isVersionLT(SkPicturePriv::kCombineBlendArithmeticFilters))) {
213 SkV4 k;
214 for (int i = 0; i < 4; ++i) {
215 k[i] = buffer.readScalar();
216 }
217 coefficients = k;
218 enforcePremul = buffer.readBool();
219 blender = SkBlenders::Arithmetic(k.x, k.y, k.z, k.w, enforcePremul);
220 if (!buffer.validate(SkToBool(blender))) {
221 return nullptr; // A null arithmetic blender is an error condition
222 }
223 }
224 } else if (mode == kCustom_SkBlendMode) {
225 blender = buffer.readBlender();
226 } else {
227 if (!buffer.validate(mode <= (unsigned) SkBlendMode::kLastMode)) {
228 return nullptr;
229 }
230 blender = SkBlender::Mode((SkBlendMode)mode);
231 }
232
233 return make_blend(std::move(blender),
234 common.getInput(kBackground),
235 common.getInput(kForeground),
236 common.cropRect(),
237 coefficients,
238 enforcePremul);
239}
240
241void SkBlendImageFilter::flatten(SkWriteBuffer& buffer) const {
242 this->SkImageFilter_Base::flatten(buffer);
243 if (fArithmeticCoefficients.has_value()) {
244 buffer.write32(kArithmetic_SkBlendMode);
245
246 const SkV4& k = *fArithmeticCoefficients;
247 buffer.writeScalar(k[0]);
248 buffer.writeScalar(k[1]);
249 buffer.writeScalar(k[2]);
250 buffer.writeScalar(k[3]);
251 buffer.writeBool(fEnforcePremul);
252 } else if (auto bm = as_BB(fBlender)->asBlendMode()) {
253 buffer.write32((unsigned)bm.value());
254 } else {
256 buffer.writeFlattenable(fBlender.get());
257 }
258}
259
260///////////////////////////////////////////////////////////////////////////////////////////////////
261
262sk_sp<SkShader> SkBlendImageFilter::makeBlendShader(sk_sp<SkShader> bg, sk_sp<SkShader> fg) const {
263 // A null input shader signifies transparent black when image filtering, but SkShaders::Blend
264 // expects non-null shaders. So we have to do some clean up.
265 if (!bg || !fg) {
266 // If we don't affect transparent black and both inputs are null, then return a null
267 // shader to skip any evaluation.
268 if (!this->onAffectsTransparentBlack() && !bg && !fg) {
269 return nullptr;
270 }
271 // Otherwise if only one input is null, we might be able to just return that one.
272 if (auto bm = as_BB(fBlender)->asBlendMode()) {
274 if (SkBlendMode_AsCoeff(*bm, &src, &dst)) {
275 if (bg && (dst == SkBlendModeCoeff::kOne ||
276 dst == SkBlendModeCoeff::kISA ||
277 dst == SkBlendModeCoeff::kISC)) {
278 return bg;
279 }
280 if (fg && (src == SkBlendModeCoeff::kOne ||
281 src == SkBlendModeCoeff::kIDA)) {
282 return fg;
283 }
284 }
285 }
286 // If we made it this far, the blend has non-trivial behavior even when one of the
287 // inputs is transparent black, so replace the null shaders with that color.
288 if (!bg) { bg = SkShaders::Color(SK_ColorTRANSPARENT); }
289 if (!fg) { fg = SkShaders::Color(SK_ColorTRANSPARENT); }
290 }
291
292 return SkShaders::Blend(fBlender, std::move(bg), std::move(fg));
293}
294
295skif::FilterResult SkBlendImageFilter::onFilterImage(const skif::Context& ctx) const {
296 // We could just request 'desiredOutput' for the blend's required input size, since that's what
297 // it is expected to fill. However, some blend modes restrict the output to something other
298 // than the union of the foreground and background. To make this restriction available to both
299 // children before evaluating them, we determine the maximum possible output the blend can
300 // produce from the contentBounds and require that for both children to produce.
301 auto requiredInput = this->onGetOutputLayerBounds(ctx.mapping(), ctx.source().layerBounds());
302 if (requiredInput) {
303 if (!requiredInput->intersect(ctx.desiredOutput())) {
304 return {};
305 }
306 } else {
307 requiredInput = ctx.desiredOutput();
308 }
309
310 skif::Context inputCtx = ctx.withNewDesiredOutput(*requiredInput);
312 builder.add(this->getChildOutput(kBackground, inputCtx));
313 builder.add(this->getChildOutput(kForeground, inputCtx));
314 return builder.eval(
315 [&](SkSpan<sk_sp<SkShader>> inputs) -> sk_sp<SkShader> {
316 return this->makeBlendShader(inputs[kBackground], inputs[kForeground]);
317 }, requiredInput);
318}
319
320skif::LayerSpace<SkIRect> SkBlendImageFilter::onGetInputLayerBounds(
321 const skif::Mapping& mapping,
322 const skif::LayerSpace<SkIRect>& desiredOutput,
323 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
324
325 skif::LayerSpace<SkIRect> requiredInput;
326 std::optional<skif::LayerSpace<SkIRect>> maxOutput;
327 if (contentBounds && (maxOutput = this->onGetOutputLayerBounds(mapping, *contentBounds))) {
328 // See comment in onFilterImage().
329 requiredInput = *maxOutput;
330 if (!requiredInput.intersect(desiredOutput)) {
331 // Don't bother recursing if we know the blend will discard everything
333 }
334 } else {
335 // The content and/or the output of the child are unbounded so the intersection with the
336 // desired output is simply the desired output.
337 requiredInput = desiredOutput;
338 }
339
340 // Return the union of both FG and BG required inputs to ensure both have all necessary pixels
342 this->getChildInputLayerBounds(kBackground, mapping, requiredInput, contentBounds);
344 this->getChildInputLayerBounds(kForeground, mapping, requiredInput, contentBounds);
345
346 bgInput.join(fgInput);
347 return bgInput;
348}
349
350std::optional<skif::LayerSpace<SkIRect>> SkBlendImageFilter::onGetOutputLayerBounds(
351 const skif::Mapping& mapping,
352 std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
353 // Blending is (k0*FG*BG + k1*FG + k2*BG + k3) for arithmetic blenders OR
354 // ( 0*FG*BG + srcCoeff*FG + dstCoeff*BG + 0 ) for Porter-Duff blend modes OR
355 // un-inspectable(FG, BG) for advanced blend modes and other runtime blenders.
356 //
357 // There are six possible output bounds that can be produced:
358 // 1. No output: K = (0,0,0,0) or (srcCoeff,dstCoeff) = (kZero,kZero)
359 // 2. intersect(FG,BG): K = (non-zero, 0,0,0) or (srcCoeff,dstCoeff) = (kZero|kDA, kZero|kSA)
360 // 3. FG-only: K = (0, non-zero, 0,0) or (srcCoeff,dstCoeff) = (!kZero&!kDA, kZero|kSA)
361 // 4. BG-only: K = (0,0, non-zero, 0) or (srcCoeff,dstCoeff) = (kZero|kDA, !kZero&!kSA)
362 // 5. union(FG,BG): K = (*,*,*,0) or (srcCoeff,dstCoeff) = (!kZero&!kDA, !kZero&!kSA)
363 // or an advanced blend mode.
364 // 6. infinite: K = (*,*,*, non-zero) or a runtime blender other than SkBlenders::Arithmetic.
365 bool transparentOutsideFG = false;
366 bool transparentOutsideBG = false;
367 if (auto bm = as_BB(fBlender)->asBlendMode()) {
368 SkASSERT(*bm != SkBlendMode::kClear); // Should have been caught at creation time
370 if (SkBlendMode_AsCoeff(*bm, &src, &dst)) {
371 // If dst's coefficient is 0 then nothing can produce non-transparent content outside
372 // of the foreground. When dst coefficient is SA, it will always be 0 outside the FG.
373 // For purposes of transparency analysis, SC == SA.
374 transparentOutsideFG = dst == SkBlendModeCoeff::kZero || dst == SkBlendModeCoeff::kSA
376 // And the reverse is true for src and the background content.
377 transparentOutsideBG = src == SkBlendModeCoeff::kZero || src == SkBlendModeCoeff::kDA;
378 }
379 // NOTE: advanced blends use src-over for their alpha channel, which should produce the
380 // union of FG and BG. That is the outcome if we leave transparentOutsideFG/BG false.
381 } else if (fArithmeticCoefficients.has_value()) {
382 [[maybe_unused]] static constexpr SkV4 kClearCoeff = {0.f, 0.f, 0.f, 0.f};
383 const SkV4& k = *fArithmeticCoefficients;
384 SkASSERT(k != kClearCoeff); // Should have been converted to an empty filter
385
386 if (k[3] != 0.f) {
387 // The arithmetic equation produces non-transparent black everywhere
389 } else {
390 // Given the earlier assert and if, then (k[1] == k[2] == 0) implies k[0] != 0. If only
391 // one of k[1] or k[2] are non-zero then, regardless of k[0], then only that bounds
392 // has non-transparent content.
393 transparentOutsideFG = k[2] == 0.f;
394 transparentOutsideBG = k[1] == 0.f;
395 }
396 } else {
397 // A non-arithmetic runtime blender, so pessimistically assume it can return non-transparent
398 // black anywhere.
400 }
401
402 auto foregroundBounds = this->getChildOutputLayerBounds(kForeground, mapping, contentBounds);
403 auto backgroundBounds = this->getChildOutputLayerBounds(kBackground, mapping, contentBounds);
404 if (transparentOutsideFG) {
405 if (transparentOutsideBG) {
406 // Output is the intersection of both
407 if (!foregroundBounds && backgroundBounds) {
408 foregroundBounds = *backgroundBounds;
409 } else if (backgroundBounds && !foregroundBounds->intersect(*backgroundBounds)) {
411 }
412 // When both fore and background are infinite, foregroundBounds remains uninstantiated.
413 // When only foreground is provided, it's left unmodified, which is the correct result.
414 }
415 return foregroundBounds;
416 } else {
417 if (!transparentOutsideBG) {
418 // Output is the union of both (infinite blend-induced bounds were detected earlier).
419 if (foregroundBounds && backgroundBounds) {
420 backgroundBounds->join(*foregroundBounds);
421 } else {
422 // At least one of the union arguments is unbounded, so the union is infinite
423 backgroundBounds.reset();
424 }
425 }
426 return backgroundBounds;
427 }
428}
429
430SkRect SkBlendImageFilter::computeFastBounds(const SkRect& bounds) const {
431 // TODO: This is a prime example of why computeFastBounds() and onGetOutputLayerBounds() should
432 // be combined into the same function.
433 bool transparentOutsideFG = false;
434 bool transparentOutsideBG = false;
435 if (auto bm = as_BB(fBlender)->asBlendMode()) {
436 SkASSERT(*bm != SkBlendMode::kClear); // Should have been caught at creation time
438 if (SkBlendMode_AsCoeff(*bm, &src, &dst)) {
439 // If dst's coefficient is 0 then nothing can produce non-transparent content outside
440 // of the foreground. When dst coefficient is SA, it will always be 0 outside the FG.
441 transparentOutsideFG = dst == SkBlendModeCoeff::kZero || dst == SkBlendModeCoeff::kSA;
442 // And the reverse is true for src and the background content.
443 transparentOutsideBG = src == SkBlendModeCoeff::kZero || src == SkBlendModeCoeff::kDA;
444 }
445 } else if (fArithmeticCoefficients.has_value()) {
446 [[maybe_unused]] static constexpr SkV4 kClearCoeff = {0.f, 0.f, 0.f, 0.f};
447 const SkV4& k = *fArithmeticCoefficients;
448 SkASSERT(k != kClearCoeff); // Should have been converted to an empty image filter
449
450 if (k[3] != 0.f) {
451 // The arithmetic equation produces non-transparent black everywhere
453 } else {
454 // Given the earlier assert and if, then (k[1] == k[2] == 0) implies k[0] != 0. If only
455 // one of k[1] or k[2] are non-zero then, regardless of k[0], then only that bounds
456 // has non-transparent content.
457 transparentOutsideFG = k[2] == 0.f;
458 transparentOutsideBG = k[1] == 0.f;
459 }
460 } else {
461 // A non-arithmetic runtime blender, so pessimistically assume it can return non-transparent
462 // black anywhere.
464 }
465
466 SkRect foregroundBounds = this->getInput(kForeground) ?
467 this->getInput(kForeground)->computeFastBounds(bounds) : bounds;
468 SkRect backgroundBounds = this->getInput(kBackground) ?
469 this->getInput(kBackground)->computeFastBounds(bounds) : bounds;
470 if (transparentOutsideFG) {
471 if (transparentOutsideBG) {
472 // Output is the intersection of both
473 if (!foregroundBounds.intersect(backgroundBounds)) {
474 return SkRect::MakeEmpty();
475 }
476 }
477 return foregroundBounds;
478 } else {
479 if (!transparentOutsideBG) {
480 // Output is the union of both (infinite bounds were detected earlier).
481 backgroundBounds.join(foregroundBounds);
482 }
483 return backgroundBounds;
484 }
485}
#define SkASSERT(cond)
Definition SkAssert.h:116
void SkRegisterBlendImageFilterFlattenable()
constexpr uint8_t kCustom_SkBlendMode
SK_API bool SkBlendMode_AsCoeff(SkBlendMode mode, SkBlendModeCoeff *src, SkBlendModeCoeff *dst)
SkBlendMode
Definition SkBlendMode.h:38
@ kSrcOver
r = s + (1-sa)*d
@ kLastMode
last valid value
@ kClear
r = 0
SkBlendModeCoeff
Definition SkBlendMode.h:84
SkBlenderBase * as_BB(SkBlender *blend)
constexpr SkColor SK_ColorTRANSPARENT
Definition SkColor.h:99
#define SK_FLATTENABLE_HOOKS(type)
#define SK_REGISTER_FLATTENABLE(type)
#define SK_IMAGEFILTER_UNFLATTEN_COMMON(localVar, expectedCount)
static constexpr bool SkToBool(const T &x)
Definition SkTo.h:35
virtual std::optional< SkBlendMode > asBlendMode() const
static sk_sp< SkBlender > Mode(SkBlendMode mode)
static sk_sp< SkBlender > Arithmetic(float k1, float k2, float k3, float k4, bool enforcePremul)
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
virtual bool onAffectsTransparentBlack() const
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 MatrixCapability onGetCTMCapability() const
virtual SkRect computeFastBounds(const SkRect &bounds) const
static sk_sp< SkImageFilter > Arithmetic(SkScalar k1, SkScalar k2, SkScalar k3, SkScalar k4, bool enforcePMColor, sk_sp< SkImageFilter > background, sk_sp< SkImageFilter > foreground, const CropRect &cropRect={})
static sk_sp< SkImageFilter > Empty()
static sk_sp< SkImageFilter > Crop(const SkRect &rect, SkTileMode tileMode, sk_sp< SkImageFilter > input)
static sk_sp< SkImageFilter > Blend(SkBlendMode mode, sk_sp< SkImageFilter > background, sk_sp< SkImageFilter > foreground=nullptr, const CropRect &cropRect={})
@ kCombineBlendArithmeticFilters
static SkRect MakeLargeS32()
Definition SkRectPriv.h:33
const LayerSpace< SkIRect > & desiredOutput() const
const FilterResult & source() const
Context withNewDesiredOutput(const LayerSpace< SkIRect > &desiredOutput) const
const Mapping & mapping() const
LayerSpace< SkIRect > layerBounds() const
@ kBackground
Suitable for threads that shouldn't disrupt high priority work.
Definition embedder.h:258
float SkScalar
Definition extension.cpp:12
static const uint8_t buffer[]
Optional< SkRect > bounds
Definition SkRecords.h:189
it will be possible to load the file into Perfetto s trace viewer disable asset Prevents usage of any non test fonts unless they were explicitly Loaded via prefetched default font Indicates whether the embedding started a prefetch of the default font manager before creating the engine run In non interactive mode
Definition switches.h:228
dst
Definition cp.py:12
Definition ref_ptr.h:256
static constexpr SkRect MakeEmpty()
Definition SkRect.h:595
bool intersect(const SkRect &r)
Definition SkRect.cpp:114
void join(const SkRect &r)
Definition SkRect.cpp:126
Definition SkM44.h:98
float w
Definition SkM44.h:99
float y
Definition SkM44.h:99
float x
Definition SkM44.h:99
float z
Definition SkM44.h:99