blob: 70bc42a67fca40c1245d8f1684e0921cdb49a0a5 [file] [log] [blame]
/*
* Copyright 2013 The Android Open Source Project
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "include/effects/SkImageFilters.h"
#include "include/core/SkBlendMode.h"
#include "include/core/SkBlender.h"
#include "include/core/SkColor.h"
#include "include/core/SkFlattenable.h"
#include "include/core/SkImageFilter.h"
#include "include/core/SkM44.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkShader.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkBlenders.h"
#include "include/private/base/SkSpan_impl.h"
#include "include/private/base/SkTo.h"
#include "src/core/SkBlendModePriv.h"
#include "src/core/SkBlenderBase.h"
#include "src/core/SkImageFilterTypes.h"
#include "src/core/SkImageFilter_Base.h"
#include "src/core/SkPicturePriv.h"
#include "src/core/SkReadBuffer.h"
#include "src/core/SkRectPriv.h"
#include "src/core/SkWriteBuffer.h"
#include <cstdint>
#include <optional>
#include <utility>
namespace {
class SkBlendImageFilter : public SkImageFilter_Base {
// Input image filter indices
static constexpr int kBackground = 0;
static constexpr int kForeground = 1;
public:
SkBlendImageFilter(sk_sp<SkBlender> blender,
const std::optional<SkV4>& coefficients,
bool enforcePremul,
sk_sp<SkImageFilter> inputs[2])
: SkImageFilter_Base(inputs, 2)
, fBlender(std::move(blender))
, fArithmeticCoefficients(coefficients)
, fEnforcePremul(enforcePremul) {
// A null blender represents src-over, which should have been filled in by the factory
SkASSERT(fBlender);
}
SkRect computeFastBounds(const SkRect& bounds) const override;
protected:
void flatten(SkWriteBuffer&) const override;
private:
static constexpr uint32_t kArithmetic_SkBlendMode = kCustom_SkBlendMode + 1;
friend void ::SkRegisterBlendImageFilterFlattenable();
SK_FLATTENABLE_HOOKS(SkBlendImageFilter)
static sk_sp<SkFlattenable> LegacyArithmeticCreateProc(SkReadBuffer& buffer);
MatrixCapability onGetCTMCapability() const override { return MatrixCapability::kComplex; }
bool onAffectsTransparentBlack() const override {
// An arbitrary runtime blender or an arithmetic runtime blender with k3 != 0 affects
// transparent black.
return !as_BB(fBlender)->asBlendMode().has_value() &&
(!fArithmeticCoefficients.has_value() || (*fArithmeticCoefficients)[3] != 0.f);
}
skif::FilterResult onFilterImage(const skif::Context&) const override;
skif::LayerSpace<SkIRect> onGetInputLayerBounds(
const skif::Mapping& mapping,
const skif::LayerSpace<SkIRect>& desiredOutput,
std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
std::optional<skif::LayerSpace<SkIRect>> onGetOutputLayerBounds(
const skif::Mapping& mapping,
std::optional<skif::LayerSpace<SkIRect>> contentBounds) const override;
sk_sp<SkShader> makeBlendShader(sk_sp<SkShader> bg, sk_sp<SkShader> fg) const;
sk_sp<SkBlender> fBlender;
// Normally runtime SkBlenders are pessimistic about the bounds they affect. For Arithmetic,
// we remember the coefficients so that bounds can be reasoned about.
std::optional<SkV4> fArithmeticCoefficients;
bool fEnforcePremul; // Remembered to serialize the Arithmetic variant correctly
};
sk_sp<SkImageFilter> make_blend(sk_sp<SkBlender> blender,
sk_sp<SkImageFilter> background,
sk_sp<SkImageFilter> foreground,
const SkImageFilters::CropRect& cropRect,
std::optional<SkV4> coefficients = {},
bool enforcePremul = false) {
if (!blender) {
blender = SkBlender::Mode(SkBlendMode::kSrcOver);
}
auto cropped = [cropRect](sk_sp<SkImageFilter> filter) {
if (cropRect) {
filter = SkImageFilters::Crop(*cropRect, std::move(filter));
}
return filter;
};
if (auto bm = as_BB(blender)->asBlendMode()) {
if (bm == SkBlendMode::kSrc) {
return cropped(std::move(foreground));
} else if (bm == SkBlendMode::kDst) {
return cropped(std::move(background));
} else if (bm == SkBlendMode::kClear) {
return SkImageFilters::Empty();
}
}
sk_sp<SkImageFilter> inputs[2] = { std::move(background), std::move(foreground) };
sk_sp<SkImageFilter> filter{new SkBlendImageFilter(blender, coefficients,
enforcePremul, inputs)};
return cropped(std::move(filter));
}
} // anonymous namespace
sk_sp<SkImageFilter> SkImageFilters::Blend(SkBlendMode mode,
sk_sp<SkImageFilter> background,
sk_sp<SkImageFilter> foreground,
const CropRect& cropRect) {
return make_blend(SkBlender::Mode(mode),
std::move(background),
std::move(foreground),
cropRect);
}
sk_sp<SkImageFilter> SkImageFilters::Blend(sk_sp<SkBlender> blender,
sk_sp<SkImageFilter> background,
sk_sp<SkImageFilter> foreground,
const CropRect& cropRect) {
return make_blend(std::move(blender), std::move(background), std::move(foreground), cropRect);
}
sk_sp<SkImageFilter> SkImageFilters::Arithmetic(SkScalar k1,
SkScalar k2,
SkScalar k3,
SkScalar k4,
bool enforcePMColor,
sk_sp<SkImageFilter> background,
sk_sp<SkImageFilter> foreground,
const CropRect& cropRect) {
auto blender = SkBlenders::Arithmetic(k1, k2, k3, k4, enforcePMColor);
if (!blender) {
// Arithmetic() returns null on an error, not to optimize src-over
return nullptr;
}
return make_blend(std::move(blender),
std::move(background),
std::move(foreground),
cropRect,
// Carry arithmetic coefficients and premul behavior into image filter for
// serialization and bounds analysis
SkV4{k1, k2, k3, k4},
enforcePMColor);
}
void SkRegisterBlendImageFilterFlattenable() {
SK_REGISTER_FLATTENABLE(SkBlendImageFilter);
// TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name
SkFlattenable::Register("SkXfermodeImageFilter_Base", SkBlendImageFilter::CreateProc);
SkFlattenable::Register("SkXfermodeImageFilterImpl", SkBlendImageFilter::CreateProc);
SkFlattenable::Register("ArithmeticImageFilterImpl",
SkBlendImageFilter::LegacyArithmeticCreateProc);
SkFlattenable::Register("SkArithmeticImageFilter",
SkBlendImageFilter::LegacyArithmeticCreateProc);
}
sk_sp<SkFlattenable> SkBlendImageFilter::LegacyArithmeticCreateProc(SkReadBuffer& buffer) {
// Newer SKPs should be using the updated Blend CreateProc.
if (!buffer.validate(buffer.isVersionLT(SkPicturePriv::kCombineBlendArithmeticFilters))) {
SkASSERT(false); // debug-only, so release will just see a failed deserialization
return nullptr;
}
SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 2);
float k[4];
for (int i = 0; i < 4; ++i) {
k[i] = buffer.readScalar();
}
const bool enforcePremul = buffer.readBool();
return SkImageFilters::Arithmetic(k[0], k[1], k[2], k[3], enforcePremul,
common.getInput(0), common.getInput(1), common.cropRect());
}
sk_sp<SkFlattenable> SkBlendImageFilter::CreateProc(SkReadBuffer& buffer) {
SK_IMAGEFILTER_UNFLATTEN_COMMON(common, 2);
sk_sp<SkBlender> blender;
std::optional<SkV4> coefficients;
bool enforcePremul = false;
const uint32_t mode = buffer.read32();
if (mode == kArithmetic_SkBlendMode) {
// Should only see this sentinel value in newer SKPs
if (buffer.validate(!buffer.isVersionLT(SkPicturePriv::kCombineBlendArithmeticFilters))) {
SkV4 k;
for (int i = 0; i < 4; ++i) {
k[i] = buffer.readScalar();
}
coefficients = k;
enforcePremul = buffer.readBool();
blender = SkBlenders::Arithmetic(k.x, k.y, k.z, k.w, enforcePremul);
if (!buffer.validate(SkToBool(blender))) {
return nullptr; // A null arithmetic blender is an error condition
}
}
} else if (mode == kCustom_SkBlendMode) {
blender = buffer.readBlender();
} else {
if (!buffer.validate(mode <= (unsigned) SkBlendMode::kLastMode)) {
return nullptr;
}
blender = SkBlender::Mode((SkBlendMode)mode);
}
return make_blend(std::move(blender),
common.getInput(kBackground),
common.getInput(kForeground),
common.cropRect(),
coefficients,
enforcePremul);
}
void SkBlendImageFilter::flatten(SkWriteBuffer& buffer) const {
this->SkImageFilter_Base::flatten(buffer);
if (fArithmeticCoefficients.has_value()) {
buffer.write32(kArithmetic_SkBlendMode);
const SkV4& k = *fArithmeticCoefficients;
buffer.writeScalar(k[0]);
buffer.writeScalar(k[1]);
buffer.writeScalar(k[2]);
buffer.writeScalar(k[3]);
buffer.writeBool(fEnforcePremul);
} else if (auto bm = as_BB(fBlender)->asBlendMode()) {
buffer.write32((unsigned)bm.value());
} else {
buffer.write32(kCustom_SkBlendMode);
buffer.writeFlattenable(fBlender.get());
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////
sk_sp<SkShader> SkBlendImageFilter::makeBlendShader(sk_sp<SkShader> bg, sk_sp<SkShader> fg) const {
// A null input shader signifies transparent black when image filtering, but SkShaders::Blend
// expects non-null shaders. So we have to do some clean up.
if (!bg || !fg) {
// If we don't affect transparent black and both inputs are null, then return a null
// shader to skip any evaluation.
if (!this->onAffectsTransparentBlack() && !bg && !fg) {
return nullptr;
}
// Otherwise if only one input is null, we might be able to just return that one.
if (auto bm = as_BB(fBlender)->asBlendMode()) {
SkBlendModeCoeff src, dst;
if (SkBlendMode_AsCoeff(*bm, &src, &dst)) {
if (bg && (dst == SkBlendModeCoeff::kOne ||
dst == SkBlendModeCoeff::kISA ||
dst == SkBlendModeCoeff::kISC)) {
return bg;
}
if (fg && (src == SkBlendModeCoeff::kOne ||
src == SkBlendModeCoeff::kIDA)) {
return fg;
}
}
}
// If we made it this far, the blend has non-trivial behavior even when one of the
// inputs is transparent black, so replace the null shaders with that color.
if (!bg) { bg = SkShaders::Color(SK_ColorTRANSPARENT); }
if (!fg) { fg = SkShaders::Color(SK_ColorTRANSPARENT); }
}
return SkShaders::Blend(fBlender, std::move(bg), std::move(fg));
}
skif::FilterResult SkBlendImageFilter::onFilterImage(const skif::Context& ctx) const {
// We could just request 'desiredOutput' for the blend's required input size, since that's what
// it is expected to fill. However, some blend modes restrict the output to something other
// than the union of the foreground and background. To make this restriction available to both
// children before evaluating them, we determine the maximum possible output the blend can
// produce from the contentBounds and require that for both children to produce.
auto requiredInput = this->onGetOutputLayerBounds(ctx.mapping(), ctx.source().layerBounds());
if (requiredInput) {
if (!requiredInput->intersect(ctx.desiredOutput())) {
return {};
}
} else {
requiredInput = ctx.desiredOutput();
}
skif::Context inputCtx = ctx.withNewDesiredOutput(*requiredInput);
skif::FilterResult::Builder builder{ctx};
builder.add(this->getChildOutput(kBackground, inputCtx));
builder.add(this->getChildOutput(kForeground, inputCtx));
return builder.eval(
[&](SkSpan<sk_sp<SkShader>> inputs) -> sk_sp<SkShader> {
return this->makeBlendShader(inputs[kBackground], inputs[kForeground]);
}, requiredInput);
}
skif::LayerSpace<SkIRect> SkBlendImageFilter::onGetInputLayerBounds(
const skif::Mapping& mapping,
const skif::LayerSpace<SkIRect>& desiredOutput,
std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
skif::LayerSpace<SkIRect> requiredInput;
std::optional<skif::LayerSpace<SkIRect>> maxOutput;
if (contentBounds && (maxOutput = this->onGetOutputLayerBounds(mapping, *contentBounds))) {
// See comment in onFilterImage().
requiredInput = *maxOutput;
if (!requiredInput.intersect(desiredOutput)) {
// Don't bother recursing if we know the blend will discard everything
return skif::LayerSpace<SkIRect>::Empty();
}
} else {
// The content and/or the output of the child are unbounded so the intersection with the
// desired output is simply the desired output.
requiredInput = desiredOutput;
}
// Return the union of both FG and BG required inputs to ensure both have all necessary pixels
skif::LayerSpace<SkIRect> bgInput =
this->getChildInputLayerBounds(kBackground, mapping, requiredInput, contentBounds);
skif::LayerSpace<SkIRect> fgInput =
this->getChildInputLayerBounds(kForeground, mapping, requiredInput, contentBounds);
bgInput.join(fgInput);
return bgInput;
}
std::optional<skif::LayerSpace<SkIRect>> SkBlendImageFilter::onGetOutputLayerBounds(
const skif::Mapping& mapping,
std::optional<skif::LayerSpace<SkIRect>> contentBounds) const {
// Blending is (k0*FG*BG + k1*FG + k2*BG + k3) for arithmetic blenders OR
// ( 0*FG*BG + srcCoeff*FG + dstCoeff*BG + 0 ) for Porter-Duff blend modes OR
// un-inspectable(FG, BG) for advanced blend modes and other runtime blenders.
//
// There are six possible output bounds that can be produced:
// 1. No output: K = (0,0,0,0) or (srcCoeff,dstCoeff) = (kZero,kZero)
// 2. intersect(FG,BG): K = (non-zero, 0,0,0) or (srcCoeff,dstCoeff) = (kZero|kDA, kZero|kSA)
// 3. FG-only: K = (0, non-zero, 0,0) or (srcCoeff,dstCoeff) = (!kZero&!kDA, kZero|kSA)
// 4. BG-only: K = (0,0, non-zero, 0) or (srcCoeff,dstCoeff) = (kZero|kDA, !kZero&!kSA)
// 5. union(FG,BG): K = (*,*,*,0) or (srcCoeff,dstCoeff) = (!kZero&!kDA, !kZero&!kSA)
// or an advanced blend mode.
// 6. infinite: K = (*,*,*, non-zero) or a runtime blender other than SkBlenders::Arithmetic.
bool transparentOutsideFG = false;
bool transparentOutsideBG = false;
if (auto bm = as_BB(fBlender)->asBlendMode()) {
SkASSERT(*bm != SkBlendMode::kClear); // Should have been caught at creation time
SkBlendModeCoeff src, dst;
if (SkBlendMode_AsCoeff(*bm, &src, &dst)) {
// If dst's coefficient is 0 then nothing can produce non-transparent content outside
// of the foreground. When dst coefficient is SA, it will always be 0 outside the FG.
// For purposes of transparency analysis, SC == SA.
transparentOutsideFG = dst == SkBlendModeCoeff::kZero || dst == SkBlendModeCoeff::kSA
|| dst == SkBlendModeCoeff::kSC;
// And the reverse is true for src and the background content.
transparentOutsideBG = src == SkBlendModeCoeff::kZero || src == SkBlendModeCoeff::kDA;
}
// NOTE: advanced blends use src-over for their alpha channel, which should produce the
// union of FG and BG. That is the outcome if we leave transparentOutsideFG/BG false.
} else if (fArithmeticCoefficients.has_value()) {
[[maybe_unused]] static constexpr SkV4 kClearCoeff = {0.f, 0.f, 0.f, 0.f};
const SkV4& k = *fArithmeticCoefficients;
SkASSERT(k != kClearCoeff); // Should have been converted to an empty filter
if (k[3] != 0.f) {
// The arithmetic equation produces non-transparent black everywhere
return skif::LayerSpace<SkIRect>::Unbounded();
} else {
// Given the earlier assert and if, then (k[1] == k[2] == 0) implies k[0] != 0. If only
// one of k[1] or k[2] are non-zero then, regardless of k[0], then only that bounds
// has non-transparent content.
transparentOutsideFG = k[2] == 0.f;
transparentOutsideBG = k[1] == 0.f;
}
} else {
// A non-arithmetic runtime blender, so pessimistically assume it can return non-transparent
// black anywhere.
return skif::LayerSpace<SkIRect>::Unbounded();
}
auto foregroundBounds = this->getChildOutputLayerBounds(kForeground, mapping, contentBounds);
auto backgroundBounds = this->getChildOutputLayerBounds(kBackground, mapping, contentBounds);
if (transparentOutsideFG) {
if (transparentOutsideBG) {
// Output is the intersection of both
if (!foregroundBounds && backgroundBounds) {
foregroundBounds = *backgroundBounds;
} else if (backgroundBounds && !foregroundBounds->intersect(*backgroundBounds)) {
return skif::LayerSpace<SkIRect>::Empty();
}
// When both fore and background are infinite, foregroundBounds remains uninstantiated.
// When only foreground is provided, it's left unmodified, which is the correct result.
}
return foregroundBounds;
} else {
if (!transparentOutsideBG) {
// Output is the union of both (infinite blend-induced bounds were detected earlier).
if (foregroundBounds && backgroundBounds) {
backgroundBounds->join(*foregroundBounds);
} else {
// At least one of the union arguments is unbounded, so the union is infinite
backgroundBounds.reset();
}
}
return backgroundBounds;
}
}
SkRect SkBlendImageFilter::computeFastBounds(const SkRect& bounds) const {
// TODO: This is a prime example of why computeFastBounds() and onGetOutputLayerBounds() should
// be combined into the same function.
bool transparentOutsideFG = false;
bool transparentOutsideBG = false;
if (auto bm = as_BB(fBlender)->asBlendMode()) {
SkASSERT(*bm != SkBlendMode::kClear); // Should have been caught at creation time
SkBlendModeCoeff src, dst;
if (SkBlendMode_AsCoeff(*bm, &src, &dst)) {
// If dst's coefficient is 0 then nothing can produce non-transparent content outside
// of the foreground. When dst coefficient is SA, it will always be 0 outside the FG.
transparentOutsideFG = dst == SkBlendModeCoeff::kZero || dst == SkBlendModeCoeff::kSA;
// And the reverse is true for src and the background content.
transparentOutsideBG = src == SkBlendModeCoeff::kZero || src == SkBlendModeCoeff::kDA;
}
} else if (fArithmeticCoefficients.has_value()) {
[[maybe_unused]] static constexpr SkV4 kClearCoeff = {0.f, 0.f, 0.f, 0.f};
const SkV4& k = *fArithmeticCoefficients;
SkASSERT(k != kClearCoeff); // Should have been converted to an empty image filter
if (k[3] != 0.f) {
// The arithmetic equation produces non-transparent black everywhere
return SkRectPriv::MakeLargeS32();
} else {
// Given the earlier assert and if, then (k[1] == k[2] == 0) implies k[0] != 0. If only
// one of k[1] or k[2] are non-zero then, regardless of k[0], then only that bounds
// has non-transparent content.
transparentOutsideFG = k[2] == 0.f;
transparentOutsideBG = k[1] == 0.f;
}
} else {
// A non-arithmetic runtime blender, so pessimistically assume it can return non-transparent
// black anywhere.
return SkRectPriv::MakeLargeS32();
}
SkRect foregroundBounds = this->getInput(kForeground) ?
this->getInput(kForeground)->computeFastBounds(bounds) : bounds;
SkRect backgroundBounds = this->getInput(kBackground) ?
this->getInput(kBackground)->computeFastBounds(bounds) : bounds;
if (transparentOutsideFG) {
if (transparentOutsideBG) {
// Output is the intersection of both
if (!foregroundBounds.intersect(backgroundBounds)) {
return SkRect::MakeEmpty();
}
}
return foregroundBounds;
} else {
if (!transparentOutsideBG) {
// Output is the union of both (infinite bounds were detected earlier).
backgroundBounds.join(foregroundBounds);
}
return backgroundBounds;
}
}