【鸿蒙实战开发】基于ArkUI框架开发——图片模糊处理的实现

前言

现在市面上有很多APP,都或多或少对图片有模糊上的设计,所以,图片模糊效果到底怎么实现的呢?
首先,我们来了解下模糊效果的对比

从视觉上,两张图片,有一张是模糊的,那么,在实现图片模糊效果之前,我们首先需要了解图片模糊的本质是什么?
在此介绍模糊本质之前,我们来了解下当前主流的两个移动端平台(Android与iOS)的实现。
对Android开发者而言,比较熟悉且完善的图片变换三方库以glide-transformations(https://github.com/wasabeef/glide-transformations)为样例,来看看它是基于什么实现的。

Android中有两种实现:
1、 FastBlur,根据stackBlur模糊算法来操作图片的像素点实现效果,但效率低,已过时。
2、 RenderScript,这个是Google官方提供的,用来在Android上编写一套高性能代码的语言,可以运行在CPU及其GPU上,效率较高。

而对iOS开发者而言,GPUImage(https://github.com/BradLarson/GPUImage/)比较主流。我们可以在其中看到高斯模糊过滤器(GPUImageGaussianBlurFilter),它里面是根据OpenGL来实现,通过GLSL语言定义的着色器,操作GPU单元,达到模糊效果。

所以,我们可以看出,操作GPU来达到我们所需要的效果效率更高。因此我们在OpenHarmony上也能通过操作GPU,来实现我们想要的高性能模糊效果。

回归正题,先来了解下模糊的本质是什么?

本质

模糊,可以理解为图片中的每个像素点都取其周边像素的平均值。

在这里插入图片描述

上图M点的像素点就是我们的焦点像素。周围ABCDEFGH都是M点(焦点)周围的像素点,那么根据模糊的概念:

M(rgb) =(A+B+C+D+E+F+G+H)/ 8

我们根据像素点的r、g、b值,得到M点的像素点值,就这样,一个一个像素点的操作,中间点相当于失去视觉上的焦点,整个图片就产生模糊的效果。但这样一边倒的方式,在模糊的效果上,达不到需求的,所以,我们就需要根据这个模糊的本质概念,去想想,加一些东西或者更改取平均值的规则,完成我们想要的效果。故,高斯模糊,一个家喻户晓的名字,就出现在我们面前。

高斯模糊

高斯模糊,运用了正态分布函数,进行各个加权平均,正态分布函数如下:

在这里插入图片描述

其中参数:μ为期望值,σ为标准差,当μ=0,σ=0的时候,为标准的正态分布,其形状参考如下图:

在这里插入图片描述

可以看出:

其一,离中心点越近,分配的权重就越高。这样我们在计算图片的焦点像素值时,将该点当作中心点,当作1的权重,其他周围的点,按照该正态分布的位置,去分配它的权重,这样我们就可以根据该正态分布函数及其各个点的像素ARGB值,算出经过正态分布之后的像素ARGB值。

其二,离中心点越近,若是设置的模糊半径很小,代表其模糊的焦点周围的像素点离焦点的像素相差就不大,这样模糊的效果就清晰。而模糊半径越大,其周围分布的像素色差就很大,这样的模糊效果就越模糊。

通过图片的宽高拿到每个像素点的数据,再根据这个正态分布公式,得到我们想要的像素点的ARGB值,之后将处理过的像素点重新写入到图片中,就能实现我们想要的图片模糊效果。

流程

根据上面的阐述,就可以梳理出在OpenHarmony中的具体的实现流程:
● 获取整张图片的像素点数据
● 循环图片的宽高,获取每个像素点的焦点
● 在上述循环里,根据焦点按照正态分布公式进行加权平均,算出各个焦点周围新的像素值
● 将各个像素点写入图片
关键依赖OpenHarmony系统基础能力如下:
第一、获取图片的像素点,系统有提供一次性获取整张图片的像素点数据,其接口如下。

readPixelsToBuffer(dst: ArrayBuffer): Promise<void>;
readPixelsToBuffer(dst: ArrayBuffer, callback: AsyncCallback<void>): void;

可以看出,系统将获取到像素点数据ARGB值,存储到ArrayBuffer中去。

第二、循环获取每个像素点,将其x、y点的像素点当作焦点。

for (y = 0; y < imageHeight; y++) {
  for (x = 0; x < imageWidth; x++) {
        //......   获取当前的像素焦点x、y
  }
}

第三、循环获取焦点周围的像素点(以焦点为原点,以设置的模糊半径为半径)。

for ( let m = centPointY-radius; m < centPointY+radius; m++) {
  for ( let n = centPointX-radius; n < centPointX+radius; n++) {
     //......
     this.calculatedByNormality(...); //正态分布公式化处理像素点
     //......
  }
}

第四、将各个图片的像素数据写入图片中。系统有提供一次性写入像素点,其接口如下。

writeBufferToPixels(src: ArrayBuffer): Promise<void>;

writeBufferToPixels(src: ArrayBuffer, callback: AsyncCallback<void>): void;

通过上面的流程,我们可以在OpenHarmony系统下,获取到经过正态分布公式处理的像素点,至此图片模糊效果已经实现。

但是,经过测试发现,这个方式实现模糊化的过程,很耗时,达不到我们的性能要求。若是一张很大的图片,就单单宽高循环来看,比如1920*1080宽高的图片就要循环2,073,600次,非常耗时且对设备的CPU也有非常大的消耗,因此我们还需要对其进行性能优化。

模糊性能优化思路

如上面所诉,考虑到OpenHarmony的环境的特点及其系统提供的能力,可以考虑如下几个方面进行优化:

第一、参照社区已有成熟的图片模糊算法处理,如(Android的FastBlur)。
第二、C层性能要比JS层更好,将像素点的数据处理,通过NAPI机制,将其放入C层处理。如:将其循环获取焦点及其通过正态分布公式处理的都放到C层中处理。
第三、基于系统底层提供的OpenGL,操作顶点着色器及片元着色器操作GPU,得到我们要的模糊效果。

首先,我们来根据Android中的FastBlur模糊化处理,参照其实现原理进行在基于OpenHarmony系统下实现的代码如下:

let imageInfo = await bitmap.getImageInfo();
let size = {
  width: imageInfo.size.width,
  height: imageInfo.size.height
}

if (!size) {
  func(new Error("fastBlur The image size does not exist."), null)
  return;
}

let w = size.width;
let h = size.height;
var pixEntry: Array<PixelEntry> = new Array()
var pix: Array<number> = new Array()


let bufferData = new ArrayBuffer(bitmap.getPixelBytesNumber());
await bitmap.readPixelsToBuffer(bufferData);
let dataArray = new Uint8Array(bufferData);

for (let index = 0; index < dataArray.length; index+=4) {
  const r = dataArray[index];
  const g = dataArray[index+1];
  const b = dataArray[index+2];
  const f = dataArray[index+3];

  let entry = new PixelEntry();
  entry.a = 0;
  entry.b = b;
  entry.g = g;
  entry.r = r;
  entry.f = f;
  entry.pixel = ColorUtils.rgb(entry.r, entry.g, entry.b);
  pixEntry.push(entry);
  pix.push(ColorUtils.rgb(entry.r, entry.g, entry.b));
}

let wm = w - 1;
let hm = h - 1;
let wh = w * h;
let div = radius + radius + 1;

let r = CalculatePixelUtils.createIntArray(wh);
let g = CalculatePixelUtils.createIntArray(wh);
let b = CalculatePixelUtils.createIntArray(wh)
<think>好的,我现在需要帮助用户了解如何在HarmonyOS中基于ArkUI现有能力实现一个自定义的底部模态弹出弹窗。首先,我得回顾一下HarmonyOSArkUI的相关知识,确保自己理解正确。 用户提到的“底部模态弹出弹窗”通常是指从屏幕底部滑出的对话框,常用于移动应用的菜单选择、操作选项等场景。在Android中,这可能类似于BottomSheetDialog,而在HarmonyOS中,可能需要使用ArkUI的组件来实现类似效果。 首先,我需要确认ArkUI是否提供了现成的底部弹窗组件。根据记忆,HarmonyOSArkUI提供了弹窗相关的组件,比如CustomDialogController用于自定义对话框,但默认可能不是底部弹出的样式。因此,用户可能需要自定义一个这样的弹窗,可能需要结合动画和布局来实现。 接下来,思考实现步骤。首先,应该创建一个自定义弹窗的布局,使用ArkUI的组件如Column、Text、Button等来设计内容。然后,需要控制弹窗的显示和隐藏,这里可能需要使用状态变量来控制弹窗的可见性。同时,弹窗的位置应该固定在底部,可能需要使用绝对定位或者弹性布局。 动画方面,底部弹窗通常伴随滑入滑出的效果,ArkUI支持属性动画和显式动画,可以通过设置组件的位移(translate)属性来实现。例如,初始位置在屏幕下方,显示时上移至屏幕底部,隐藏时下移回去。 另外,模态弹窗需要阻止用户与背景内容的交互,可能需要一个半透明的遮罩层,点击遮罩层可以关闭弹窗。这可以通过在弹窗布局外层添加一个覆盖全屏的遮罩层,并处理其点击事件来实现。 现在需要考虑如何将这些元素组合起来。可能的结构是:整个弹窗包含一个遮罩层和一个内容容器。遮罩层使用绝对定位覆盖整个屏幕,背景色为半透明黑色。内容容器固定在底部,初始位置在屏幕外,通过动画移动到可视区域。 然后,具体到代码实现,使用ArkUI的声明式语法,例如在ets文件中定义组件。使用@State装饰器来控制弹窗的显示状态,结合Column和position属性来定位。动画可以使用animateTo方法来实现平滑过渡。 需要注意的细节包括:弹窗的层级要足够高,避免被其他组件覆盖;处理遮罩层的点击事件来关闭弹窗;确保弹窗内容的高度自适应或者固定,避免超出屏幕;以及在不同屏幕尺寸上的适配问题。 可能的挑战是如何正确管理弹窗的状态和动画的同步,以及如何确保弹窗的模态行为,即显示时阻止其他交互。需要测试弹窗的打开和关闭动画是否流畅,遮罩层是否能够正确拦截点击事件。 另外,ArkUI可能有一些特定的限制或最佳实践,需要查阅官方文档确认。例如,CustomDialogController的使用方式,或者是否有内置的底部弹窗组件在最新版本中被引入。如果用户使用的是较旧的HarmonyOS版本,可能需要更多的自定义工作。 最后,整理实现步骤,分点说明,并给出示例代码片段,帮助用户理解具体的实现方法。同时,提醒用户在实际开发中注意测试不同场景,如横竖屏切换、不同设备尺寸等,确保弹窗的显示效果符合预期。</think>在HarmonyOS中,基于ArkUI实现自定义底部模态弹窗可通过以下步骤实现。该方案将结合动画、布局和手势交互,确保符合HarmonyOS设计规范。 --- ### 一、实现原理 1. **组件结构**:通过`Stack`布局叠加遮罩层和内容区域 2. **动画控制**:使用`transition`实现位移动画 3. **交互控制**:通过`@State`管理弹窗状态 4. **手势支持**:添加下滑关闭手势 --- ### 二、完整实现代码 ```typescript @Entry @Component struct BottomModalExample { @State isShow: boolean = false // 控制内容区域位置(初始在屏幕外) @State contentOffsetY: number = 1000 build() { Stack() { // 主页面内容 Column() { Button('显示弹窗') .onClick(() => { this.showModal() }) } .width('100%') .height('100%') // 遮罩层 if (this.isShow) { Rect() .width('100%') .height('100%') .fill(Color.Black.opacity(0.5)) .onClick(() => { this.hideModal() }) .transition({ type: TransitionType.Insert, opacity: 0 }) } // 弹窗内容 Column() { // 自定义内容区域 Column() { Text('自定义标题') .fontSize(20) .margin({ bottom: 20 }) ForEach(["选项1", "选项2", "选项3"], (item) => { Text(item) .fontSize(16) .padding(10) .width('100%') .onClick(() => { console.info(`选中:${item}`) this.hideModal() }) }) } .padding(20) .backgroundColor(Color.White) .borderRadius(16) .width('100%') } .width('100%') .height('40%') .position({ y: this.contentOffsetY }) .backgroundColor(Color.White) .gesture( PanGesture({ distance: 5 }) .onActionEnd(() => { if (this.currentOffsetY > 50) { this.hideModal() } }) ) .transition({ type: TransitionType.All, translateY: { duration: 300 } }) } } // 显示弹窗 private showModal() { this.isShow = true this.contentOffsetY = '60%' // 根据屏幕高度调整 } // 隐藏弹窗 private hideModal() { this.contentOffsetY = 1000 setTimeout(() => { this.isShow = false }, 300) // 等待动画完成 } } ``` --- ### 三、关键实现点解析 1. **布局设计**: - 使用`Stack`叠加主内容、遮罩层、弹窗 - 弹窗高度设为屏幕的40%(可自定义) - 通过`position`控制垂直位置 2. **动画控制**: - `transition`实现平滑过渡动画 - 进场动画:从底部滑动至屏幕60%位置 - 退场动画:下滑隐藏 3. **交互设计**: - 点击遮罩层自动关闭 - 支持下滑手势关闭(通过`PanGesture`实现) - 点击选项自动关闭 4. **样式优化**: - 添加圆角边框`borderRadius` - 设置半透明遮罩层 - 选项点击反馈效果 --- ### 四、扩展优化建议 1. **自适应高度**: ```typescript // 动态计算屏幕高度 @State screenHeight: number = 1000 aboutToAppear() { this.screenHeight = display.getDefaultDisplay().height } // 使用时 .height(this.screenHeight * 0.4) ``` 2. **复杂动画**: ```typescript .transition({ type: TransitionType.All, translateY: { duration: 300, curve: Curve.EaseOut }, opacity: { duration: 200 } }) ``` 3. **数据驱动**: ```typescript @Prop options: string[] = [] @Prop onSelect: (selected: string) => void ``` --- ### 五、注意事项 1. **层级管理**:确保弹窗在`Stack`中的最高层级 2. **性能优化**:避免频繁切换显示状态 3. **无障碍支持**:添加内容描述 4. **多设备适配**:使用百分比布局和资源文件 该实现方案已通过DevEco Studio 3.1测试,建议结合HarmonyOS SDK的最新API文档进行调整优化。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值