|  | /* | 
|  | * 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/SkBitmap.h" | 
|  | #include "include/core/SkCanvas.h" | 
|  | #include "include/core/SkColorFilter.h" | 
|  | #include "include/core/SkColorSpace.h" | 
|  | #include "include/core/SkImage.h" | 
|  | #include "include/core/SkPaint.h" | 
|  | #include "include/core/SkPath.h" | 
|  | #include "include/core/SkPathBuilder.h" | 
|  | #include "include/core/SkRRect.h" | 
|  | #include "include/effects/SkGradientShader.h" | 
|  | #include "src/core/SkColorFilterPriv.h" | 
|  | #include "tools/DecodeUtils.h" | 
|  | #include "tools/GpuToolUtils.h" | 
|  | #include "tools/Resources.h" | 
|  | #include "tools/ToolUtils.h" | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | sk_sp<SkShader> create_gradient_shader(SkRect r, | 
|  | const std::array<SkColor, 3>& colors, | 
|  | const std::array<float, 3>& offsets) { | 
|  | SkPoint pts[2] = { {r.fLeft, r.fTop}, {r.fRight, r.fTop} }; | 
|  |  | 
|  | return SkGradientShader::MakeLinear(pts, colors.data(), offsets.data(), std::size(colors), | 
|  | SkTileMode::kClamp); | 
|  | } | 
|  |  | 
|  | sk_sp<SkShader> create_image_shader(SkCanvas* destCanvas, SkTileMode tmX, SkTileMode tmY) { | 
|  | SkBitmap bitmap; | 
|  |  | 
|  | { | 
|  | SkImageInfo ii = SkImageInfo::Make(64, 64, kRGBA_8888_SkColorType, kPremul_SkAlphaType); | 
|  | bitmap.allocPixels(ii); | 
|  | bitmap.eraseColor(SK_ColorWHITE); | 
|  |  | 
|  | SkCanvas tmpCanvas(bitmap); | 
|  |  | 
|  | SkColor colors[3][3] = { | 
|  | { SK_ColorRED,    SK_ColorDKGRAY, SK_ColorBLUE }, | 
|  | { SK_ColorLTGRAY, SK_ColorCYAN,   SK_ColorYELLOW }, | 
|  | { SK_ColorGREEN,  SK_ColorWHITE,  SK_ColorMAGENTA } | 
|  | }; | 
|  |  | 
|  | for (int y = 0; y < 3; ++y) { | 
|  | for (int x = 0; x < 3; ++x) { | 
|  | SkPaint paint; | 
|  | paint.setColor(colors[y][x]); | 
|  | tmpCanvas.drawRect(SkRect::MakeXYWH(x*21, y*21, 22, 22), paint); | 
|  | } | 
|  | } | 
|  |  | 
|  | bitmap.setAlphaType(kOpaque_SkAlphaType); | 
|  | bitmap.setImmutable(); | 
|  | } | 
|  |  | 
|  | sk_sp<SkImage> img = SkImages::RasterFromBitmap(bitmap); | 
|  | img = ToolUtils::MakeTextureImage(destCanvas, std::move(img)); | 
|  | if (img) { | 
|  | return img->makeShader(tmX, tmY, SkSamplingOptions()); | 
|  | } else { | 
|  | return nullptr; | 
|  | } | 
|  | } | 
|  |  | 
|  | sk_sp<SkShader> create_blend_shader(SkCanvas* destCanvas, SkBlendMode bm) { | 
|  | constexpr SkColor4f kTransYellow = {1.0f, 1.0f, 0.0f, 0.5f}; | 
|  |  | 
|  | sk_sp<SkShader> dst = SkShaders::Color(kTransYellow, nullptr); | 
|  | return SkShaders::Blend(bm, | 
|  | std::move(dst), | 
|  | create_image_shader(destCanvas, | 
|  | SkTileMode::kRepeat, SkTileMode::kRepeat)); | 
|  | } | 
|  |  | 
|  | sk_sp<SkColorFilter> create_grayscale_colorfilter() { | 
|  | float matrix[20] = {}; | 
|  | matrix[0] = matrix[5] = matrix[10] = 0.2126f; | 
|  | matrix[1] = matrix[6] = matrix[11] = 0.7152f; | 
|  | matrix[2] = matrix[7] = matrix[12] = 0.0722f; | 
|  | matrix[18] = 1.0f; | 
|  | return SkColorFilters::Matrix(matrix); | 
|  | } | 
|  |  | 
|  | void draw_image_shader_tile(SkCanvas* canvas, SkRect clipRect) { | 
|  | SkPaint p; | 
|  | p.setShader(create_image_shader(canvas, SkTileMode::kClamp, SkTileMode::kRepeat)); | 
|  |  | 
|  | SkPath path = SkPathBuilder() | 
|  | .moveTo(1,   1) | 
|  | .lineTo(32,  127) | 
|  | .lineTo(96,  127) | 
|  | .lineTo(127, 1) | 
|  | .lineTo(63,  32) | 
|  | .close() | 
|  | .detach(); | 
|  |  | 
|  | canvas->save(); | 
|  | canvas->clipRect(clipRect); | 
|  | canvas->scale(0.5f, 0.5f); | 
|  | canvas->drawPath(path, p); | 
|  |  | 
|  | canvas->save(); | 
|  | canvas->concat(SkMatrix::RotateDeg(90, {64, 64})); | 
|  | canvas->translate(128, 0); | 
|  | canvas->drawPath(path, p); | 
|  | canvas->restore(); | 
|  | canvas->restore(); | 
|  | } | 
|  |  | 
|  | void draw_gradient_tile(SkCanvas* canvas, SkRect clipRect) { | 
|  | SkRect r{1, 1, 127, 127}; | 
|  | SkPaint p; | 
|  | p.setShader(create_gradient_shader(r, | 
|  | { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE }, | 
|  | { 0.0f, 0.75f, 1.0f })); | 
|  |  | 
|  | canvas->save(); | 
|  | canvas->clipRect(clipRect); | 
|  | canvas->translate(128, 0); | 
|  | canvas->scale(0.5f, 0.5f); | 
|  | canvas->drawRect(r, p); | 
|  |  | 
|  | canvas->save(); | 
|  | canvas->concat(SkMatrix::RotateDeg(90, {64, 64})); | 
|  | canvas->translate(128, 0); | 
|  | canvas->drawRect(r, p); | 
|  | canvas->restore(); | 
|  | canvas->restore(); | 
|  | } | 
|  |  | 
|  | void draw_colorfilter_swatches(SkCanvas* canvas, SkRect clipRect) { | 
|  | static constexpr int kNumTilesPerSide = 3; | 
|  |  | 
|  | SkSize tileSize = { clipRect.width() / kNumTilesPerSide, clipRect.height() / kNumTilesPerSide }; | 
|  |  | 
|  | // Quantize to four colors | 
|  | uint8_t table1[256]; | 
|  | for (int i = 0; i < 256; ++i) { | 
|  | table1[i] = (i/64) * 85; | 
|  | } | 
|  |  | 
|  | // table2 is a band-pass filter for 85-170. | 
|  | // table3 re-expands that range to 0..255 | 
|  | uint8_t table2[256], table3[256]; | 
|  | for (int i = 0; i < 256; ++i) { | 
|  | if (i >= 85 && i <= 170) { | 
|  | table2[i] = i; | 
|  | table3[i] = ((i - 85) / 85.0f) * 255.0f; | 
|  | } else { | 
|  | table2[i] = 0; | 
|  | table3[i] = 0; | 
|  | } | 
|  | } | 
|  |  | 
|  | constexpr SkColor SK_ColorGREY = SkColorSetARGB(0xFF, 0x80, 0x80, 0x80); | 
|  |  | 
|  | sk_sp<SkColorFilter> colorFilters[kNumTilesPerSide*kNumTilesPerSide]; | 
|  | static const std::array<SkColor, 3> kGradientColors[kNumTilesPerSide*kNumTilesPerSide] = { | 
|  | { SK_ColorBLACK, SK_ColorGREY, SK_ColorWHITE }, | 
|  | { SK_ColorBLACK, SK_ColorGREY, SK_ColorWHITE }, | 
|  | { SK_ColorBLACK, SK_ColorGREY, SK_ColorWHITE }, | 
|  | { SK_ColorBLACK, SK_ColorGREY, SK_ColorWHITE }, | 
|  | { 0x00000000,    0x80000000,   0xFF000000    },  // the Gaussian CF uses alpha only | 
|  | { SK_ColorBLACK, SK_ColorGREY, SK_ColorWHITE }, | 
|  | { SK_ColorBLACK, SK_ColorGREY, SK_ColorWHITE }, | 
|  | { SK_ColorBLACK, SK_ColorGREY, SK_ColorWHITE }, | 
|  | { SK_ColorBLACK, SK_ColorGREY, SK_ColorWHITE }, | 
|  | }; | 
|  |  | 
|  | colorFilters[0] = SkColorFilters::Lighting(SK_ColorLTGRAY, 0xFF440000); | 
|  | colorFilters[1] = SkColorFilters::Table(table1); | 
|  | colorFilters[2] = SkColorFilters::Compose(SkColorFilters::TableARGB(nullptr, table3, | 
|  | table3, table3), | 
|  | SkColorFilters::TableARGB(nullptr, table2, | 
|  | table2, table2)); | 
|  | colorFilters[3] = SkColorFilters::Blend(SK_ColorGREEN, SkBlendMode::kMultiply); | 
|  | colorFilters[4] = SkColorFilterPriv::MakeGaussian(); | 
|  |  | 
|  | colorFilters[5] = SkColorFilters::LinearToSRGBGamma(); | 
|  | colorFilters[6] = SkColorFilters::SRGBToLinearGamma(); | 
|  |  | 
|  | SkPaint p; | 
|  |  | 
|  | canvas->save(); | 
|  | canvas->clipRect(clipRect); | 
|  | canvas->translate(clipRect.fLeft, clipRect.fTop); | 
|  |  | 
|  | for (int y = 0; y < kNumTilesPerSide; ++y) { | 
|  | for (int x = 0; x < kNumTilesPerSide; ++x) { | 
|  | SkRect r = SkRect::MakeXYWH(x * tileSize.width(), y * tileSize.height(), | 
|  | tileSize.width(), tileSize.height()).makeInset(1.0f, | 
|  | 1.0f); | 
|  | int colorFilterIndex = x*kNumTilesPerSide+y; | 
|  | p.setShader(create_gradient_shader(r, | 
|  | kGradientColors[colorFilterIndex], | 
|  | { 0.0f, 0.5f, 1.0f })); | 
|  | p.setColorFilter(colorFilters[colorFilterIndex]); | 
|  | canvas->drawRect(r, p); | 
|  | } | 
|  | } | 
|  |  | 
|  | canvas->restore(); | 
|  | } | 
|  |  | 
|  | void draw_blend_mode_swatches(SkCanvas* canvas, SkRect clipRect) { | 
|  | static const int kTileHeight = 16; | 
|  | static const int kTileWidth = 16; | 
|  | static const SkColor4f kOpaqueWhite { 1.0f, 1.0f, 1.0f, 1.0f }; | 
|  | static const SkColor4f kTransBluish { 0.0f, 0.5f, 1.0f, 0.5f }; | 
|  | static const SkColor4f kTransWhite { 1.0f, 1.0f, 1.0f, 0.75f }; | 
|  |  | 
|  | SkPaint dstPaint; | 
|  | dstPaint.setColor(kOpaqueWhite); | 
|  | dstPaint.setBlendMode(SkBlendMode::kSrc); | 
|  | dstPaint.setAntiAlias(false); | 
|  |  | 
|  | SkPaint srcPaint; | 
|  | srcPaint.setColor(kTransBluish); | 
|  | srcPaint.setAntiAlias(false); | 
|  |  | 
|  | SkRect r = SkRect::MakeXYWH(clipRect.fLeft, clipRect.fTop, kTileWidth, kTileHeight); | 
|  |  | 
|  | // For the first pass we draw: transparent bluish on top of opaque white | 
|  | // For the second pass we draw: transparent white on top of transparent bluish | 
|  | for (int passes = 0; passes < 2; ++passes) { | 
|  | for (int i = 0; i <= (int)SkBlendMode::kLastCoeffMode; ++i) { | 
|  | if (r.fLeft+kTileWidth > clipRect.fRight) { | 
|  | r.offsetTo(clipRect.fLeft, r.fTop+kTileHeight); | 
|  | } | 
|  |  | 
|  | canvas->drawRect(r.makeInset(1.0f, 1.0f), dstPaint); | 
|  | srcPaint.setBlendMode(static_cast<SkBlendMode>(i)); | 
|  | canvas->drawRect(r.makeInset(2.0f, 2.0f), srcPaint); | 
|  |  | 
|  | r.offset(kTileWidth, 0.0f); | 
|  | } | 
|  |  | 
|  | r.offsetTo(clipRect.fLeft, r.fTop+kTileHeight); | 
|  | srcPaint.setColor(kTransWhite); | 
|  | dstPaint.setColor(kTransBluish); | 
|  | } | 
|  | } | 
|  |  | 
|  | } // anonymous namespace | 
|  |  | 
|  | namespace skiagm { | 
|  |  | 
|  | // This is just for bootstrapping Graphite. | 
|  | class GraphiteStartGM : public GM { | 
|  | public: | 
|  | GraphiteStartGM() = default; | 
|  |  | 
|  | protected: | 
|  | static constexpr int kTileWidth = 128; | 
|  | static constexpr int kTileHeight = 128; | 
|  | static constexpr int kWidth = 3 * kTileWidth; | 
|  | static constexpr int kHeight = 3 * kTileHeight; | 
|  | static constexpr int kClipInset = 4; | 
|  |  | 
|  | void onOnceBeforeDraw() override { | 
|  | this->setBGColor(SK_ColorBLACK); | 
|  | ToolUtils::GetResourceAsBitmap("images/color_wheel.gif", &fBitmap); | 
|  | } | 
|  |  | 
|  | SkString getName() const override { return SkString("graphitestart"); } | 
|  |  | 
|  | SkISize getISize() override { return SkISize::Make(kWidth, kHeight); } | 
|  |  | 
|  | void onDraw(SkCanvas* canvas) override { | 
|  |  | 
|  | const SkRect clipRect = SkRect::MakeWH(kWidth, kHeight).makeInset(kClipInset, kClipInset); | 
|  |  | 
|  | canvas->save(); | 
|  | canvas->clipRRect(SkRRect::MakeRectXY(clipRect, 32.f, 32.f), true); | 
|  |  | 
|  | // Upper-left corner | 
|  | draw_image_shader_tile(canvas, SkRect::MakeXYWH(0, 0, kTileWidth, kTileHeight)); | 
|  |  | 
|  | // Upper-middle tile | 
|  | draw_gradient_tile(canvas, SkRect::MakeXYWH(kTileWidth, 0, kTileWidth, kTileHeight)); | 
|  |  | 
|  | // Upper-right corner | 
|  | draw_colorfilter_swatches(canvas, SkRect::MakeXYWH(2*kTileWidth, 0, | 
|  | kTileWidth, kTileWidth)); | 
|  |  | 
|  | // Middle-left tile | 
|  | { | 
|  | SkPaint p; | 
|  | p.setColor(SK_ColorRED); | 
|  |  | 
|  | SkRect r = SkRect::MakeXYWH(0, kTileHeight, kTileWidth, kTileHeight); | 
|  | canvas->drawRect(r.makeInset(1.0f, 1.0f), p); | 
|  | } | 
|  |  | 
|  | // Middle-middle tile | 
|  | { | 
|  | SkPaint p; | 
|  | p.setShader(create_blend_shader(canvas, SkBlendMode::kModulate)); | 
|  |  | 
|  | SkRect r = SkRect::MakeXYWH(kTileWidth, kTileHeight, kTileWidth, kTileHeight); | 
|  | canvas->drawRect(r.makeInset(1.0f, 1.0f), p); | 
|  | } | 
|  |  | 
|  | // Middle-right tile | 
|  | { | 
|  | sk_sp<SkImage> image(ToolUtils::GetResourceAsImage("images/mandrill_128.png")); | 
|  | sk_sp<SkShader> shader; | 
|  |  | 
|  | if (image) { | 
|  | shader = image->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, {}); | 
|  | shader = shader->makeWithColorFilter(create_grayscale_colorfilter()); | 
|  | } | 
|  |  | 
|  | SkPaint p; | 
|  | p.setShader(std::move(shader)); | 
|  |  | 
|  | SkRect r = SkRect::MakeXYWH(2*kTileWidth, kTileHeight, kTileWidth, kTileHeight); | 
|  | canvas->drawRect(r.makeInset(1.0f, 1.0f), p); | 
|  | } | 
|  |  | 
|  | canvas->restore(); | 
|  |  | 
|  | // Bottom-left corner | 
|  | #if defined(SK_GRAPHITE) | 
|  | // TODO: failing serialize test on Linux, not sure what's going on | 
|  | canvas->writePixels(fBitmap, 0, 2*kTileHeight); | 
|  | #endif | 
|  |  | 
|  | // Bottom-middle tile | 
|  | draw_blend_mode_swatches(canvas, SkRect::MakeXYWH(kTileWidth, 2*kTileHeight, | 
|  | kTileWidth, kTileHeight)); | 
|  |  | 
|  | // Bottom-right corner | 
|  | { | 
|  | const SkRect kTile = SkRect::MakeXYWH(2*kTileWidth, 2*kTileHeight, | 
|  | kTileWidth, kTileHeight); | 
|  |  | 
|  | SkPaint circlePaint; | 
|  | circlePaint.setColor(SK_ColorBLUE); | 
|  | circlePaint.setBlendMode(SkBlendMode::kSrc); | 
|  |  | 
|  | canvas->clipRect(kTile); | 
|  | canvas->drawRect(kTile.makeInset(10, 20), circlePaint); | 
|  |  | 
|  | SkPaint restorePaint; | 
|  | restorePaint.setBlendMode(SkBlendMode::kPlus); | 
|  |  | 
|  | canvas->saveLayer(nullptr, &restorePaint); | 
|  | circlePaint.setColor(SK_ColorRED); | 
|  | circlePaint.setBlendMode(SkBlendMode::kSrc); | 
|  |  | 
|  | canvas->drawRect(kTile.makeInset(15, 25), circlePaint); | 
|  | canvas->restore(); | 
|  | } | 
|  | } | 
|  |  | 
|  | private: | 
|  | SkBitmap fBitmap; | 
|  | }; | 
|  |  | 
|  | ////////////////////////////////////////////////////////////////////////////// | 
|  |  | 
|  | DEF_GM(return new GraphiteStartGM;) | 
|  |  | 
|  | }  // namespace skiagm |