Android 上使用Opengl进行滤镜渲染效率较高,比起单纯的使用CPU给用户带来的体验会好很多。滤镜的对象是图片,图片是以Bitmap的形式表示,Opengl不能直接处理Bitmap,在Android上一般是通过GLSurfaceView来进行渲染的,也可以说成Android需要借助GLSurfaceView来完成对图片的渲染。

  GlSurfaceView 的图片来源依然是Bitmap,但是Bitmap需要以纹理(Texture)的形式载入到Opengl中。因此我首先来看一下载入纹理的步骤:

  1.GLES20.glGenTextures() : 生成纹理资源的句柄

  2.GLES20.glBindTexture(): 绑定句柄

  3.GLUtils.texImage2D() :将bitmap传递到已经绑定的纹理中

  4.GLES20.glTexParameteri() :设置纹理属性,过滤方式,拉伸方式等

  这里做滤镜使用Android4.x以后提供的 Effect 类来完成,Effect类实现也是通过Shader的方式来完成的,这些Shader程序内置在Android中,我们只需要按照一定的方式来调用就行了。在Android上使用GLSurfaceView来显示并完成图片的渲染,实现渲染需要实现GLSurfaceView.Render接口,该接口有三个方法:onDrawFrame(GL10 gl) ,该方法按照一定的刷新频率反复执行;onSurfaceChanged(GL10 gl, int width, int height),该方法在窗口重绘的时候执行;onSurfaceCreated(GL10 gl, EGLConfig config) 在创建SurfaceView的时候执行。

  使用Effect类会用到EffectFactory 和 EffectContex,在下面的例子中看看具体的使用方式。

  首先定义一个Activity:EffectivefilterActivity

package com.example.effectsfilterdemo;import java.nio.IntBuffer;import android.app.Activity;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.opengl.GLSurfaceView;import android.os.Bundle;import android.util.Log;import android.view.Menu;import android.view.MenuInflater;import android.view.MenuItem;public class EffectsFilterActivity extends Activity {    private GLSurfaceView mEffectView;    private TextureRenderer renderer;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);                renderer = new TextureRenderer();        renderer.setImageBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.puppy));        renderer.setCurrentEffect(R.id.none);                mEffectView = (GLSurfaceView) findViewById(R.id.effectsview);        //mEffectView = new GLSurfaceView(this);        mEffectView.setEGLContextClientVersion(2);        //mEffectView.setRenderer(this);        mEffectView.setRenderer(renderer);        mEffectView.setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);                //setContentView(mEffectView);    }        @Override    public boolean onCreateOptionsMenu(Menu menu) {        Log.i("info", "menu create");        MenuInflater inflater = getMenuInflater();        inflater.inflate(R.menu.main, menu);        return true;    }    @Override    public boolean onOptionsItemSelected(MenuItem item) {        renderer.setCurrentEffect(item.getItemId());        mEffectView.requestRender();        return true;    }}

  EffectivefilterActivity 中使用了两个布局文件,一个用于Activity的布局,另一个用于菜单的布局。

  R.layout.main:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="fill_parent"    android:layout_height="fill_parent"    android:orientation="vertical" >    <android.opengl.GLSurfaceView        android:id="@+id/effectsview"        android:layout_width="fill_parent"        android:layout_height="wrap_content"         /></LinearLayout>

  R.menu.main:

<menu xmlns:android="http://schemas.android.com/apk/res/android" >    <item        android:id="@+id/none"        android:showAsAction="never"        android:title="none"/>    <item        android:id="@+id/autofix"        android:showAsAction="never"        android:title="autofix"/>    <item        android:id="@+id/bw"        android:showAsAction="never"        android:title="bw"/>    <item        android:id="@+id/brightness"        android:showAsAction="never"        android:title="brightness"/>    <item        android:id="@+id/contrast"        android:showAsAction="never"        android:title="contrast"/>    <item        android:id="@+id/crossprocess"        android:showAsAction="never"        android:title="crossprocess"/>    <item        android:id="@+id/documentary"        android:showAsAction="never"        android:title="documentary"/>    <item        android:id="@+id/duotone"        android:showAsAction="never"        android:title="duotone"/>    <item        android:id="@+id/filllight"        android:showAsAction="never"        android:title="filllight"/>    <item        android:id="@+id/fisheye"        android:showAsAction="never"        android:title="fisheye"/>    <item        android:id="@+id/flipvert"        android:showAsAction="never"        android:title="flipvert"/>    <item        android:id="@+id/fliphor"        android:showAsAction="never"        android:title="fliphor"/>    <item        android:id="@+id/grain"        android:showAsAction="never"        android:title="grain"/>    <item        android:id="@+id/grayscale"        android:showAsAction="never"        android:title="grayscale"/>    <item        android:id="@+id/lomoish"        android:showAsAction="never"        android:title="lomoish"/>    <item        android:id="@+id/negative"        android:showAsAction="never"        android:title="negative"/>    <item        android:id="@+id/posterize"        android:showAsAction="never"        android:title="posterize"/>    <item        android:id="@+id/rotate"        android:showAsAction="never"        android:title="rotate"/>    <item        android:id="@+id/saturate"        android:showAsAction="never"        android:title="saturate"/>    <item        android:id="@+id/sepia"        android:showAsAction="never"        android:title="sepia"/>    <item        android:id="@+id/sharpen"        android:showAsAction="never"        android:title="sharpen"/>    <item        android:id="@+id/temperature"        android:showAsAction="never"        android:title="temperature"/>    <item        android:id="@+id/tint"        android:showAsAction="never"        android:title="tint"/>    <item        android:id="@+id/vignette"        android:showAsAction="never"        android:title="vignette"/></menu>

  在R.layout.main中只定义了一个GLSurfaceView用于显示图片,R.menu.main用于显示多个菜单项,通过点击菜单来完成调用不同滤镜实现对图片的处理。

  接下来看比较关键的Renderer接口的实现。

package com.example.effectsfilterdemo;import android.content.Context;import android.graphics.Bitmap;import android.graphics.Color;import android.media.effect.Effect;import android.media.effect.EffectContext;import android.media.effect.EffectFactory;import android.opengl.GLES20;import android.opengl.GLSurfaceView;import android.opengl.GLUtils;import android.util.Log;import java.nio.ByteBuffer;import java.nio.ByteOrder;import java.nio.FloatBuffer;import java.util.LinkedList;import java.util.Queue;import javax.microedition.khronos.egl.EGLConfig;import javax.microedition.khronos.opengles.GL10;public class TextureRenderer implements GLSurfaceView.Renderer{    private int mProgram;    private int mTexSamplerHandle;    private int mTexCoordHandle;    private int mPosCoordHandle;    private FloatBuffer mTexVertices;    private FloatBuffer mPosVertices;    private int mViewWidth;    private int mViewHeight;    private int mTexWidth;    private int mTexHeight;        private Context mContext;    private final Queue<Runnable> mRunOnDraw;    private int[] mTextures = new int[2];    int mCurrentEffect;    private EffectContext mEffectContext;    private Effect mEffect;    private int mImageWidth;    private int mImageHeight;    private boolean initialized = false;    private static final String VERTEX_SHADER =        "attribute vec4 a_position;\n" +        "attribute vec2 a_texcoord;\n" +        "varying vec2 v_texcoord;\n" +        "void main() {\n" +        "  gl_Position = a_position;\n" +        "  v_texcoord = a_texcoord;\n" +        "}\n";    private static final String FRAGMENT_SHADER =        "precision mediump float;\n" +        "uniform sampler2D tex_sampler;\n" +        "varying vec2 v_texcoord;\n" +        "void main() {\n" +        "  gl_FragColor = texture2D(tex_sampler, v_texcoord);\n" +        "}\n";    private static final float[] TEX_VERTICES = {        0.0f, 1.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f, 0.0f    };    private static final float[] POS_VERTICES = {        -1.0f, -1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f    };    private static final int FLOAT_SIZE_BYTES = 4;        public TextureRenderer() {        // TODO Auto-generated constructor stub        mRunOnDraw = new LinkedList<>();    }    public void init() {        // Create program        mProgram = GLToolbox.createProgram(VERTEX_SHADER, FRAGMENT_SHADER);        // Bind attributes and uniforms        mTexSamplerHandle = GLES20.glGetUniformLocation(mProgram,                "tex_sampler");        mTexCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_texcoord");        mPosCoordHandle = GLES20.glGetAttribLocation(mProgram, "a_position");        // Setup coordinate buffers        mTexVertices = ByteBuffer.allocateDirect(                TEX_VERTICES.length * FLOAT_SIZE_BYTES)                .order(ByteOrder.nativeOrder()).asFloatBuffer();        mTexVertices.put(TEX_VERTICES).position(0);        mPosVertices = ByteBuffer.allocateDirect(                POS_VERTICES.length * FLOAT_SIZE_BYTES)                .order(ByteOrder.nativeOrder()).asFloatBuffer();        mPosVertices.put(POS_VERTICES).position(0);    }    public void tearDown() {        GLES20.glDeleteProgram(mProgram);    }    public void updateTextureSize(int texWidth, int texHeight) {        mTexWidth = texWidth;        mTexHeight = texHeight;        computeOutputVertices();    }    public void updateViewSize(int viewWidth, int viewHeight) {        mViewWidth = viewWidth;        mViewHeight = viewHeight;        computeOutputVertices();    }    public void renderTexture(int texId) {        GLES20.glUseProgram(mProgram);        GLToolbox.checkGlError("glUseProgram");        GLES20.glViewport(0, 0, mViewWidth, mViewHeight);        GLToolbox.checkGlError("glViewport");        GLES20.glDisable(GLES20.GL_BLEND);        GLES20.glVertexAttribPointer(mTexCoordHandle, 2, GLES20.GL_FLOAT, false,                0, mTexVertices);        GLES20.glEnableVertexAttribArray(mTexCoordHandle);        GLES20.glVertexAttribPointer(mPosCoordHandle, 2, GLES20.GL_FLOAT, false,                0, mPosVertices);        GLES20.glEnableVertexAttribArray(mPosCoordHandle);        GLToolbox.checkGlError("vertex attribute setup");        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);        GLToolbox.checkGlError("glActiveTexture");        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);//把已经处理好的Texture传到GL上面        GLToolbox.checkGlError("glBindTexture");        GLES20.glUniform1i(mTexSamplerHandle, 0);        GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);        GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);    }    private void computeOutputVertices() { //调整AspectRatio 保证landscape和portrait的时候显示比例相同,图片不会被拉伸 if (mPosVertices != null) {            float imgAspectRatio = mTexWidth / (float)mTexHeight;            float viewAspectRatio = mViewWidth / (float)mViewHeight;            float relativeAspectRatio = viewAspectRatio / imgAspectRatio;            float x0, y0, x1, y1;            if (relativeAspectRatio > 1.0f) {                x0 = -1.0f / relativeAspectRatio;                y0 = -1.0f;                x1 = 1.0f / relativeAspectRatio;                y1 = 1.0f;            } else {                x0 = -1.0f;                y0 = -relativeAspectRatio;                x1 = 1.0f;                y1 = relativeAspectRatio;            }            float[] coords = new float[] { x0, y0, x1, y0, x0, y1, x1, y1 };            mPosVertices.put(coords).position(0);        }    }        private void initEffect() {        EffectFactory effectFactory = mEffectContext.getFactory();        if (mEffect != null) {            mEffect.release();        }        /**         * Initialize the correct effect based on the selected menu/action item         */        switch (mCurrentEffect) {        case R.id.none:            break;        case R.id.autofix:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_AUTOFIX);            mEffect.setParameter("scale", 0.5f);            break;        case R.id.bw:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_BLACKWHITE);            mEffect.setParameter("black", .1f);            mEffect.setParameter("white", .7f);            break;        case R.id.brightness:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_BRIGHTNESS);            mEffect.setParameter("brightness", 2.0f);            break;        case R.id.contrast:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_CONTRAST);            mEffect.setParameter("contrast", 1.4f);            break;        case R.id.crossprocess:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_CROSSPROCESS);            break;        case R.id.documentary:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_DOCUMENTARY);            break;        case R.id.duotone:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_DUOTONE);            mEffect.setParameter("first_color", Color.YELLOW);            mEffect.setParameter("second_color", Color.DKGRAY);            break;        case R.id.filllight:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_FILLLIGHT);            mEffect.setParameter("strength", .8f);            break;        case R.id.fisheye:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_FISHEYE);            mEffect.setParameter("scale", .5f);            break;        case R.id.flipvert:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_FLIP);            mEffect.setParameter("vertical", true);            break;        case R.id.fliphor:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_FLIP);            mEffect.setParameter("horizontal", true);            break;        case R.id.grain:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_GRAIN);            mEffect.setParameter("strength", 1.0f);            break;        case R.id.grayscale:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_GRAYSCALE);            break;        case R.id.lomoish:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_LOMOISH);            break;        case R.id.negative:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_NEGATIVE);            break;        case R.id.posterize:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_POSTERIZE);            break;        case R.id.rotate:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_ROTATE);            mEffect.setParameter("angle", 180);            break;        case R.id.saturate:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_SATURATE);            mEffect.setParameter("scale", .5f);            break;        case R.id.sepia:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_SEPIA);            break;        case R.id.sharpen:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_SHARPEN);            break;        case R.id.temperature:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_TEMPERATURE);            mEffect.setParameter("scale", .9f);            break;        case R.id.tint:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_TINT);            mEffect.setParameter("tint", Color.MAGENTA);            break;        case R.id.vignette:            mEffect = effectFactory.createEffect(EffectFactory.EFFECT_VIGNETTE);            mEffect.setParameter("scale", .5f);            break;        default:            break;        }    }        public void setCurrentEffect(int effect) {        mCurrentEffect = effect;    }        public void setImageBitmap(final Bitmap bmp){        runOnDraw(new Runnable() {                        @Override            public void run() {                // TODO Auto-generated method stub                loadTexture(bmp);            }        });    }        private void loadTexture(Bitmap bmp){        GLES20.glGenTextures(2, mTextures , 0);        updateTextureSize(bmp.getWidth(), bmp.getHeight());                mImageWidth = bmp.getWidth();        mImageHeight = bmp.getHeight();        GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextures[0]);        GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, 0, bmp, 0);        GLToolbox.initTexParams();    }        private void applyEffect() {        if(mEffect == null){            Log.i("info","apply Effect null mEffect");        }                mEffect.apply(mTextures[0], mImageWidth, mImageHeight, mTextures[1]);    }    private void renderResult() {        if (mCurrentEffect != R.id.none) {            renderTexture(mTextures[1]);        } else {            renderTexture(mTextures[0]);        }    }    @Override    public void onDrawFrame(GL10 gl) {        // TODO Auto-generated method stub        if(!initialized){            init();            mEffectContext = EffectContext.createWithCurrentGlContext();            initialized = true;        }                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);        synchronized (mRunOnDraw) {            while (!mRunOnDraw.isEmpty()) {                mRunOnDraw.poll().run();            }        }                if (mCurrentEffect != R.id.none) {            initEffect();            applyEffect();        }        renderResult();    }    @Override    public void onSurfaceChanged(GL10 gl, int width, int height) {        // TODO Auto-generated method stub        updateViewSize(width, height);    }    @Override    public void onSurfaceCreated(GL10 gl, EGLConfig config) {        // TODO Auto-generated method stub            }        protected void runOnDraw(final Runnable runnable) {        synchronized (mRunOnDraw) {            mRunOnDraw.add(runnable);        }    }}

  这里有一个地方需要注意,任何使用Opengl接口的方法调用需要在Opengl Context中进行,否则会出现:call to OpenGL ES API with no current context (logged once per thread) 报错信息。所谓的Opengl Context 其实就是需要在onDrawFrame(GL10 gl),onSurfaceChanged(GL10 gl, int width, int height),onSurfaceCreated(GL10 gl, EGLConfig config)中调用,注意到这三个方法都有一个参数GL10。这里还有一个地方就是在载入纹理之前需要载入位图,使用了runOnDraw()方法将loadTexure的步骤放在onDrawFrame() 中来完成,巧妙的为外界提供了一个接口并使得操作在具有Opengl Context的黄金中完成。

  最后来看看辅助的工具类(GLToolbox),该类完成Shader程序的创建,应用程序提供Shader 源码给该工具类编译:

package com.example.effectsfilterdemo;import android.opengl.GLES20;public class GLToolbox {    public static int loadShader(int shaderType, String source) {        int shader = GLES20.glCreateShader(shaderType);        if (shader != 0) {            GLES20.glShaderSource(shader, source);            GLES20.glCompileShader(shader);            int[] compiled = new int[1];            GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, 0);            if (compiled[0] == 0) {                String info = GLES20.glGetShaderInfoLog(shader);                GLES20.glDeleteShader(shader);                shader = 0;                throw new RuntimeException("Could not compile shader " +                shaderType + ":" + info);            }        }        return shader;    }    public static int createProgram(String vertexSource,            String fragmentSource) {        int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, vertexSource);        if (vertexShader == 0) {            return 0;        }        int pixelShader = loadShader(GLES20.GL_FRAGMENT_SHADER, fragmentSource);        if (pixelShader == 0) {            return 0;        }        int program = GLES20.glCreateProgram();        if (program != 0) {            GLES20.glAttachShader(program, vertexShader);            checkGlError("glAttachShader");            GLES20.glAttachShader(program, pixelShader);            checkGlError("glAttachShader");            GLES20.glLinkProgram(program);            int[] linkStatus = new int[1];            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus,                    0);            if (linkStatus[0] != GLES20.GL_TRUE) {                String info = GLES20.glGetProgramInfoLog(program);                GLES20.glDeleteProgram(program);                program = 0;                throw new RuntimeException("Could not link program: " + info);            }        }        return program;    }    public static void checkGlError(String op) {        int error;        while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {            throw new RuntimeException(op + ": glError " + error);        }    }    public static void initTexParams() {        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,                GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,                GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,                GLES20.GL_CLAMP_TO_EDGE);        GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,                GLES20.GL_CLAMP_TO_EDGE);   }}

  这里就不提供整个工程了,结合上面的代码,自己在资源文件中提供一个图片载入就可以看到效果了。

更多相关文章

  1. android中Selector中的相关属性及配置写法
  2. android 图片解码显示流程
  3. android uiautomator 截取图片
  4. 浅谈android的selector,背景选择器
  5. Android中的Selector 背景选择器
  6. 浅谈android的selector背景选择器
  7. android的selector背景选择器
  8. Android中图片Bitmap的缩放
  9. 浅谈android的selector背景选择器

随机推荐

  1. mysql创建删除表的实例详解
  2. MySQL两种临时表的用法详解
  3. 初探SQL语句复合主键与联合主键
  4. SQL执行步骤的具体分析
  5. 浅析mysql union和union all
  6. mysql having用法解析
  7. mysql oracle和sqlserver分页查询实例解
  8. mysql中的limit用法有哪些(推荐)
  9. MySQL删除有外键约束的表数据方法介绍
  10. mysql 5.7.19 二进制最新安装