/*
 * Copyright 2012 Google Inc.
 *
 * 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/SkBitmap.h"
#include "include/core/SkBlendMode.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorPriv.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageInfo.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/SkGradientShader.h"
#include "include/private/base/SkTDArray.h"
#include "src/base/SkTLazy.h"
#include "tools/GpuToolUtils.h"
#include "tools/ToolUtils.h"

#include <utility>

static sk_sp<SkShader> make_shader(SkBlendMode mode) {
    SkPoint pts[2];
    SkColor colors[2];

    pts[0].set(0, 0);
    pts[1].set(SkIntToScalar(100), 0);
    colors[0] = SK_ColorRED;
    colors[1] = SK_ColorBLUE;
    auto shaderA = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);

    pts[0].set(0, 0);
    pts[1].set(0, SkIntToScalar(100));
    colors[0] = SK_ColorBLACK;
    colors[1] = SkColorSetARGB(0x80, 0, 0, 0);
    auto shaderB = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);

    return SkShaders::Blend(mode, std::move(shaderA), std::move(shaderB));
}

class ComposeShaderGM : public skiagm::GM {
protected:
    void onOnceBeforeDraw() override {
        fShader = make_shader(SkBlendMode::kDstIn);
    }

    SkString getName() const override { return SkString("composeshader"); }

    SkISize getISize() override { return SkISize::Make(120, 120); }

    void onDraw(SkCanvas* canvas) override {
        SkPaint paint;
        paint.setColor(SK_ColorGREEN);
        canvas->drawRect(SkRect::MakeWH(100, 100), paint);
        paint.setShader(fShader);
        canvas->drawRect(SkRect::MakeWH(100, 100), paint);
    }

protected:
    sk_sp<SkShader> fShader;

private:
    typedef GM INHERITED ;
};
DEF_GM( return new ComposeShaderGM; )

class ComposeShaderAlphaGM : public skiagm::GM {
public:
    ComposeShaderAlphaGM() {}

protected:
    SkString getName() const override { return SkString("composeshader_alpha"); }

    SkISize getISize() override { return SkISize::Make(750, 220); }

    void onDraw(SkCanvas* canvas) override {
        sk_sp<SkShader> shaders[] = {
            make_shader(SkBlendMode::kDstIn),
            make_shader(SkBlendMode::kSrcOver),
        };

        SkPaint paint;
        paint.setColor(SK_ColorGREEN);

        const SkRect r = SkRect::MakeXYWH(5, 5, 100, 100);

        for (size_t y = 0; y < std::size(shaders); ++y) {
            canvas->save();
            for (int alpha = 0xFF; alpha > 0; alpha -= 0x28) {
                paint.setAlphaf(1.0f);
                paint.setShader(nullptr);
                canvas->drawRect(r, paint);

                paint.setAlpha(alpha);
                paint.setShader(shaders[y]);
                canvas->drawRect(r, paint);

                canvas->translate(r.width() + 5, 0);
            }
            canvas->restore();
            canvas->translate(0, r.height() + 5);
        }
    }

private:
    typedef GM INHERITED ;
};
DEF_GM( return new ComposeShaderAlphaGM; )

// creates a square bitmap with red background and a green circle in the center
static void draw_color_bm(SkBitmap* bm, int length) {
    SkPaint paint;
    paint.setColor(SK_ColorGREEN);

    bm->allocN32Pixels(length, length);
    bm->eraseColor(SK_ColorRED);

    SkCanvas canvas(*bm);
    canvas.drawCircle(SkIntToScalar(length/2), SkIntToScalar(length/2), SkIntToScalar(length/2),
                      paint);
}

// creates a square alpha8 bitmap with transparent background and an opaque circle in the center
static void draw_alpha8_bm(SkBitmap* bm, int length) {
    SkPaint circlePaint;
    circlePaint.setColor(SK_ColorBLACK);

    bm->allocPixels(SkImageInfo::MakeA8(length, length));
    bm->eraseColor(SK_ColorTRANSPARENT);

    SkCanvas canvas(*bm);
    canvas.drawCircle(SkIntToScalar(length/2), SkIntToScalar(length/2), SkIntToScalar(length/4),
                      circlePaint);
}

// creates a linear gradient shader
static sk_sp<SkShader> make_linear_gradient_shader(int length) {
    SkPoint pts[2];
    SkColor colors[2];
    pts[0].set(0, 0);
    pts[1].set(SkIntToScalar(length), 0);
    colors[0] = SK_ColorBLUE;
    colors[1] = SkColorSetARGB(0, 0, 0, 0xFF);
    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
}


class ComposeShaderBitmapGM : public skiagm::GM {
public:
    ComposeShaderBitmapGM(bool use_lm) : fUseLocalMatrix(use_lm) {}

protected:
    SkString getName() const override {
        return SkStringPrintf("composeshader_bitmap%s", fUseLocalMatrix ? "_lm" : "");
    }

    SkISize getISize() override {
        return SkISize::Make(7 * (squareLength + 5), 2 * (squareLength + 5));
    }

    void onDraw(SkCanvas* canvas) override {
        if (!fInitialized) {
            draw_color_bm(&fColorBitmap, squareLength);
            sk_sp<SkImage> img = SkImages::RasterFromBitmap(fColorBitmap);
            img = ToolUtils::MakeTextureImage(canvas, std::move(img));
            if (img) {
                fColorBitmapShader = img->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat,
                                                     SkSamplingOptions(), SkMatrix::I());
            }
            draw_alpha8_bm(&fAlpha8Bitmap, squareLength);
            img = SkImages::RasterFromBitmap(fAlpha8Bitmap);
            img = ToolUtils::MakeTextureImage(canvas, std::move(img));
            if (img) {
                fAlpha8BitmapShader = fAlpha8Bitmap.makeShader(SkTileMode::kRepeat,
                                                               SkTileMode::kRepeat,
                                                               SkSamplingOptions(),
                                                               SkMatrix::I());
            }
            fLinearGradientShader = make_linear_gradient_shader(squareLength);
            fInitialized = true;
        }

        SkBlendMode mode = SkBlendMode::kDstOver;

        SkMatrix lm = SkMatrix::Translate(0, squareLength * 0.5f);

        sk_sp<SkShader> shaders[] = {
            // gradient should appear over color bitmap
            SkShaders::Blend(mode, fLinearGradientShader, fColorBitmapShader),
            // gradient should appear over alpha8 bitmap colorized by the paint color
            SkShaders::Blend(mode, fLinearGradientShader, fAlpha8BitmapShader),
        };
        if (fUseLocalMatrix) {
            for (unsigned i = 0; i < std::size(shaders); ++i) {
                shaders[i] = shaders[i] ? shaders[i]->makeWithLocalMatrix(lm) : nullptr;
            }
        }

        SkPaint paint;
        paint.setColor(SK_ColorYELLOW);

        const SkRect r = SkRect::MakeIWH(squareLength, squareLength);

        for (size_t y = 0; y < std::size(shaders); ++y) {
            canvas->save();
            for (int alpha = 0xFF; alpha > 0; alpha -= 0x28) {
                paint.setAlpha(alpha);
                paint.setShader(shaders[y]);
                canvas->drawRect(r, paint);

                canvas->translate(r.width() + 5, 0);
            }
            canvas->restore();
            canvas->translate(0, r.height() + 5);
        }
    }

private:
    /** This determines the length and width of the bitmaps used in the ComposeShaders.  Values
     *  above 20 may cause an SkASSERT to fail in SkSmallAllocator. However, larger values will
     *  work in a release build.  You can change this parameter and then compile a release build
     *  to have this GM draw larger bitmaps for easier visual inspection.
     */
    inline static constexpr int squareLength = 20;

    const bool fUseLocalMatrix;

    bool fInitialized = false;
    SkBitmap fColorBitmap;
    SkBitmap fAlpha8Bitmap;
    sk_sp<SkShader> fColorBitmapShader;
    sk_sp<SkShader> fAlpha8BitmapShader;
    sk_sp<SkShader> fLinearGradientShader;

    using INHERITED = GM;
};
DEF_GM( return new ComposeShaderBitmapGM(false); )
DEF_GM( return new ComposeShaderBitmapGM(true); )

DEF_SIMPLE_GM(composeshader_bitmap2, canvas, 200, 200) {
    int width = 255;
    int height = 255;
    SkTDArray<uint8_t> dst8Storage;
    dst8Storage.resize(width * height);
    SkTDArray<uint32_t> dst32Storage;
    dst32Storage.resize(width * height * sizeof(int32_t));
    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            dst8Storage[y * width + x] = (y + x) / 2;
            dst32Storage[y * width + x] = SkPackARGB32(0xFF, x, y, 0);
        }
    }
    SkPaint paint;
    paint.setAntiAlias(true);
    paint.setColor(SK_ColorBLUE);
    SkRect r = {0, 0, SkIntToScalar(width), SkIntToScalar(height)};
    canvas->drawRect(r, paint);
    SkBitmap skBitmap, skMask;
    SkImageInfo imageInfo = SkImageInfo::Make(width, height,
            SkColorType::kN32_SkColorType, kPremul_SkAlphaType);
    skBitmap.installPixels(imageInfo, dst32Storage.begin(), width * sizeof(int32_t),
                           nullptr, nullptr);
    imageInfo = SkImageInfo::Make(width, height,
            SkColorType::kAlpha_8_SkColorType, kPremul_SkAlphaType);
    skMask.installPixels(imageInfo, dst8Storage.begin(), width, nullptr, nullptr);
    sk_sp<SkImage> skSrc = skBitmap.asImage();
    sk_sp<SkImage> skMaskImage = skMask.asImage();
    paint.setShader(
        SkShaders::Blend(SkBlendMode::kSrcIn,
                         skMaskImage->makeShader(SkSamplingOptions()),
                         skSrc->makeShader(SkSamplingOptions())));
    canvas->drawRect(r, paint);
}

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

static sk_sp<SkShader> make_src_shader(SkScalar size) {
    const SkPoint pts[] = { { 0, 0 }, { 0, size } };
    const SkColor colors[] = { 0xFF0000FF, 0x000000FF };
    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
}

static sk_sp<SkShader> make_dst_shader(SkScalar size) {
    const SkPoint pts[] = { { 0, 0 }, { size, 0 } };
    const SkColor colors[] = { SK_ColorRED, 0x00FF0000 };
    return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp);
}

const SkScalar gCellSize = 100;

static void draw_cell(SkCanvas* canvas, sk_sp<SkShader> src, sk_sp<SkShader> dst,
                      SkBlendMode mode, SkAlpha alpha) {
    const SkRect r = SkRect::MakeWH(gCellSize, gCellSize);
    SkPaint p;
    p.setAlpha(alpha);

    SkAutoCanvasRestore acr(canvas, false);
    canvas->saveLayer(&r, &p);
    p.setAlpha(0xFF);

    p.setShader(dst);
    p.setBlendMode(SkBlendMode::kSrc);
    canvas->drawRect(r, p);

    p.setShader(src);
    p.setBlendMode(mode);
    canvas->drawRect(r, p);
}

static void draw_composed(SkCanvas* canvas, sk_sp<SkShader> src, sk_sp<SkShader> dst,
                          SkBlendMode mode, SkAlpha alpha) {
    SkPaint p;
    p.setAlpha(alpha);
    p.setShader(SkShaders::Blend(mode, dst, src));
    canvas->drawRect(SkRect::MakeWH(gCellSize, gCellSize), p);
}

static void draw_pair(SkCanvas* canvas, sk_sp<SkShader> src, sk_sp<SkShader> dst,
                      SkBlendMode mode) {
    SkAutoCanvasRestore acr(canvas, true);

    const SkScalar gap = 4;
    SkRect r = SkRect::MakeWH(2 * gCellSize + gap, 2 * gCellSize + gap);
    r.outset(gap + 1.5f, gap + 1.5f);
    SkPaint p;
    p.setStyle(SkPaint::kStroke_Style);
    canvas->drawRect(r, p); // border

    SkAlpha alpha = 0xFF;
    for (int y = 0; y < 2; ++y) {
        draw_cell(canvas, src, dst, mode, alpha);
        canvas->save();
        canvas->translate(gCellSize + gap, 0);
        draw_composed(canvas, src, dst, mode, alpha);
        canvas->restore();

        canvas->translate(0, gCellSize + gap);
        alpha = 0x80;
    }
}

DEF_SIMPLE_GM(composeshader_grid, canvas, 882, 882) {
    auto src = make_src_shader(gCellSize);
    auto dst = make_dst_shader(gCellSize);

    const SkScalar margin = 15;
    const SkScalar dx = 2*gCellSize + margin;
    const SkScalar dy = 2*gCellSize + margin;

    canvas->translate(margin, margin);
    canvas->save();
    for (int m = 0; m < 16; ++m) {
        SkBlendMode mode = static_cast<SkBlendMode>(m);
        draw_pair(canvas, src, dst, mode);
        if ((m % 4) == 3) {
            canvas->restore();
            canvas->translate(0, dy);
            canvas->save();
        } else {
            canvas->translate(dx, 0);
        }
    }
    canvas->restore();
}
