/* * Copyright 2020 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/SkCanvas.h" #include "include/core/SkData.h" #include "include/core/SkPaint.h" #include "include/core/SkSize.h" #include "include/core/SkString.h" #include "include/core/SkSurface.h" #include "include/effects/SkRuntimeEffect.h" #include "tools/DecodeUtils.h" #include "tools/Resources.h" class KawaseBlurFilter { public: // Downsample scale factor used to improve performance static constexpr float kInputScale = 0.25f; // Downsample scale factor used to improve performance static constexpr float kInverseInputScale = 1.0f / kInputScale; // Maximum number of render passes static constexpr uint32_t kMaxPasses = 4; // To avoid downscaling artifacts, we interpolate the blurred fbo with the full composited // image, up to this radius. static constexpr float kMaxCrossFadeRadius = 30.0f; KawaseBlurFilter() { SkString blurString(R"( uniform shader src; uniform float in_inverseScale; uniform float2 in_blurOffset; half4 main(float2 xy) { float2 scaled_xy = float2(xy.x * in_inverseScale, xy.y * in_inverseScale); half4 c = src.eval(scaled_xy); c += src.eval(scaled_xy + float2( in_blurOffset.x, in_blurOffset.y)); c += src.eval(scaled_xy + float2( in_blurOffset.x, -in_blurOffset.y)); c += src.eval(scaled_xy + float2(-in_blurOffset.x, in_blurOffset.y)); c += src.eval(scaled_xy + float2(-in_blurOffset.x, -in_blurOffset.y)); return half4(c.rgb * 0.2, 1.0); } )"); SkString mixString(R"( uniform shader in_blur; uniform shader in_original; uniform float in_inverseScale; uniform float in_mix; half4 main(float2 xy) { float2 scaled_xy = float2(xy.x * in_inverseScale, xy.y * in_inverseScale); half4 blurred = in_blur.eval(scaled_xy); half4 composition = in_original.eval(xy); return mix(composition, blurred, in_mix); } )"); auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString); if (!blurEffect) { SkDEBUGFAILF("RuntimeShader error: %s\n", error.c_str()); } fBlurEffect = std::move(blurEffect); auto [mixEffect, error2] = SkRuntimeEffect::MakeForShader(mixString); if (!mixEffect) { SkDEBUGFAILF("RuntimeShader error: %s\n", error2.c_str()); } fMixEffect = std::move(mixEffect); } static sk_sp MakeSurface(SkCanvas* canvas, const SkImageInfo& info) { if (sk_sp surface = canvas->makeSurface(info)) { return surface; } // serialize-8888 returns null from makeSurface; fallback to a raster surface. return SkSurfaces::Raster(info); } void draw(SkCanvas* canvas, sk_sp input, int blurRadius) { // NOTE: this is only experimental and the current blur params cause points to be sampled // beyond the input blur radius. // Kawase is an approximation of Gaussian, but it behaves differently from it. // A radius transformation is required for approximating them, and also to introduce // non-integer steps, necessary to smoothly interpolate large radii. float tmpRadius = (float)blurRadius / 6.0f; float numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius)); float radiusByPasses = tmpRadius / (float)numberOfPasses; SkImageInfo scaledInfo = SkImageInfo::MakeN32Premul((float)input->width() * kInputScale, (float)input->height() * kInputScale); auto drawSurface = MakeSurface(canvas, scaledInfo); const float stepX = radiusByPasses; const float stepY = radiusByPasses; // start by drawing and downscaling and doing the first blur pass SkRuntimeShaderBuilder blurBuilder(fBlurEffect); blurBuilder.child("src") = input->makeShader(SkSamplingOptions(SkFilterMode::kLinear)); blurBuilder.uniform("in_inverseScale") = kInverseInputScale; blurBuilder.uniform("in_blurOffset") = SkV2{stepX * kInverseInputScale, stepY * kInverseInputScale}; SkPaint paint; paint.setShader(blurBuilder.makeShader()); drawSurface->getCanvas()->drawIRect(scaledInfo.bounds(), paint); // DEBUG draw each of the stages canvas->save(); canvas->drawImage(drawSurface->makeImageSnapshot(), input->width() / 4, 0, SkSamplingOptions()); canvas->translate(input->width() / 4, input->height() * 0.75); // And now we'll ping pong between our surfaces, to accumulate the result of various // offsets. auto lastDrawTarget = drawSurface; if (numberOfPasses > 1) { auto readSurface = drawSurface; drawSurface = MakeSurface(canvas, scaledInfo); for (auto i = 1; i < numberOfPasses; i++) { const float stepScale = (float)i * kInputScale; blurBuilder.child("src") = readSurface->makeImageSnapshot()->makeShader( SkSamplingOptions(SkFilterMode::kLinear)); blurBuilder.uniform("in_inverseScale") = 1.0f; blurBuilder.uniform("in_blurOffset") = SkV2{stepX * stepScale , stepY * stepScale}; paint.setShader(blurBuilder.makeShader()); drawSurface->getCanvas()->drawIRect(scaledInfo.bounds(), paint); // DEBUG draw each of the stages canvas->drawImage(drawSurface->makeImageSnapshot(), 0, 0, SkSamplingOptions()); canvas->translate(0, input->height() * 0.75); // Swap buffers for next iteration auto tmp = drawSurface; drawSurface = readSurface; readSurface = tmp; } lastDrawTarget = readSurface; } // restore translations done for debug and offset canvas->restore(); SkAutoCanvasRestore acr(canvas, true); canvas->translate(input->width(), 0); // do the final composition and when we scale our blur up. It will be interpolated // with the larger composited texture to hide downscaling artifacts. SkRuntimeShaderBuilder mixBuilder(fMixEffect); mixBuilder.child("in_blur") = lastDrawTarget->makeImageSnapshot()->makeShader( SkSamplingOptions(SkFilterMode::kLinear)); mixBuilder.child("in_original") = input->makeShader(SkSamplingOptions(SkFilterMode::kLinear)); mixBuilder.uniform("in_inverseScale") = kInputScale; mixBuilder.uniform("in_mix") = std::min(1.0f, (float)blurRadius / kMaxCrossFadeRadius); paint.setShader(mixBuilder.makeShader()); canvas->drawIRect(input->bounds(), paint); } private: sk_sp fBlurEffect; sk_sp fMixEffect; }; class KawaseBlurRT : public skiagm::GM { public: KawaseBlurRT() {} SkString getName() const override { return SkString("kawase_blur_rt"); } SkISize getISize() override { return {1280, 768}; } void onOnceBeforeDraw() override { fMandrill = ToolUtils::GetResourceAsImage("images/mandrill_256.png"); } void onDraw(SkCanvas* canvas) override { canvas->drawImage(fMandrill, 0, 0); canvas->translate(256, 0); KawaseBlurFilter blurFilter; blurFilter.draw(canvas, fMandrill, 45); canvas->translate(512, 0); blurFilter.draw(canvas, fMandrill, 55); } private: sk_sp fMandrill; }; DEF_GM(return new KawaseBlurRT;)