1 /*
2 * Copyright 2020 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 #define ATRACE_TAG ATRACE_TAG_GRAPHICS
18
19 #include "BlurFilter.h"
20 #include <SkCanvas.h>
21 #include <SkData.h>
22 #include <SkPaint.h>
23 #include <SkRRect.h>
24 #include <SkRuntimeEffect.h>
25 #include <SkSize.h>
26 #include <SkString.h>
27 #include <SkSurface.h>
28 #include <log/log.h>
29 #include <utils/Trace.h>
30
31 namespace android {
32 namespace renderengine {
33 namespace skia {
34
BlurFilter()35 BlurFilter::BlurFilter() {
36 SkString blurString(R"(
37 uniform shader input;
38 uniform float2 in_blurOffset;
39 uniform float2 in_maxSizeXY;
40
41 half4 main(float2 xy) {
42 half4 c = sample(input, xy);
43 c += sample(input, float2( clamp( in_blurOffset.x + xy.x, 0, in_maxSizeXY.x),
44 clamp(in_blurOffset.y + xy.y, 0, in_maxSizeXY.y)));
45 c += sample(input, float2( clamp( in_blurOffset.x + xy.x, 0, in_maxSizeXY.x),
46 clamp(-in_blurOffset.y + xy.y, 0, in_maxSizeXY.y)));
47 c += sample(input, float2( clamp( -in_blurOffset.x + xy.x, 0, in_maxSizeXY.x),
48 clamp(in_blurOffset.y + xy.y, 0, in_maxSizeXY.y)));
49 c += sample(input, float2( clamp( -in_blurOffset.x + xy.x, 0, in_maxSizeXY.x),
50 clamp(-in_blurOffset.y + xy.y, 0, in_maxSizeXY.y)));
51
52 return half4(c.rgb * 0.2, 1.0);
53 }
54 )");
55
56 auto [blurEffect, error] = SkRuntimeEffect::MakeForShader(blurString);
57 if (!blurEffect) {
58 LOG_ALWAYS_FATAL("RuntimeShader error: %s", error.c_str());
59 }
60 mBlurEffect = std::move(blurEffect);
61
62 SkString mixString(R"(
63 uniform shader blurredInput;
64 uniform shader originalInput;
65 uniform float mixFactor;
66
67 half4 main(float2 xy) {
68 return half4(mix(sample(originalInput, xy), sample(blurredInput, xy), mixFactor));
69 }
70 )");
71
72 auto [mixEffect, mixError] = SkRuntimeEffect::MakeForShader(mixString);
73 if (!mixEffect) {
74 LOG_ALWAYS_FATAL("RuntimeShader error: %s", mixError.c_str());
75 }
76 mMixEffect = std::move(mixEffect);
77 }
78
generate(GrRecordingContext * context,const uint32_t blurRadius,const sk_sp<SkImage> input,const SkRect & blurRect) const79 sk_sp<SkImage> BlurFilter::generate(GrRecordingContext* context, const uint32_t blurRadius,
80 const sk_sp<SkImage> input, const SkRect& blurRect) const {
81 // Kawase is an approximation of Gaussian, but it behaves differently from it.
82 // A radius transformation is required for approximating them, and also to introduce
83 // non-integer steps, necessary to smoothly interpolate large radii.
84 float tmpRadius = (float)blurRadius / 2.0f;
85 float numberOfPasses = std::min(kMaxPasses, (uint32_t)ceil(tmpRadius));
86 float radiusByPasses = tmpRadius / (float)numberOfPasses;
87
88 // create blur surface with the bit depth and colorspace of the original surface
89 SkImageInfo scaledInfo = input->imageInfo().makeWH(std::ceil(blurRect.width() * kInputScale),
90 std::ceil(blurRect.height() * kInputScale));
91
92 const float stepX = radiusByPasses;
93 const float stepY = radiusByPasses;
94
95 // For sampling Skia's API expects the inverse of what logically seems appropriate. In this
96 // case you might expect Translate(blurRect.fLeft, blurRect.fTop) X Scale(kInverseInputScale)
97 // but instead we must do the inverse.
98 SkMatrix blurMatrix = SkMatrix::Translate(-blurRect.fLeft, -blurRect.fTop);
99 blurMatrix.postScale(kInputScale, kInputScale);
100
101 // start by downscaling and doing the first blur pass
102 SkSamplingOptions linear(SkFilterMode::kLinear, SkMipmapMode::kNone);
103 SkRuntimeShaderBuilder blurBuilder(mBlurEffect);
104 blurBuilder.child("input") =
105 input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear, blurMatrix);
106 blurBuilder.uniform("in_blurOffset") = SkV2{stepX * kInputScale, stepY * kInputScale};
107 blurBuilder.uniform("in_maxSizeXY") =
108 SkV2{blurRect.width() * kInputScale, blurRect.height() * kInputScale};
109
110 sk_sp<SkImage> tmpBlur(blurBuilder.makeImage(context, nullptr, scaledInfo, false));
111
112 // And now we'll build our chain of scaled blur stages
113 for (auto i = 1; i < numberOfPasses; i++) {
114 const float stepScale = (float)i * kInputScale;
115 blurBuilder.child("input") =
116 tmpBlur->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linear);
117 blurBuilder.uniform("in_blurOffset") = SkV2{stepX * stepScale, stepY * stepScale};
118 blurBuilder.uniform("in_maxSizeXY") =
119 SkV2{blurRect.width() * kInputScale, blurRect.height() * kInputScale};
120 tmpBlur = blurBuilder.makeImage(context, nullptr, scaledInfo, false);
121 }
122
123 return tmpBlur;
124 }
125
getShaderTransform(const SkCanvas * canvas,const SkRect & blurRect,float scale)126 static SkMatrix getShaderTransform(const SkCanvas* canvas, const SkRect& blurRect, float scale) {
127 // 1. Apply the blur shader matrix, which scales up the blured surface to its real size
128 auto matrix = SkMatrix::Scale(scale, scale);
129 // 2. Since the blurred surface has the size of the layer, we align it with the
130 // top left corner of the layer position.
131 matrix.postConcat(SkMatrix::Translate(blurRect.fLeft, blurRect.fTop));
132 // 3. Finally, apply the inverse canvas matrix. The snapshot made in the BlurFilter is in the
133 // original surface orientation. The inverse matrix has to be applied to align the blur
134 // surface with the current orientation/position of the canvas.
135 SkMatrix drawInverse;
136 if (canvas != nullptr && canvas->getTotalMatrix().invert(&drawInverse)) {
137 matrix.postConcat(drawInverse);
138 }
139 return matrix;
140 }
141
drawBlurRegion(SkCanvas * canvas,const SkRRect & effectRegion,const uint32_t blurRadius,const float blurAlpha,const SkRect & blurRect,sk_sp<SkImage> blurredImage,sk_sp<SkImage> input)142 void BlurFilter::drawBlurRegion(SkCanvas* canvas, const SkRRect& effectRegion,
143 const uint32_t blurRadius, const float blurAlpha,
144 const SkRect& blurRect, sk_sp<SkImage> blurredImage,
145 sk_sp<SkImage> input) {
146 ATRACE_CALL();
147
148 SkPaint paint;
149 paint.setAlphaf(blurAlpha);
150
151 const auto blurMatrix = getShaderTransform(canvas, blurRect, kInverseInputScale);
152 SkSamplingOptions linearSampling(SkFilterMode::kLinear, SkMipmapMode::kNone);
153 const auto blurShader = blurredImage->makeShader(SkTileMode::kClamp, SkTileMode::kClamp,
154 linearSampling, &blurMatrix);
155
156 if (blurRadius < kMaxCrossFadeRadius) {
157 // For sampling Skia's API expects the inverse of what logically seems appropriate. In this
158 // case you might expect the matrix to simply be the canvas matrix.
159 SkMatrix inputMatrix;
160 if (!canvas->getTotalMatrix().invert(&inputMatrix)) {
161 ALOGE("matrix was unable to be inverted");
162 }
163
164 SkRuntimeShaderBuilder blurBuilder(mMixEffect);
165 blurBuilder.child("blurredInput") = blurShader;
166 blurBuilder.child("originalInput") =
167 input->makeShader(SkTileMode::kClamp, SkTileMode::kClamp, linearSampling,
168 inputMatrix);
169 blurBuilder.uniform("mixFactor") = blurRadius / kMaxCrossFadeRadius;
170
171 paint.setShader(blurBuilder.makeShader(nullptr, true));
172 } else {
173 paint.setShader(blurShader);
174 }
175
176 if (effectRegion.isRect()) {
177 if (blurAlpha == 1.0f) {
178 paint.setBlendMode(SkBlendMode::kSrc);
179 }
180 canvas->drawRect(effectRegion.rect(), paint);
181 } else {
182 paint.setAntiAlias(true);
183 canvas->drawRRect(effectRegion, paint);
184 }
185 }
186
187 } // namespace skia
188 } // namespace renderengine
189 } // namespace android
190