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 uiautomator 截取图片
  2. android 图片解码显示流程
  3. Android中图片Bitmap的缩放
  4. Android 异步加载图片分析
  5. 2011.10.14(2)——— android 仿照微信的图片展示功能 之 放大超过
  6. Android 开机图片/文字/动画 修改
  7. Qt for Android 调用android原生接口分享图片或文字

随机推荐

  1. Android——字符高亮显示
  2. 【Android测试工具】02. Android抓包解析
  3. Android开机自启
  4. android 的动画
  5. Android: PLEASE DO NOT USE A WAKE LOCK
  6. android emulator-5554 disconnected! Ca
  7. Android learning - Activities
  8. 移动APP自动化漏洞检测平台架构
  9. ktolin在Android中布局界面拼接
  10. android权限管理