Android实时滤镜实现

目录

1、概述

2、模块原理

2.1、 播放器

2.2、 显示渲染

2.3、 颜色滤镜

3、总结

 

1、概述

本文介绍一个在Android上面实现的一个实时调整滤镜参数的播放器例子。市面上一些美颜,画质增强等播放器的大致原理都是如此。通过调整图1中的三个参数就可以实时看到画面颜色、明暗、艳丽程度的变化。

图1

 

 

2、模块原理

这个例子可以分为三个模块:解码播放、显示渲染、颜色参数调整转化。本文重点是颜色滤镜参数的调整。

2.1、 播放器

这里采用的是系统的媒体播放器MediapPayer播放一个放在工程raw资源路径下的MP4文件。播放代码如下:

 private void setupPlayer() {
        try {
            mMediaPlayer = MediaPlayer.create(this, R.raw.testfile);
            mMediaPlayer.setSurface(mSurface);
            mMediaPlayer.setLooping(true);
        } catch (Exception e) {
            e.printStackTrace();
        }

        mMediaPlayer.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
            @Override
            public void onPrepared(MediaPlayer mediaPlayer) {
                mMediaPlayer.start();
            }
        });

    }

这个播放器的输入是testfile.mp4,解码出来的画面会通过Surface传给渲染层。

 

2.2、 显示渲染

这里渲染采用的是OpenGl ES 2.0,由于跨平台,兼容性好被广泛应用。Android对OpenGL ES支持非常好,可以在JNI中开发也有对应的JAVA接口,本文采用的是JAVA。

下面是使用OpenGL ES的步骤:

1、在layout文件中配置一个GLSurfaceView

    <android.opengl.GLSurfaceView
        android:id="@+id/video_view"
        android:layout_width="match_parent"
        android:layout_height="320dp" />

2、在Activity文件中设置GlSurfaceView。这里主要是设置Render回调,onSurfaveCreate是初始化一些值,onDrawFrame是画每一帧。

mVideoView = findViewById(R.id.video_view);
mVideoView.setEGLContextClientVersion(2);
mVideoView.setRenderer(new GLSurfaceView.Renderer() {
            @Override
            public void onSurfaceCreated(GL10 gl10, EGLConfig eglConfig) {
                mReactShape = new RectShape();
                int textureId = GLUtil.generateOESTexture();
                mSurfaceTexture = new SurfaceTexture(textureId);
                mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
                    @Override
                    public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                        mFrameAvailable = true;
                    }
                });
                mSurface = new Surface(mSurfaceTexture);
                mReactShape.setTextureId(textureId);
                setupPlayer();
            }

            @Override
            public void onSurfaceChanged(GL10 gl10, int i, int i1) {

            }

            @Override
            public void onDrawFrame(GL10 gl10) {
                if (mFrameAvailable) {
                    mSurfaceTexture.updateTexImage();
                    mFrameAvailable = false;
                }

                float[] colorFilter = mColorFilterMatrixUtil.getColorFilterArray16();
                mReactShape.setColorFilterArray(colorFilter);
                mReactShape.draw();
            }
        });

3、在第二步是配置渲染的线程(GLSurfaceView 有自己独立的线程),具体内容显示还需要一个载体。这里采用的是一个矩形的ReactShape来画每一帧。

package com.test.videocolorfilter;

import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.opengl.Matrix;
import android.util.Log;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;


public class RectShape {

    private float width = 2;
    private float height = 2;

    private int mTextureId = 0;
    private int mProgram = -1;
    private int mPositionHandle;
    private int mTexCoordHandle;
    private int mMVPHandle;
    private int mColorFilterHandle;
    private FloatBuffer mVertices, mTexCoord;
    private float[] mModelProjection = new float[16];
    private float[] mColorFilterArray = new float[16];

    private final static String VERTEX_SHADER =
            "uniform mat4 uMVPMatrix;\n" +
                    "attribute vec4 a_Position;\n" +
                    "attribute vec2 aTexCoor;\n" +
                    "varying vec2 vTextureCoord;\n" +
                    "uniform float uAngle;\n" +
                    "void main(){\n" +
                    "    gl_Position = uMVPMatrix * a_Position;\n" +
                    "    vTextureCoord = aTexCoor;\n" +
                    "}\n";
    private final static String FRAGMENT_SHADER =
            "#extension GL_OES_EGL_image_external : require\n" +
                    "precision highp float;\n" +
                    "varying vec2 vTextureCoord;\n" +
                    "uniform samplerExternalOES uTexture;\n" +
                    "uniform mat4 uColorFilterMatrix;\n" +
                    "void main() {\n" +
                    "     vec4 val = texture2D(uTexture, vTextureCoord); \n" +
                    "     gl_FragColor = val*uColorFilterMatrix; \n" +
                    "}\n";

    public RectShape() {
        init();
    }


    protected void init() {

        Matrix.setIdentityM(mModelProjection, 0);
        Matrix.setIdentityM(mColorFilterArray, 0);

        float[] vertices = new float[]{
                -width / 2, -height / 2, 0,
                width / 2, height / 2, 0,
                -width / 2, height / 2, 0,
                width / 2, height / 2, 0,
                -width / 2, -height / 2, 0,
                width / 2, -height / 2, 0,
        };

        ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length * 4);
        vbb.order(ByteOrder.nativeOrder());
        mVertices = vbb.asFloatBuffer();
        mVertices.put(vertices);
        mVertices.position(0);

        // create program
        mProgram = GLUtil.createProgram(VERTEX_SHADER, FRAGMENT_SHADER);
        mPositionHandle = GLES20.glGetAttribLocation(mProgram, "a_Position");
        mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoor");
        mMVPHandle = GLES20.glGetUniformLocation(mProgram, "uMVPMatrix");
        mColorFilterHandle = GLES20.glGetUniformLocation(mProgram, "uColorFilterMatrix");
        //setup buffers for the texture 2D coordinates
        float[] texCoord = new float[]{
                0.0f, 1.0f,
                1.0f, 0.0f,
                0.0f, 0.0f,
                1.0f, 0.0f,
                0.0f, 1.0f,
                1.0f, 1.0f,
        };

        ByteBuffer cbb = ByteBuffer.allocateDirect(texCoord.length * 4);
        cbb.order(ByteOrder.nativeOrder()); //set the byte order
        mTexCoord = cbb.asFloatBuffer(); //convert to byte buffer
        mTexCoord.put(texCoord); //load data to byte buffer
        mTexCoord.position(0); //set the start point

    }

    public void setTextureId(int textureId) {
        mTextureId = textureId;
    }

    public void draw() {
        draw(mTextureId);
    }

    public void draw(int textureId) {
//        Log.d("RectShape", "draw "+ textureId);
        GLES20.glUseProgram(mProgram);
        GLES20.glUniformMatrix4fv(mMVPHandle, 1, false, mModelProjection, 0);
        GLES20.glUniformMatrix4fv(mColorFilterHandle, 1, false, mColorFilterArray, 0);

        if (textureId > 0) {
            // render texture
            GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
            GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, textureId);
        }
        // draw triangles
        GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, 0, mVertices);
        GLES20.glEnableVertexAttribArray(mPositionHandle);
        GLES20.glVertexAttribPointer(mTexCoordHandle, 2, GLES20.GL_FLOAT, false, 0, mTexCoord);
        GLES20.glEnableVertexAttribArray(mTexCoordHandle);
        GLES20.glDrawArrays(GLES20.GL_TRIANGLES, 0, mVertices.capacity() / 3);

    }

    public void setColorFilterArray(float filter[]) {
        System.arraycopy(filter, 0, mColorFilterArray, 0, 16);
    }


    public void release() {
        if (mProgram >= 0) {
            GLES20.glDeleteProgram(mProgram);
            mProgram = -1;
        }
    }

}

这个类最主要的是部分是两个shader和draw()函数。

shader采用的是GLSL语言,在openGL ES 2.0以后开始使用,分为顶点着色器和片元着色器;顶点着色器指定了顶点转化关系和几何姿态的调整以及投影关系;片元着色器指定的是像素色彩以及光照等变化,这部分通过编译最终执行在GPU中。

draw()函数是将纹理,几何坐标,纹理坐标、姿态矩阵、颜色矩阵等值传给shader。这些值可以每一帧都不一样。

 

2.3、 颜色滤镜

看道这篇文章的同学应该都比较喜欢或熟悉用RGB来描述图像,RGB三基色描述在图像数字化确实比较方便。但是对于眼睛或者设计师调整却并不方便。在滤镜调整过程中大家其实更偏向另外一种颜色模型HSL(hue,saturation,lightness)下图是HSL的原理模型:

图 2

Android对HSL调整有很好的支持,在SDK的android.graphic包中有一个ColorMatrix类。这个类时管理一个5x4的颜色矩阵,支持色相、饱和度、明度调整接口。ColorMatrix T表示如下:

T=\begin{bmatrix} a&b &c &d,&e \\ f &g&h&i &j\\ k&l &m& n& o \\p &q& r &s &t \end{bmatrix}       

颜色C表示如下:

        C=\begin{vmatrix}R \\ G \\ B \\ A \\1 \end{vmatrix}

新颜色C`

 C` = \begin{vmatrix}R` \\ G` \\ B` \\ A` \\1 \end{vmatrix} = T*C

R' = a*R + b*G + c*B + d*A + e;
           G' = f*R + g*G + h*B + i*A + j;
           B' = k*R + l*G + m*B + n*A + o;
           A' = p*R + q*G + r*B + s*A + t;

从上图模型可以看出,hue(色相)是调整颜色属性,直观地说就是让颜色偏绿点还是偏红点,效果如下图:

图 3-色相调整效果

 

saturation(饱和度)指的是色彩纯度。是色彩的构成要素之一。纯度越高,表现越鲜明,纯度较低,表现则较黯淡。效果如下图;

图4-饱和度调整效果

lightness(明度)是调整颜色的能量程度,直白说就是黑还是白,调整效果如下图:

图5-明度调整效果

 

3、总结

  • 本文的滤镜只是采用的颜色调整,还没有实现锐化、模糊、马赛克等像素位置变化的效果。
  • 播放器采用的最简单的本地MediaPlayer对于一些直播可能需要替换支持跟多协议的播放器如ijkplayer。
  • 本文的配套的工程github地址https://github.com/liyang-hello/VideoColorFilter,如有兴趣可以下载。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值