/*
 * Copyright 2021 Google LLC
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "gm/gm.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkImage.h"
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkShader.h"
#include "include/core/SkSize.h"
#include "include/core/SkString.h"
#include "include/core/SkTileMode.h"
#include "include/core/SkTypes.h"
#include "include/effects/SkGradient.h"
#include "include/effects/SkImageFilters.h"
#include "include/effects/SkLumaColorFilter.h"
#include "include/effects/SkPerlinNoiseShader.h"
#include "include/effects/SkRuntimeEffect.h"
#include "src/core/SkRuntimeEffectPriv.h"
#include "tools/Resources.h"

#include <math.h>

// A tint filter maps colors to a given range (gradient), based on the input luminance:
//
//   c' = lerp(lo, hi, luma(c))
static sk_sp<SkColorFilter> MakeTintColorFilter(SkColor lo, SkColor hi, bool useSkSL) {
    const auto r_lo = SkColorGetR(lo),
    g_lo = SkColorGetG(lo),
    b_lo = SkColorGetB(lo),
    a_lo = SkColorGetA(lo),
    r_hi = SkColorGetR(hi),
    g_hi = SkColorGetG(hi),
    b_hi = SkColorGetB(hi),
    a_hi = SkColorGetA(hi);

    // We map component-wise:
    //
    //   r' = lo.r + (hi.r - lo.r) * luma
    //   g' = lo.g + (hi.g - lo.g) * luma
    //   b' = lo.b + (hi.b - lo.b) * luma
    //   a' = lo.a + (hi.a - lo.a) * luma
    //
    // The input luminance is stored in the alpha channel
    // (and RGB are cleared -- see SkLumaColorFilter). Thus:
    const float tint_matrix[] = {
        0, 0, 0, (r_hi - r_lo) / 255.0f, SkIntToScalar(r_lo) / 255.0f,
        0, 0, 0, (g_hi - g_lo) / 255.0f, SkIntToScalar(g_lo) / 255.0f,
        0, 0, 0, (b_hi - b_lo) / 255.0f, SkIntToScalar(b_lo) / 255.0f,
        0, 0, 0, (a_hi - a_lo) / 255.0f, SkIntToScalar(a_lo) / 255.0f,
    };

    sk_sp<SkColorFilter> inner = SkLumaColorFilter::Make(),
                         outer = SkColorFilters::Matrix(tint_matrix);

    // Prove that we can implement compose-color-filter using runtime effects
    if (useSkSL) {
        auto [effect, error] = SkRuntimeEffect::MakeForColorFilter(SkString(R"(
            uniform colorFilter inner;
            uniform colorFilter outer;
            half4 main(half4 c) { return outer.eval(inner.eval(c)); }
        )"));
        SkASSERT(effect);
        sk_sp<SkColorFilter> children[] = { inner, outer };
        return effect->makeColorFilter(nullptr, children, std::size(children));
    } else {
        return outer->makeComposed(inner);
    }
}

DEF_SIMPLE_GM(composeCF, canvas, 200, 200) {
    // This GM draws a simple color-filter network, using the existing "makeComposed" API, and also
    // using a runtime color filter that does the same thing.
    SkPaint paint;
    const SkColor4f gradient_colors[] = {
            SkColors::kRed, SkColors::kGreen, SkColors::kBlue, SkColors::kRed};
    paint.setShader(SkShaders::SweepGradient({50, 50},
                                             {{gradient_colors, {}, SkTileMode::kClamp}, {}}));

    canvas->save();
    for (bool useSkSL : {false, true}) {
        auto cf0 = MakeTintColorFilter(0xff300000, 0xffa00000, useSkSL);  // red tint
        auto cf1 = MakeTintColorFilter(0xff003000, 0xff00a000, useSkSL);  // green tint

        paint.setColorFilter(cf0);
        canvas->drawRect({0, 0, 100, 100}, paint);
        canvas->translate(100, 0);

        paint.setColorFilter(cf1);
        canvas->drawRect({0, 0, 100, 100}, paint);

        canvas->restore();
        canvas->translate(0, 100);
    }
}

DEF_SIMPLE_GM(composeCFIF, canvas, 604, 200) {
    // This GM draws a ::Shader image filter composed with a ::ColorFilter image filter in two
    // ways (direct and via ::Compose). This ensures the use (or non-use in this case) of the source
    // image is the same across both means of composition.
    auto cf = MakeTintColorFilter(0xff300000, 0xffa00000, /*useSkSL=*/false);
    auto shader = SkShaders::MakeTurbulence(0.01f, 0.01f, 2, 0.f);

    auto shaderIF = SkImageFilters::Shader(shader, SkImageFilters::Dither::kNo);
    auto directCompose = SkImageFilters::ColorFilter(cf, shaderIF);
    auto indirectCompose = SkImageFilters::Compose(
            /*outer=*/SkImageFilters::ColorFilter(cf, nullptr),
            /*inner=*/shaderIF);

    { // Directly draw the shader composed with the color filter
        canvas->save();
            canvas->clipRect({0, 0, 200, 200});
            SkPaint p;
            p.setShader(shader);
            p.setColorFilter(cf);
            canvas->drawPaint(p);
        canvas->restore();
    }
    canvas->translate(202, 0);
    { // Draw with the directly composed image filter
        canvas->save();
            canvas->clipRect({0, 0, 200, 200});
            SkPaint p;
            p.setImageFilter(directCompose);
            canvas->drawPaint(p);
        canvas->restore();
    }
    canvas->translate(202, 0);
    {
        // Draw with the indirectly composed image filter
        canvas->save();
            canvas->clipRect({0, 0, 200, 200});
            SkPaint p;
            p.setImageFilter(indirectCompose);
            canvas->drawPaint(p);
        canvas->restore();
    }
}
