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