原文: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<void>;
readPixelsToBuffer(dst: ArrayBuffer, callback: AsyncCallback<void>): 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<PixelEntry> = new Array()
var pix: Array<number> = 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<number>;
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_…