原文:https://mp.weixin.qq.com/s/vwXVj5vmAxDRG_jTk_8hPA,点击链接查看更多技术内容。

现在市面上有很多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;
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内,文章就介绍到这了,欢迎有兴趣的朋友,可以参考学习下,下面提供具体的项目源码地址。

项目地址:https://gitee.com/openharmony-tpc/ImageKnife/tree/master/gpu_transform

基于ArkUI框架开发——图片模糊处理的实现的更多相关文章

  1. 基于SSH框架开发的《高校大学生选课系统》的质量属性的实现

    基于SSH框架开发的<高校大学生选课系统>的质量属性的实现 对于可用性采取的是错误预防战术,即阻止错误演变为故障:在本系统主要体现在以下两个方面:(1)对于学生登录模块,由于初次登陆,学生 ...

  2. MapReduce教程(一)基于MapReduce框架开发<转>

    1 MapReduce编程 1.1 MapReduce简介 MapReduce是一种编程模型,用于大规模数据集(大于1TB)的并行运算,用于解决海量数据的计算问题. MapReduce分成了两个部分: ...

  3. 基于NopCommerce框架开发的微信小程序UrShop

    Urshop小程序商城 介绍 UrShop小程序商城 2.0发布啦,发布地址https://gitee.com/urselect/urshop UrShop 根据NopCommerce框架开发的,基于 ...

  4. java基于ssm框架开发的公交查询系统源码公交系统源码公交路线查询项目有论文

    简介 java基于ssm的公交路线查询系统,用户可以查询公交站点公交车路线以及公交换乘方案,还可以查看公交车路线地图,以及该站点所有的公交车路线. 演示视频: https://www.ixigua.c ...

  5. 基于ssh框架开发的购物系统的质量属性

    根据前面的博客,我们已经大致了解了ssh架构开发整体概念:Struts是一个实现了MVC模式的经典的框架:Hibernate是轻量级Java EE应用的持久层解决方案,以面向对象的方式提供了持久化类到 ...

  6. java基于ssm框架开发的视频论坛网站源码

    简介 Java基于ssm开发的视频论坛网站,普通用户可以浏览视频搜索视频评论点赞收藏视频,关注用户.还可以浏览新闻,发布帖子到论坛. 演示视频 https://www.bilibili.com/vid ...

  7. 基于uniapp框架开发飞书小程序总结

    前期准备 飞书官方客户端文档:https://open.feishu.cn/document/home/intro 飞书官方工具资源文档:https://open.feishu.cn/document ...

  8. 0009 基于DRF框架开发(02 创建模型)

    上一节介绍了DRF开发的基本流程,共五个步骤: 1 创建模型 2 创建序列化器 3 编写视图 4 配置URL 5 运行测试 本节主要讲解创建模型. 构建学校,教师,学生三个模型,这三个模型之间的关系是 ...

  9. 齐博软件 著名的老牌CMS开源系统 X1.0基于thinkphp开发的高性能免费开源PHP开放平台齐博x1.0基于thinkphp框架开发的高性能免费开源系统 主推圈子 论坛 预定拼团分销商城模块

    齐博X1--标签变量大全 1.网站名称: {$webdb.webname} 2.网址: {$webdb[www_url]} {:get_url('home')} 3.网站SEO关键词: 首页:{$we ...

  10. 随机获取数据库中的某一条数据(基于yii2框架开发)

    注意: 使用PHP函数array_rand()得到的是这个数组中的那个值相对应的下标键值,需要配合原来的数组进行,例如: $rand_keys = array_rand($ids,1); $id = ...

随机推荐

  1. Ubuntu防火墙相关

    查看防火墙当前状态 sudo ufw status 开启防火墙 sudo ufw enable 关闭防火墙 sudo ufw disable 查看防火墙版本 sudo ufw version 默认允许 ...

  2. 【C++ OOP 03 友元】各种友元例子以及如何类外写成员函数

    [友元] 在程序里,有些私有属性 也想让类外特殊的一些函数或者类进行访问,就需要用到友元的技术 友元的目的就是让一个函数或者类 访问另一个类中私有成员 友元的关键字为 friend 友元的三种实现 全 ...

  3. 在Ubuntu搭建DHCP服务器

    一.提供DHCP的服务器,自己必须有固定的IP地址 不然局域网就乱了,服务器自身启动(比如搭建完DHCP服务后,重新启动了服务器)的时候,DHCP服务器没有IP地址,无法和自己的DHCP服务通信. 在 ...

  4. 【Azure 应用服务】Azure Function在执行Function的时候,如果失败了,是否可以重试呢?

    问题描述 Azure Function在执行Function的时候,如果失败了,是否可以重试呢? 问题解答 Function app默认是不开启重试的,但是可以修改 host.json 文件来定义重试 ...

  5. 树莓派修改根文件系统为f2fs

    目录 前言 操作简述 我的实际操作步骤 1. 准备 2. 查看树莓派分区信息 3. 备份根分区 4. 格式化树莓派TF卡根分区为f2fs文件系统 5.恢复备份 前言 在TF卡.固态硬盘之类的nand存 ...

  6. 【转载】Java并发之AQS详解

    一.概述 谈到并发,不得不谈ReentrantLock:而谈到ReentrantLock,不得不谈AbstractQueuedSynchronizer(AQS)! 类如其名,抽象的队列式的同步器,AQ ...

  7. C++ //常用查找算法 find_if

    1 //常用查找算法 find_if 2 #include<iostream> 3 #include<string> 4 #include<vector> 5 #i ...

  8. Python 初学者容易踩的坑

    哈喽大家好,我是咸鱼. 今天咸鱼列出了一些大家在初学 Python 的时候容易踩的一些坑,看看你有没有中招过. 原文:https://www.bitecode.dev/p/unexpected-pyt ...

  9. npm包(npm install --legacy-bundling) 通过npm-pack-all 打包tgz,放到内网(不联网)nexus发布(npm publish)

    npm包(npm install --legacy-bundling) 通过npm-pack-all 打包tgz,放到内网(不联网)nexus发布(npm publish) 需求 内网不联网,安装指定 ...

  10. node开发命令行脚本 / commander

    1. 脚本第一行添加 #!/usr/bin/env node // index.js #!/usr/bin/env node console.log('hello world') 2. package ...