The foreword 0.

Recently there is a need to achieve gaussian blur image, the problem comes, how to do: Java algorithm, control bitmap to implement gaussian algorithm 2. Renderscript 3. NDK 4. Opengl When dealing with large images, OpengL is undoubtedly the most efficient, Java is the worst, NDK and RenderScript are similar. So I’m going to do it with OpengL.

Look at the effect

The original image

After the fuzzy

1. Gaussian algorithm

How to realize fuzzy, first understand what is fuzzy, fuzzy can be understood as an intermediate point pixel to take the average value of adjacent pixels around, which achieves a fuzzy, equivalent to the middle node lost details, to achieve a smooth. The next question is, since each point takes the average of the surrounding pixels, how should the weight be allocated? If you use a simple average, it’s obviously not very reasonable, because the graph is continuous, the closer the points are, the more distant the points are. Therefore, the weighted average is more reasonable. The closer the distance is, the greater the weight is, and the farther the distance is, the smaller the weight is. So this is the Gaussian function, which is the normal distribution that we’re used to.

Here we need a normal distribution in two dimensions

I don’t know what a Gaussian function is, so go back to your high school textbook.

2. Code implementation

class BlurImageView(context: Context, attributeSet: AttributeSet?) : GLSurfaceView(context, attributeSet)
{
    init
    {
        setEGLContextClientVersion(3)

    }

    fun setImageBitmap(bitmap: Bitmap)
    {
        setRenderer(BlurImageViewRender(context, bitmap))
        renderMode = GLSurfaceView.RENDERMODE_CONTINUOUSLY
    }
}
Copy the code

As you can see, the BlurImageView inherits from the GLSurfaceView, and since using Opengles, the GLSurfaceView integrates with EGL, we don’t need to handle it ourselves. Then look at the BlurImageViewRender

class BlurImageViewRender(private val context: Context, private val bitmap: Bitmap) : GLSurfaceView.Renderer { override fun onSurfaceCreated(gl: GL10? , config: EGLConfig?) { } private funreadSlgl(fileName: String): String
    {
        val buffer = StringBuffer()
        try
        {
            val inReader = BufferedReader(InputStreamReader(context.assets.open(fileName)))
            var item = inReader.readLine()
            while(item ! = null) { buffer.append(item).append("\n")
                item = inReader.readLine()
            }
            inReader.close()
        }
        catch (e: IOException)
        {
            e.printStackTrace()
        }

        returnbuffer.toString() } override fun onSurfaceChanged(gl: GL10? , width: Int, height: Int) { val vertex =readSlgl("vertex.slgl")
        val fragment = readSlgl("fragment.slgl")
        prepare(vertex, fragment, bitmap, width, height)
        //        bitmap.recycle()
        GLES30.glViewport(0, 0, width, height)
    }

    override fun onDrawFrame(gl: GL10?)
    {
        draw()
    }

    companion object
    {
        init
        {
            System.loadLibrary("blurimageview")
        }
    }

    external fun prepare(vertex: String, fragment: String, bitmap: Bitmap, scrWidth: Int, scrHeight: Int)

    external fun draw()
}
Copy the code

Draw is used by OpengL to draw the corresponding vertex data, such as prepare and draw. The key to the whole code is the fragment shader. First, according to the analysis in the previous section, to achieve Gaussian fuzzy filtering, we need a two-dimensional square as the weight and obtain it from the two-dimensional Gaussian curve equation. The problem with this process, however, is that it can quickly consume a lot of performance. Taking a 32×32 fuzzy kernel as an example, we had to sample each fragment from a texture 1024 times!

Fortunately, the Gauss equation has a very clever property that allows us to decompose a two-dimensional equation into two smaller equations: one describing horizontal weights and the other describing vertical weights. We first apply horizontal blur over the entire texture with equal weight, then apply vertical blur over the changed texture. With this feature, the result is the same, but with incredible performance savings, because we now only need to do 32+32 samples instead of 1024! This is called two-step Gaussian blur.

#version 300 es
precision mediump float;
in vec2 textureCoord;
uniform sampler2D sampler;
out vec4 fragColor;
uniform bool isVertical;
void main() {vec2 tex_offset =vec2(1.0/300.0,1.0/300.0); vec4 orColor=texture(sampler,textureCoord);float orAlpha=orColor.a;
   float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
   vec3 color=orColor.rgb*weight[0];
   if(! isVertical) {for(int i=1; i<5; i++) { color+=texture(sampler,textureCoord+vec2(tex_offset.x *float(I), 0.0)). The RGB * weight [I]; color+=texture(sampler,textureCoord-vec2(tex_offset.x *float(I), 0.0)). The RGB * weight [I]; }}else
   {
      for(int i=1; i<5; I++) + = {color texture (sampler, textureCoord + vec2 (0.0, tex_offset. * yfloat(i))).rgb*weight[i]; Color + = texture (sampler, textureCoord - vec2 (0.0, tex_offset. * yfloat(i))).rgb*weight[i];
      }
   }
   fragColor=vec4(color,orAlpha);
}
Copy the code

Here we take a 9*9 Gaussian kernel, isVertical is used to determine whether vertical blur or horizontal blur. Let’s start with horizontal blurring

for(int i=1; i<5; i++) { color+=texture(sampler,textureCoord+vec2(tex_offset.x *float(I), 0.0)). The RGB * weight [I]; color+=texture(sampler,textureCoord-vec2(tex_offset.x *float(I), 0.0)). The RGB * weight [I]; }Copy the code

We multiply the pixels of the position offset by the pixels of the current position by the corresponding weights, and then add them together to average them, as described in the previous section. The vertical blur is the same as the horizontal blur in that the offset is relative to the y coordinate.

void prepareFrameBuffer(int width, int height) {

    glGenFramebuffers(2, FBUFFERS);
    glGenTextures(2, FBUFFERTEXTURE);
    for (int i = 0; i < 2; i++) {
        glBindFramebuffer(GL_FRAMEBUFFER, FBUFFERS[i]);
        glBindTexture(GL_TEXTURE_2D, FBUFFERTEXTURE[i]);
        glTexImage2D(
                GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL
        );
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
        glGenerateMipmap(GL_TEXTURE_2D);
        glFramebufferTexture2D(
                GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, FBUFFERTEXTURE[i], 0
        );
        if(glCheckFramebufferStatus(GL_FRAMEBUFFER) ! = GL_FRAMEBUFFER_COMPLETE) { LOGE("frame buffer not completed"); }}}Copy the code

Two frame buffers are created, one for horizontal Gaussian blur and one for vertical Gaussian blur. Now look at the draw method

JNIEXPORT void JNICALL
Java_com_skateboard_blurimageview_BlurImageViewRender_draw(JNIEnv *env, jobject thiz) {
    int isVertical = 0;
    bool isFirst = true;
    glUseProgram(program);
    for (int i = 0; i < 12; i++) {
        glBindFramebuffer(GL_FRAMEBUFFER, FBUFFERS[isVertical]);
        int isVerticalLocation = glGetUniformLocation(program, "isVertical");
        glUniform1i(isVerticalLocation, isVertical);
        if (isFirst) {
            glBindTexture(GL_TEXTURE_2D, texture);
            isFirst = false;
        } else {
            glBindTexture(GL_TEXTURE_2D, FBUFFERTEXTURE[!isVertical]);
        }
        glBindVertexArray(VAO);
        int modelLocation = glGetUniformLocation(program, "model"); GLM: : mat4 modelMatrix = GLM: : mat4 (1.0 f); ModelMatrix = GLM ::rotate(modelMatrix, GLM ::radians(180.0f), GLM ::vec3(0.0, 0.0, 1.0)); glUniformMatrix4fv(modelLocation, 1, GL_FALSE, &modelMatrix[0][0]); glDrawArrays(GL_TRIANGLES, 0, 6); isVertical = ! isVertical; } glBindFramebuffer(GL_FRAMEBUFFER, 0); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); GlClearColor (1.0, 1.0, 1.0, 1.0); glEnable(GL_DEPTH_TEST); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBindVertexArray(VAO);setMatrix();
    glBindTexture(GL_TEXTURE_2D, FBUFFERTEXTURE[0]);
    glDrawArrays(GL_TRIANGLES, 0, 6);
    glBindVertexArray(0);
}
Copy the code

It can be found that there have been a total of 12 Gaussian blurring (6 horizontal and 6 vertical), the more times the blurring degree increases. Other related code is the basis of Opengles, such as generate programs, generate textures, etc., not to mention.

3. The last

Finally, the source address github is attached

Follow my official account