zoukankan      html  css  js  c++  java
  • Android OpenGL 编写简单滤镜

      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;
    " +
            "attribute vec2 a_texcoord;
    " +
            "varying vec2 v_texcoord;
    " +
            "void main() {
    " +
            "  gl_Position = a_position;
    " +
            "  v_texcoord = a_texcoord;
    " +
            "}
    ";
    
        private static final String FRAGMENT_SHADER =
            "precision mediump float;
    " +
            "uniform sampler2D tex_sampler;
    " +
            "varying vec2 v_texcoord;
    " +
            "void main() {
    " +
            "  gl_FragColor = texture2D(tex_sampler, v_texcoord);
    " +
            "}
    ";
    
        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);
       }
    
    }

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

  • 相关阅读:
    SPI Flash(W25Q16DV) 驱动
    SPI Flash(W25Q16DV) 基本操作
    SPI OLED 驱动
    dategrip破解
    mysql中find_in_set()函数的使用
    MYSQL中,CAST函数的使用规则
    关于Java对象作为参数传递是传值还是传引用的问题
    easyPOI导出excel报错
    ### Cause: java.lang.reflect.UndeclaredThrowableException
    用count(*)还是count(列名) || Mysql中的count()与sum()区别
  • 原文地址:https://www.cnblogs.com/zhuyp1015/p/4513355.html
Copyright © 2011-2022 走看看