現(xiàn)在市面上有很多 APP,都或多或少對圖片有模糊上的設(shè)計,所以圖片模糊效果到底怎么實現(xiàn)的呢?
首先,我們來了解下模糊效果的對比:
從視覺上,兩張圖片,有一張是模糊的,那么,在實現(xiàn)圖片模糊效果之前,我們首先需要了解圖片模糊的本質(zhì)是什么?在此介紹模糊本質(zhì)之前,我們來了解下當(dāng)前主流的兩個移動端平臺(Android 與 iOS)的實現(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ì)
模糊,可以理解為圖片中的每個像素點都取其周邊像素的平均值。
上圖M點的像素點就是我們的焦點像素。周圍 ABCDEFGH 都是 M 點(焦點)周圍的像素點,那么根據(jù)模糊的概念:
?我們根據(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ù)如下:
其中參數(shù):μ 為期望值,σ 為標(biāo)準(zhǔn)差,當(dāng) μ=0,σ=0 的時候,為標(biāo)準(zhǔn)的正態(tài)分布,其形狀參考如下圖:
可以看出:其一,離中心點越近,分配的權(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
審核編輯 :李倩
-
Android
+關(guān)注
關(guān)注
12文章
3924瀏覽量
127147 -
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)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論