基于ArkUI框架开发——图片模糊处理的实现

news2025/1/15 17:33:02

原文:基于ArkUI框架开发——图片模糊处理的实现,点击链接查看更多技术内容。

现在市面上有很多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 (= 0; y < imageHeight; y++) {
  for (= 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<void>;

writeBufferToPixels(src: ArrayBuffer, callback: AsyncCallback<void>): 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.= 0;
  entry.= b;
  entry.= g;
  entry.= r;
  entry.= 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 (= 0; i < 256 * divsum; i++) {
  dv[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 (= 0; y < h; y++) {
  rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
  for (= -radius; i <= radius; i++) {
    p = pix[yi + Math.min(wm, Math.max(i, 0))];
    sir = stack[+ radius];
    sir[0] = (& 0xff0000) >> 16;
    sir[1] = (& 0x00ff00) >> 8;
    sir[2] = (& 0x0000ff);
    rbs = r1 - Math.abs(i);
    rsum += sir[0] * rbs;
    gsum += sir[1] * rbs;
    bsum += sir[2] * rbs;
    if (> 0) {
      rinsum += sir[0];
      ginsum += sir[1];
      binsum += sir[2];
    } else {
      routsum += sir[0];
      goutsum += sir[1];
      boutsum += sir[2];
    }
  }
  stackpointer = radius;

  for (= 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 (== 0) {
      vmin[x] = Math.min(+ radius + 1, wm);
    }
    p = pix[yw + vmin[x]];

    sir[0] = (& 0xff0000) >> 16;
    sir[1] = (& 0x00ff00) >> 8;
    sir[2] = (& 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 (= 0; x < w; x++) {
  rinsum = ginsum = binsum = routsum = goutsum = boutsum = rsum = gsum = bsum = 0;
  yp = -radius * w;
  for (= -radius; i <= radius; i++) {
    yi = Math.max(0, yp) + x;

    sir = stack[+ 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 (> 0) {
      rinsum += sir[0];
      ginsum += sir[1];
      binsum += sir[2];
    } else {
      routsum += sir[0];
      goutsum += sir[1];
      boutsum += sir[2];
    }

    if (< hm) {
      yp += w;
    }
  }
  yi = x;
  stackpointer = radius;
  for (= 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 (== 0) {
      vmin[y] = Math.min(+ 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内,文章就介绍到这了,欢迎有兴趣的朋友,可以参考学习下,下面提供具体的项目源码地址。

项目地址:OpenHarmony-TPC/ImageKnife - Gitee.com

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/491536.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

面向万物智联的应用框架的思考和探索(中)

原文&#xff1a;面向万物智联的应用框架的思考和探索&#xff08;中&#xff09;&#xff0c;点击链接查看更多技术内容。 应用框架&#xff0c;是操作系统连接开发者生态&#xff0c;实现用户体验的关键基础设施。其中&#xff0c;开发效率和运行体验是永恒的诉求&#xff0c…

【路径规划】基于麻雀搜索算法的栅格法路径规划 机器人路径规划【Matlab代码#21】

文章目录 1. 原始SSA算法2. 机器人路径规划环境创建3. 路径规划模型建立4. 部分代码展示5. 仿真结果展示6. 资源获取方式 1. 原始SSA算法 2. 机器人路径规划环境创建 对机器人工作空间的进行环境建模是机器人路径规划研究的重要前提。栅格法为环境建模提供了一种简洁有效的方法…

法规标准-GB/T 33577标准解读(2017版)

GB/T 33577是做什么的&#xff1f; GB/T 33577全名为智能交通系统-前方车辆碰撞预警系统(FVCWS)-性能要求和测试步骤&#xff0c;其中主要是对FVCWS系统的功能要求、性能要求及测试步骤进行了介绍。由于ISO 15623-2013内容与本法规内容相同&#xff0c;故可沿用此法规内容 FV…

【谷粒商城之消息队列RabbitMQ】

本笔记内容为尚硅谷谷粒商城消息队列RabbitMQ部分 目录 一、概述 二、简介 三、Docker安装RabbitMQ 四、Springboot整合RabbitMQ 1、引入spring-boot-starter-amqp 2、application.yml配置 3、测试RabbitMQ 1. AmqpAdmin-管理组件 2.RabbitTemplate-消息发送处理组件…

Wikidata实操

1. Wikidata 简介 Wikidata 即维基数据&#xff0c;是维基百科的一个项目。个项目已经在维基百科德国分部开始进行&#xff0c;项目完成之后&#xff0c;将会交给维基百科基金会进行操作和维护。&#xff08;具体百度即可&#xff0c;不多赘述&#xff09; 官网&#xff1a;htt…

操作系统考试复习—第三章 优先级倒置 死锁问题

当前OS广泛采用优先级调度算法和抢占方式&#xff0c;然而在系统中存在着影响进程运行的资源从而可能产生"优先级倒置"现象 具体解释为&#xff1a;在原本的调度算法设计中&#xff0c;高优先级进程可以抢占低优先级的CPU资源&#xff0c;先执行高优先级任务。但是存…

【STM32】在使用STM32Cube.IDE时更改时钟频率后代码跳进异常中断

目录 1、前言2、问题与复现办法3、解决的问题的过程 1、前言 这是在项目中无意发现的问题&#xff0c;其实有同样更复杂的工程可以运行&#xff0c;但是后来发现新建一个简单工程反而运行不了了&#xff0c;但是同样更复杂的工程可以运行说明本来同事原来已经不知道在哪里找到…

Vmware安装Kali

需要准备两个东西&#xff0c;kali镜像和VMware软件 下载kali iso 下载界面有三个可选择的 install是安装版&#xff0c;安装使用&#xff1b; Live版可以直接启动运行&#xff1b; netinstaller是网络安装&#xff0c;需要从网络上下载&#xff0c;文件本身只有引导作用&…

Idea Jrebel 报错:Cannot reactivate, offline seat in use ...

Idea Jrebel 报错&#xff1a;Cannot reactivate, offline seat in use ... 一、问题描述 在使用idea Jrebel续期的时候&#xff0c;修改idea激活服务器地址时&#xff0c;遇到报错&#xff1a;Cannot reactivate, offline seat in use. Click Work online in JRebel configura…

基于aspnet个人博客网站dzkf6606程序

系统使用Visual studio.net2010作为系统开发环境&#xff0c;并采用ASP.NET技术&#xff0c;使用C#语言&#xff0c;以SQL Server为后台数据库。 1&#xff0e;系统登录&#xff1a;系统登录是用户访问系统的路口&#xff0c;设计了系统登录界面&#xff0c;包括用户名、密码和…

探索卡尔曼滤波在位姿估计中的魅力:无人机与自动驾驶的关键技术揭秘

摘要&#xff1a;在本博客中&#xff0c;我们将探讨卡尔曼滤波在位姿估计领域的应用&#xff0c;特别是在无人机和自动驾驶场景中的重要性。我们将详细介绍卡尔曼滤波的原理、优势及其在无人机、自动驾驶等实际案例中的应用。此外&#xff0c;我们还将关注卡尔曼滤波在其他领域…

【服务器数据恢复】同友存储上的虚拟机数据恢复案例

服务器数据恢复环境&#xff1a; 同友存储&#xff0c;底层由数块物理硬盘组建的raid5磁盘阵列&#xff0c;存储池划分若干lun&#xff0c;每个lun下有数台虚拟机。 服务器故障&#xff1a; 未知原因导致存储崩溃&#xff0c;无法启动&#xff0c;虚拟机全部丢失&#xff0c;其…

linux中基础开发工具的使用

1.linux中的软件包管理器 1.1什么是软件包 在Linux下安装软件, 一个通常的办法是下载到程序的源代码, 并进行编译, 得到可执行程序.但是这样太麻烦了, 于是有些人把一些常用的软件提前编译好, 做成软件包(可以理解成windows上的安装程序)放在一个服务器上, 通过包管理器可以很…

软件管理员密码的作用 如何设置软件管理员密码?

在使用夏冰加密软件的过程中&#xff0c;很多软件都是可以设置软件管理员密码的。那么你知道管理员密码有什么用吗&#xff1f;又该如何设置软件管理员密码呢&#xff1f;下面我们来了解一下吧。 软件管理员密码是什么意思&#xff1f; 软件管理员密码就是软件的密码&#xff…

毕业5年,技术越来越好,混的却越来越差...

别人都是越来越好&#xff0c;而我是越来越差&#xff01; 17年&#xff0c;从一个普通的本科毕业&#xff0c;那个时候的我&#xff0c;很迷茫&#xff0c;简历上的求职岗位都不知道写什么&#xff0c;因为家里是农村的&#xff0c;朴实的父母也帮不上什么忙&#xff0c;关于…

KDBR-IV变压器空负载短路损耗测试仪

一、产品概述 本产品是我公司针对不良电力用户偷逃基本电费、私自增容问题而研发设计的仪器&#xff0c;用于变压器容量、空载、负载等特性参数测量的高精密仪器。本仪器为多功能测量仪器&#xff0c;相当于往常两种测试仪器&#xff1a;即变压器容量测试仪变压器特性参数测试仪…

【因子挖掘】遗传规划概述

在多因子选股的框架下&#xff0c;因子的产生通常有两条途径&#xff1a; 先有逻辑&#xff0c;后有公式&#xff1a;根据经济学逻辑、历史经验、直觉进行人工构造一些因子&#xff1b; 例如&#xff1a;动量&#xff08;Momentum&#xff09;因子&#xff1a;当最近的股价呈现…

Cadence Allegro 布局操作Move命令的应用

在布局的时候&#xff0c;常常需要对一些元素去进行移动位置以方便进行设计。 1、执行菜单命令Edit-Move&#xff0c;此时PCB界面的左下角会显示Move&#xff0c;就表示正在执行移动命令&#xff0c;如图1所示。 图1 移动命令 2、在PCB界面右边的Find面板中所选择需要进行移动…

Charles抓包工具使用

一、Charles的安装与激活 安装 官方地址&#xff1a;https://www.charlesproxy.com/ 根据自己系统安装最新版本即可 安装后可直接打开使用 激活 打开Charles -> 【Help】 -> 【Register Charles】 -> 输入 Registered Name &#xff1a; https://zhile.io Lic…

智能座舱的“宏大蓝图”和“残酷现实”

配图来自Canva可画 2023年上海车展各大车企发布新车、新配置和新战略好不热闹&#xff0c;“智能驾驶”、“智能座舱”等关键词频频出现&#xff0c;智能化已然成为车企技术比拼的关键。 Unity中国发布最新智能座舱解决方案&#xff0c;可为车企提供成熟、可量产落地的HMI&…