/*
 * Copyright 2015 Google Inc.
 *
 * 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/SkFlattenable.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageFilter.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkSamplingOptions.h"
#include "include/private/base/SkAssert.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/SkSamplingPriv.h"
#include "src/core/SkWriteBuffer.h"

#include <optional>
#include <utility>

namespace {

class SkImageImageFilter final : public SkImageFilter_Base {
public:
    SkImageImageFilter(sk_sp<SkImage> image,
                       const SkRect& srcRect,
                       const SkRect& dstRect,
                       const SkSamplingOptions& sampling)
            : SkImageFilter_Base(nullptr, 0)
            , fImage(std::move(image))
            , fSrcRect(srcRect)
            , fDstRect(dstRect)
            , fSampling(sampling) {
        // The dst rect should be non-empty
        SkASSERT(fImage && !dstRect.isEmpty());
    }

    SkRect computeFastBounds(const SkRect&) const override { return SkRect(fDstRect); }

protected:
    void flatten(SkWriteBuffer&) const override;

private:
    friend void ::SkRegisterImageImageFilterFlattenable();
    SK_FLATTENABLE_HOOKS(SkImageImageFilter)

    MatrixCapability onGetCTMCapability() const override { return MatrixCapability::kComplex; }

    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<SkImage> fImage;
    // The src rect is relative to the image's contents, so is not technically in the parameter
    // coordinate space that responds to the layer matrix (unlike fDstRect).
    SkRect fSrcRect;
    skif::ParameterSpace<SkRect> fDstRect;
    SkSamplingOptions fSampling;
};

} // end namespace

sk_sp<SkImageFilter> SkImageFilters::Image(sk_sp<SkImage> image,
                                           const SkRect& srcRect,
                                           const SkRect& dstRect,
                                           const SkSamplingOptions& sampling) {
    if (srcRect.isEmpty() || dstRect.isEmpty() || !image) {
        // There is no content to draw, so the filter should produce transparent black
        return SkImageFilters::Empty();
    } else {
        SkRect imageBounds = SkRect::Make(image->dimensions());
        if (imageBounds.contains(srcRect)) {
            // No change to srcRect and dstRect needed
            return sk_sp<SkImageFilter>(new SkImageImageFilter(
                    std::move(image), srcRect, dstRect, sampling));
        } else {
            SkMatrix srcToDst = SkMatrix::RectToRect(srcRect, dstRect);
            if (!imageBounds.intersect(srcRect)) {
                // No overlap, so draw empty
                return SkImageFilters::Empty();
            }

            // Adjust dstRect to match the updated src (which is stored in imageBounds)
            SkRect mappedBounds = srcToDst.mapRect(imageBounds);
            if (mappedBounds.isEmpty()) {
                return SkImageFilters::Empty();
            }
            return sk_sp<SkImageFilter>(new SkImageImageFilter(
                    std::move(image), imageBounds, mappedBounds, sampling));
        }
    }
}

void SkRegisterImageImageFilterFlattenable() {
    SK_REGISTER_FLATTENABLE(SkImageImageFilter);
    // TODO (michaelludwig) - Remove after grace period for SKPs to stop using old name
    SkFlattenable::Register("SkImageSourceImpl", SkImageImageFilter::CreateProc);
}

sk_sp<SkFlattenable> SkImageImageFilter::CreateProc(SkReadBuffer& buffer) {
    SkSamplingOptions sampling;
    if (buffer.isVersionLT(SkPicturePriv::kImageFilterImageSampling_Version)) {
        sampling = SkSamplingPriv::FromFQ(buffer.checkFilterQuality(), kLinear_SkMediumAs);
    } else {
        sampling = buffer.readSampling();
    }

    SkRect src, dst;
    buffer.readRect(&src);
    buffer.readRect(&dst);

    sk_sp<SkImage> image(buffer.readImage());
    if (!image) {
        return nullptr;
    }

    return SkImageFilters::Image(std::move(image), src, dst, sampling);
}

void SkImageImageFilter::flatten(SkWriteBuffer& buffer) const {
    buffer.writeSampling(fSampling);
    buffer.writeRect(fSrcRect);
    buffer.writeRect(SkRect(fDstRect));
    buffer.writeImage(fImage.get());
}

///////////////////////////////////////////////////////////////////////////////////////////////////

skif::FilterResult SkImageImageFilter::onFilterImage(const skif::Context& ctx) const {
    return skif::FilterResult::MakeFromImage(ctx, fImage, fSrcRect, fDstRect, fSampling);
}

skif::LayerSpace<SkIRect> SkImageImageFilter::onGetInputLayerBounds(
        const skif::Mapping&,
        const skif::LayerSpace<SkIRect>&,
        std::optional<skif::LayerSpace<SkIRect>>) const {
    // This is a leaf filter, it requires no input and no further recursion
    return skif::LayerSpace<SkIRect>::Empty();
}

std::optional<skif::LayerSpace<SkIRect>> SkImageImageFilter::onGetOutputLayerBounds(
        const skif::Mapping& mapping,
        std::optional<skif::LayerSpace<SkIRect>>) const {
    // The output is the transformed bounds of the image.
    return mapping.paramToLayer(fDstRect).roundOut();
}
