精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

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

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

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

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

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

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

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

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

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

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

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

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

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

模糊的本質(zhì)

模糊,可以理解為圖片中的每個像素點都取其周邊像素的平均值。

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

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

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

高斯模糊

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

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

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

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

實現(xiàn)流程

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

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

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

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

接口如下:

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

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

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

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

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

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

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

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

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

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

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

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

//頂點坐標(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 對象,確定渲染表面的配置信息:

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)的頂點著色器與片元著色器,加載到 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)建一個空的著色器程序?qū)ο螅?/span>

program=glCreateProgram();

將著色器對象附加到 program 對象:

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

連接一個 program 對象:

glLinkProgram(program);

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

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

通過上述的 LoadShader 函數(shù)將其片元著色器加載到 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");
然后我們就需要將圖片的整個像素數(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)的像素點了。

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

但是,在這里,因為 OpenGL 里面的坐標(biāo)系,在 2D 的思維空間上,與我們通常認(rèn)知的是倒立的,所以需要對像素點進(jì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)分布公式操作像素點實現(xiàn)模糊效果,引出性能問題,最后到基于 OpenGL 實現(xiàn)模糊效果的優(yōu)化,最后性能上也從模糊一張大圖片要十幾秒提升到 100ms 內(nèi)。文章就介紹到這了,歡迎有興趣的朋友,可以參考學(xué)習(xí)下,下面提供具體的項目源碼地址。

項目地址:

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

審核編輯 :李倩


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

    關(guān)注

    12

    文章

    3924

    瀏覽量

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

    關(guān)注

    68

    文章

    10826

    瀏覽量

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

    關(guān)注

    57

    文章

    2313

    瀏覽量

    42747

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

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

收藏 人收藏

    評論

    相關(guān)推薦

    為什么AD在PCB時,另存清晰的圖片再打開會變得模糊

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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