作者:田勇?OpenHarmony知識體系工作組
現在市面上有很多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可以看出,系統將獲取到像素點數據ARGB值,存儲到ArrayBuffer中去。 ? 第二、循環獲取每個像素點,將其x、y點的像素點當作焦點。; readPixelsToBuffer(dst:?ArrayBuffer,?callback:?AsyncCallback ):?void;
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通過上面的流程,我們可以在OpenHarmony系統下,獲取到經過正態分布公式處理的像素點,至此圖片模糊效果已經實現。 ? 但是,經過測試發現,這個方式實現模糊化的過程,很耗時,達不到我們的性能要求。若是一張很大的圖片,就單單寬高循環來看,比如1920*1080寬高的圖片就要循環2,073,600次,非常耗時且對設備的CPU也有非常大的消耗,因此我們還需要對其進行性能優化。 ?; writeBufferToPixels(src:?ArrayBuffer,?callback:?AsyncCallback ):?void;
?
?
模糊性能優化思路
如上面所訴,考慮到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從上面代碼,可以看出,按照FastBlur的邏輯,還是逃不開上層去處理單個像素點,逃不開圖片寬高的循環。經過測試也發現,在一張400*300的圖片上,完成圖片的模糊需要十幾秒,所以第一個優化方案,在js環境上是行不通的。 其次,將其像素點處理,通過NAPI的機制,將像素點數據ArrayBuffer傳入到C層,由于在C層也需要循環去處理每個像素點,傳入大數據的ArrayBuffer時對系統的native的消耗嚴重。最后經過測試也發現,模糊的過程也很緩慢,達不到性能要求。 所以對比分析之后,最終的優化方案是采取系統底層提供的OpenGL,通過GPU去操作系統的圖形處理器,解放出CPU的能力。 ?= new Array() var pix: Array = 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); let rsum, gsum, bsum, x, y, i, p, yp, yi, yw: number; let vmin = CalculatePixelUtils.createIntArray(Math.max(w, h)); let divsum = (div + 1) >> 1; divsum *= divsum; let dv = CalculatePixelUtils.createIntArray(256 * divsum); for (i = 0; i < 256 * divsum; i++) { dv[i] = (i / divsum); } yw = yi = 0; let stack = CalculatePixelUtils.createInt2DArray(div, 3); let stackpointer, stackstart, rbs, routsum, goutsum, boutsum, rinsum, ginsum, binsum: number; let sir: Array ; let r1 = radius + 1; for (y = 0; y < h; 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; x < w; 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; x < w; 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 (i < hm) { yp += w; } } yi = x; stackpointer = radius; for (y = 0; y < h; y++) { // Preserve alpha channel: ( 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; } } let bufferNewData = new ArrayBuffer(bitmap.getPixelBytesNumber()); let dataNewArray = new Uint8Array(bufferNewData); let index = 0; for (let i = 0; i < dataNewArray.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++; } await bitmap.writeBufferToPixels(bufferNewData); if (func) { func("success", bitmap); }
基于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)的內存塊中,再由屏幕顯示這個緩沖區。 ? 那模糊的效果怎么來實現呢? 首先我們來定義我們的頂點著色器及其片元著色器。如下代碼: ? 頂點著色器:
const char vShaderStr[] = "#version 300 es " "layout(location = 0) in vec4 a_position; " "layout(location = 1) in vec2 a_texCoord; " "out vec2 v_texCoord; " "void main() " "{ " " gl_Position = a_position; " " v_texCoord = a_texCoord; " ??????"}?????????????????????????????????????????? ";? 片元著色器:
const char fShaderStr0[] = "#version 300 es " "precision mediump float; " "in vec2 v_texCoord; " "layout(location = 0) out vec4 outColor; " "uniform sampler2D s_TextureMap; " "void main() " "{ " " outColor = texture(s_TextureMap, v_texCoord); " ????"}";其中version代表OpenGL的版本,layout在GLSL中是用于著色器的輸入或者輸出,uniform為一致變量。在著色器執行期間一致變量的值是不變的,只能在全局范圍進行聲明,gl_Position是OpenGL內置的變量(輸出屬性-變換后的頂點的位置,用于后面的固定的裁剪等操作。所有的頂點著色器都必須寫這個值),texture函數是openGL采用2D紋理繪制。然后,我們還需要定義好初始的頂點坐標數據等;
//頂點坐標 const GLfloat vVertices[] = { -1.0f, -1.0f, 0.0f, // bottom left 1.0f, -1.0f, 0.0f, // bottom right -1.0f, 1.0f, 0.0f, // top left 1.0f, 1.0f, 0.0f, // top right }; //正常紋理坐標 const GLfloat vTexCoors[] = { 0.0f, 1.0f, // bottom left 1.0f, 1.0f, // bottom right 0.0f, 0.0f, // top left 1.0f, 0.0f, // top right }; //fbo 紋理坐標與正常紋理方向不同(上下鏡像) const GLfloat vFboTexCoors[] = { 0.0f, 0.0f, // bottom left 1.0f, 0.0f, // bottom right 0.0f, 1.0f, // top left 1.0f, 1.0f, // top right };下面就進行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中
GLuint GLUtils::LoadShader(GLenum shaderType, const char *pSource) { GLuint shader = 0; shader = glCreateShader(shaderType); if(shader) { glShaderSource(shader, 1, &pSource, NULL); glCompileShader(shader); GLint compiled = 0; glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); if (!compiled){ GLint infoLen = 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::LoadShader Could not link shader:%{public}s", buf); free(buf); } glDeleteShader(shader); shader = 0; } } } return shader; }創建一個空的著色器程序對象
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。所以我們需要在我們模糊的片元著色器上,定義開發者輸入,其模糊的片元著色器代碼如下:
const char blurShaderStr[] = "#version 300 es " "precision highp float; " "uniform lowp sampler2D s_TextureMap; " "in vec2 v_texCoord; " "layout(location = 0) out vec4 outColor; " "uniform highp int blurRadius; " "uniform highp vec2 blurOffset; " " " "uniform highp float sumWeight; " "float PI = 3.1415926; " "float getWeight(int i) " "{ " "float sigma = float(blurRadius) / 3.0; " "return (1.0 / sqrt(2.0 * PI * sigma * sigma)) * exp(-float(i * i) / (2.0 * sigma * sigma)) / sumWeight; " "} " "vec2 clampCoordinate(vec2 coordinate) " "{ " " return vec2(clamp(coordinate.x, 0.0, 1.0), clamp(coordinate.y, 0.0, 1.0)); " "} " " " "void main() " "{ " "vec4 sourceColor = texture(s_TextureMap, v_texCoord); " "if (blurRadius <= 1) " "{ " "outColor = sourceColor; " "return; " "} " "float weight = getWeight(0); " "vec3 finalColor = sourceColor.rgb * weight; " "for (int i = 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("Create Program"); LOGI("gl--> EGLRender::SetIntParams Could not create program."); 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) { throw new Error("this pixelMap data is empty"); } if (width <= 0 || height <= 0) { throw new Error("this pixelMap of width and height is invalidation"); } 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_value EGLRender::RenderSetData(napi_env env, napi_callback_info info) { .... void* buffer; size_t bufferLength; napi_status buffStatus= napi_get_arraybuffer_info(env,args[0],&buffer,&bufferLength); if (buffStatus != napi_ok) { return nullptr; } .... EGLRender::GetInstance()->SetImageData(uint8_buf, width, height); return nullptr; ?}將其數據綁定到OpenGL中的紋理中去
void EGLRender::SetImageData(uint8_t *pData, int width, int height){ 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_value EGLRender::Rendering(napi_env env, napi_callback_info info){ // 渲染 glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_SHORT, (const void *)0); glBindVertexArray(GL_NONE); glBindTexture(GL_TEXTURE_2D, GL_NONE); return nullptr; }最后,就剩下獲取圖片像素的ArrayBuffer數據了,通過glReadPixels讀取到指定區域內的像素點了
glReadPixels(x,y,surfaceWidth,surfaceHeight,GL_RGBA,GL_UNSIGNED_BYTE,pixels);但是,在這里,因為OpenGL里面的坐標系,在2D的思維空間上,與我們通常認知的是倒立的,所以需要對像素點進行處理,得到我們想要的像素點集
int totalLength= width * height * 4; int oneLineLength = width * 4; uint8_t* tmp = (uint8_t*)malloc(totalLength); memcpy(tmp, *buf, totalLength); memset(*buf,0,sizeof(uint8_t)*totalLength); for(int i = 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綜上,本篇文章介紹了由單純的在JS中用正態分布公式操作像素點實現模糊效果,引出性能問題,最后到基于OpenGL實現模糊效果的優化,最后性能上也從模糊一張大圖片要十幾秒提升到100ms內。 ?{ ..... let that = this; return new Promise((resolve, rejects) => { that.onDraw(); let buf = this.render.native_EglBitmapFromGLSurface(x, y, width, height); if (!buf) { rejects(new Error("get pixelMap fail")) } else { let initOptions = { size: { width: width, height: height }, editable: true, } image.createPixelMap(buf, initOptions).then(p => { resolve(p); }).catch((e) => { rejects(e) }) } }) }
編輯:黃飛
?
評論
查看更多