1 /*
2 * Copyright (c) 2023-2023 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 * http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16 #include "render/rs_kawase_blur.h"
17 #include "platform/common/rs_log.h"
18 #include "platform/common/rs_system_properties.h"
19 #include "common/rs_optional_trace.h"
20 #include "include/gpu/GrDirectContext.h"
21 #include "effect/runtime_shader_builder.h"
22
23 namespace OHOS {
24 namespace Rosen {
25 // Advanced Filter
26 #define PROPERTY_HIGPU_VERSION "const.gpu.vendor"
27 #define PROPERTY_DEBUG_SUPPORT_AF "persist.sys.graphic.supports_af"
28 static constexpr uint32_t BLUR_SAMPLE_COUNT = 5;
29
30 // Advanced Filter: we can get normalized uv offset from width and height
31 struct OffsetInfo {
32 float offsetX;
33 float offsetY;
34 int width;
35 int height;
36 };
37
38 // Advanced Filter
IsAdvancedFilterUsable()39 static bool IsAdvancedFilterUsable()
40 {
41 return false;
42 }
43
44 static const bool IS_ADVANCED_FILTER_USABLE_CHECK_ONCE = IsAdvancedFilterUsable();
45
KawaseBlurFilter()46 KawaseBlurFilter::KawaseBlurFilter()
47 {
48 std::string blurString(
49 R"(
50 uniform shader imageInput;
51 uniform float2 in_blurOffset;
52 uniform float2 in_maxSizeXY;
53
54 half4 main(float2 xy) {
55 half4 c = imageInput.eval(xy);
56 c += imageInput.eval(float2(clamp(in_blurOffset.x + xy.x, 0, in_maxSizeXY.x),
57 clamp(in_blurOffset.y + xy.y, 0, in_maxSizeXY.y)));
58 c += imageInput.eval(float2(clamp(in_blurOffset.x + xy.x, 0, in_maxSizeXY.x),
59 clamp(-in_blurOffset.y + xy.y, 0, in_maxSizeXY.y)));
60 c += imageInput.eval(float2(clamp(-in_blurOffset.x + xy.x, 0, in_maxSizeXY.x),
61 clamp(in_blurOffset.y + xy.y, 0, in_maxSizeXY.y)));
62 c += imageInput.eval(float2(clamp(-in_blurOffset.x + xy.x, 0, in_maxSizeXY.x),
63 clamp(-in_blurOffset.y + xy.y, 0, in_maxSizeXY.y)));
64 return half4(c.rgb * 0.2, 1.0);
65 }
66 )");
67
68 std::string mixString(
69 R"(
70 uniform shader blurredInput;
71 uniform shader originalInput;
72 uniform float mixFactor;
73 uniform float inColorFactor;
74
75 highp float random(float2 xy) {
76 float t = dot(xy, float2(78.233, 12.9898));
77 return fract(sin(t) * 43758.5453);
78 }
79 half4 main(float2 xy) {
80 highp float noiseGranularity = inColorFactor / 255.0;
81 half4 finalColor = mix(originalInput.eval(xy), blurredInput.eval(xy), mixFactor);
82 float noise = mix(-noiseGranularity, noiseGranularity, random(xy));
83 finalColor.rgb += noise;
84 return finalColor;
85 }
86 )");
87
88 auto blurEffect = Drawing::RuntimeEffect::CreateForShader(blurString);
89 if (!blurEffect) {
90 ROSEN_LOGE("KawaseBlurFilter::RuntimeShader blurEffect create failed");
91 return;
92 }
93 blurEffect_ = std::move(blurEffect);
94
95 // Advanced Filter
96 if (IS_ADVANCED_FILTER_USABLE_CHECK_ONCE) {
97 setupBlurEffectAdvancedFilter();
98 }
99
100 auto mixEffect = Drawing::RuntimeEffect::CreateForShader(mixString);
101 if (!mixEffect) {
102 ROSEN_LOGE("KawaseBlurFilter::RuntimeShader mixEffect create failed");
103 return;
104 }
105 mixEffect_ = std::move(mixEffect);
106
107 SetupSimpleFilter();
108 }
109
110 KawaseBlurFilter::~KawaseBlurFilter() = default;
111
112 // Advanced Filter
setupBlurEffectAdvancedFilter()113 void KawaseBlurFilter::setupBlurEffectAdvancedFilter()
114 {
115 std::string blurStringAF(
116 R"(
117 uniform shader imageInput;
118 uniform float2 in_blurOffset[5];
119
120 half4 main(float2 xy) {
121 half4 c = half4(0, 0, 0, 0);
122 for (int i = 0; i < 5; ++i) {
123 c += imageInput.eval(float2(xy.x + in_blurOffset[i].x, xy.y + in_blurOffset[i].y));
124 }
125 return half4(c.rgb * 0.2, 1.0);
126 }
127 )");
128
129 Drawing::RuntimeEffectOptions ops;
130 ops.useAF = true;
131 auto blurEffectAF = Drawing::RuntimeEffect::CreateForShader(blurStringAF, ops);
132 if (!blurEffectAF) {
133 ROSEN_LOGE("%s: RuntimeShader blurEffectAF create failed", __func__);
134 return;
135 }
136 blurEffectAF_ = std::move(blurEffectAF);
137 }
138
SetupSimpleFilter()139 void KawaseBlurFilter::SetupSimpleFilter()
140 {
141 std::string simpleShader(
142 R"(
143 uniform shader imageInput;
144 half4 main(float2 xy) {
145 return imageInput.eval(xy);
146 }
147 )");
148
149 auto simpleFilter = Drawing::RuntimeEffect::CreateForShader(simpleShader);
150 if (!simpleFilter) {
151 ROSEN_LOGE("KawaseBlurFilter::RuntimeShader Failed to create simple filter");
152 return;
153 }
154 simpleFilter_ = std::move(simpleFilter);
155 }
156
getNormalizedOffset(SkV2 * offsets,const uint32_t offsetCount,const OffsetInfo & offsetInfo)157 static void getNormalizedOffset(SkV2* offsets, const uint32_t offsetCount, const OffsetInfo& offsetInfo)
158 {
159 if (offsets == nullptr || offsetCount != BLUR_SAMPLE_COUNT) {
160 ROSEN_LOGE("%s: Invalid offsets.", __func__);
161 return;
162 }
163 if (std::fabs(offsetInfo.width) < 1e-6 || std::fabs(offsetInfo.height) < 1e-6) {
164 ROSEN_LOGE("%s: Invalid width or height.", __func__);
165 return;
166 }
167 SkV2 normalizedOffsets[BLUR_SAMPLE_COUNT] = {
168 SkV2{0.0f, 0.0f},
169 SkV2{offsetInfo.offsetX / offsetInfo.width, offsetInfo.offsetY / offsetInfo.height},
170 SkV2{-offsetInfo.offsetX / offsetInfo.width, offsetInfo.offsetY / offsetInfo.height},
171 SkV2{offsetInfo.offsetX / offsetInfo.width, -offsetInfo.offsetY / offsetInfo.height},
172 SkV2{-offsetInfo.offsetX / offsetInfo.width, -offsetInfo.offsetY / offsetInfo.height}
173 };
174 for (uint32_t i = 0; i < BLUR_SAMPLE_COUNT; ++i) {
175 offsets[i] = normalizedOffsets[i];
176 }
177 }
178
GetShaderTransform(const Drawing::Canvas * canvas,const Drawing::Rect & blurRect,float scaleW,float scaleH)179 Drawing::Matrix KawaseBlurFilter::GetShaderTransform(const Drawing::Canvas* canvas, const Drawing::Rect& blurRect,
180 float scaleW, float scaleH)
181 {
182 Drawing::Matrix matrix;
183 matrix.SetScale(scaleW, scaleH);
184 Drawing::Matrix translateMatrix;
185 translateMatrix.Translate(blurRect.GetLeft(), blurRect.GetTop());
186 matrix.PostConcat(translateMatrix);
187 return matrix;
188 }
189
CheckInputImage(Drawing::Canvas & canvas,const std::shared_ptr<Drawing::Image> & image,const KawaseParameter & param,std::shared_ptr<Drawing::Image> & checkedImage)190 void KawaseBlurFilter::CheckInputImage(Drawing::Canvas& canvas, const std::shared_ptr<Drawing::Image>& image,
191 const KawaseParameter& param, std::shared_ptr<Drawing::Image>& checkedImage)
192 {
193 #ifdef RS_ENABLE_GPU
194 auto src = param.src;
195 auto srcRect = Drawing::RectI(src.GetLeft(), src.GetTop(), src.GetRight(), src.GetBottom());
196 if (image->GetImageInfo().GetBound() != srcRect) {
197 auto resizedImage = std::make_shared<Drawing::Image>();
198 auto gpuCtx = canvas.GetGPUContext();
199 if ((gpuCtx == nullptr) || (resizedImage == nullptr)) {
200 ROSEN_LOGE("KawaseBlurFilter::canvas context or resizedImage is null.");
201 return;
202 }
203
204 if (resizedImage->BuildSubset(image, srcRect, *gpuCtx)) {
205 checkedImage = resizedImage;
206 ROSEN_LOGD("KawaseBlurFilter::resize image success");
207 } else {
208 ROSEN_LOGE("KawaseBlurFilter::resize image failed, use original image");
209 }
210 }
211 #endif
212 }
213
OutputOriginalImage(Drawing::Canvas & canvas,const std::shared_ptr<Drawing::Image> & image,const KawaseParameter & param)214 void KawaseBlurFilter::OutputOriginalImage(Drawing::Canvas& canvas, const std::shared_ptr<Drawing::Image>& image,
215 const KawaseParameter& param)
216 {
217 auto src = param.src;
218 auto dst = param.dst;
219 Drawing::Brush brush;
220 if (param.colorFilter) {
221 Drawing::Filter filter;
222 filter.SetColorFilter(param.colorFilter);
223 brush.SetFilter(filter);
224 }
225 Drawing::Matrix inputMatrix;
226 float scaleW = dst.GetWidth() / image->GetWidth();
227 float scaleH = dst.GetHeight() / image->GetHeight();
228 inputMatrix.Translate(-src.GetLeft(), -src.GetTop());
229 inputMatrix.PostScale(scaleW, scaleH);
230 Drawing::Matrix matrix;
231 matrix.Translate(dst.GetLeft(), dst.GetTop());
232 inputMatrix.PostConcat(matrix);
233 Drawing::SamplingOptions linear(Drawing::FilterMode::LINEAR, Drawing::MipmapMode::NONE);
234 const auto inputShader = Drawing::ShaderEffect::CreateImageShader(*image, Drawing::TileMode::CLAMP,
235 Drawing::TileMode::CLAMP, linear, inputMatrix);
236 brush.SetShaderEffect(inputShader);
237 canvas.AttachBrush(brush);
238 canvas.DrawRect(dst);
239 canvas.DetachBrush();
240 }
241
ApplySimpleFilter(Drawing::Canvas & canvas,const std::shared_ptr<Drawing::Image> & input,const Drawing::Matrix & blurMatrix,const Drawing::ImageInfo & scaledInfo,const Drawing::SamplingOptions & linear) const242 std::shared_ptr<Drawing::ShaderEffect> KawaseBlurFilter::ApplySimpleFilter(Drawing::Canvas& canvas,
243 const std::shared_ptr<Drawing::Image>& input, const Drawing::Matrix& blurMatrix,
244 const Drawing::ImageInfo& scaledInfo, const Drawing::SamplingOptions& linear) const
245 {
246 Drawing::RuntimeShaderBuilder simpleBlurBuilder(simpleFilter_);
247 simpleBlurBuilder.SetChild("imageInput", Drawing::ShaderEffect::CreateImageShader(*input, Drawing::TileMode::CLAMP,
248 Drawing::TileMode::CLAMP, linear, blurMatrix));
249 #ifdef RS_ENABLE_GPU
250 std::shared_ptr<Drawing::Image> tmpSimpleBlur(simpleBlurBuilder.MakeImage(
251 canvas.GetGPUContext().get(), nullptr, scaledInfo, false));
252 #else
253 std::shared_ptr<Drawing::Image> tmpSimpleBlur(simpleBlurBuilder.MakeImage(nullptr, nullptr, scaledInfo, false));
254 #endif
255 return Drawing::ShaderEffect::CreateImageShader(*tmpSimpleBlur, Drawing::TileMode::CLAMP, Drawing::TileMode::CLAMP,
256 linear, Drawing::Matrix());
257 }
258
ApplyKawaseBlur(Drawing::Canvas & canvas,const std::shared_ptr<Drawing::Image> & image,const KawaseParameter & param)259 bool KawaseBlurFilter::ApplyKawaseBlur(Drawing::Canvas& canvas, const std::shared_ptr<Drawing::Image>& image,
260 const KawaseParameter& param)
261 {
262 if (!blurEffect_ || !mixEffect_ || !image) {
263 ROSEN_LOGE("KawaseBlurFilter::shader error, use Gauss instead");
264 return false;
265 }
266 static auto useKawaseOriginal = RSSystemProperties::GetKawaseOriginalEnabled();
267 if (param.radius <= 0 || useKawaseOriginal) {
268 ROSEN_LOGD("KawaseBlurFilter::input invalid radius : %{public}d", param.radius);
269 OutputOriginalImage(canvas, image, param);
270 return true;
271 }
272 auto input = image;
273 CheckInputImage(canvas, image, param, input);
274 ComputeRadiusAndScale(param.radius);
275 RS_OPTIONAL_TRACE_BEGIN("ApplyKawaseBlur " + GetDescription());
276 int maxPasses = supportLargeRadius ? kMaxPassesLargeRadius : kMaxPasses;
277 float dilatedConvolutionFactor = supportLargeRadius ? kDilatedConvolutionLargeRadius : kDilatedConvolution;
278 if (abs(dilatedConvolutionFactor) <= 1e-6) {
279 dilatedConvolutionFactor = 4.6f; // 4.6 : radio between gauss and kawase
280 }
281 float tmpRadius = static_cast<float>(blurRadius_) / dilatedConvolutionFactor;
282 int numberOfPasses = std::min(maxPasses, std::max(static_cast<int>(ceil(tmpRadius)), 1)); // 1 : min pass num
283 float radiusByPasses = tmpRadius / numberOfPasses;
284 ROSEN_LOGD("KawaseBlurFilter::kawase radius : %{public}f, scale : %{public}f, pass num : %{public}d",
285 blurRadius_, blurScale_, numberOfPasses);
286 int width = std::max(static_cast<int>(std::ceil(param.dst.GetWidth())), input->GetWidth());
287 int height = std::max(static_cast<int>(std::ceil(param.dst.GetHeight())), input->GetHeight());
288 auto blurParams = BlurParams{numberOfPasses, width, height, radiusByPasses};
289 auto blurImage = ExecutePingPongBlur(canvas, input, param, blurParams);
290 RS_OPTIONAL_TRACE_END();
291 if (!blurImage) {
292 return false;
293 }
294 return ApplyBlur(canvas, input, blurImage, param);
295 }
296
ExecutePingPongBlur(Drawing::Canvas & canvas,const std::shared_ptr<Drawing::Image> & input,const KawaseParameter & inParam,const BlurParams & blur) const297 std::shared_ptr<Drawing::Image> KawaseBlurFilter::ExecutePingPongBlur(Drawing::Canvas& canvas,
298 const std::shared_ptr<Drawing::Image>& input, const KawaseParameter& inParam, const BlurParams& blur) const
299 {
300 auto originImageInfo = input->GetImageInfo();
301 auto scaledInfo = Drawing::ImageInfo(std::ceil(blur.width * blurScale_), std::ceil(blur.height * blurScale_),
302 originImageInfo.GetColorType(), originImageInfo.GetAlphaType(), originImageInfo.GetColorSpace());
303 Drawing::Matrix blurMatrix;
304 blurMatrix.Translate(-inParam.src.GetLeft(), -inParam.src.GetTop());
305 float scaleW = static_cast<float>(scaledInfo.GetWidth()) / input->GetWidth();
306 float scaleH = static_cast<float>(scaledInfo.GetHeight()) / input->GetHeight();
307 blurMatrix.PostScale(scaleW, scaleH);
308 Drawing::SamplingOptions linear(Drawing::FilterMode::LINEAR, Drawing::MipmapMode::NONE);
309
310 // Advanced Filter: check is AF usable only the first time
311 bool isUsingAF = IS_ADVANCED_FILTER_USABLE_CHECK_ONCE && blurEffectAF_ != nullptr;
312 Drawing::RuntimeShaderBuilder blurBuilder(isUsingAF ? blurEffectAF_ : blurEffect_);
313 if (RSSystemProperties::GetBlurExtraFilterEnabled() && simpleFilter_) {
314 blurBuilder.SetChild("imageInput", ApplySimpleFilter(canvas, input, blurMatrix, scaledInfo, linear));
315 } else {
316 blurBuilder.SetChild("imageInput", Drawing::ShaderEffect::CreateImageShader(*input, Drawing::TileMode::CLAMP,
317 Drawing::TileMode::CLAMP, linear, blurMatrix));
318 }
319
320 if (isUsingAF) {
321 SkV2 firstPassOffsets[BLUR_SAMPLE_COUNT];
322 OffsetInfo firstPassOffsetInfo = {blur.radiusByPass * blurScale_, blur.radiusByPass * blurScale_,
323 scaledInfo.GetWidth(), scaledInfo.GetHeight()};
324 getNormalizedOffset(firstPassOffsets, BLUR_SAMPLE_COUNT, firstPassOffsetInfo);
325 blurBuilder.SetUniform("in_blurOffset", firstPassOffsetInfo.offsetX, firstPassOffsetInfo.offsetY,
326 firstPassOffsetInfo.width, firstPassOffsetInfo.height);
327 } else {
328 blurBuilder.SetUniform("in_blurOffset", blur.radiusByPass * blurScale_, blur.radiusByPass * blurScale_);
329 blurBuilder.SetUniform("in_maxSizeXY", blur.width * blurScale_, blur.height * blurScale_);
330 }
331 #ifdef RS_ENABLE_GPU
332 std::shared_ptr<Drawing::Image> tmpBlur(blurBuilder.MakeImage(
333 canvas.GetGPUContext().get(), nullptr, scaledInfo, false));
334 #else
335 std::shared_ptr<Drawing::Image> tmpBlur(blurBuilder.MakeImage(nullptr, nullptr, scaledInfo, false));
336 #endif
337 // And now we'll build our chain of scaled blur stages
338 for (auto i = 1; i < blur.numberOfPasses; i++) {
339 const float stepScale = static_cast<float>(i) * blurScale_;
340 blurBuilder.SetChild("imageInput", Drawing::ShaderEffect::CreateImageShader(*tmpBlur, Drawing::TileMode::CLAMP,
341 Drawing::TileMode::CLAMP, linear, Drawing::Matrix()));
342
343 // Advanced Filter
344 if (isUsingAF) {
345 SkV2 offsets[BLUR_SAMPLE_COUNT];
346 OffsetInfo offsetInfo = {blur.radiusByPass * stepScale, blur.radiusByPass * stepScale,
347 scaledInfo.GetWidth(), scaledInfo.GetHeight()};
348 getNormalizedOffset(offsets, BLUR_SAMPLE_COUNT, offsetInfo);
349 blurBuilder.SetUniform("in_blurOffset", offsetInfo.offsetX, offsetInfo.offsetY, offsetInfo.width,
350 offsetInfo.height);
351 } else {
352 blurBuilder.SetUniform("in_blurOffset", blur.radiusByPass * stepScale, blur.radiusByPass * stepScale);
353 blurBuilder.SetUniform("in_maxSizeXY", blur.width * blurScale_, blur.height * blurScale_);
354 }
355 #ifdef RS_ENABLE_GPU
356 tmpBlur = blurBuilder.MakeImage(canvas.GetGPUContext().get(), nullptr, scaledInfo, false);
357 #else
358 tmpBlur = blurBuilder.MakeImage(nullptr, nullptr, scaledInfo, false);
359 #endif
360 }
361 return tmpBlur;
362 }
363
ApplyBlur(Drawing::Canvas & canvas,const std::shared_ptr<Drawing::Image> & image,const std::shared_ptr<Drawing::Image> & blurImage,const KawaseParameter & param) const364 bool KawaseBlurFilter::ApplyBlur(Drawing::Canvas& canvas, const std::shared_ptr<Drawing::Image>& image,
365 const std::shared_ptr<Drawing::Image>& blurImage, const KawaseParameter& param) const
366 {
367 if (!mixEffect_ || !image || !blurImage) {
368 ROSEN_LOGE("KawaseBlurFilter::ApplyBlur input error, use Gauss instead");
369 return false;
370 }
371 auto src = param.src;
372 auto dst = param.dst;
373 Drawing::SamplingOptions linear(Drawing::FilterMode::LINEAR, Drawing::MipmapMode::NONE);
374 const auto blurMatrix = GetShaderTransform(&canvas, dst, dst.GetWidth() / blurImage->GetWidth(),
375 dst.GetHeight() / blurImage->GetHeight());
376 const auto blurShader = Drawing::ShaderEffect::CreateImageShader(*blurImage, Drawing::TileMode::CLAMP,
377 Drawing::TileMode::CLAMP, linear, blurMatrix);
378 Drawing::Brush brush;
379 brush.SetAlphaF(param.alpha);
380 if (param.colorFilter) {
381 Drawing::Filter filter;
382 filter.SetColorFilter(param.colorFilter);
383 brush.SetFilter(filter);
384 }
385 static auto addRandomColor = RSSystemProperties::GetRandomColorEnabled();
386 if (addRandomColor) {
387 Drawing::Matrix inputMatrix;
388 inputMatrix.Translate(-src.GetLeft(), -src.GetTop());
389 inputMatrix.PostScale(dst.GetWidth() / image->GetWidth(), dst.GetHeight() / image->GetHeight());
390 Drawing::Matrix matrix;
391 matrix.Translate(dst.GetLeft(), dst.GetTop());
392 inputMatrix.PostConcat(matrix);
393 Drawing::RuntimeShaderBuilder mixBuilder(mixEffect_);
394 mixBuilder.SetChild("blurredInput", blurShader);
395 mixBuilder.SetChild("originalInput", Drawing::ShaderEffect::CreateImageShader(*image, Drawing::TileMode::CLAMP,
396 Drawing::TileMode::CLAMP, linear, inputMatrix));
397 float mixFactor = (abs(kMaxCrossFadeRadius) <= 1e-6) ? 1.f : (blurRadius_ / kMaxCrossFadeRadius);
398 mixBuilder.SetUniform("mixFactor", std::min(1.0f, mixFactor));
399 static auto factor = RSSystemProperties::GetKawaseRandomColorFactor();
400 mixBuilder.SetUniform("inColorFactor", factor);
401 ROSEN_LOGD("KawaseBlurFilter::kawase random color factor : %{public}f", factor);
402 brush.SetShaderEffect(mixBuilder.MakeShader(nullptr, image->IsOpaque()));
403 } else {
404 brush.SetShaderEffect(blurShader);
405 }
406 canvas.AttachBrush(brush);
407 canvas.DrawRect(dst);
408 canvas.DetachBrush();
409 return true;
410 }
411
ComputeRadiusAndScale(int radius)412 void KawaseBlurFilter::ComputeRadiusAndScale(int radius)
413 {
414 blurRadius_ = radius * 4; // 4 : scale between gauss radius and kawase
415 AdjustRadiusAndScale();
416 }
417
AdjustRadiusAndScale()418 void KawaseBlurFilter::AdjustRadiusAndScale()
419 {
420 static constexpr int radiusStep1 = 50; // 50 : radius step1
421 static constexpr int radiusStep2 = 150; // 150 : radius step2
422 static constexpr int radiusStep3 = 400; // 400 : radius step3
423 static constexpr float scaleFactor1 = 0.25f; // 0.25 : downSample scale for step1
424 static constexpr float scaleFactor2 = 0.125f; // 0.125 : downSample scale for step2
425 static constexpr float scaleFactor3 = 0.0625f; // 0.0625 : downSample scale for step3
426 auto radius = static_cast<int>(blurRadius_);
427 if (radius > radiusStep3) {
428 blurScale_ = scaleFactor3;
429 } else if (radius > radiusStep2) {
430 blurScale_ = scaleFactor2;
431 } else if (radius > radiusStep1) {
432 blurScale_ = scaleFactor1;
433 } else {
434 blurScale_ = baseBlurScale;
435 }
436 }
437
GetDescription() const438 std::string KawaseBlurFilter::GetDescription() const
439 {
440 return "blur radius is " + std::to_string(blurRadius_);
441 }
442 } // namespace Rosen
443 } // namespace OHOS