GPUImageView 工作过程简单分析

·  阅读 964

最近有点时间了,来看下GPUImageView的源码,这里主要以静态的为GPUImageView设置一个Bitmap并添加滤镜的流程为基础,分析下工作原理。

简单使用

GPUImageView的使用很简单:


<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <jp.co.cyberagent.android.gpuimage.GPUImageView
        android:id="@+id/gpuImageView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_centerInParent="true"
        app:gpuimage_surface_type="texture_view" />

</RelativeLayout>

复制代码

很简单,直接在xml文件中添加 GPUImageView ,这里设置了一个属性 app:gpuimage_surface_type="texture_view" ,意为用 textureView 作为代理绘制图片,然后在代码中:


val bitmap = xxx//获取bitmap对象
val ratio = bitmap.width.toFloat() / bitmap.height.toFloat()//这一步是设置固定宽高比
gpuImageView.setImage(bitmap)//设置图片资源
gpuImageView.filter = GPUImageFilter()//设置滤镜
gpuImageView.requestRender()//需要更新绘制时都要调用

复制代码

ok,流程很简单,添加了一个bitmap图片并添加了一个默认滤镜(原图)。

GPUImageView

先看下 GPUImageView 的定义:


public class GPUImageView extends FrameLayout {

    private int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
    private View surfaceView;
    private View coverView;
    private GPUImage gpuImage;
    private boolean isShowLoading = true;
    private GPUImageFilter filter;
    public Size forceSize = null;
    private float ratio = 0.0f;

    public final static int RENDERMODE_WHEN_DIRTY = 0;
    public final static int RENDERMODE_CONTINUOUSLY = 1;

    public GPUImageView(Context context) {
        super(context);
        init(context, null);
    }

    public GPUImageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context, attrs);
    }

    private void init(Context context, AttributeSet attrs) {
        GLUtil.init(context);//保存一份当前Context的引用 注意这里是用static引用的context,所以有内存泄漏的可能性
        if (attrs != null) {//解析xml中写的一些属性 也不多 就两个
            TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.GPUImageView, 0, 0);
            try {
                surfaceType = a.getInt(R.styleable.GPUImageView_gpuimage_surface_type, surfaceType);//surface的类型 SurfaceView还是TextureView
                isShowLoading = a.getBoolean(R.styleable.GPUImageView_gpuimage_show_loading, isShowLoading);//是否需要显示loading 貌似没啥用
            } finally {
                a.recycle();
            }
        }
        gpuImage = new GPUImage(context);//创建一个GPUImage的对象,并持有其引用
        if (surfaceType == SURFACE_TYPE_TEXTURE_VIEW) {
            surfaceView = new GPUImageGLTextureView(context, attrs);//GPUImageGLTextureView,并持有其引用
            gpuImage.setGLTextureView((GLTextureView) surfaceView);//让GPUImage也持有一份其引用
        } else {
            surfaceView = new GPUImageGLSurfaceView(context, attrs);//GPUImageGLSurfaceView,并持有其引用
            gpuImage.setGLSurfaceView((GLSurfaceView) surfaceView);//让GPUImage也持有一份其引用
        }
        addView(surfaceView);//将创建的surfaceView添加给自己FrameLayout
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (ratio != 0.0f) {//根据ratio调整宽高
            int width = MeasureSpec.getSize(widthMeasureSpec);
            int height = MeasureSpec.getSize(heightMeasureSpec);

            int newHeight;
            int newWidth;
            if (width / ratio < height) {
                newWidth = width;
                newHeight = Math.round(width / ratio);
            } else {
                newHeight = height;
                newWidth = Math.round(height * ratio);
            }

            int newWidthSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY);
            int newHeightSpec = MeasureSpec.makeMeasureSpec(newHeight, MeasureSpec.EXACTLY);
            super.onMeasure(newWidthSpec, newHeightSpec);
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
}

复制代码

GPUImageView本身是一个FrameLayout,内部会添加一个SurfaceView,然后持有一个GPUImage对象的引用,如果有设置ratio的话,宽高会根据ratio来变。在初始化的时候,会对OpenGL工具类做一个初始化,其实就是保存context的引用;另外会根据当前xml熟悉的配置,选择surfaceView的实现。

然后看看GPUImage:


public class GPUImage {


    private final Context context;
    private final GPUImageRenderer renderer;
   
    /**
     * Instantiates a new GPUImage object.
     *
     * @param context the context
     */
    public GPUImage(final Context context) {
        if (!supportsOpenGLES2(context)) {
            throw new IllegalStateException("OpenGL ES 2.0 is not supported on this phone.");
        }

        this.context = context;
        filter = new GPUImageFilter();//默认滤镜是GPUImageFilter,也就是原画
        renderer = new GPUImageRenderer(filter);//创建一个GPUImageRender对象并持有其引用
    }
}

复制代码

这个 GPUImage 初始化时会默认使用原图滤镜,也就是 GPUImageFilter ,然后用这个滤镜创建一个 GPUImageRenderer 对象。滤镜的实现后面再看,这里看看Renderer:


public class GPUImageRenderer implements GLSurfaceView.Renderer, GLTextureView.Renderer, PreviewCallback {

    public static final float CUBE[] = {
            -1.0f, -1.0f,
            1.0f, -1.0f,
            -1.0f, 1.0f,
            1.0f, 1.0f,
    };

    private GPUImageFilter filter;

    private final FloatBuffer glCubeBuffer;
    private final FloatBuffer glTextureBuffer;

    private final Queue<Runnable> runOnDraw;
    private final Queue<Runnable> runOnDrawEnd;


    public GPUImageRenderer(final GPUImageFilter filter) {
        this.filter = filter;
        //两个Runnable队列,就是任务队列
        runOnDraw = new LinkedList<>();
        runOnDrawEnd = new LinkedList<>();

        //顶点数组和纹理数组
        glCubeBuffer = ByteBuffer.allocateDirect(CUBE.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        glCubeBuffer.put(CUBE).position(0);

        glTextureBuffer = ByteBuffer.allocateDirect(TEXTURE_NO_ROTATION.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        //坐标旋转
        setRotation(Rotation.NORMAL, false, false);
    }
    
    //...
    
}

复制代码

这个Render会保存一份滤镜的引用,内部有两个任务队列,看名字就知道应该是执行绘制任务的队列。另外这里会用FloatBuffer,创建两个坐标数组。buffer这里是Java NIO的内容,坐标数组有点复杂,涉及到多种坐标变换。首先我们知道Android中屏幕坐标都是左上角为原点,向右为x轴,向下为y轴,除了屏幕之外,每一个View的坐标也是如此。而OpenGL中,有很多很多坐标概念,其中主要用的世界坐标系,他的原点在正中间,向右为x轴正方向,向上为y轴正方向,这个概念就跟我们平时数学中的平面坐标系是一个意思了。除此之外,还有一个纹理坐标系,原点是该纹理的左下角,向右为x轴正方向,向上为y轴正方向,且OpenGL的坐标数值是归一化的-1到1或0-1的值。,举个例子,假如手机屏幕宽度是1080,在Android中某个点横坐标是540,那么在OpenGL中就是 540/1080 = 0.5。如果要显示一张图片作为OpenGL纹理的话,涉及到一个坐标转换或者说翻转的问题,这里我也不大清楚具体逻辑。

再回到前面 GPUImageView ,其中surfaceView的有两个实现,一个是 GPUImageGLTextureView ,一个是 GPUImageGLSurfaceView 。在创建好surfaceView对象后,会调用 GPUImagesetGLTextureViewsetGLSurfaceView 使其保存一份surfaceView的引用:


 /**
     * Sets the GLSurfaceView which will display the preview.
     *
     * @param view the GLSurfaceView
     */
    public void setGLSurfaceView(final GLSurfaceView view) {
        surfaceType = SURFACE_TYPE_SURFACE_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();
    }

    /**
     * Sets the GLTextureView which will display the preview.
     *
     * @param view the GLTextureView
     */
    public void setGLTextureView(final GLTextureView view) {
        surfaceType = SURFACE_TYPE_TEXTURE_VIEW;
        glTextureView = view;
        //同样是做一些初始化配置
        glTextureView.setEGLContextClientVersion(2);
        glTextureView.setEGLConfigChooser(8, 8, 8, 8, 16, 0);
        glTextureView.setOpaque(false);
        glTextureView.setRenderer(renderer);//同样是类似于SurfaceView,持有一个Render的引用
        glTextureView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
        glTextureView.requestRender();
    }


复制代码

做的处理几乎一样,都是保存一份view的引用,然后设置OpenGL的一些配置,比如版本为2,颜色啊深度什么的,完了会调 setRenderer() 方法,让view再保存一份当前 GPUImage 对象中的 renderer 对象的引用,最后设置一下渲染模式为主动刷新(字面意思,画布脏了再去刷新,相对的还有一个模式是被动刷新,也就是自动的不断刷新)。之后主动调用 requestRender() 去渲染一次。

看到这里会发现,两中surfaceView做的操作几乎一模一样,会不会这俩其实就是一个View呢,再来看看他们各自的实现:


 private class GPUImageGLSurfaceView extends GLSurfaceView {
        public GPUImageGLSurfaceView(Context context) {
            super(context);
        }

        public GPUImageGLSurfaceView(Context context, AttributeSet attrs) {
            super(context, attrs);
        }

        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            if (forceSize != null) {
                super.onMeasure(MeasureSpec.makeMeasureSpec(forceSize.width, MeasureSpec.EXACTLY),
                        MeasureSpec.makeMeasureSpec(forceSize.height, MeasureSpec.EXACTLY));
            } else {
                super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            }
        }
    }

private class GPUImageGLTextureView extends GLTextureView {
    public GPUImageGLTextureView(Context context) {
        super(context);
    }

    public GPUImageGLTextureView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        if (forceSize != null) {
            super.onMeasure(MeasureSpec.makeMeasureSpec(forceSize.width, MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(forceSize.height, MeasureSpec.EXACTLY));
        } else {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    }
}

复制代码

GPUImageGLSurfaceView 直接就是继承自Android原生的 GLSurfaceViewGPUImageGLTextureView 继承自GPUImage的 GLTextureView 类,但是这个 GLTextureView 又是继承自 Android原生的 TextureView ,而 GLTextureView 的实现又很有意思,其实就是完全照着 GLSurfaceView 的样子设计的。再来回到前面为 GPUImage 设置render的地方,以 setGLTextureView 为例,可以看到会调textureView的 setRenderer() 方法:


public void setRenderer(Renderer renderer) {
        checkRenderThreadState();
        if (eglConfigChooser == null) {
            eglConfigChooser = new SimpleEGLConfigChooser(true);
        }
        if (eglContextFactory == null) {
            eglContextFactory = new DefaultContextFactory();
        }
        if (eglWindowSurfaceFactory == null) {
            eglWindowSurfaceFactory = new DefaultWindowSurfaceFactory();
        }
        this.renderer = renderer;
        //创建一个GLThread,是一个线程,调start()方法开始运行
        glThread = new GLThread(mThisWeakRef);
        glThread.start();
}

复制代码

在确保配置正确的前提下,会创建一个 GLThread 对象并调用 start() 方法,这个线程就是GL线程了,主要负责处理OpenGL ES的调用。看看实现:


static class GLThread extends Thread {
    GLThread(WeakReference<GLTextureView> glTextureViewWeakRef) {
        super();
        width = 0;
        height = 0;
        requestRender = true;
        renderMode = RENDERMODE_CONTINUOUSLY;
        this.glTextureViewWeakRef = glTextureViewWeakRef;
    }

    @Override
    public void run() {
        setName("GLThread " + getId());
        if (LOG_THREADS) {
            Log.i("GLThread", "starting tid=" + getId());
        }

        try {
            guardedRun();//进行了异常检测的run(),所以是guarded(保护)
        } catch (InterruptedException e) {
            // fall thru and exit normally
        } finally {
            glThreadManager.threadExiting(this);
        }
    }
}

复制代码

mThisWeakRef 是对View自身的一个弱引用,线程的 run() 方法内会用 try catch 来较安全的调用 guardeRun() 方法:


        private void guardedRun() throws InterruptedException {
            eglHelper = new EglHelper(glTextureViewWeakRef);
            haveEglContext = false;
            haveEglSurface = false;
            try {
                GL10 gl = null;
                boolean createEglContext = false;
                boolean createEglSurface = false;
                boolean createGlInterface = false;
                boolean lostEglContext = false;
                boolean sizeChanged = false;
                boolean wantRenderNotification = false;
                boolean doRenderNotification = false;
                boolean askedToReleaseEglContext = false;
                int w = 0;
                int h = 0;
                Runnable event = null;

                while (true) {
                    synchronized (glThreadManager) {
                        while (true) {
                            if (shouldExit) {
                                return;
                            }

                            if (!eventQueue.isEmpty()) {
                                event = eventQueue.remove(0);
                                break;
                            }

                            // Update the pause state.
                            boolean pausing = false;
                            if (paused != requestPaused) {
                                pausing = requestPaused;
                                paused = requestPaused;
                                glThreadManager.notifyAll();
                                if (LOG_PAUSE_RESUME) {
                                    Log.i("GLThread", "paused is now " + paused + " tid=" + getId());
                                }
                            }

                            // Do we need to give up the EGL context?
                            if (shouldReleaseEglContext) {
                                if (LOG_SURFACE) {
                                    Log.i("GLThread", "releasing EGL context because asked to tid=" + getId());
                                }
                                stopEglSurfaceLocked();
                                stopEglContextLocked();
                                shouldReleaseEglContext = false;
                                askedToReleaseEglContext = true;
                            }

                            // Have we lost the EGL context?
                            if (lostEglContext) {
                                stopEglSurfaceLocked();
                                stopEglContextLocked();
                                lostEglContext = false;
                            }

                            // When pausing, release the EGL surface:
                            if (pausing && haveEglSurface) {
                                if (LOG_SURFACE) {
                                    Log.i("GLThread", "releasing EGL surface because paused tid=" + getId());
                                }
                                stopEglSurfaceLocked();
                            }

                            // When pausing, optionally release the EGL Context:
                            if (pausing && haveEglContext) {
                                GLTextureView view = glTextureViewWeakRef.get();
                                boolean preserveEglContextOnPause =
                                        view == null ? false : view.preserveEGLContextOnPause;
                                if (!preserveEglContextOnPause
                                        || glThreadManager.shouldReleaseEGLContextWhenPausing()) {
                                    stopEglContextLocked();
                                    if (LOG_SURFACE) {
                                        Log.i("GLThread", "releasing EGL context because paused tid=" + getId());
                                    }
                                }
                            }

                            // When pausing, optionally terminate EGL:
                            if (pausing) {
                                if (glThreadManager.shouldTerminateEGLWhenPausing()) {
                                    eglHelper.finish();
                                    if (LOG_SURFACE) {
                                        Log.i("GLThread", "terminating EGL because paused tid=" + getId());
                                    }
                                }
                            }

                            // Have we lost the TextureView surface?
                            if ((!hasSurface) && (!waitingForSurface)) {
                                if (LOG_SURFACE) {
                                    Log.i("GLThread", "noticed textureView surface lost tid=" + getId());
                                }
                                if (haveEglSurface) {
                                    stopEglSurfaceLocked();
                                }
                                waitingForSurface = true;
                                surfaceIsBad = false;
                                glThreadManager.notifyAll();
                            }

                            // Have we acquired the surface view surface?
                            if (hasSurface && waitingForSurface) {
                                if (LOG_SURFACE) {
                                    Log.i("GLThread", "noticed textureView surface acquired tid=" + getId());
                                }
                                waitingForSurface = false;
                                glThreadManager.notifyAll();
                            }

                            if (doRenderNotification) {
                                if (LOG_SURFACE) {
                                    Log.i("GLThread", "sending render notification tid=" + getId());
                                }
                                wantRenderNotification = false;
                                doRenderNotification = false;
                                renderComplete = true;
                                glThreadManager.notifyAll();
                            }

                            // Ready to draw?
                            if (readyToDraw()) {

                                // If we don't have an EGL context, try to acquire one.
                                if (!haveEglContext) {
                                    if (askedToReleaseEglContext) {
                                        askedToReleaseEglContext = false;
                                    } else if (glThreadManager.tryAcquireEglContextLocked(this)) {
                                        try {
                                            eglHelper.start();
                                        } catch (RuntimeException t) {
                                            glThreadManager.releaseEglContextLocked(this);
                                            throw t;
                                        }
                                        haveEglContext = true;
                                        createEglContext = true;

                                        glThreadManager.notifyAll();
                                    }
                                }

                                if (haveEglContext && !haveEglSurface) {
                                    haveEglSurface = true;
                                    createEglSurface = true;
                                    createGlInterface = true;
                                    sizeChanged = true;
                                }

                                if (haveEglSurface) {
                                    if (this.sizeChanged) {
                                        sizeChanged = true;
                                        w = width;
                                        h = height;
                                        wantRenderNotification = true;
                                        if (LOG_SURFACE) {
                                            Log.i("GLThread", "noticing that we want render notification tid=" + getId());
                                        }

                                        // Destroy and recreate the EGL surface.
                                        createEglSurface = true;

                                        this.sizeChanged = false;
                                    }
                                    requestRender = false;
                                    glThreadManager.notifyAll();
                                    break;
                                }
                            }

                            // By design, this is the only place in a GLThread thread where we wait().
                            if (LOG_THREADS) {
                                Log.i("GLThread", "waiting tid=" + getId() + " haveEglContext: " + haveEglContext
                                        + " haveEglSurface: " + haveEglSurface + " paused: " + paused + " hasSurface: "
                                        + hasSurface + " surfaceIsBad: " + surfaceIsBad + " waitingForSurface: "
                                        + waitingForSurface + " width: " + width + " height: " + height
                                        + " requestRender: " + requestRender + " renderMode: " + renderMode);
                            }
                            glThreadManager.wait();
                        }
                    } // end of synchronized(glThreadManager)

                    if (event != null) {
                        event.run();
                        event = null;
                        continue;
                    }

                    if (createEglSurface) {
                        if (LOG_SURFACE) {
                            Log.w("GLThread", "egl createSurface");
                        }
                        if (!eglHelper.createSurface()) {
                            synchronized (glThreadManager) {
                                surfaceIsBad = true;
                                glThreadManager.notifyAll();
                            }
                            continue;
                        }
                        createEglSurface = false;
                    }

                    if (createGlInterface) {
                        gl = (GL10) eglHelper.createGL();

                        glThreadManager.checkGLDriver(gl);
                        createGlInterface = false;
                    }

                    if (createEglContext) {
                        if (LOG_RENDERER) {
                            Log.w("GLThread", "onSurfaceCreated");
                        }
                        GLTextureView view = glTextureViewWeakRef.get();
                        if (view != null) {
                            view.renderer.onSurfaceCreated(gl, eglHelper.eglConfig);
                        }
                        createEglContext = false;
                    }

                    if (sizeChanged) {
                        if (LOG_RENDERER) {
                            Log.w("GLThread", "onSurfaceChanged(" + w + ", " + h + ")");
                        }
                        GLTextureView view = glTextureViewWeakRef.get();
                        if (view != null) {
                            view.renderer.onSurfaceChanged(gl, w, h);
                        }
                        sizeChanged = false;
                    }

                    if (LOG_RENDERER_DRAW_FRAME) {
                        Log.w("GLThread", "onDrawFrame tid=" + getId());
                    }
                    {
                        GLTextureView view = glTextureViewWeakRef.get();
                        if (view != null) {
                            view.renderer.onDrawFrame(gl);
                        }
                    }
                    int swapError = eglHelper.swap();
                    switch (swapError) {
                        case EGL10.EGL_SUCCESS:
                            break;
                        case EGL11.EGL_CONTEXT_LOST:
                            if (LOG_SURFACE) {
                                Log.i("GLThread", "egl context lost tid=" + getId());
                            }
                            lostEglContext = true;
                            break;
                        default:
                            // Other errors typically mean that the current surface is bad,
                            // probably because the TextureView surface has been destroyed,
                            // but we haven't been notified yet.
                            // Log the error to help developers understand why rendering stopped.
                            EglHelper.logEglErrorAsWarning("GLThread", "eglSwapBuffers", swapError);

                            synchronized (glThreadManager) {
                                surfaceIsBad = true;
                                glThreadManager.notifyAll();
                            }
                            break;
                    }

                    if (wantRenderNotification) {
                        doRenderNotification = true;
                    }
                }
            } finally {
                /*
                 * clean-up everything...
                 */
                synchronized (glThreadManager) {
                    stopEglSurfaceLocked();
                    stopEglContextLocked();
                }
            }
        }


复制代码

代码很长,大意是在不断循环执行的过程中,首先设置好环境变量,包括上下文、surface等,然后执行绘制操作;绘制时根据前面设置的绘制模式,如果是主动刷新,就是在 glThreadManager.wait() 后阻塞,等到主动请求刷新的时候再去执行任务;被动刷新模式,就会一刻不停的循环执行任务。在绘制操作中,会通过 GLTextureView view = glTextureViewWeakRef.get(); 获取当前 GLTextureView 对象的引用,调用他内部的 renderonSurfaceCreated onSurfaceChanged onDrawFrame 方法。这三个方法在Render中的实现是这样的:


@Override
public void onSurfaceCreated(final GL10 unused, final EGLConfig config) {
    GLES20.glClearColor(backgroundRed, backgroundGreen, backgroundBlue, 1);
    GLES20.glDisable(GLES20.GL_DEPTH_TEST);
    filter.ifNeedInit();//调用滤镜的初始化方法
}

@Override
public void onSurfaceChanged(final GL10 gl, final int width, final int height) {
    outputWidth = width;
    outputHeight = height;
    GLES20.glViewport(0, 0, width, height);//控制当前SurfaceView的绘制区域
    GLES20.glUseProgram(filter.getProgram());
    filter.onOutputSizeChanged(width, height);//为滤镜设置当前的宽高
    adjustImageScaling();//字面意思就是绘制的纹理(图片)的宽高适应 其实也就是通过修改纹理坐标来适应宽高
    synchronized (surfaceChangedWaiter) {
        surfaceChangedWaiter.notifyAll();
    }
}

@Override
public void onDrawFrame(final GL10 gl) {
    GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
    runAll(runOnDraw);//这里是从循环拿出队列中的任务,依次执行
    generateOesTexture();//生成纹理,主要是获取当前的 glTextureId, glCubeBuffer, glTextureBuffer
    filter.onDraw(glTextureId, glCubeBuffer, glTextureBuffer);//滤镜的应用
    runAll(runOnDrawEnd);//最后再执行第二个任务队列
}

 private void runAll(Queue<Runnable> queue) {
    synchronized (queue) {
        while (!queue.isEmpty()) {
            queue.poll().run();
        }
    }
}
复制代码

前面说Render有两个绘制任务队列,就是在 onDrawFrame() 方法中,把队列中的任务拿出来执行了。

setImage()

接下来看设置图片资源并渲染的具体流程。


/**
 * Sets the image on which the filter should be applied.
 *
 * @param bitmap the new image
 */
public void setImage(final Bitmap bitmap) {
    gpuImage.setImage(bitmap);
}

/**
 * Sets the image on which the filter should be applied from a Uri.
 *
 * @param uri the uri of the new image
 */
public void setImage(final Uri uri) {
    gpuImage.setImage(uri);
}

/**
 * Sets the image on which the filter should be applied from a File.
 *
 * @param file the file of the new image
 */
public void setImage(final File file) {
    gpuImage.setImage(file);
}

复制代码

GPUImageViewsetImage() 会调用 GPUImagesetImage() 方法,且有多个重载方法,分别可以接收不同类型的输入, bitmap urifile 。后两种输入,是会自己构建一个图片加载流程,最终还是要获取 bitmap


 /**
 * Sets the image on which the filter should be applied from a Uri.
 *
 * @param uri the uri of the new image
 */
public void setImage(final Uri uri) {
    new LoadImageUriTask(this, uri).execute();
}

/**
 * Sets the image on which the filter should be applied from a File.
 *
 * @param file the file of the new image
 */
public void setImage(final File file) {
    new LoadImageFileTask(this, file).execute();
}

复制代码

就直接以 bitmap 为例看一下:


/**
 * Sets the image on which the filter should be applied.
 *
 * @param bitmap the new image
 */
public void setImage(final Bitmap bitmap) {
    currentBitmap = bitmap;
    renderer.setImageBitmap(bitmap, false);
    requestRender();
}

复制代码

保存一份bitmap的引用,把bitmap传给render,然后主动请求一次渲染。那先看一下给render干了什么:


public void setImageBitmap(final Bitmap bitmap, final boolean recycle) {
    if (bitmap == null) {
        return;
    }

    runOnDraw(new Runnable() {

        @Override
        public void run() {
            Bitmap resizedBitmap = null;
            if (bitmap.getWidth() % 2 == 1) {
                resizedBitmap = Bitmap.createBitmap(bitmap.getWidth() + 1, bitmap.getHeight(),
                        Bitmap.Config.ARGB_8888);
                resizedBitmap.setDensity(bitmap.getDensity());
                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();
        }
    });
}

 protected void runOnDraw(final Runnable runnable) {
    synchronized (runOnDraw) {
        runOnDraw.add(runnable);
    }
}

复制代码

runOnDraw() 是把当前要做的操作以一个Runnable任务的形式,插入到绘制任务队列末尾。具体的操作内容呢,首先会检测下图片宽度,如果是奇数,就会先创建一个原图宽度+1、高度等于原图的图片,用Canvas的方式绘制一张新的图片,效果等同于为图片补边。之后会调用 adjustImageScaleing() 方法,这个方法前面有看到过,做的其实就是通过纹理坐标的变换来做宽高适配和旋转:


private void adjustImageScaling() {
    float outputWidth = this.outputWidth;
    float outputHeight = this.outputHeight;
    if (rotation == Rotation.ROTATION_270 || rotation == Rotation.ROTATION_90) {
        outputWidth = this.outputHeight;
        outputHeight = this.outputWidth;
    }

    float ratio1 = outputWidth / imageWidth;
    float ratio2 = outputHeight / imageHeight;
    float ratioMax = Math.max(ratio1, ratio2);
    int imageWidthNew = Math.round(imageWidth * ratioMax);
    int imageHeightNew = Math.round(imageHeight * ratioMax);

    float ratioWidth = imageWidthNew / outputWidth;
    float ratioHeight = imageHeightNew / outputHeight;

    float[] cube = CUBE;
    float[] textureCords = TextureRotationUtil.getRotation(rotation, flipHorizontal, flipVertical);
    if (scaleType == GPUImage.ScaleType.CENTER_CROP) {
        float distHorizontal = (1 - 1 / ratioWidth) / 2;
        float distVertical = (1 - 1 / ratioHeight) / 2;
        textureCords = new float[]{
                addDistance(textureCords[0], distHorizontal), addDistance(textureCords[1], distVertical),
                addDistance(textureCords[2], distHorizontal), addDistance(textureCords[3], distVertical),
                addDistance(textureCords[4], distHorizontal), addDistance(textureCords[5], distVertical),
                addDistance(textureCords[6], distHorizontal), addDistance(textureCords[7], distVertical),
        };
    } else {
        cube = new float[]{
                CUBE[0] / ratioHeight, CUBE[1] / ratioWidth,
                CUBE[2] / ratioHeight, CUBE[3] / ratioWidth,
                CUBE[4] / ratioHeight, CUBE[5] / ratioWidth,
                CUBE[6] / ratioHeight, CUBE[7] / ratioWidth,
        };
    }

    glCubeBuffer.clear();
    glCubeBuffer.put(cube).position(0);
    glTextureBuffer.clear();
    glTextureBuffer.put(textureCords).position(0);
}

复制代码

setFilter()

接下来看添加滤镜:


 /**
 * Set the filter to be applied on the image.
 *
 * @param filter Filter that should be applied on the image.
 */
public void setFilter(GPUImageFilter filter) {
    this.filter = filter;
    gpuImage.setFilter(filter);
    requestRender();
}

复制代码

首先保存一份filter的引用,然后调 GPUImagesetFilter() 方法把filter传过去,最后再主动刷新一次。看看 GPUImagesetFilter() 实现:


/**
 * Sets the filter which should be applied to the image which was (or will
 * be) set by setImage(...).
 *
 * @param filter the new filter
 */
public void setFilter(final GPUImageFilter filter) {
    this.filter = filter;
    renderer.setFilter(this.filter);
    requestRender();
}

复制代码

类似的,最终还是去到了render中:


public void setFilter(final GPUImageFilter filter) {
    runOnDraw(new Runnable() {

        @Override
        public void run() {
            final GPUImageFilter oldFilter = GPUImageRenderer.this.filter;
            GPUImageRenderer.this.filter = filter;
            if (oldFilter != null) {
                oldFilter.destroy();
            }
            GPUImageRenderer.this.filter.ifNeedInit();
            GLES20.glUseProgram(GPUImageRenderer.this.filter.getProgram());
            GPUImageRenderer.this.filter.onOutputSizeChanged(outputWidth, outputHeight);
        }
    });
}

复制代码

创建了一个新的绘制任务,具体做的是:先获取Render中保存的Filter,这个是上一次绘制的。在更新了Filter后,调用旧Filter的 destroy() 方法清理一些资源等,之后调用新Filter的初始化方法,将当前宽高数据传给新的Filter。 大概总结一下,往后的包括图片和滤镜的设置,都是在 GLTreadonDrawFrame() 方法中区执行绘制的。

分类:
Android
标签:
分类:
Android
标签:
收藏成功!
已添加到「」, 点击更改