/*
 * Copyright 2014 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/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkColorFilter.h"
#include "include/core/SkImage.h"
#include "include/core/SkImageFilter.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPoint.h"
#include "include/core/SkPoint3.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
#include "include/core/SkScalar.h"
#include "include/core/SkShader.h"
#include "include/core/SkSurface.h"
#include "include/core/SkTileMode.h"
#include "include/effects/SkGradient.h"
#include "include/effects/SkImageFilters.h"

#include <utility>

namespace {

sk_sp<SkImage> make_checkerboard();
sk_sp<SkImage> make_gradient_circle(int width, int height);
void draw(SkCanvas*, sk_sp<SkImage>, const SkIRect&, sk_sp<SkImageFilter>);

}  // namespace

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

DEF_SIMPLE_GM(imagefilterscropexpand, canvas, 730, 650) {
    SkIRect cropRect = SkIRect::MakeXYWH(10, 10, 44, 44);

    sk_sp<SkImage> gradientCircle(make_gradient_circle(64, 64));
    auto checkerboard = make_checkerboard();

    sk_sp<SkImageFilter> gradientCircleSource(SkImageFilters::Image(std::move(gradientCircle),
                                                                    SkFilterMode::kLinear));
    sk_sp<SkImageFilter> noopCropped(SkImageFilters::Offset(0, 0, nullptr, &cropRect));
    // This color matrix saturates the green component but only partly increases the opacity.
    // For the opaque checkerboard, the opacity boost doesn't matter but it does impact the
    // area outside the checkerboard.
    float matrix[20] = { 1, 0, 0, 0, 0,
                         0, 1, 0, 0, 1,
                         0, 0, 1, 0, 0,
                         0, 0, 0, 1, 32.0f/255 };
    sk_sp<SkColorFilter> cfAlphaTrans(SkColorFilters::Matrix(matrix));

    SkRect r = SkRect::MakeWH(SkIntToScalar(64), SkIntToScalar(64));
    SkScalar MARGIN = SkIntToScalar(12);

    SkPoint3 pointLocation = SkPoint3::Make(0, 0, SkIntToScalar(10));
    SkScalar kd = SkIntToScalar(2);
    SkScalar surfaceScale = SkIntToScalar(1);
    SkIRect bounds;
    r.roundOut(&bounds);

    SkPaint paint;
    canvas->translate(MARGIN, MARGIN);
    for (int outset = -15; outset <= 20; outset += 5) {
        canvas->save();
        SkIRect bigRect = cropRect;
        bigRect.outset(outset, outset);

        draw(canvas, checkerboard, bigRect,
             SkImageFilters::ColorFilter(cfAlphaTrans, noopCropped,  &bigRect));

        draw(canvas, checkerboard, bigRect,
             SkImageFilters::Blur(0.3f, 0.3f,  noopCropped, &bigRect));

        draw(canvas, checkerboard, bigRect,
             SkImageFilters::Blur(8.0f, 8.0f,  noopCropped, &bigRect));

        draw(canvas, checkerboard, bigRect,
             SkImageFilters::Dilate(2, 2, noopCropped, &bigRect));

        draw(canvas, checkerboard, bigRect,
             SkImageFilters::Erode(2, 2, noopCropped, &bigRect));

        draw(canvas, checkerboard, bigRect,
             SkImageFilters::DropShadow(10, 10, 3, 3, SK_ColorBLUE, noopCropped, &bigRect));

        draw(canvas, checkerboard, bigRect,
             SkImageFilters::DisplacementMap(SkColorChannel::kR, SkColorChannel::kR, 12,
                                             gradientCircleSource, noopCropped, &bigRect));

        draw(canvas, checkerboard, bigRect,
             SkImageFilters::Offset(SkIntToScalar(-8), SkIntToScalar(16), noopCropped, &bigRect));

        draw(canvas, checkerboard, bigRect,
             SkImageFilters::PointLitDiffuse(pointLocation, SK_ColorWHITE, surfaceScale, kd,
                                             noopCropped, &bigRect));

        canvas->restore();
        canvas->translate(0, SkIntToScalar(80));
    }
}

namespace {
    sk_sp<SkImage> make_checkerboard() {
        auto surf = SkSurfaces::Raster(SkImageInfo::MakeN32Premul(64, 64));
        auto canvas = surf->getCanvas();
        canvas->clear(0xFFFF0000);
        SkPaint darkPaint;
        darkPaint.setColor(0xFF404040);
        SkPaint lightPaint;
        lightPaint.setColor(0xFFA0A0A0);
        for (int y = 8; y < 48; y += 16) {
            for (int x = 8; x < 48; x += 16) {
                canvas->save();
                canvas->translate(SkIntToScalar(x), SkIntToScalar(y));
                canvas->drawRect(SkRect::MakeXYWH(0, 0, 8, 8), darkPaint);
                canvas->drawRect(SkRect::MakeXYWH(8, 0, 8, 8), lightPaint);
                canvas->drawRect(SkRect::MakeXYWH(0, 8, 8, 8), lightPaint);
                canvas->drawRect(SkRect::MakeXYWH(8, 8, 8, 8), darkPaint);
                canvas->restore();
            }
        }
        return surf->makeImageSnapshot();
    }

    sk_sp<SkImage> make_gradient_circle(int width, int height) {
        SkScalar x = SkIntToScalar(width / 2);
        SkScalar y = SkIntToScalar(height / 2);
        SkScalar radius = std::min(x, y) * 0.8f;
        auto surface(SkSurfaces::Raster(SkImageInfo::MakeN32Premul(width, height)));
        SkCanvas* canvas = surface->getCanvas();
        canvas->clear(0x00000000);
        const SkColor4f colors[2] = {SkColors::kWhite, SkColors::kBlack};
        SkPaint paint;
        paint.setShader(SkShaders::RadialGradient({x, y}, radius,
                                                  {{colors, {}, SkTileMode::kClamp}, {}}));
        canvas->drawCircle(x, y, radius, paint);

        return surface->makeImageSnapshot();
    }

    void draw(SkCanvas* canvas, sk_sp<SkImage> image, const SkIRect& layerRect,
                     sk_sp<SkImageFilter> filter) {
          SkPaint paint;
          paint.setImageFilter(std::move(filter));

          // We don't pass 'layerRect' in as the saveLayer bounds hint because that is not the
          // pre-filtering local bounds (which would be image->dimensions()). We could clip before
          // the saveLayer but every 'filter' should have been constructed with a crop rect equal
          // to 'layerRect', so do an unclipped saveLayer to ensure that the crop rect itself is
          // what restricts the output of the filter.
          canvas->saveLayer(nullptr, &paint);
          canvas->drawImage(image, 0, 0);
          canvas->restore();

          SkPaint strokePaint;
          strokePaint.setColor(0xFFFF0000);
          strokePaint.setStyle(SkPaint::kStroke_Style);
          canvas->drawRect(SkRect::Make(layerRect), strokePaint);

          canvas->translate(SkIntToScalar(80), 0);
    }
}  // namespace
