0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

鸿蒙上实现“图片模糊”效果

OpenHarmony技术社区 来源:OpenHarmony技术社区 2023-05-08 09:54 次阅读

现在市面上有很多 APP,都或多或少对图片有模糊上的设计,所以图片模糊效果到底怎么实现的呢?

首先,我们来了解下模糊效果的对比:

aad8bc3c-ed42-11ed-90ce-dac502259ad0.jpg

从视觉上,两张图片,有一张是模糊的,那么,在实现图片模糊效果之前,我们首先需要了解图片模糊的本质是什么?在此介绍模糊本质之前,我们来了解下当前主流的两个移动端平台(AndroidiOS)的实现。

对 Android 开发者而言,比较熟悉且完善的图片变换三方库以 glide-transformations 为样例,来看看它是基于什么实现的。

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

而对 iOS 开发者而言,GPUImage 比较主流:

https://github.com/BradLarson/GPUImage/
我们可以在其中看到高斯模糊过滤器(GPUImageGaussianBlurFilter),它里面是根据 OpenGL 来实现,通过 GLSL 语言定义的着色器,操作 GPU 单元,达到模糊效果。所以,我们可以看出,操作 GPU 来达到我们所需要的效果效率更高。因此我们在 OpenHarmony 上也能通过操作 GPU,来实现我们想要的高性能模糊效果。回归正题,先来了解下模糊的本质是什么?

模糊的本质

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

aaf00928-ed42-11ed-90ce-dac502259ad0.png

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

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

高斯模糊

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

ab13faf4-ed42-11ed-90ce-dac502259ad0.png

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

ab237592-ed42-11ed-90ce-dac502259ad0.png可以看出:其一,离中心点越近,分配的权重就越高。这样我们在计算图片的焦点像素值时,将该点当作中心点,当作 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(letm=centPointY-radius;m< centPointY+radius; m++) {
  for(letn=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 系统下实现的代码如下:

letimageInfo=awaitbitmap.getImageInfo();
letsize={
width:imageInfo.size.width,
height:imageInfo.size.height
}

if(!size){
func(newError("fastBlurTheimagesizedoesnotexist."),null)
return;
}

letw=size.width;
leth=size.height;
varpixEntry:Array<PixelEntry>=newArray()
varpix:Array<number>=newArray()


letbufferData=newArrayBuffer(bitmap.getPixelBytesNumber());
awaitbitmap.readPixelsToBuffer(bufferData);
letdataArray=newUint8Array(bufferData);

for(letindex=0;indexdataArray.length;index+=4){
constr=dataArray[index];
constg=dataArray[index+1];
constb=dataArray[index+2];
constf=dataArray[index+3];

letentry=newPixelEntry();
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));
}

letwm=w-1;
lethm=h-1;
letwh=w*h;
letdiv=radius+radius+1;

letr=CalculatePixelUtils.createIntArray(wh);
letg=CalculatePixelUtils.createIntArray(wh);
letb=CalculatePixelUtils.createIntArray(wh);

letrsum,gsum,bsum,x,y,i,p,yp,yi,yw:number;
letvmin=CalculatePixelUtils.createIntArray(Math.max(w,h));

letdivsum=(div+1)>>1;
divsum*=divsum;
letdv=CalculatePixelUtils.createIntArray(256*divsum);
for(i=0;i256*divsum;i++){
dv[i]=(i/divsum);
}
yw=yi=0;
letstack=CalculatePixelUtils.createInt2DArray(div,3);
letstackpointer,stackstart,rbs,routsum,goutsum,boutsum,rinsum,ginsum,binsum:number;
letsir:Array<number>;
letr1=radius+1;
for(y=0;yh;y++){
rinsum=ginsum=binsum=routsum=goutsum=boutsum=rsum=gsum=bsum=0;
for(i=-radius;i<= radius;i++){
p=pix[yi+Math.min(wm,Math.max(i,0))];
sir=stack[i+radius];
sir[0]=(p&0xff0000)>>16;
sir[1]=(p&0x00ff00)>>8;
sir[2]=(p&0x0000ff);
rbs=r1-Math.abs(i);
rsum+=sir[0]*rbs;
gsum+=sir[1]*rbs;
bsum+=sir[2]*rbs;
if(i>0){
rinsum+=sir[0];
ginsum+=sir[1];
binsum+=sir[2];
}else{
routsum+=sir[0];
goutsum+=sir[1];
boutsum+=sir[2];
}
}
stackpointer=radius;

for(x=0;xw;x++){

r[yi]=dv[rsum];
g[yi]=dv[gsum];
b[yi]=dv[bsum];

rsum-=routsum;
gsum-=goutsum;
bsum-=boutsum;

stackstart=stackpointer-radius+div;
sir=stack[stackstart%div];

routsum-=sir[0];
goutsum-=sir[1];
boutsum-=sir[2];

if(y==0){
vmin[x]=Math.min(x+radius+1,wm);
}
p=pix[yw+vmin[x]];

sir[0]=(p&0xff0000)>>16;
sir[1]=(p&0x00ff00)>>8;
sir[2]=(p&0x0000ff);

rinsum+=sir[0];
ginsum+=sir[1];
binsum+=sir[2];

rsum+=rinsum;
gsum+=ginsum;
bsum+=binsum;

stackpointer=(stackpointer+1)%div;
sir=stack[(stackpointer)%div];

routsum+=sir[0];
goutsum+=sir[1];
boutsum+=sir[2];

rinsum-=sir[0];
ginsum-=sir[1];
binsum-=sir[2];

yi++;
}
yw+=w;
}
for(x=0;xw;x++){
rinsum=ginsum=binsum=routsum=goutsum=boutsum=rsum=gsum=bsum=0;
yp=-radius*w;
for(i=-radius;i<= radius;i++){
yi=Math.max(0,yp)+x;

sir=stack[i+radius];

sir[0]=r[yi];
sir[1]=g[yi];
sir[2]=b[yi];

rbs=r1-Math.abs(i);

rsum+=r[yi]*rbs;
gsum+=g[yi]*rbs;
bsum+=b[yi]*rbs;

if(i>0){
rinsum+=sir[0];
ginsum+=sir[1];
binsum+=sir[2];
}else{
routsum+=sir[0];
goutsum+=sir[1];
boutsum+=sir[2];
}

if(ihm){
yp+=w;
}
}
yi=x;
stackpointer=radius;
for(y=0;yh;y++){
//Preservealphachannel:(0xff000000&pix[yi])
pix[yi]=(0xff000000&pix[Math.round(yi)])|(dv[Math.round(rsum)]<< 16)|(dv[
Math.round(gsum)]<< 8)|dv[Math.round(bsum)];

rsum-=routsum;
gsum-=goutsum;
bsum-=boutsum;

stackstart=stackpointer-radius+div;
sir=stack[stackstart%div];

routsum-=sir[0];
goutsum-=sir[1];
boutsum-=sir[2];

if(x==0){
vmin[y]=Math.min(y+r1,hm)*w;
}
p=x+vmin[y];

sir[0]=r[p];
sir[1]=g[p];
sir[2]=b[p];

rinsum+=sir[0];
ginsum+=sir[1];
binsum+=sir[2];

rsum+=rinsum;
gsum+=ginsum;
bsum+=binsum;

stackpointer=(stackpointer+1)%div;
sir=stack[stackpointer];

routsum+=sir[0];
goutsum+=sir[1];
boutsum+=sir[2];

rinsum-=sir[0];
ginsum-=sir[1];
binsum-=sir[2];

yi+=w;
}
}


letbufferNewData=newArrayBuffer(bitmap.getPixelBytesNumber());
letdataNewArray=newUint8Array(bufferNewData);
letindex=0;

for(leti=0;idataNewArray.length;i+=4){
dataNewArray[i]=ColorUtils.red(pix[index]);
dataNewArray[i+1]=ColorUtils.green(pix[index]);
dataNewArray[i+2]=ColorUtils.blue(pix[index]);
dataNewArray[i+3]=pixEntry[index].f;
index++;
}
awaitbitmap.writeBufferToPixels(bufferNewData);
if(func){
func("success",bitmap);
}
从上面代码,可以看出,按照 FastBlur 的逻辑,还是逃不开上层去处理单个像素点,逃不开图片宽高的循环。经过测试也发现,在一张 400*300 的图片上,完成图片的模糊需要十几秒,所以第一个优化方案,在 js 环境上是行不通的。其次,将其像素点处理,通过 NAPI 的机制,将像素点数据 ArrayBuffer 传入到 C 层,由于在 C 层也需要循环去处理每个像素点,传入大数据的 ArrayBuffer 时对系统的 native 的消耗严重。最后经过测试也发现,模糊的过程也很缓慢,达不到性能要求。所以对比分析之后,最终的优化方案是采取系统底层提供的 OpenGL,通过 GPU 去操作系统的图形处理器,解放出 CPU 的能力。

基于OpenGL操作GPU来提升模糊性能

在进行基于 OpenGL 进行性能提升前,我们需要了解 OpenGL 中的顶点着色器(vertex shader)及其片元着色器(fragment shader)。着色器(shader)是运行在 GPU 上的最小单元,功能是将输入转换输出且各个 shader 之间是不能通信的,需要使用的开发语言 GLSL。这里就不介绍 GLSL 的语言规则了。①顶点着色器(vertex shader)确定要画图片的各个顶点(如:三角形的角的顶点),注意:每个顶点运行一次。一旦最终位置已知,OpenGL 将获取可见的顶点集,并将它们组装成点、线和三角形。且以逆时针绘制的。②片元着色器(fragment shader)生成点、线或三角形的每个片元的最终颜色,并对每个 fragment 运行一次。fragment 是单一颜色的小矩形区域,类似于计算机屏幕上的像素,简单的说,就是将顶点着色器形成的点、线或者三角形区域,添加颜色。片元着色器的主要目的是告诉 GPU 每个片元的最终颜色应该是什么。对于图元(primitive)的每个 fragment,片元着色器将被调用一次,因此如果一个三角形映射到 10000 个片元,那么片元着色器将被调用 10000 次。OpenGL 简单的绘制流程:读取顶点信息运行顶点着色器图元装配运行片元着色器→往帧缓冲区写入屏幕上最终效果简单的说,就是根据顶点着色器形成的点、线、三角形形成的区域,由片元着色器对其着色,之后就将这些数据写入帧缓冲区(Frame Buffer)的内存块中,再由屏幕显示这个缓冲区。那模糊的效果怎么来实现呢?首先我们来定义我们的顶点着色器及其片元着色器。如下代码:

顶点着色器:

constcharvShaderStr[]=
"#version300es
"
"layout(location=0)invec4a_position;
"
"layout(location=1)invec2a_texCoord;
"
"outvec2v_texCoord;
"
"voidmain()
"
"{
"
"gl_Position=a_position;
"
"v_texCoord=a_texCoord;
"
"}
";

片元着色器:

constcharfShaderStr0[]=
"#version300es
"
"precisionmediumpfloat;
"
"invec2v_texCoord;
"
"layout(location=0)outvec4outColor;
"
"uniformsampler2Ds_TextureMap;
"
"voidmain()
"
"{
"
"outColor=texture(s_TextureMap,v_texCoord);
"
"}";
其中 version 代表 OpenGL 的版本,layout 在 GLSL 中是用于着色器的输入或者输出,uniform 为一致变量。在着色器执行期间一致变量的值是不变的,只能在全局范围进行声明,gl_Position 是 OpenGL 内置的变量(输出属性-变换后的顶点的位置,用于后面的固定的裁剪等操作,所有的顶点着色器都必须写这个值),texture 函数是 openGL 采用 2D 纹理绘制。

然后,我们还需要定义好初始的顶点坐标数据等。

//顶点坐标
constGLfloatvVertices[]={
-1.0f,-1.0f,0.0f,//bottomleft
1.0f,-1.0f,0.0f,//bottomright
-1.0f,1.0f,0.0f,//topleft
1.0f,1.0f,0.0f,//topright
};

//正常纹理坐标
constGLfloatvTexCoors[]={
0.0f,1.0f,//bottomleft
1.0f,1.0f,//bottomright
0.0f,0.0f,//topleft
1.0f,0.0f,//topright
};

//fbo纹理坐标与正常纹理方向不同(上下镜像)
constGLfloatvFboTexCoors[]={
0.0f,0.0f,//bottomleft
1.0f,0.0f,//bottomright
0.0f,1.0f,//topleft
1.0f,1.0f,//topright
};

下面就进行 OpenGL 的初始化操作,获取 display,用来创建 EGLSurface 的。

m_eglDisplay=eglGetDisplay(EGL_DEFAULT_DISPLAY);

初始化 EGL 方法:

eglInitialize(m_eglDisplay,&eglMajVers,&eglMinVers)

获取 EGLConfig 对象,确定渲染表面的配置信息:

eglChooseConfig(m_eglDisplay,confAttr,&m_eglConf,1,&numConfigs)

创建渲染表面 EGLSurface,使用 eglCreatePbufferSurface 创建屏幕外渲染区域。

m_eglSurface=eglCreatePbufferSurface(m_eglDisplay,m_eglConf,surfaceAttr)

创建渲染上下文 EGLContext:

m_eglCtx=eglCreateContext(m_eglDisplay,m_eglConf,EGL_NO_CONTEXT,ctxAttr);

绑定上下文:

eglMakeCurrent(m_eglDisplay,m_eglSurface,m_eglSurface,m_eglCtx)

通过默认的顶点着色器与片元着色器,加载到 GPU 中:

GLuintGLUtils::LoadShader(GLenumshaderType,constchar*pSource)
{
GLuintshader=0;
shader=glCreateShader(shaderType);
if(shader)
{
glShaderSource(shader,1,&pSource,NULL);
glCompileShader(shader);
GLintcompiled=0;
glGetShaderiv(shader,GL_COMPILE_STATUS,&compiled);
if(!compiled){
GLintinfoLen=0;
glGetShaderiv(shader,GL_INFO_LOG_LENGTH,&infoLen);
if(infoLen)
{
char*buf=(char*)malloc((size_t)infoLen);
if(buf)
{
glGetShaderInfoLog(shader,infoLen,NULL,buf);
LOGI("gl-->GLUtils::LoadShaderCouldnotlinkshader:%{public}s",buf);
free(buf);
}
glDeleteShader(shader);
shader=0;
}
}
}
returnshader;
}

创建一个空的着色器程序对象:

program=glCreateProgram();

将着色器对象附加到 program 对象:

glAttachShader(program,vertexShaderHandle);
glAttachShader(program,fragShaderHandle);

连接一个 program 对象:

glLinkProgram(program);

创建并初始化缓冲区对象的数据存储:

glGenBuffers(3,m_VboIds);
glBindBuffer(GL_ARRAY_BUFFER,m_VboIds[0]);
glBufferData(GL_ARRAY_BUFFER,sizeof(vVertices),vVertices,GL_STATIC_DRAW);

glBindBuffer(GL_ARRAY_BUFFER,m_VboIds[1]);
glBufferData(GL_ARRAY_BUFFER,sizeof(vFboTexCoors),vTexCoors,GL_STATIC_DRAW);

glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,m_VboIds[2]);
glBufferData(GL_ELEMENT_ARRAY_BUFFER,sizeof(indices),indices,GL_STATIC_DRAW);

glGenVertexArrays(1,m_VaoIds);

glBindVertexArray(m_VaoIds[0]);
到这,整个 OpenGL 的初始化操作,差不多完成了,接下来,我们就要去基于 OpenGL 去实现我们想要的模糊效果。考虑到模糊的效果,那么我们需要给开发者提供模糊半径 blurRadius、模糊偏移量 blurOffset、模糊的权重 sumWeight。

所以我们需要在我们模糊的片元着色器上,定义开发者输入,其模糊的片元着色器代码如下:

constcharblurShaderStr[]=
"#version300es
"
"precisionhighpfloat;
"
"uniformlowpsampler2Ds_TextureMap;
"
"invec2v_texCoord;
"
"layout(location=0)outvec4outColor;
"

"uniformhighpintblurRadius;
"
"uniformhighpvec2blurOffset;
"
"
"
"uniformhighpfloatsumWeight;
"
"floatPI=3.1415926;
"
"floatgetWeight(inti)
"
"{
"
"floatsigma=float(blurRadius)/3.0;
"
"return(1.0/sqrt(2.0*PI*sigma*sigma))*exp(-float(i*i)/(2.0*sigma*sigma))/sumWeight;
"
"}
"

"vec2clampCoordinate(vec2coordinate)
"
"{
"
"returnvec2(clamp(coordinate.x,0.0,1.0),clamp(coordinate.y,0.0,1.0));
"
"}
"
"
"
"voidmain()
"
"{
"
"vec4sourceColor=texture(s_TextureMap,v_texCoord);
"
"if(blurRadius<= 1)
"
"{
"
"outColor=sourceColor;
"
"return;
"
"}
"
"floatweight=getWeight(0);
"
"vec3finalColor=sourceColor.rgb*weight;
"
"for(inti=1;i< blurRadius; i++)
"
"{
"
"weight=getWeight(i);
"
"finalColor+=texture(s_TextureMap,clampCoordinate(v_texCoord-blurOffset*float(i))).rgb*weight;
"
"finalColor+=texture(s_TextureMap,clampCoordinate(v_texCoord+blurOffset*float(i))).rgb*weight;
"
"}
"
"outColor=vec4(finalColor,sourceColor.a);
"
"}
";
里面的逻辑暂时就不介绍了,有兴趣的朋友可以去研究研究。

通过上述的 LoadShader 函数将其片元着色器加载到 GPU 的运行单元中去。

m_ProgramObj=GLUtils::CreateProgram(vShaderStr,blurShaderStr,m_VertexShader,
m_FragmentShader);
if(!m_ProgramObj)
{
GLUtils::CheckGLError("CreateProgram");
LOGI("gl-->EGLRender::SetIntParamsCouldnotcreateprogram.");
return;
}

m_SamplerLoc=glGetUniformLocation(m_ProgramObj,"s_TextureMap");
m_TexSizeLoc=glGetUniformLocation(m_ProgramObj,"u_texSize");
然后我们就需要将图片的整个像素数据传入。

定义好 ts 层的方法:

setImageData(buf:ArrayBuffer,width:number,height:number){
if(!buf){
thrownewError("thispixelMapdataisempty");
}
if(width<= 0||height<= 0){
thrownewError("thispixelMapofwidthandheightisinvalidation");
}
this.width=width;
this.height=height;
this.ifNeedInit();
this.onReadySize();
this.setSurfaceFilterType();
this.render.native_EglRenderSetImageData(buf,width,height);
};

将 ArrayBuffer 数据传入 NAPI 层。通过 napi_get_arraybuffer_info NAPI 获取 ArrayBuffer 数据。

napi_valueEGLRender::RenderSetData(napi_envenv,napi_callback_infoinfo){
....

void*buffer;
size_tbufferLength;
napi_statusbuffStatus=napi_get_arraybuffer_info(env,args[0],&buffer,&bufferLength);
if(buffStatus!=napi_ok){
returnnullptr;
}

....

EGLRender::GetInstance()->SetImageData(uint8_buf,width,height);
returnnullptr;
}

将其数据绑定到 OpenGL 中的纹理中去:

voidEGLRender::SetImageData(uint8_t*pData,intwidth,intheight){
if(pData&&m_IsGLContextReady)
{
...

m_RenderImage.width=width;
m_RenderImage.height=height;
m_RenderImage.format=IMAGE_FORMAT_RGBA;
NativeImageUtil::AllocNativeImage(&m_RenderImage);
memcpy(m_RenderImage.ppPlane[0],pData,width*height*4);

glBindTexture(GL_TEXTURE_2D,m_ImageTextureId);
glTexImage2D(GL_TEXTURE_2D,0,GL_RGBA,m_RenderImage.width,m_RenderImage.height,0,GL_RGBA,GL_UNSIGNED_BYTE,m_RenderImage.ppPlane[0]);
glBindTexture(GL_TEXTURE_2D,GL_NONE);

....
}
}

然后就是让开发者自己定义模糊半径及其模糊偏移量,通过 OpenGL 提供的。

glUniform1i(location,(int)value);设置int片元着色器blurRadius变量
glUniform2f(location,value[0],value[1]);设置float数组片元着色器blurOffset变量
将半径及其偏移量设置到模糊的片元着色器上。

之后,通过 GPU 将其渲染:

napi_valueEGLRender::Rendering(napi_envenv,napi_callback_infoinfo){
//渲染
glDrawElements(GL_TRIANGLES,6,GL_UNSIGNED_SHORT,(constvoid*)0);
glBindVertexArray(GL_NONE);
glBindTexture(GL_TEXTURE_2D,GL_NONE);
returnnullptr;
}

最后,就剩下获取图片像素的 ArrayBuffer 数据了,通过 glReadPixels 读取到指定区域内的像素点了。

glReadPixels(x,y,surfaceWidth,surfaceHeight,GL_RGBA,GL_UNSIGNED_BYTE,pixels);

但是,在这里,因为 OpenGL 里面的坐标系,在 2D 的思维空间上,与我们通常认知的是倒立的,所以需要对像素点进行处理,得到我们想要的像素点集。

inttotalLength=width*height*4;
intoneLineLength=width*4;
uint8_t*tmp=(uint8_t*)malloc(totalLength);
memcpy(tmp,*buf,totalLength);
memset(*buf,0,sizeof(uint8_t)*totalLength);
for(inti=0;i< height;i ++){
       memcpy(*buf+oneLineLength*i,tmp+totalLength-oneLineLength*(i+1),oneLineLength);
}
free(tmp);

最后在上层,通过系统提供的 createPixelMap 得到我们想要的图片,也就是模糊的图片。

getPixelMap(x:number,y:number,width:number,height:number):Promise{
.....

letthat=this;
returnnewPromise((resolve,rejects)=>{
that.onDraw();
letbuf=this.render.native_EglBitmapFromGLSurface(x,y,width,height);
if(!buf){
rejects(newError("getpixelMapfail"))
}else{
letinitOptions={
size:{
width:width,
height:height
},
editable:true,
}
image.createPixelMap(buf,initOptions).then(p=>{
resolve(p);
}).catch((e)=>{
rejects(e)
})
}
})
}
综上,本篇文章介绍了由单纯的在 JS 中用正态分布公式操作像素点实现模糊效果,引出性能问题,最后到基于 OpenGL 实现模糊效果的优化,最后性能上也从模糊一张大图片要十几秒提升到 100ms 内。文章就介绍到这了,欢迎有兴趣的朋友,可以参考学习下,下面提供具体的项目源码地址。

项目地址:

https://gitee.com/openharmony-tpc/ImageKnife/tree/master/gpu_transform

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Android
    +关注

    关注

    12

    文章

    3936

    浏览量

    127403
  • cpu
    cpu
    +关注

    关注

    68

    文章

    10863

    浏览量

    211755
  • 鸿蒙
    +关注

    关注

    57

    文章

    2351

    浏览量

    42853

原文标题:鸿蒙上实现“图片模糊”效果

文章出处:【微信号:gh_834c4b3d87fe,微信公众号:OpenHarmony技术社区】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    为什么AD在PCB时,另存清晰的图片再打开会变得模糊

    如题,经常在做PCB后,进行3D视觉确认,效果是不错,但如果要另存图片(一般是QQ截图另存)后,再打开图片,PCB就会变得模糊, 大神们,有好办法吗?
    发表于 09-26 22:13

    如何让模糊图片变清晰,视频变高清

    基于人工智能高精密图像处理算法,以及得益于高性能GPU保障,我们开发完成了在线秒速给黑白照片上色、图像清晰度增强平台。迪视厅【DistinctAi】影视动漫补帧、超分辨,黑白图片上色、模糊图像高清
    发表于 12-16 15:20

    鸿蒙原子化服务卡片添加到桌面后图片非常模糊是为什么?

    布局只有一个Image,设置了src,在服务中心图片是清晰的,添加到桌面后图片四周有边距,并且图片非常模糊
    发表于 04-11 11:54

    求助,鸿蒙如何实现一个渐变的圆形图片

    鸿蒙如何实现一个渐变的圆形图片
    发表于 06-10 11:04

    基于ArkUI框架开发——图片模糊处理的实现

    现在市面上有很多APP,都或多或少对图片模糊上的设计,所以,图片模糊效果到底怎么实现的呢? 首
    发表于 05-05 14:53

    js实现无缝跑马灯效果图片轮播滚动跑马灯效果

    介绍了js实现无缝跑马灯效果以及使用JS实现图片轮播滚动跑马灯效果,小编分享了程序示例供大家参考,有需要的小伙伴可以看看。
    发表于 12-18 14:12 4.7w次阅读

    标号模糊阴影效果设计与实现

    的技术手段给二维标号添加模糊阴影的方法。结合一系列的对比实验,对比了均值滤波、高斯滤波和中值滤波3种滤波手段对阴影的模糊效果实现了二维标号阴影的
    发表于 02-24 11:41 1次下载
    标号<b class='flag-5'>模糊</b>阴影<b class='flag-5'>效果</b>设计与<b class='flag-5'>实现</b>

    鸿蒙上使用Python进行物联网编程

    炫耀!然而,这却是非常重要的一步:在鸿蒙上用使用 Python 进行物联网编程是可行的!!! 既然可行,加上 Python 语言天生的优势(易于掌握,开发效率高),那么真的值得持续打造,将鸿蒙上的 Python 进行到底。 所以,今天的主题就是利用 GPIO 搭配 I2C
    的头像 发表于 09-28 09:55 4298次阅读
    在<b class='flag-5'>鸿蒙上</b>使用Python进行物联网编程

    鸿蒙上安装按钮实现下载、暂停、取消、显示等操作

    今天给大家分享在鸿蒙上一个按钮实现下载、暂停、取消、显示下载进度操作。
    的头像 发表于 01-04 14:32 2304次阅读

    鸿蒙 TabList 配合 Fraction 实现顶部切换效果演示

    今天我想着配合鸿蒙里面提供的顶部切换控件 tablist,来实现顶部 tab 切换,然后下面多个 fraction 的效果。废话不多说,我们正式开始。
    的头像 发表于 01-04 14:41 2483次阅读

    鸿蒙上实现“数字华容道”小游戏

    本篇文章教大家如何在鸿蒙上实现“数字华容道”小游戏。
    的头像 发表于 12-26 09:52 1250次阅读

    鸿蒙上实现简单的“每日新闻”

    这是一篇讲解如何实现基于鸿蒙 JS 的简单的每日新闻。
    的头像 发表于 12-26 09:58 865次阅读

    鸿蒙上开发“小蜜蜂”游戏

    小时候我们有个熟悉的游戏叫小蜜蜂。本文教大家在鸿蒙上学做这个小蜜蜂游戏。
    的头像 发表于 04-03 11:27 1691次阅读

    基于OpenGL操作GPU来提升图片模糊性能实现

    本篇文章介绍了由单纯的在JS中用正态分布公式操作像素点实现模糊效果,引出性能问题,最后到基于OpenGL实现模糊
    发表于 05-10 14:57 1345次阅读
    基于OpenGL操作GPU来提升<b class='flag-5'>图片</b><b class='flag-5'>模糊</b>性能<b class='flag-5'>实现</b>

    鸿蒙ArkTS声明式开发:跨平台支持列表【图像效果】 通用属性

    设置组件的模糊、阴影、球面效果以及设置图片的图像效果
    的头像 发表于 06-04 16:34 659次阅读
    <b class='flag-5'>鸿蒙</b>ArkTS声明式开发:跨平台支持列表【图像<b class='flag-5'>效果</b>】 通用属性