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
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的原理模型:
图 2Android对HSL调整有很好的支持,在SDK的android.graphic包中有一个ColorMatrix类。这个类时管理一个5x4的颜色矩阵,支持色相、饱和度、明度调整接口。ColorMatrix T表示如下:
颜色C表示如下:
新颜色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,如有兴趣可以下载。
更多相关文章
- Android(安卓)中自定义组件例子一(中级)
- Android(安卓)studio 自定义logcat各种信息输出颜色
- [Android]如何在Android(安卓)studio中增加一个selector资源(用于
- android 显示16色的图片:输入用颜色矩阵,显示对应的16色位图
- Button 或 ImageButton 背景设为 透明 或半透明
- TextView颜色
- Android(安卓)ProgressBar圆形进度条颜色设置
- Android(安卓)自定义码表图
- Android游戏设计中的音频控制——音量调整