0
  • 聊天消息
  • 系統(tǒng)消息
  • 評(píng)論與回復(fù)
登錄后你可以
  • 下載海量資料
  • 學(xué)習(xí)在線課程
  • 觀看技術(shù)視頻
  • 寫文章/發(fā)帖/加入社區(qū)
會(huì)員中心
創(chuàng)作中心

完善資料讓更多小伙伴認(rèn)識(shí)你,還能領(lǐng)取20積分哦,立即完善>

3天內(nèi)不再提示

鴻蒙上實(shí)現(xiàn)“圖片模糊”效果

OpenHarmony技術(shù)社區(qū) ? 來源:OpenHarmony技術(shù)社區(qū) ? 2023-05-08 09:54 ? 次閱讀

現(xiàn)在市面上有很多 APP,都或多或少對(duì)圖片有模糊上的設(shè)計(jì),所以圖片模糊效果到底怎么實(shí)現(xiàn)的呢?

首先,我們來了解下模糊效果的對(duì)比:

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

從視覺上,兩張圖片,有一張是模糊的,那么,在實(shí)現(xiàn)圖片模糊效果之前,我們首先需要了解圖片模糊的本質(zhì)是什么?在此介紹模糊本質(zhì)之前,我們來了解下當(dāng)前主流的兩個(gè)移動(dòng)端平臺(tái)(AndroidiOS)的實(shí)現(xiàn)。

對(duì) Android 開發(fā)者而言,比較熟悉且完善的圖片變換三方庫以 glide-transformations 為樣例,來看看它是基于什么實(shí)現(xiàn)的。

https://github.com/wasabeef/glide-transformations
Android 中有兩種實(shí)現(xiàn):①FastBlur,根據(jù) stackBlur 模糊算法來操作圖片的像素點(diǎn)實(shí)現(xiàn)效果,但效率低,已過時(shí)。②RenderScript,這個(gè)是 Google 官方提供的,用來在 Android 上編寫一套高性能代碼的語言,可以運(yùn)行在 CPU 及其 GPU 上,效率較高。

而對(duì) iOS 開發(fā)者而言,GPUImage 比較主流:

https://github.com/BradLarson/GPUImage/
我們可以在其中看到高斯模糊過濾器(GPUImageGaussianBlurFilter),它里面是根據(jù) OpenGL 來實(shí)現(xiàn),通過 GLSL 語言定義的著色器,操作 GPU 單元,達(dá)到模糊效果。所以,我們可以看出,操作 GPU 來達(dá)到我們所需要的效果效率更高。因此我們?cè)?OpenHarmony 上也能通過操作 GPU,來實(shí)現(xiàn)我們想要的高性能模糊效果。回歸正題,先來了解下模糊的本質(zhì)是什么?

模糊的本質(zhì)

模糊,可以理解為圖片中的每個(gè)像素點(diǎn)都取其周邊像素的平均值。

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

上圖M點(diǎn)的像素點(diǎn)就是我們的焦點(diǎn)像素。周圍 ABCDEFGH 都是 M 點(diǎn)(焦點(diǎn))周圍的像素點(diǎn),那么根據(jù)模糊的概念:

ab0315f4-ed42-11ed-90ce-dac502259ad0.png ?我們根據(jù)像素點(diǎn)的 r、g、b 值,得到 M 點(diǎn)的像素點(diǎn)值,就這樣,一個(gè)一個(gè)像素點(diǎn)的操作,中間點(diǎn)相當(dāng)于失去視覺上的焦點(diǎn),整個(gè)圖片就產(chǎn)生模糊的效果。但這樣一邊倒的方式,在模糊的效果上,達(dá)不到需求的,所以,我們就需要根據(jù)這個(gè)模糊的本質(zhì)概念,去想想,加一些東西或者更改取平均值的規(guī)則,完成我們想要的效果。故,高斯模糊,一個(gè)家喻戶曉的名字,就出現(xiàn)在我們面前。

高斯模糊

高斯模糊,運(yùn)用了正態(tài)分布函數(shù),進(jìn)行各個(gè)加權(quán)平均,正態(tài)分布函數(shù)如下:

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

其中參數(shù):μ 為期望值,σ 為標(biāo)準(zhǔn)差,當(dāng) μ=0,σ=0 的時(shí)候,為標(biāo)準(zhǔn)的正態(tài)分布,其形狀參考如下圖:

ab237592-ed42-11ed-90ce-dac502259ad0.png可以看出:其一,離中心點(diǎn)越近,分配的權(quán)重就越高。這樣我們?cè)谟?jì)算圖片的焦點(diǎn)像素值時(shí),將該點(diǎn)當(dāng)作中心點(diǎn),當(dāng)作 1 的權(quán)重,其他周圍的點(diǎn),按照該正態(tài)分布的位置,去分配它的權(quán)重。這樣我們就可以根據(jù)該正態(tài)分布函數(shù)及其各個(gè)點(diǎn)的像素 ARGB 值,算出經(jīng)過正態(tài)分布之后的像素 ARGB 值。其二,離中心點(diǎn)越近,若是設(shè)置的模糊半徑很小,代表其模糊的焦點(diǎn)周圍的像素點(diǎn)離焦點(diǎn)的像素相差就不大,這樣模糊的效果就清晰。而模糊半徑越大,其周圍分布的像素色差就很大,這樣的模糊效果就越模糊。通過圖片的寬高拿到每個(gè)像素點(diǎn)的數(shù)據(jù),再根據(jù)這個(gè)正態(tài)分布公式,得到我們想要的像素點(diǎn)的 ARGB 值,之后將處理過的像素點(diǎn)重新寫入到圖片中,就能實(shí)現(xiàn)我們想要的圖片模糊效果。

實(shí)現(xiàn)流程

根據(jù)上面的闡述,就可以梳理出在 OpenHarmony 中的具體的實(shí)現(xiàn)流程:

  • 獲取整張圖片的像素點(diǎn)數(shù)據(jù)
  • 循環(huán)圖片的寬高,獲取每個(gè)像素點(diǎn)的焦點(diǎn)
  • 在上述循環(huán)里,根據(jù)焦點(diǎn)按照正態(tài)分布公式進(jìn)行加權(quán)平均,算出各個(gè)焦點(diǎn)周圍新的像素值
  • 將各個(gè)像素點(diǎn)寫入圖片

關(guān)鍵依賴 OpenHarmony 系統(tǒng)基礎(chǔ)能力如下:

第一、獲取圖片的像素點(diǎn),系統(tǒng)有提供一次性獲取整張圖片的像素點(diǎn)數(shù)據(jù)。

接口如下:

readPixelsToBuffer(dst:ArrayBuffer):Promise<void>;
readPixelsToBuffer(dst:ArrayBuffer,callback:AsyncCallback<void>):void;
可以看出,系統(tǒng)將獲取到像素點(diǎn)數(shù)據(jù) ARGB 值,存儲(chǔ)到 ArrayBuffer 中去。

第二、循環(huán)獲取每個(gè)像素點(diǎn),將其 x、y 點(diǎn)的像素點(diǎn)當(dāng)作焦點(diǎn)。

for(y=0;yfor(x=0;x//......獲取當(dāng)前的像素焦點(diǎn)x、y
}
}

第三、循環(huán)獲取焦點(diǎn)周圍的像素點(diǎn)(以焦點(diǎn)為原點(diǎn),以設(shè)置的模糊半徑為半徑)。

for(letm=centPointY-radius;mfor(letn=centPointX-radius;n//......
this.calculatedByNormality(...);//正態(tài)分布公式化處理像素點(diǎn)
//......
}
}
第四、將各個(gè)圖片的像素?cái)?shù)據(jù)寫入圖片中。系統(tǒng)有提供一次性寫入像素點(diǎn),其接口如下。
writeBufferToPixels(src:ArrayBuffer):Promise<void>;

writeBufferToPixels(src:ArrayBuffer,callback:AsyncCallback<void>):void;
通過上面的流程,我們可以在 OpenHarmony 系統(tǒng)下,獲取到經(jīng)過正態(tài)分布公式處理的像素點(diǎn),至此圖片模糊效果已經(jīng)實(shí)現(xiàn)。但是,經(jīng)過測(cè)試發(fā)現(xiàn),這個(gè)方式實(shí)現(xiàn)模糊化的過程,很耗時(shí),達(dá)不到我們的性能要求。若是一張很大的圖片,就單單寬高循環(huán)來看,比如 1920*1080 寬高的圖片就要循環(huán) 2,073,600 次,非常耗時(shí)且對(duì)設(shè)備的 CPU 也有非常大的消耗,因此我們還需要對(duì)其進(jìn)行性能優(yōu)化。

模糊性能優(yōu)化思路

如上面所訴,考慮到 OpenHarmony 的環(huán)境的特點(diǎn)及其系統(tǒng)提供的能力,可以考慮如下幾個(gè)方面進(jìn)行優(yōu)化:第一:參照社區(qū)已有成熟的圖片模糊算法處理,如(Android 的 FastBlur)。第二:C 層性能要比 JS 層更好,將像素點(diǎn)的數(shù)據(jù)處理,通過 NAPI 機(jī)制,將其放入 C 層處理。如:將其循環(huán)獲取焦點(diǎn)及其通過正態(tài)分布公式處理的都放到 C 層中處理。第三:基于系統(tǒng)底層提供的 OpenGL,操作頂點(diǎn)著色器及片元著色器操作 GPU,得到我們要的模糊效果。

首先,我們來根據(jù) Android 中的 FastBlur 模糊化處理,參照其實(shí)現(xiàn)原理進(jìn)行在基于 OpenHarmony 系統(tǒng)下實(shí)現(xiàn)的代碼如下:

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 的邏輯,還是逃不開上層去處理單個(gè)像素點(diǎn),逃不開圖片寬高的循環(huán)。經(jīng)過測(cè)試也發(fā)現(xiàn),在一張 400*300 的圖片上,完成圖片的模糊需要十幾秒,所以第一個(gè)優(yōu)化方案,在 js 環(huán)境上是行不通的。其次,將其像素點(diǎn)處理,通過 NAPI 的機(jī)制,將像素點(diǎn)數(shù)據(jù) ArrayBuffer 傳入到 C 層,由于在 C 層也需要循環(huán)去處理每個(gè)像素點(diǎn),傳入大數(shù)據(jù)的 ArrayBuffer 時(shí)對(duì)系統(tǒng)的 native 的消耗嚴(yán)重。最后經(jīng)過測(cè)試也發(fā)現(xiàn),模糊的過程也很緩慢,達(dá)不到性能要求。所以對(duì)比分析之后,最終的優(yōu)化方案是采取系統(tǒng)底層提供的 OpenGL,通過 GPU 去操作系統(tǒng)的圖形處理器,解放出 CPU 的能力。

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

在進(jìn)行基于 OpenGL 進(jìn)行性能提升前,我們需要了解 OpenGL 中的頂點(diǎn)著色器(vertex shader)及其片元著色器(fragment shader)。著色器(shader)是運(yùn)行在 GPU 上的最小單元,功能是將輸入轉(zhuǎn)換輸出且各個(gè) shader 之間是不能通信的,需要使用的開發(fā)語言 GLSL。這里就不介紹 GLSL 的語言規(guī)則了。①頂點(diǎn)著色器(vertex shader)確定要畫圖片的各個(gè)頂點(diǎn)(如:三角形的角的頂點(diǎn)),注意:每個(gè)頂點(diǎn)運(yùn)行一次。一旦最終位置已知,OpenGL 將獲取可見的頂點(diǎn)集,并將它們組裝成點(diǎn)、線和三角形。且以逆時(shí)針繪制的。②片元著色器(fragment shader)生成點(diǎn)、線或三角形的每個(gè)片元的最終顏色,并對(duì)每個(gè) fragment 運(yùn)行一次。fragment 是單一顏色的小矩形區(qū)域,類似于計(jì)算機(jī)屏幕上的像素,簡(jiǎn)單的說,就是將頂點(diǎn)著色器形成的點(diǎn)、線或者三角形區(qū)域,添加顏色。片元著色器的主要目的是告訴 GPU 每個(gè)片元的最終顏色應(yīng)該是什么。對(duì)于圖元(primitive)的每個(gè) fragment,片元著色器將被調(diào)用一次,因此如果一個(gè)三角形映射到 10000 個(gè)片元,那么片元著色器將被調(diào)用 10000 次。OpenGL 簡(jiǎn)單的繪制流程:讀取頂點(diǎn)信息運(yùn)行頂點(diǎn)著色器圖元裝配運(yùn)行片元著色器→往幀緩沖區(qū)寫入屏幕上最終效果簡(jiǎn)單的說,就是根據(jù)頂點(diǎn)著色器形成的點(diǎn)、線、三角形形成的區(qū)域,由片元著色器對(duì)其著色,之后就將這些數(shù)據(jù)寫入幀緩沖區(qū)(Frame Buffer)的內(nèi)存塊中,再由屏幕顯示這個(gè)緩沖區(qū)。那模糊的效果怎么來實(shí)現(xiàn)呢?首先我們來定義我們的頂點(diǎn)著色器及其片元著色器。如下代碼:

頂點(diǎn)著色器:

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 為一致變量。在著色器執(zhí)行期間一致變量的值是不變的,只能在全局范圍進(jìn)行聲明,gl_Position 是 OpenGL 內(nèi)置的變量(輸出屬性-變換后的頂點(diǎn)的位置,用于后面的固定的裁剪等操作,所有的頂點(diǎn)著色器都必須寫這個(gè)值),texture 函數(shù)是 openGL 采用 2D 紋理繪制。

然后,我們還需要定義好初始的頂點(diǎn)坐標(biāo)數(shù)據(jù)等。

//頂點(diǎn)坐標(biāo)
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
};

//正常紋理坐標(biāo)
constGLfloatvTexCoors[]={
0.0f,1.0f,//bottomleft
1.0f,1.0f,//bottomright
0.0f,0.0f,//topleft
1.0f,0.0f,//topright
};

//fbo紋理坐標(biāo)與正常紋理方向不同(上下鏡像)
constGLfloatvFboTexCoors[]={
0.0f,0.0f,//bottomleft
1.0f,0.0f,//bottomright
0.0f,1.0f,//topleft
1.0f,1.0f,//topright
};

下面就進(jìn)行 OpenGL 的初始化操作,獲取 display,用來創(chuàng)建 EGLSurface 的。

m_eglDisplay=eglGetDisplay(EGL_DEFAULT_DISPLAY);

初始化 EGL 方法:

eglInitialize(m_eglDisplay,&eglMajVers,&eglMinVers)

獲取 EGLConfig 對(duì)象,確定渲染表面的配置信息:

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

創(chuàng)建渲染表面 EGLSurface,使用 eglCreatePbufferSurface 創(chuàng)建屏幕外渲染區(qū)域。

m_eglSurface=eglCreatePbufferSurface(m_eglDisplay,m_eglConf,surfaceAttr)

創(chuàng)建渲染上下文 EGLContext:

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

綁定上下文:

eglMakeCurrent(m_eglDisplay,m_eglSurface,m_eglSurface,m_eglCtx)

通過默認(rèn)的頂點(diǎn)著色器與片元著色器,加載到 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;
}

創(chuàng)建一個(gè)空的著色器程序?qū)ο螅?/span>

program=glCreateProgram();

將著色器對(duì)象附加到 program 對(duì)象:

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

連接一個(gè) program 對(duì)象:

glLinkProgram(program);

創(chuàng)建并初始化緩沖區(qū)對(duì)象的數(shù)據(jù)存儲(chǔ):

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]);
到這,整個(gè) OpenGL 的初始化操作,差不多完成了,接下來,我們就要去基于 OpenGL 去實(shí)現(xiàn)我們想要的模糊效果。考慮到模糊的效果,那么我們需要給開發(fā)者提供模糊半徑 blurRadius、模糊偏移量 blurOffset、模糊的權(quán)重 sumWeight。

所以我們需要在我們模糊的片元著色器上,定義開發(fā)者輸入,其模糊的片元著色器代碼如下:

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
"{
"
"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);
"
"}
";
里面的邏輯暫時(shí)就不介紹了,有興趣的朋友可以去研究研究。

通過上述的 LoadShader 函數(shù)將其片元著色器加載到 GPU 的運(yùn)行單元中去。

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");
然后我們就需要將圖片的整個(gè)像素?cái)?shù)據(jù)傳入。

定義好 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 數(shù)據(jù)傳入 NAPI 層。通過 napi_get_arraybuffer_info NAPI 獲取 ArrayBuffer 數(shù)據(jù)。

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;
}

將其數(shù)據(jù)綁定到 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);

....
}
}

然后就是讓開發(fā)者自己定義模糊半徑及其模糊偏移量,通過 OpenGL 提供的。

glUniform1i(location,(int)value);設(shè)置int片元著色器blurRadius變量
glUniform2f(location,value[0],value[1]);設(shè)置float數(shù)組片元著色器blurOffset變量
將半徑及其偏移量設(shè)置到模糊的片元著色器上。

之后,通過 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 數(shù)據(jù)了,通過 glReadPixels 讀取到指定區(qū)域內(nèi)的像素點(diǎn)了。

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

但是,在這里,因?yàn)?OpenGL 里面的坐標(biāo)系,在 2D 的思維空間上,與我們通常認(rèn)知的是倒立的,所以需要對(duì)像素點(diǎn)進(jìn)行處理,得到我們想要的像素點(diǎn)集。

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;imemcpy(*buf+oneLineLength*i,tmp+totalLength-oneLineLength*(i+1),oneLineLength);
}
free(tmp);

最后在上層,通過系統(tǒng)提供的 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 中用正態(tài)分布公式操作像素點(diǎn)實(shí)現(xiàn)模糊效果,引出性能問題,最后到基于 OpenGL 實(shí)現(xiàn)模糊效果的優(yōu)化,最后性能上也從模糊一張大圖片要十幾秒提升到 100ms 內(nèi)。文章就介紹到這了,歡迎有興趣的朋友,可以參考學(xué)習(xí)下,下面提供具體的項(xiàng)目源碼地址。

項(xiàng)目地址:

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

審核編輯 :李倩


聲明:本文內(nèi)容及配圖由入駐作者撰寫或者入駐合作網(wǎng)站授權(quán)轉(zhuǎn)載。文章觀點(diǎn)僅代表作者本人,不代表電子發(fā)燒友網(wǎng)立場(chǎng)。文章及其配圖僅供工程師學(xué)習(xí)之用,如有內(nèi)容侵權(quán)或者其他違規(guī)問題,請(qǐng)聯(lián)系本站處理。 舉報(bào)投訴
  • Android
    +關(guān)注

    關(guān)注

    12

    文章

    3917

    瀏覽量

    127042
  • cpu
    cpu
    +關(guān)注

    關(guān)注

    68

    文章

    10807

    瀏覽量

    210852
  • 鴻蒙
    +關(guān)注

    關(guān)注

    57

    文章

    2303

    瀏覽量

    42691

原文標(biāo)題:鴻蒙上實(shí)現(xiàn)“圖片模糊”效果

文章出處:【微信號(hào):gh_834c4b3d87fe,微信公眾號(hào):OpenHarmony技術(shù)社區(qū)】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。

收藏 人收藏

    評(píng)論

    相關(guān)推薦

    為什么AD在PCB時(shí),另存清晰的圖片再打開會(huì)變得模糊?

    如題,經(jīng)常在做PCB后,進(jìn)行3D視覺確認(rèn),效果是不錯(cuò),但如果要另存圖片(一般是QQ截圖另存)后,再打開圖片,PCB就會(huì)變得模糊, 大神們,有好辦法嗎?
    發(fā)表于 09-26 22:13

    如何讓模糊圖片變清晰,視頻變高清

    基于人工智能高精密圖像處理算法,以及得益于高性能GPU保障,我們開發(fā)完成了在線秒速給黑白照片上色、圖像清晰度增強(qiáng)平臺(tái)。迪視廳【DistinctAi】影視動(dòng)漫補(bǔ)幀、超分辨,黑白圖片上色、模糊圖像高清
    發(fā)表于 12-16 15:20

    鴻蒙原子化服務(wù)卡片添加到桌面后圖片非常模糊是為什么?

    布局只有一個(gè)Image,設(shè)置了src,在服務(wù)中心圖片是清晰的,添加到桌面后圖片四周有邊距,并且圖片非常模糊
    發(fā)表于 04-11 11:54

    求助,鴻蒙如何實(shí)現(xiàn)一個(gè)漸變的圓形圖片?

    鴻蒙如何實(shí)現(xiàn)一個(gè)漸變的圓形圖片?
    發(fā)表于 06-10 11:04

    基于ArkUI框架開發(fā)——圖片模糊處理的實(shí)現(xiàn)

    現(xiàn)在市面上有很多APP,都或多或少對(duì)圖片模糊上的設(shè)計(jì),所以,圖片模糊效果到底怎么實(shí)現(xiàn)的呢? 首
    發(fā)表于 05-05 14:53

    js實(shí)現(xiàn)無縫跑馬燈效果圖片輪播滾動(dòng)跑馬燈效果

    介紹了js實(shí)現(xiàn)無縫跑馬燈效果以及使用JS實(shí)現(xiàn)圖片輪播滾動(dòng)跑馬燈效果,小編分享了程序示例供大家參考,有需要的小伙伴可以看看。
    發(fā)表于 12-18 14:12 ?4.7w次閱讀

    標(biāo)號(hào)模糊陰影效果設(shè)計(jì)與實(shí)現(xiàn)

    的技術(shù)手段給二維標(biāo)號(hào)添加模糊陰影的方法。結(jié)合一系列的對(duì)比實(shí)驗(yàn),對(duì)比了均值濾波、高斯濾波和中值濾波3種濾波手段對(duì)陰影的模糊效果,實(shí)現(xiàn)了二維標(biāo)號(hào)陰影的
    發(fā)表于 02-24 11:41 ?1次下載
    標(biāo)號(hào)<b class='flag-5'>模糊</b>陰影<b class='flag-5'>效果</b>設(shè)計(jì)與<b class='flag-5'>實(shí)現(xiàn)</b>

    鴻蒙上使用Python進(jìn)行物聯(lián)網(wǎng)編程

    炫耀!然而,這卻是非常重要的一步:在鴻蒙上用使用 Python 進(jìn)行物聯(lián)網(wǎng)編程是可行的?。?! 既然可行,加上 Python 語言天生的優(yōu)勢(shì)(易于掌握,開發(fā)效率高),那么真的值得持續(xù)打造,將鴻蒙上的 Python 進(jìn)行到底。 所以,今天的主題就是利用 GPIO 搭配 I2C
    的頭像 發(fā)表于 09-28 09:55 ?4230次閱讀
    在<b class='flag-5'>鴻蒙上</b>使用Python進(jìn)行物聯(lián)網(wǎng)編程

    鴻蒙上安裝按鈕實(shí)現(xiàn)下載、暫停、取消、顯示等操作

    今天給大家分享在鴻蒙上一個(gè)按鈕實(shí)現(xiàn)下載、暫停、取消、顯示下載進(jìn)度操作。
    的頭像 發(fā)表于 01-04 14:32 ?2225次閱讀

    鴻蒙 TabList 配合 Fraction 實(shí)現(xiàn)頂部切換效果演示

    今天我想著配合鴻蒙里面提供的頂部切換控件 tablist,來實(shí)現(xiàn)頂部 tab 切換,然后下面多個(gè) fraction 的效果。廢話不多說,我們正式開始。
    的頭像 發(fā)表于 01-04 14:41 ?2416次閱讀

    鴻蒙上實(shí)現(xiàn)“數(shù)字華容道”小游戲

    本篇文章教大家如何在鴻蒙上實(shí)現(xiàn)“數(shù)字華容道”小游戲。
    的頭像 發(fā)表于 12-26 09:52 ?1174次閱讀

    鴻蒙上實(shí)現(xiàn)簡(jiǎn)單的“每日新聞”

    這是一篇講解如何實(shí)現(xiàn)基于鴻蒙 JS 的簡(jiǎn)單的每日新聞。
    的頭像 發(fā)表于 12-26 09:58 ?820次閱讀

    鴻蒙上開發(fā)“小蜜蜂”游戲

    小時(shí)候我們有個(gè)熟悉的游戲叫小蜜蜂。本文教大家在鴻蒙上學(xué)做這個(gè)小蜜蜂游戲。
    的頭像 發(fā)表于 04-03 11:27 ?1601次閱讀

    基于OpenGL操作GPU來提升圖片模糊性能實(shí)現(xiàn)

    本篇文章介紹了由單純的在JS中用正態(tài)分布公式操作像素點(diǎn)實(shí)現(xiàn)模糊效果,引出性能問題,最后到基于OpenGL實(shí)現(xiàn)模糊
    發(fā)表于 05-10 14:57 ?1247次閱讀
    基于OpenGL操作GPU來提升<b class='flag-5'>圖片</b><b class='flag-5'>模糊</b>性能<b class='flag-5'>實(shí)現(xiàn)</b>

    鴻蒙ArkTS聲明式開發(fā):跨平臺(tái)支持列表【圖像效果】 通用屬性

    設(shè)置組件的模糊、陰影、球面效果以及設(shè)置圖片的圖像效果。
    的頭像 發(fā)表于 06-04 16:34 ?560次閱讀
    <b class='flag-5'>鴻蒙</b>ArkTS聲明式開發(fā):跨平臺(tái)支持列表【圖像<b class='flag-5'>效果</b>】 通用屬性