/*
 * 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;
    }
}
