
/*
 * Copyright 2013 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */
#include "gm.h"
#include "SkBitmap.h"
#include "SkGradientShader.h"
#include "SkXfermode.h"
#include "SkColorPriv.h"

#if SK_SUPPORT_GPU
#include "GrContext.h"
#include "SkGpuDevice.h"
#endif

namespace skiagm {

/**
 * This tests drawing device-covering rects with solid colors and bitmap shaders over a
 * checkerboard background using different xfermodes.
 */
class Xfermodes3GM : public GM {
public:
    Xfermodes3GM() {}

protected:
    virtual SkString onShortName() SK_OVERRIDE {
        return SkString("xfermodes3");
    }

    virtual SkISize onISize() SK_OVERRIDE {
        return SkISize::Make(630, 1215);
    }

    virtual void onDrawBackground(SkCanvas* canvas) SK_OVERRIDE {
        SkPaint bgPaint;
        bgPaint.setColor(0xFF70D0E0);
        canvas->drawPaint(bgPaint);
    }

    virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
        canvas->translate(SkIntToScalar(10), SkIntToScalar(20));

        SkPaint labelP;
        labelP.setAntiAlias(true);
        sk_tool_utils::set_portable_typeface(&labelP);

        static const SkColor kSolidColors[] = {
            SK_ColorTRANSPARENT,
            SK_ColorBLUE,
            0x80808000
        };

        static const SkColor kBmpAlphas[] = {
            0xff,
            0x80,
        };

        SkAutoTUnref<SkCanvas> tempCanvas(this->possiblyCreateTempCanvas(canvas, kSize, kSize));

        int test = 0;
        int x = 0, y = 0;
        static const struct { SkPaint::Style fStyle; SkScalar fWidth; } kStrokes[] = {
            {SkPaint::kFill_Style, 0},
            {SkPaint::kStroke_Style, SkIntToScalar(kSize) / 2},
        };
        for (size_t s = 0; s < SK_ARRAY_COUNT(kStrokes); ++s) {
            for (size_t m = 0; m <= SkXfermode::kLastMode; ++m) {
                SkXfermode::Mode mode = static_cast<SkXfermode::Mode>(m);
                canvas->drawText(SkXfermode::ModeName(mode),
                                 strlen(SkXfermode::ModeName(mode)),
                                 SkIntToScalar(x),
                                 SkIntToScalar(y + kSize + 3) + labelP.getTextSize(),
                                 labelP);
                for (size_t c = 0; c < SK_ARRAY_COUNT(kSolidColors); ++c) {
                    SkPaint modePaint;
                    modePaint.setXfermodeMode(mode);
                    modePaint.setColor(kSolidColors[c]);
                    modePaint.setStyle(kStrokes[s].fStyle);
                    modePaint.setStrokeWidth(kStrokes[s].fWidth);

                    this->drawMode(canvas, x, y, kSize, kSize, modePaint, tempCanvas.get());

                    ++test;
                    x += kSize + 10;
                    if (!(test % kTestsPerRow)) {
                        x = 0;
                        y += kSize + 30;
                    }
                }
                for (size_t a = 0; a < SK_ARRAY_COUNT(kBmpAlphas); ++a) {
                    SkPaint modePaint;
                    modePaint.setXfermodeMode(mode);
                    modePaint.setAlpha(kBmpAlphas[a]);
                    modePaint.setShader(fBmpShader);
                    modePaint.setStyle(kStrokes[s].fStyle);
                    modePaint.setStrokeWidth(kStrokes[s].fWidth);

                    this->drawMode(canvas, x, y, kSize, kSize, modePaint, tempCanvas.get());

                    ++test;
                    x += kSize + 10;
                    if (!(test % kTestsPerRow)) {
                        x = 0;
                        y += kSize + 30;
                    }
                }
            }
        }
    }

private:
    /**
     * GrContext has optimizations around full rendertarget draws that can be replaced with clears.
     * We are trying to test those. We could use saveLayer() to create small SkGpuDevices but
     * saveLayer() uses the texture cache. This means that the actual render target may be larger
     * than the layer. Because the clip will contain the layer's bounds, no draws will be full-RT.
     * So when running on a GPU canvas we explicitly create a temporary canvas using a texture with
     * dimensions exactly matching the layer size.
     */
    SkCanvas* possiblyCreateTempCanvas(SkCanvas* baseCanvas, int w, int h) {
        SkCanvas* tempCanvas = NULL;
#if SK_SUPPORT_GPU
        GrContext* context = baseCanvas->getGrContext();
        if (context) {
            GrSurfaceDesc desc;
            desc.fWidth = w;
            desc.fHeight = h;
            desc.fConfig = SkImageInfo2GrPixelConfig(baseCanvas->imageInfo());
            desc.fFlags = kRenderTarget_GrSurfaceFlag;
            SkAutoTUnref<GrSurface> surface(context->createUncachedTexture(desc, NULL, 0));
            SkAutoTUnref<SkBaseDevice> device(SkGpuDevice::Create(surface.get(),
                                          SkSurfaceProps(SkSurfaceProps::kLegacyFontHost_InitType)));
            if (device.get()) {
                tempCanvas = SkNEW_ARGS(SkCanvas, (device.get()));
            }
        }
#endif
        return tempCanvas;
    }

    void drawMode(SkCanvas* canvas,
                  int x, int y, int w, int h,
                  const SkPaint& modePaint, SkCanvas* layerCanvas) {
        canvas->save();

        canvas->translate(SkIntToScalar(x), SkIntToScalar(y));

        SkRect r = SkRect::MakeWH(SkIntToScalar(w), SkIntToScalar(h));

        SkCanvas* modeCanvas;
        if (NULL == layerCanvas) {
            canvas->saveLayer(&r, NULL);
            modeCanvas = canvas;
        } else {
            modeCanvas = layerCanvas;
        }

        SkPaint bgPaint;
        bgPaint.setAntiAlias(false);
        bgPaint.setShader(fBGShader);
        modeCanvas->drawRect(r, bgPaint);
        modeCanvas->drawRect(r, modePaint);
        modeCanvas = NULL;

        if (NULL == layerCanvas) {
            canvas->restore();
        } else {
            SkAutoROCanvasPixels ropixels(layerCanvas);
            SkBitmap bitmap;
            if (ropixels.asROBitmap(&bitmap)) {
                canvas->drawBitmap(bitmap, 0, 0);
            }
        }

        r.inset(-SK_ScalarHalf, -SK_ScalarHalf);
        SkPaint borderPaint;
        borderPaint.setStyle(SkPaint::kStroke_Style);
        canvas->drawRect(r, borderPaint);

        canvas->restore();
    }

    virtual void onOnceBeforeDraw() SK_OVERRIDE {
        static const uint32_t kCheckData[] = {
            SkPackARGB32(0xFF, 0x40, 0x40, 0x40),
            SkPackARGB32(0xFF, 0xD0, 0xD0, 0xD0),
            SkPackARGB32(0xFF, 0xD0, 0xD0, 0xD0),
            SkPackARGB32(0xFF, 0x40, 0x40, 0x40)
        };
        SkBitmap bg;
        bg.allocN32Pixels(2, 2, true);
        SkAutoLockPixels bgAlp(bg);
        memcpy(bg.getPixels(), kCheckData, sizeof(kCheckData));

        SkMatrix lm;
        lm.setScale(SkIntToScalar(kCheckSize), SkIntToScalar(kCheckSize));
        fBGShader.reset(SkShader::CreateBitmapShader(bg,
                                                     SkShader::kRepeat_TileMode,
                                                     SkShader::kRepeat_TileMode,
                                                     &lm));

        SkPaint bmpPaint;
        static const SkPoint kCenter = { SkIntToScalar(kSize) / 2, SkIntToScalar(kSize) / 2 };
        static const SkColor kColors[] = { SK_ColorTRANSPARENT, 0x80800000,
                                          0xF020F060, SK_ColorWHITE };
        bmpPaint.setShader(SkGradientShader::CreateRadial(kCenter,
                                                          3 * SkIntToScalar(kSize) / 4,
                                                          kColors,
                                                          NULL,
                                                          SK_ARRAY_COUNT(kColors),
                                                          SkShader::kRepeat_TileMode))->unref();

        SkBitmap bmp;
        bmp.allocN32Pixels(kSize, kSize);
        SkCanvas bmpCanvas(bmp);

        bmpCanvas.clear(SK_ColorTRANSPARENT);
        SkRect rect = { SkIntToScalar(kSize) / 8, SkIntToScalar(kSize) / 8,
                        7 * SkIntToScalar(kSize) / 8, 7 * SkIntToScalar(kSize) / 8};
        bmpCanvas.drawRect(rect, bmpPaint);

        fBmpShader.reset(SkShader::CreateBitmapShader(bmp,
                                                      SkShader::kClamp_TileMode,
                                                      SkShader::kClamp_TileMode));
    }

    enum {
        kCheckSize = 8,
        kSize = 30,
        kTestsPerRow = 15,
    };

    SkAutoTUnref<SkShader> fBGShader;
    SkAutoTUnref<SkShader> fBmpShader;

    typedef GM INHERITED;
};

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

DEF_GM(return new Xfermodes3GM;)

}
