大家好呀!近期我在做模糊背景动效时踩了不少坑,尤其是在高分辨率图片模糊 + 多层过渡的场景下,一不小心就会造成 帧率骤降、渲染阻塞甚至线程卡死。想象下这个场景——用户打开相册、弹出底部模态或半透明浮层,如果模糊动效在页面刚显示就卡顿甚至掉帧,那体验会直线暴跌。🫠 模糊 + 动效 是 HarmonyOS UI 中最常见的一种「美感与性能冲突」场景,只要优化得当,体验可以提升数倍。
一、图像模糊动效为何如此重要?
模糊效果是一种常见的图像处理技术,它通过弱化图像细节来突出主体,使视觉焦点更加明确。我们常用的模糊形式有高斯模糊(Gaussian Blur)、背景毛玻璃(Frosted Glass)或动态遮罩模糊,它们可以增强界面空间感,同时清晰区分元素层级。 微博热搜点击跳转后的页面,背景处理就使用到了图像模糊动效。
二、性能问题分析神器:AppAnalyzer
Step 1: 使用 App Analyzer 测试页面间转场场景。
发现在“转场卡顿率”指标上存在异常,诊断原因中提示故障原因:动态模糊绘制丢帧。
Step 2: 展开“动态模糊绘制丢帧诊断”。
- 诊断出SearchResultPage.ts文件中的Row组件渲染时存在动态模糊绘制过程,导致丢帧41次。
Step 3: 根据诊断结果定位到代码。
- 代码中:Row组件使用backgroundBlurStyle提供背景材质的动态模糊能力,诊断无误。
三、图像模糊动效为何如此「吃资源」?
系统针对背景的动态模糊提供了API:backgroundBlurStyle。动态模糊中的模糊效果会随着每帧模糊内容和模糊半径的变化,进行实时变化的模糊。模糊算法需要进行精细的像素级计算,当组件需要实时渲染时,这就要求在极短的时间内完成模糊处理。尤其是在组件同时执行动画渲染任务时,计算压力会进一步加大,容易导致卡顿、丢帧等性能问题。在微博中点击热搜,跳转的页面背景使用到了图像模糊动效,给页面转场带来了压力。 因此,推荐在性能优化时使用静态模糊,静态模糊是指对输入的静态内容进行模糊处理并获取一张模糊后的图像的模糊。适用于需要为静态图像提供模糊化效果的场景。Effect Kit在Filter图像效果类中提供了blur接口,可用于定义模糊半径,数值越大模糊效果越明显。
四、优化实战(附代码)
将上述场景抽象出动态模糊场景:Image组件从资源文件中读取一张图片,通过直接对Image组件设置blur,为Image添加动态模糊效果。
Image($r('app.media.test'))
.width('100%')
.height('100%')
.objectFit(ImageFit.Fill)
.blur(13) // add motion blur effect
优化思路:使用静态模糊对图片模糊动效进行优化。分为以下三步:
Step 1: 通过createPixelMap创建图片的PixelMap。
let context: Context = this.getUIContext().getHostContext()!;
await context.resourceManager.getRawFileContent('test.png').then((fileData: Uint8Array) => {
let buffer: ArrayBuffer = fileData.buffer.slice(0); // create an ArrayBuffer instance
this.imgSource = image.createImageSource(buffer); // create an image source instance
})
.catch((err: BusinessError) => {
hilog.error(0x000, 'testTag', `getRawFileContent failed, code=${err.code}, message=${err.message}`);
})
// create attributes for pixels
let opts: image.InitializationOptions = {
editable: true, // is it editable
pixelFormat: 3, // pixel format. 3 represents RGBA_8888
size: {
// create image size
height: 4,
width: 6
}
};
// create PixelMap
await this.imgSource!.createPixelMap(opts).then((pixelMap: image.PixelMap) => {
// ......
})
Step 2: 通过createEffect创建Filter实例。
await this.imgSource!.createPixelMap(opts).then((pixelMap: image.PixelMap) => {
const blurRadius: number = 3;
let headFilter: effectKit.Filter = effectKit.createEffect(pixelMap); // create Filter Instance
if (headFilter !== null) {
headFilter.blur(blurRadius); // set static blur. Add the blur effect to the effect list
// retrieve the image of the source image with the added linked list effect PixelMap
headFilter.getEffectPixelMap().then((pixelMap: image.PixelMap) => {
this.pixelMap = pixelMap;
});
}
})
Step 3: 通过Filter图像效果类中的blur,为Image添加模糊效果。
Image(this.pixelMap)
.width('100%')
.height('100%')
.objectFit(ImageFit.Fill)
五、效果对比
使用DevEco Studio内置的Profiler中的帧率分析工具Frame抓取点击按钮触发转场过程的trace来分析静态模糊和动态模糊场景下的性能差异。
动态模糊场景
动态模糊转场平均渲染耗时为6.113ms。同时从Present Fence(图形上屏信号)标签可以看出动态模糊转场平均帧率为108.0fps。
静态模糊场景
静态模糊转场平均渲染耗时为3.357ms。同时从Present Fence标签可以看出静态模糊转场平均帧率为119.9fps。和动态模糊转场相比平均渲染耗时减少了约45%。