Camera2 教程 2:图片动态切换滤镜
2020-03-22 本文已影响0人
古风子

章节目录:
1 直接替换流程
2 动态替换滤镜效果
2.1 JGPUImage用于封装滤镜类和render操作
2.2 动态切换滤镜类
2.2.1 将新的滤镜对象传入render类中,创建一个队列任务
1. 销毁老的滤镜类,并不销毁对象,只是重置初始化状态和删除着色器程序,避免对象频繁创建
2. 初始化新的滤镜类,主要完成着色器程序的加载和着色器中各个属性的引用字段创建
3. 使用新的滤镜程序
4. 更新view显示的大小
2.2.2 requestRender请求刷新,执行onDrawFrame
2.3 JGPUImage封装GlSurfaceView操作
2.4 Activity调用
3 动态替换图片
3.1 删除旧的图片纹理id
3.2 设置新的图片资源
3.3 onDrawFrame
3.3 Activity调用方法
4. 效果图
5. 代码工程
在上一篇文章中,我们是使用默认的着色器文件处理图片的,显示的是正常的图片效果;本章节,我们讲下,怎样动态切换着色器处理效果
1 直接替换流程
正常效果的滤镜处理文件
- JGPUImageFilter
public class JGPUImageFilter {
public static final String NO_FILTER_VERTEX_SHADER = "" +
"attribute vec4 position;\n" +
"attribute vec4 inputTextureCoordinate;\n" +
" \n" +
"varying vec2 textureCoordinate;\n" +
" \n" +
"void main()\n" +
"{\n" +
" gl_Position = position;\n" +
" textureCoordinate = inputTextureCoordinate.xy;\n" +
"}";
public static final String NO_FILTER_FRAGMENT_SHADER = "" +
"varying highp vec2 textureCoordinate;\n" +
" \n" +
"uniform sampler2D inputImageTexture;\n" +
" \n" +
"void main()\n" +
"{\n" +
" gl_FragColor = texture2D(inputImageTexture, textureCoordinate);\n" +
"}";
...
}
我们继承JGPUImageFilter 实现一个黑白滤镜作色器效果
- JGPUImageGrayFilter
public class JGPUImageGrayFilter extends JGPUImageFilter {
private static final String NO_FILTER_FRAGMENT_SHADER =
"precision mediump float;\n"
+ "uniform sampler2D inputImageTexture;\n"
+ "varying vec2 textureCoordinate;\n"
+ "const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);\n"
+ "void main() {\n"
+ "lowp vec4 textureColor = texture2D(inputImageTexture, textureCoordinate);\n"
+ "float luminance = dot(textureColor.rgb, W);\n"
+ "gl_FragColor = vec4(vec3(luminance), textureColor.w);\n"
+ "}\n";
public JGPUImageGrayFilter() {
this(NO_FILTER_VERTEX_SHADER, NO_FILTER_FRAGMENT_SHADER);
}
public JGPUImageGrayFilter(final String vertexShader, final String fragmentShader) {
super(vertexShader,fragmentShader);
}
}
Activity中替换滤镜类对象
public class PicFilterBaseActivity extends BaseActivity{
public class PicFilterBaseActivity extends BaseActivity{
setContentView(R.layout.activity_texture);
mGLSurfaceView = findViewById(R.id.surfaceView);
//替换成黑白滤镜
JGPUImageFilter normalFilter = new JGPUImageGrayFilter();
JGPUImageRenderer renderer = new JGPUImageRenderer(normalFilter);
setGLSurfaceViewRender(renderer);
renderer.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.texture));
处理后的效果为:

2 动态替换滤镜效果
实际应用中,我们需要动态替换各种滤镜效果,而不需要将所有流程再重新走一遍;特别是在相机预览滤镜场景,我们不可能没替换一次滤镜,就将整个相机的打开流程重新执行一遍
2.1 JGPUImage用于封装滤镜类和render操作
public JGPUImage(final Context context) {
if (!supportsOpenGLES2(context)) {
throw new IllegalStateException("OpenGL ES 2.0 is not supported on this phone.");
}
this.context = context;
filter = new JGPUImageFilter();
renderer = new JGPUImageRenderer(filter);
}
2.2 动态切换滤镜类
//JGPUImage.java
public void setFilter(final JGPUImageFilter filter) {
this.filter = filter;
renderer.setFilter(this.filter);
requestRender();
}
该方法会动态替换JGPUImageRenderer中的filter对象,并在刷新的时候,对新传入的filter重新初始化以获取新的着色器程序id和各个属性的引用
2.2.1 将新的滤镜对象传入render类中,创建一个队列任务
这里只是创建一个队列任务,当Render的onDrawFrame方法被执行时,具体逻辑才会执行;
public void setFilter(final JGPUImageFilter filter) {
runOnDraw(new Runnable() {
@Override
public void run() {
final JGPUImageFilter oldFilter = JGPUImageRenderer.this.filter;
JGPUImageRenderer.this.filter = filter;
if (oldFilter != null) {
oldFilter.destroy();//destory
}
JGPUImageRenderer.this.filter.ifNeedInit();
GLES20.glUseProgram(JGPUImageRenderer.this.filter.getProgram());
JGPUImageRenderer.this.filter.onOutputSizeChanged(outputWidth, outputHeight);
}
});
}
以上队列任务执行的时候,也就onDrawFram调用时的主要逻辑步骤为:
1. 销毁老的滤镜类,并不销毁对象,只是重置初始化状态和删除着色器程序,避免对象频繁创建
//JGPUImageFilter基类方法
public final void destroy() {
isInitialized = false;
GLES20.glDeleteProgram(glProgId);
onDestroy();//模板设计模式,固定算法框架,由基类具体实现自己的策略
}
2. 初始化新的滤镜类,主要完成着色器程序的加载和着色器中各个属性的引用字段创建
//JGPUImageFilter基类方法
public void ifNeedInit() {
if (!isInitialized) init();//模板设计模式,固定算法框架,由基类具体实现自己的策略
}
private final void init() {
onInit();
onInitialized();
}
public void onInit() {
//根据传入的作色器字符串,创建,链接,编译对象,返回程序对象id
glProgId = OpenGlUtils.loadProgram(vertexShader, fragmentShader);
//获取程序中顶点位置属性引用
glAttribPosition = GLES20.glGetAttribLocation(glProgId, "position");
//获取程序中纹理内容属性引用
glUniformTexture = GLES20.glGetUniformLocation(glProgId, "inputImageTexture");
//获取程序中顶点纹理坐标属性引用
glAttribTextureCoordinate = GLES20.glGetAttribLocation(glProgId, "inputTextureCoordinate");
//标明指初始化一次
isInitialized = true;
}
public void onInitialized() {
}
3. 使用新的滤镜程序
GLES20.glUseProgram(mFilter.getProgram());
4. 更新view显示的大小
mFilter.onOutputSizeChanged(outputWidth, outputHeight);
2.2.2 requestRender请求刷新,执行onDrawFrame
//JGPUImage.java
requestRender();
public void requestRender() {
if (glSurfaceView != null) {
glSurfaceView.requestRender();
}
}
//JGPUImageRenderer
@Override
public void onDrawFrame(final GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
//调用队列任务
runAll(runOnDraw);
//调用当前filter类渲染图片
filter.onDraw(glTextureId, glCubeBuffer, glTextureBuffer);
}
glSurfaceView通过以下方法传入
2.3 JGPUImage封装GlSurfaceView操作
public void setGLSurfaceView(final GLSurfaceView view) {
glSurfaceView = view;
glSurfaceView.setEGLContextClientVersion(2);
glSurfaceView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
glSurfaceView.getHolder().setFormat(PixelFormat.RGBA_8888);
glSurfaceView.setRenderer(renderer);
glSurfaceView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
glSurfaceView.requestRender();
}
2.4 Activity调用
以上流程完成后,我们可以通过setFilter方法,动态切换应用到图片的滤镜
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_texture);
mGLSurfaceView = findViewById(R.id.surfaceView);
//默認使用正常的濾鏡效果
jgpuImage = new GPUImage(this);
jgpuImage.setGLSurfaceView(mGLSurfaceView);
jgpuImage.setImage(BitmapFactory.decodeResource(getResources(), R.drawable.texture));
findViewById(R.id.switch_filter).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
jgpuImage.setFilter(new GPUImageGrayscaleFilter());
}
});
}
3 动态替换图片
动态切换滤镜主要使用对单个图片适用不同滤镜效果的场景;实际应用中,还需要实现对图片动态切换的场景
3.1 删除旧的图片纹理id
//JGPUImage.java
public void deleteImage() {
renderer.deleteImage();
currentBitmap = null;
requestRender();
}
//将释放老的图片资源id代码,在Render类找中放到一个onDrawFrame执行的队列任务中
public void deleteImage() {
runOnDraw(new Runnable() {
@Override
public void run() {
GLES20.glDeleteTextures(1, new int[]{
glTextureId
}, 0);
glTextureId = NO_IMAGE;
}
});
}
3.2 设置新的图片资源
//JGPUImage.java
public void setImage(final Bitmap bitmap) {
currentBitmap = bitmap;
renderer.setImageBitmap(bitmap, false);
requestRender();
}
//将创建新的图片资源id代码,在Render类找中放到一个onDrawFrame执行的队列任务中
public void setImageBitmap(final Bitmap bitmap, final boolean recycle) {
if (bitmap == null) {
return;
}
runOnDraw(new Runnable() {
@Override
public void run() {
Bitmap resizedBitmap = null;
//求余数,OPENGL处理的图片大小必须是2的整数次方
if (bitmap.getWidth() % 2 == 1) {
resizedBitmap = Bitmap.createBitmap(bitmap.getWidth() + 1, bitmap.getHeight(),
Bitmap.Config.ARGB_8888);
Canvas can = new Canvas(resizedBitmap);
can.drawARGB(0x00, 0x00, 0x00, 0x00);
can.drawBitmap(bitmap, 0, 0, null);
addedPadding = 1;
} else {
addedPadding = 0;
}
glTextureId = OpenGlUtils.loadTexture(
resizedBitmap != null ? resizedBitmap : bitmap, glTextureId, recycle);
if (resizedBitmap != null) {
resizedBitmap.recycle();
}
imageWidth = bitmap.getWidth();
imageHeight = bitmap.getHeight();
adjustImageScaling();
}
});
}
以上代码调用requestRender();刷新帧数据后,调用onDrawFrame方法
3.3 onDrawFrame
@Override
public void onDrawFrame(final GL10 gl) {
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
//依次调用删除老的纹理id和创建新额纹理id的代码
runAll(runOnDraw);
//将新的纹理id传入到着色器中进行处理
filter.onDraw(glTextureId, glCubeBuffer, glTextureBuffer);
}
3.4 Activity调用方法
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_texture);
mGLSurfaceView = findViewById(R.id.surfaceView);
//默認使用正常的濾鏡效果
jgpuImage = new GPUImage(this);
jgpuImage.setGLSurfaceView(mGLSurfaceView);
jgpuImage.setImage(BitmapFactory.decodeResource(getResources(), R.drawable.texture));
findViewById(R.id.switch_image).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//删除老的纹理
jgpuImage.deleteImage();
//设置和获取新的纹理id
jgpuImage.setImage(BitmapFactory.decodeResource(getResources(), R.drawable.texture2));
}
});
}
4. 效果图
经过以上流程后,我们看下最终的程序效果图:

5 代码工程
代码工程位置参考以下开源文件的PicFilterBaseActivity.java文件