Android 相机库CameraView源码解析 (三) : 滤镜相关类说明

1. 前言

这段时间,在使用 natario1/CameraView 来实现带滤镜的预览拍照录像功能。
由于CameraView封装的比较到位,在项目前期,的确为我们节省了不少时间。
但随着项目持续深入,对于CameraView的使用进入深水区,逐渐出现满足不了我们需求的情况。
Github中的issues中,有些BUG作者一直没有修复。

那要怎么办呢 ? 项目迫切地需要实现相关功能,只能自己硬着头皮去看它的源码,去解决这些问题。
上篇文章,我们对拍照的流程有了大致的了解,这篇文章,我们来看下滤镜相关的类,为后面带滤镜拍照的源码解析做下铺垫。

以下源码解析基于CameraView 2.7.2

implementation("com.otaliastudios:cameraview:2.7.2")

为了在博客上更好的展示,本文贴出的代码进行了部分精简

在这里插入图片描述

2. 如何设置滤镜

CameraView中,通过setFilter(Filter filter)来设置滤镜。

//初始化亮度滤镜
val brightnessFilter = BrightnessFilter()
//设置亮度值
brightnessFilter.setBrightness(1.5F)
//设置滤镜
cameraView.setFilter(brightnessFilter)

3. Filter

Filter是一个接口,定义了获取顶点着色器获取片元着色器当初始化时当销毁时当绘制时设置尺寸拷贝滤镜

public interface Filter {
    
    

    /**
     * 获取顶点着色器
     */
    String getVertexShader();

    /**
     * 获取片元着色器
     */
    String getFragmentShader();

    /**
     * 初始化时调用
     */
    void onCreate(int programHandle);

    /**
     * 销毁时调用
     * 
     */
    void onDestroy();

    /**
     * 当绘制的时候
     */
    void draw(long timestampUs, float[] transformMatrix);

    /**
     * 设置尺寸
     */
    void setSize(int width, int height);

    /**
     * 复制滤镜
     */
    Filter copy();
}

4. BaseFilter

BaseFilter是一个抽象类,实现了Filter接口,BaseFilter实现了默认的顶点着色器和片元着色器,在onCreate的时候,创建了具体执行OpenGL APIGlTextureProgramcopy的时候,会根据OneParameterFilterTwoParameterFilter接口,复制Filter

public abstract class BaseFilter implements Filter {
    
    
	//...省略了具体代码...
}

接下来来看BaseFilter的具体代码

4.1 默认的顶点着色器和片元着色器

实现了默认的顶点着色器和片元着色器

protected final static String DEFAULT_VERTEX_POSITION_NAME = "aPosition";
protected final static String DEFAULT_VERTEX_TEXTURE_COORDINATE_NAME = "aTextureCoord";
protected final static String DEFAULT_VERTEX_MVP_MATRIX_NAME = "uMVPMatrix";
protected final static String DEFAULT_VERTEX_TRANSFORM_MATRIX_NAME = "uTexMatrix";
protected final static String DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME = "vTextureCoord";

private static String createDefaultVertexShader(
        @NonNull String vertexPositionName,
        @NonNull String vertexTextureCoordinateName,
        @NonNull String vertexModelViewProjectionMatrixName,
        @NonNull String vertexTransformMatrixName,
        @NonNull String fragmentTextureCoordinateName) {
    
    
    return "uniform mat4 "+vertexModelViewProjectionMatrixName+";\n"
            + "uniform mat4 "+vertexTransformMatrixName+";\n"
            + "attribute vec4 "+vertexPositionName+";\n"
            + "attribute vec4 "+vertexTextureCoordinateName+";\n"
            + "varying vec2 "+fragmentTextureCoordinateName+";\n"
            + "void main() {\n"
            + "    gl_Position = " +vertexModelViewProjectionMatrixName+" * "
            + vertexPositionName+";\n"
            + "    "+fragmentTextureCoordinateName+" = ("+vertexTransformMatrixName+" * "
            + vertexTextureCoordinateName+").xy;\n"
            + "}\n";
}

private static String createDefaultFragmentShader(
        @NonNull String fragmentTextureCoordinateName) {
    
    
    return "#extension GL_OES_EGL_image_external : require\n"
            + "precision mediump float;\n"
            + "varying vec2 "+fragmentTextureCoordinateName+";\n"
            + "uniform samplerExternalOES sTexture;\n"
            + "void main() {\n"
            + "  gl_FragColor = texture2D(sTexture, "+fragmentTextureCoordinateName+");\n"
            + "}\n";
}

4.2 创建GlTextureProgram

GlTextureProgram是对OpenGL纹理绘制的具体实现,这里传入了顶点着色器和片元着色器等,创建了GlTextureProgram

@Override
public void onCreate(int programHandle) {
    
    
    program = new GlTextureProgram(programHandle,
            vertexPositionName,
            vertexModelViewProjectionMatrixName,
            vertexTextureCoordinateName,
            vertexTransformMatrixName);
    programDrawable = new GlRect();
}

4.3 设置尺寸并绘制

在合适的机会设置尺寸并绘制,绘制里面有三个方法onPreDrawonDrawonPostDraw,内部都是调用的GlTextureProgram对应的onPreDrawonDrawonPostDraw,而GlTextureProgram里面,我们现在只需要知道是OpenGL API具体的方法就行了。

@Override
public void setSize(int width, int height) {
    
    
    size = new Size(width, height);
}

@Override
public void draw(long timestampUs, @NonNull float[] transformMatrix) {
    
    
    onPreDraw(timestampUs, transformMatrix);
    onDraw(timestampUs);
    onPostDraw(timestampUs);
}

protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) {
    
    
    program.setTextureTransform(transformMatrix);
    program.onPreDraw(programDrawable, programDrawable.getModelMatrix());
}

protected void onDraw(long timestampUs) {
    
    
    program.onDraw(programDrawable);
}

protected void onPostDraw(long timestampUs) {
    
    
    program.onPostDraw(programDrawable);
}

4.4 拷贝滤镜

copy方法,内部调用了getClass().newInstance()来反射得到一个新的BaseFilter,并赋值了Size,如果实现了OneParameterFilterTwoParameterFilter接口,还会给设置相关的参数。

比如亮度滤镜的亮度值,就需要实现OneParameterFilterTwoParameterFilter接口,从而使设置的亮度值,赋值到新的BaseFilter

@NonNull
@Override
public final BaseFilter copy() {
    
    
    BaseFilter copy = onCopy();
    if (size != null) {
    
    
        copy.setSize(size.getWidth(), size.getHeight());
    }
    if (this instanceof OneParameterFilter) {
    
    
        ((OneParameterFilter) copy).setParameter1(((OneParameterFilter) this).getParameter1());
    }
    if (this instanceof TwoParameterFilter) {
    
    
        ((TwoParameterFilter) copy).setParameter2(((TwoParameterFilter) this).getParameter2());
    }
    return copy;
}

@NonNull
protected BaseFilter onCopy() {
    
    
    try {
    
    
        return getClass().newInstance();
    } catch (IllegalAccessException e) {
    
    
        throw new RuntimeException("Filters should have a public no-arguments constructor.", e);
    } catch (InstantiationException e) {
    
    
        throw new RuntimeException("Filters should have a public no-arguments constructor.", e);
    }
}

那么我们就会有疑问了,copy方法在什么情况下会使用呢 ?
根据源码,可以看到在带滤镜拍照相关的SnapshotGlPictureRecorder类中,会用到copy方法。

protected void onRendererFilterChanged(@NonNull Filter filter) {
    
    
    mTextureDrawer.setFilter(filter.copy());
}

就是预览和拍照用的BaseFilter其实不是同一个Fitler,而是会先copy一份,再去拍照。
因为为了预览流畅,预览和拍照其实用的不是同一个Surface(后面会讲),原来的Fitler已经被预览使用了,所以需要Copy一份,再给拍照使用。

5. 预置的滤镜

CameraView预置了一些常见的滤镜,可以直接拿来使用。

5.1 预设的滤镜大全

预设的滤镜有以下这些
在这里插入图片描述

5.2 亮度滤镜

比如BrightnessFilter是调节亮度的滤镜,其代码如下
可以看到,里面传入了相关的GLSL代码,并在onPreDraw设置了亮度值。

public class BrightnessFilter extends BaseFilter implements OneParameterFilter {
    
    

    private final static String FRAGMENT_SHADER = "#extension GL_OES_EGL_image_external : require\n"
            + "precision mediump float;\n"
            + "uniform samplerExternalOES sTexture;\n"
            + "uniform float brightness;\n"
            + "varying vec2 "+DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME+";\n"
            + "void main() {\n"
            + "  vec4 color = texture2D(sTexture, "+DEFAULT_FRAGMENT_TEXTURE_COORDINATE_NAME+");\n"
            + "  gl_FragColor = brightness * color;\n"
            + "}\n";

    private float brightness = 2.0f; // 1.0F...2.0F
    private int brightnessLocation = -1;


    public BrightnessFilter() {
    
     }

    /**
     * Sets the brightness adjustment.
     * 1.0: normal brightness.
     * 2.0: high brightness.
     *
     * @param brightness brightness.
     */
    @SuppressWarnings({
    
    "WeakerAccess", "unused"})
    public void setBrightness(float brightness) {
    
    
        if (brightness < 1.0f) brightness = 1.0f;
        if (brightness > 2.0f) brightness = 2.0f;
        this.brightness = brightness;
    }

    /**
     * Returns the current brightness.
     *
     * @see #setBrightness(float)
     * @return brightness
     */
    @SuppressWarnings({
    
    "unused", "WeakerAccess"})
    public float getBrightness() {
    
    
        return brightness;
    }

    @Override
    public void setParameter1(float value) {
    
    
        // parameter is 0...1, brightness is 1...2.
        setBrightness(value + 1);
    }

    @Override
    public float getParameter1() {
    
    
        // parameter is 0...1, brightness is 1...2.
        return getBrightness() - 1F;
    }

    @NonNull
    @Override
    public String getFragmentShader() {
    
    
        return FRAGMENT_SHADER;
    }

    @Override
    public void onCreate(int programHandle) {
    
    
        super.onCreate(programHandle);
        brightnessLocation = GLES20.glGetUniformLocation(programHandle, "brightness");
        Egloo.checkGlProgramLocation(brightnessLocation, "brightness");
    }

    @Override
    public void onDestroy() {
    
    
        super.onDestroy();
        brightnessLocation = -1;
    }

    @Override
    protected void onPreDraw(long timestampUs, @NonNull float[] transformMatrix) {
    
    
        super.onPreDraw(timestampUs, transformMatrix);
        GLES20.glUniform1f(brightnessLocation, brightness);
        Egloo.checkGlError("glUniform1f");
    }
}

6. MultiFilter

单个滤镜的调用直接调用某个滤镜就可以了,但如果是多个滤镜进行叠加,那么就需要用到MultiFilter,通过addFilter()来叠加多个滤镜。

public class MultiFilter implements Filter, OneParameterFilter, TwoParameterFilter {
    
    
	//...省略了具体代码...
}

6.1 添加滤镜

将添加的滤镜存储在filters列表中

final List<Filter> filters = new ArrayList<>();

public void addFilter(@NonNull Filter filter) {
    
    
    if (filter instanceof MultiFilter) {
    
    
        MultiFilter multiFilter = (MultiFilter) filter;
        for (Filter multiChild : multiFilter.filters) {
    
    
            addFilter(multiChild);
        }
        return;
    }
    synchronized (lock) {
    
    
        if (!filters.contains(filter)) {
    
    
            filters.add(filter);
            states.put(filter, new State());
        }
    }
}

6.2 绘制滤镜

遍历filters列表,并调用一系列OpenGL的方法,逐个绘制滤镜,上一个滤镜绘制好后,下一个滤镜在上一个滤镜的基础上再绘制,从而最终达到滤镜叠加的效果。

@Override
public void draw(long timestampUs, @NonNull float[] transformMatrix) {
    
    
    synchronized (lock) {
    
    
        for (int i = 0; i < filters.size(); i++) {
    
    
            boolean isFirst = i == 0;
            boolean isLast = i == filters.size() - 1;
            Filter filter = filters.get(i);
            State state = states.get(filter);

            maybeSetSize(filter);
            maybeCreateProgram(filter, isFirst, isLast);
            maybeCreateFramebuffer(filter, isFirst, isLast);

            //noinspection ConstantConditions
            GLES20.glUseProgram(state.programHandle);

            // Define the output framebuffer.
            // Each filter outputs into its own framebuffer object, except the
            // last filter, which outputs into the default framebuffer.
            if (!isLast) {
    
    
                state.outputFramebuffer.bind();
                GLES20.glClearColor(0, 0, 0, 0);
            } else {
    
    
                GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, 0);
            }

            // Perform the actual drawing.
            // The first filter should apply all the transformations. Then,
            // since they are applied, we should use a no-op matrix.
            if (isFirst) {
    
    
                filter.draw(timestampUs, transformMatrix);
            } else {
    
    
                filter.draw(timestampUs, Egloo.IDENTITY_MATRIX);
            }

            // Set the input for the next cycle:
            // It is the framebuffer texture from this cycle. If this is the last
            // filter, reset this value just to cleanup.
            if (!isLast) {
    
    
                state.outputTexture.bind();
            } else {
    
    
                GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, 0);
                GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
            }

            GLES20.glUseProgram(0);
        }
    }
}

7. 其他

7.1 CameraView源码解析系列

Android 相机库CameraView源码解析 (一) : 预览-CSDN博客
Android 相机库CameraView源码解析 (二) : 拍照-CSDN博客
Android 相机库CameraView源码解析 (三) : 滤镜相关类说明-CSDN博客
Android 相机库CameraView源码解析 (四) : 带滤镜拍照-CSDN博客
Android 相机库CameraView源码解析 (五) : 保存滤镜效果-CSDN博客

猜你喜欢

转载自blog.csdn.net/EthanCo/article/details/134517249