原文: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_…
发表回复