基於ArkUI框架開發——圖片模糊處理的實現
基於ArkUI框架開發——圖片模糊處理的實現
原文: https://mp.weixin.qq.com/s/vwXVj5vmAxDRG_jTk_8hPA ,點選連結檢視更多技術內容。
現在市面上有很多APP,都或多或少對圖片有模糊上的設計,所以,圖片模糊效果到底怎麼實現的呢?
首先,我們來了解下模糊效果的對比
從視覺上,兩張圖片,有一張是模糊的,那麼,在實現圖片模糊效果之前,我們首先需要了解圖片模糊的本質是什麼?
在此介紹模糊本質之前,我們來了解下當前主流的兩個移動端平臺(Android與iOS)的實現。
對Android開發者而言,比較熟悉且完善的圖片變換三方庫以glide-transformations()為樣例,來看看它是基於什麼實現的。
Android中有兩種實現:
1、 FastBlur,根據stackBlur模糊演算法來操作圖片的畫素點實現效果,但效率低,已過時。
2、 RenderScript,這個是Google官方提供的,用來在Android上編寫一套高效能程式碼的語言,可以執行在CPU及其GPU上,效率較高。
而對iOS開發者而言,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; readPixelsToBuffer(dst: ArrayBuffer, callback: AsyncCallback): void;
可以看出,系統將獲取到畫素點資料ARGB值,儲存到ArrayBuffer中去。
第二、迴圈獲取每個畫素點,將其x、y點的畫素點當作焦點。
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; writeBufferToPixels(src: ArrayBuffer, callback: AsyncCallback): void;
透過上面的流程,我們可以在OpenHarmony系統下,獲取到經過正態分佈公式處理的畫素點,至此圖片模糊效果已經實現。
但是,經過測試發現,這個方式實現模糊化的過程,很耗時,達不到我們的效能要求。若是一張很大的圖片,就單單寬高迴圈來看,比如1920*1080寬高的圖片就要迴圈2,073,600次,非常耗時且對裝置的CPU也有非常大的消耗,因此我們還需要對其進行效能最佳化。
模糊效能最佳化思路
如上面所訴,考慮到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 = 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); }
從上面程式碼,可以看出,按照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)的記憶體塊中,再由螢幕顯示這個緩衝區。
那模糊的效果怎麼來實現呢?
首先我們來定義我們的頂點著色器及其片元著色器。如下程式碼:
頂點著色器:
const char vShaderStr[] = "#version 300 es \n" "layout(location = 0) in vec4 a_position; \n" "layout(location = 1) in vec2 a_texCoord; \n" "out vec2 v_texCoord; \n" "void main() \n" "{ \n" " gl_Position = a_position; \n" " v_texCoord = a_texCoord; \n" "} \n";
片元著色器:
const char fShaderStr0[] = "#version 300 es \n" "precision mediump float; \n" "in vec2 v_texCoord; \n" "layout(location = 0) out vec4 outColor; \n" "uniform sampler2D s_TextureMap; \n" "void main() \n" "{ \n" " outColor = texture(s_TextureMap, v_texCoord); \n" "}";
其中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\n" "precision highp float;\n" "uniform lowp sampler2D s_TextureMap;\n" "in vec2 v_texCoord;\n" "layout(location = 0) out vec4 outColor;\n" "uniform highp int blurRadius;\n" "uniform highp vec2 blurOffset;\n" "\n" "uniform highp float sumWeight;\n" "float PI = 3.1415926;\n" "float getWeight(int i)\n" "{\n" "float sigma = float(blurRadius) / 3.0;\n" "return (1.0 / sqrt(2.0 * PI * sigma * sigma)) * exp(-float(i * i) / (2.0 * sigma * sigma)) / sumWeight;\n" "}\n" "vec2 clampCoordinate(vec2 coordinate)\n" "{\n" " return vec2(clamp(coordinate.x, 0.0, 1.0), clamp(coordinate.y, 0.0, 1.0));\n" "}\n" "\n" "void main()\n" "{\n" "vec4 sourceColor = texture(s_TextureMap, v_texCoord);\n" "if (blurRadius <= 1)\n" "{\n" "outColor = sourceColor;\n" "return;\n" "}\n" "float weight = getWeight(0);\n" "vec3 finalColor = sourceColor.rgb * weight;\n" "for (int i = 1; i < blurRadius; i++)\n" "{\n" "weight = getWeight(i);\n" "finalColor += texture(s_TextureMap, clampCoordinate(v_texCoord - blurOffset * float(i))).rgb * weight;\n" "finalColor += texture(s_TextureMap, clampCoordinate(v_texCoord + blurOffset * float(i))).rgb * weight;\n" "}\n" "outColor = vec4(finalColor, sourceColor.a);\n" "}\n";
裡面的邏輯暫時就不介紹了,有興趣的朋友可以去研究研究。
透過上述的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<image.PixelMap>{ ..... 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) }) } }) }
綜上,本篇文章介紹了由單純的在JS中用正態分佈公式操作畫素點實現模糊效果,引出效能問題,最後到基於OpenGL實現模糊效果的最佳化,最後效能上也從模糊一張大圖片要十幾秒提升到100ms內,文章就介紹到這了,歡迎有興趣的朋友,可以參考學習下,下面提供具體的專案原始碼地址。
專案地址:
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70011554/viewspace-2950154/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- Android 圖片高斯模糊處理Android
- 基於ArkUI框架開發-ImageKnife渲染層重構UI框架
- 基於Gin框架實現異常處理框架
- 實現一個簡單的基於 WebAssembly 的圖片處理應用Web
- 圖片模糊效果實現(RenderScript)
- 基於Web開發的圖片社群網站的設計與實現Web網站
- 【影像處理】基於OpenCV實現影像直方圖的原理OpenCV直方圖
- 基於React Hook實現圖片的裁剪ReactHook
- web前端實現圖片壓縮處理Web前端
- 記錄三種實現圖片模糊的方法
- 基於SSH框架專案使用模糊查詢的搜尋功能開發框架
- 直播app開發,螢幕效果與圖片的處理APP
- Android影象處理 - 高斯模糊的原理及實現Android
- 基於go開發日誌處理包Go
- 移動開發時批處理壓縮圖片提高開發效率移動開發
- 一個基於Vue的圖片輪播元件的實現Vue元件
- webpack圖片處理Web
- Thumbnailator處理圖片AI
- iOS 圖片處理iOS
- 圖片上傳及圖片處理
- 基於canvas實現波浪式繪製圖片Canvas
- 通過HTTP/2實現每天處理400GB圖片的實踐HTTP
- 【通過HTTP/2實現每天處理400GB圖片的實踐HTTP
- 基於RxJava2實現的簡單圖片爬蟲RxJava爬蟲
- 基於Spark的大資料實時處理開課Spark大資料
- 基帶處理器的開發實踐經驗
- [開源]基於WPF實現的Gif圖片分割器,提取GIf圖片中的每一幀
- 有沒有好的圖片處理包,能實現大圖中尋找小圖
- [草稿]關於網頁全屏背景圖片的處理方案網頁
- 【YLCircleImageView】圖片處理View
- 002.00 圖片處理
- 圖片處理--羽化特效特效
- 讀書APP原始碼,搜尋欄模糊處理實現APP原始碼
- 走近webpack(3)–圖片的處理Web
- 基於universalimageloader實現的圖片載入控制元件BlurImageView控制元件View
- iOS 開發之模糊效果的五種實現iOS
- DDGScreenShot —iOS 圖片處理--多圖片拼接 (swift)iOSSwift
- 基於 MVP 的 Android 元件化開發框架實踐MVPAndroid元件化框架