/* * 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/SkBlendMode.h" #include "include/core/SkCanvas.h" #include "include/core/SkColor.h" #include "include/core/SkColorSpace.h" #include "include/core/SkFont.h" #include "include/core/SkImage.h" #include "include/core/SkImageInfo.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/SkSurface.h" #include "include/core/SkSurfaceProps.h" #include "include/core/SkTileMode.h" #include "include/core/SkTypeface.h" #include "include/core/SkTypes.h" #include "include/effects/SkGradientShader.h" #include "include/gpu/GrDirectContext.h" #include "include/gpu/GrRecordingContext.h" #include "include/utils/SkTextUtils.h" #include "tools/ToolUtils.h" #include "tools/gpu/BackendSurfaceFactory.h" #define W 200 #define H 100 static sk_sp make_shader() { int a = 0x99; int b = 0xBB; SkPoint pts[] = { { 0, 0 }, { W, H } }; SkColor colors[] = { SkColorSetRGB(a, a, a), SkColorSetRGB(b, b, b) }; return SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp); } static sk_sp make_surface(GrRecordingContext* ctx, const SkImageInfo& info, SkPixelGeometry geo) { SkSurfaceProps props(0, geo); if (ctx) { return SkSurface::MakeRenderTarget(ctx, SkBudgeted::kNo, info, 0, &props); } else { return SkSurface::MakeRaster(info, &props); } } static void test_draw(SkCanvas* canvas, const char label[]) { SkPaint paint; paint.setAntiAlias(true); paint.setDither(true); paint.setShader(make_shader()); canvas->drawRect(SkRect::MakeWH(W, H), paint); paint.setShader(nullptr); paint.setColor(SK_ColorWHITE); SkFont font(ToolUtils::create_portable_typeface(), 32); font.setEdging(SkFont::Edging::kSubpixelAntiAlias); SkTextUtils::DrawString(canvas, label, W / 2, H * 3 / 4, font, paint, SkTextUtils::kCenter_Align); } class SurfacePropsGM : public skiagm::GM { public: SurfacePropsGM() {} protected: SkString onShortName() override { return SkString("surfaceprops"); } SkISize onISize() override { return SkISize::Make(W, H * 5); } void onDraw(SkCanvas* canvas) override { auto ctx = canvas->recordingContext(); // must be opaque to have a hope of testing LCD text const SkImageInfo info = SkImageInfo::MakeN32(W, H, kOpaque_SkAlphaType); const struct { SkPixelGeometry fGeo; const char* fLabel; } recs[] = { { kUnknown_SkPixelGeometry, "Unknown" }, { kRGB_H_SkPixelGeometry, "RGB_H" }, { kBGR_H_SkPixelGeometry, "BGR_H" }, { kRGB_V_SkPixelGeometry, "RGB_V" }, { kBGR_V_SkPixelGeometry, "BGR_V" }, }; SkScalar x = 0; SkScalar y = 0; for (const auto& rec : recs) { auto surface(make_surface(ctx, info, rec.fGeo)); if (!surface) { SkDebugf("failed to create surface! label: %s", rec.fLabel); continue; } test_draw(surface->getCanvas(), rec.fLabel); surface->draw(canvas, x, y); y += H; } } private: using INHERITED = GM; }; DEF_GM( return new SurfacePropsGM ) #ifdef SK_DEBUG static bool equal(const SkSurfaceProps& a, const SkSurfaceProps& b) { return a.flags() == b.flags() && a.pixelGeometry() == b.pixelGeometry(); } #endif class NewSurfaceGM : public skiagm::GM { public: NewSurfaceGM() {} protected: SkString onShortName() override { return SkString("surfacenew"); } SkISize onISize() override { return SkISize::Make(300, 140); } static void drawInto(SkCanvas* canvas) { canvas->drawColor(SK_ColorRED); } void onDraw(SkCanvas* canvas) override { SkImageInfo info = SkImageInfo::MakeN32Premul(100, 100); auto surf(ToolUtils::makeSurface(canvas, info, nullptr)); drawInto(surf->getCanvas()); sk_sp image(surf->makeImageSnapshot()); canvas->drawImage(image, 10, 10); auto surf2(surf->makeSurface(info)); drawInto(surf2->getCanvas()); // Assert that the props were communicated transitively through the first image SkASSERT(equal(surf->props(), surf2->props())); sk_sp image2(surf2->makeImageSnapshot()); canvas->drawImage(image2.get(), 10 + SkIntToScalar(image->width()) + 10, 10); } private: using INHERITED = GM; }; DEF_GM( return new NewSurfaceGM ) /////////////////////////////////////////////////////////////////////////////////////////////////// // The GPU backend may behave differently when images are snapped from wrapped textures and // render targets compared. namespace { enum SurfaceType { kManaged, kBackendTexture, kBackendRenderTarget }; } static sk_sp make_surface(const SkImageInfo& ii, SkCanvas* canvas, SurfaceType type) { GrDirectContext* direct = GrAsDirectContext(canvas->recordingContext()); switch (type) { case kManaged: return ToolUtils::makeSurface(canvas, ii); case kBackendTexture: if (!direct) { return nullptr; } return sk_gpu_test::MakeBackendTextureSurface(direct, ii, kTopLeft_GrSurfaceOrigin, 1); case kBackendRenderTarget: return sk_gpu_test::MakeBackendRenderTargetSurface(direct, ii, kTopLeft_GrSurfaceOrigin, 1); } return nullptr; } using MakeSurfaceFn = std::function(const SkImageInfo&)>; #define DEF_BASIC_SURFACE_TEST(name, canvas, main, W, H) \ DEF_SIMPLE_GM(name, canvas, W, H) { \ auto make = [canvas](const SkImageInfo& ii) { \ return make_surface(ii, canvas, SurfaceType::kManaged); \ }; \ main(canvas, MakeSurfaceFn(make)); \ } #define DEF_BACKEND_SURFACE_TEST(name, canvas, main, type, W, H) \ DEF_SIMPLE_GM_CAN_FAIL(name, canvas, err_msg, W, H) { \ GrDirectContext* direct = GrAsDirectContext(canvas->recordingContext()); \ if (!direct || direct->abandoned()) { \ *err_msg = "Requires non-abandoned GrDirectContext"; \ return skiagm::DrawResult::kSkip; \ } \ auto make = [canvas](const SkImageInfo& ii) { return make_surface(ii, canvas, type); }; \ main(canvas, MakeSurfaceFn(make)); \ return skiagm::DrawResult::kOk; \ } #define DEF_BET_SURFACE_TEST(name, canvas, main, W, H) \ DEF_BACKEND_SURFACE_TEST(SK_MACRO_CONCAT(name, _bet), canvas, main, \ SurfaceType::kBackendTexture, W, H) #define DEF_BERT_SURFACE_TEST(name, canvas, main, W, H) \ DEF_BACKEND_SURFACE_TEST(SK_MACRO_CONCAT(name, _bert), canvas, main, \ SurfaceType::kBackendRenderTarget, W, H) // This makes 3 GMs from the same code, normal, wrapped backend texture, and wrapped backend // render target. #define DEF_SURFACE_TESTS(name, canvas, W, H) \ static void SK_MACRO_CONCAT(name, _main)(SkCanvas*, const MakeSurfaceFn&); \ DEF_BASIC_SURFACE_TEST(name, canvas, SK_MACRO_CONCAT(name, _main), W, H) \ DEF_BET_SURFACE_TEST (name, canvas, SK_MACRO_CONCAT(name, _main), W, H) \ DEF_BERT_SURFACE_TEST (name, canvas, SK_MACRO_CONCAT(name, _main), W, H) \ static void SK_MACRO_CONCAT(name, _main)(SkCanvas * canvas, const MakeSurfaceFn& make) DEF_SURFACE_TESTS(copy_on_write_retain, canvas, 256, 256) { const SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256); sk_sp surf = make(info); surf->getCanvas()->clear(SK_ColorRED); // its important that image survives longer than the next draw, so the surface will see // an outstanding image, and have to decide if it should retain or discard those pixels sk_sp image = surf->makeImageSnapshot(); // normally a clear+opaque should trigger the discard optimization, but since we have a clip // it should not (we need the previous red pixels). surf->getCanvas()->clipRect(SkRect::MakeWH(128, 256)); surf->getCanvas()->clear(SK_ColorBLUE); // expect to see two rects: blue | red canvas->drawImage(surf->makeImageSnapshot(), 0, 0); } // Like copy_on_write_retain but draws the snapped image back to the surface it was snapped from. DEF_SURFACE_TESTS(copy_on_write_retain2, canvas, 256, 256) { const SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256); sk_sp surf = make(info); surf->getCanvas()->clear(SK_ColorBLUE); // its important that image survives longer than the next draw, so the surface will see // an outstanding image, and have to decide if it should retain or discard those pixels sk_sp image = surf->makeImageSnapshot(); surf->getCanvas()->clear(SK_ColorRED); // normally a clear+opaque should trigger the discard optimization, but since we have a clip // it should not (we need the previous red pixels). surf->getCanvas()->clipRect(SkRect::MakeWH(128, 256)); surf->getCanvas()->drawImage(image, 0, 0); // expect to see two rects: blue | red canvas->drawImage(surf->makeImageSnapshot(), 0, 0); } DEF_SURFACE_TESTS(simple_snap_image, canvas, 256, 256) { const SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256); sk_sp surf = make(info); surf->getCanvas()->clear(SK_ColorRED); sk_sp image = surf->makeImageSnapshot(); // expect to see just red canvas->drawImage(std::move(image), 0, 0); } // Like simple_snap_image but the surface dies before the image. DEF_SURFACE_TESTS(simple_snap_image2, canvas, 256, 256) { const SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256); sk_sp surf = make(info); surf->getCanvas()->clear(SK_ColorRED); sk_sp image = surf->makeImageSnapshot(); surf.reset(); // expect to see just red canvas->drawImage(std::move(image), 0, 0); } DEF_SURFACE_TESTS(copy_on_write_savelayer, canvas, 256, 256) { const SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256); sk_sp surf = make(info); surf->getCanvas()->clear(SK_ColorRED); // its important that image survives longer than the next draw, so the surface will see // an outstanding image, and have to decide if it should retain or discard those pixels sk_sp image = surf->makeImageSnapshot(); // now draw into a full-screen layer. This should (a) trigger a copy-on-write, but it should // not trigger discard, even tho its alpha (SK_ColorBLUE) is opaque, since it is in a layer // with a non-opaque paint. SkPaint paint; paint.setAlphaf(0.25f); surf->getCanvas()->saveLayer({0, 0, 256, 256}, &paint); surf->getCanvas()->clear(SK_ColorBLUE); surf->getCanvas()->restore(); // expect to see two rects: blue blended on red canvas->drawImage(surf->makeImageSnapshot(), 0, 0); } DEF_SURFACE_TESTS(surface_underdraw, canvas, 256, 256) { SkImageInfo info = SkImageInfo::MakeN32Premul(256, 256, nullptr); auto surf = make(info); const SkIRect subset = SkIRect::MakeLTRB(180, 0, 256, 256); // noisy background { SkPoint pts[] = {{0, 0}, {40, 50}}; SkColor colors[] = {SK_ColorRED, SK_ColorBLUE}; auto sh = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kRepeat); SkPaint paint; paint.setShader(sh); surf->getCanvas()->drawPaint(paint); } // save away the right-hand strip, then clear it sk_sp saveImg = surf->makeImageSnapshot(subset); { SkPaint paint; paint.setBlendMode(SkBlendMode::kClear); surf->getCanvas()->drawRect(SkRect::Make(subset), paint); } // draw the "foreground" { SkPaint paint; paint.setColor(SK_ColorGREEN); SkRect r = { 0, 10, 256, 35 }; while (r.fBottom < 256) { surf->getCanvas()->drawRect(r, paint); r.offset(0, r.height() * 2); } } // apply the "fade" { SkPoint pts[] = {{SkIntToScalar(subset.left()), 0}, {SkIntToScalar(subset.right()), 0}}; SkColor colors[] = {0xFF000000, 0}; auto sh = SkGradientShader::MakeLinear(pts, colors, nullptr, 2, SkTileMode::kClamp); SkPaint paint; paint.setShader(sh); paint.setBlendMode(SkBlendMode::kDstIn); surf->getCanvas()->drawRect(SkRect::Make(subset), paint); } // restore the original strip, drawing it "under" the current foreground { SkPaint paint; paint.setBlendMode(SkBlendMode::kDstOver); surf->getCanvas()->drawImage(saveImg, SkIntToScalar(subset.left()), SkIntToScalar(subset.top()), SkSamplingOptions(), &paint); } // show it on screen surf->draw(canvas, 0, 0); }