Camera2实现带照相框的可修改显示效果的自定义照相机

因为需要在照相页面显示一些效果,所以只能自己实现照相页面,5.0以下可以使用Camera+SurfaceView实现,在这里只考虑了5.0以上使用了Camera2+TextureView+GlSurfaceView实现。使用GlSurfaceView可以实现一些特殊预览显示效果,比如说黑白,美白,底片等。
这里是两种效果的样子:


ezgif.com-video-to-gif.gif
ezgif.com-video-to-gif.gif

下面是主要的Camera2 api的调用,这里需要注意的是图片处理,如果使用Intent传数据,就不能直接传bitmap对象,intent对图片大小有限制很容易超出崩溃,如果传递byte[] 也需要注意大小,有的手机像素过高也会导致崩溃,这里只进行了简单的图片压缩,照相的特殊效果使用的是ColorMatrix 的矩阵设置,这个仅仅是更改照相之后的照片效果,之后会介绍如何处理预览显示的效果:


import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.Image;
import android.media.ImageReader;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.util.Size;
import android.view.Surface;

import java.io.File;
import java.io.FileOutputStream;
import java.nio.ByteBuffer;
import java.util.Arrays;

/**
 * camera2 的方法调用封装
 */
public class CameraV2 {

    private Activity mActivity;
    private CameraDevice mCameraDevice;
    private String mCameraId;
    private Size mPreviewSize;
    private HandlerThread mCameraThread;
    private Handler mCameraHandler;
    private SurfaceTexture mSurfaceTexture;
    private CaptureRequest.Builder mCaptureRequestBuilder;
    private CaptureRequest mCaptureRequest;
    private CameraCaptureSession mCameraCaptureSession;
    private ImageReader mImageReader;

    private CaptureCallBack mCallBack;
    private boolean mIsPortrait=true;//默认为竖直状态

    public CameraV2(Activity activity) {
        mActivity = activity;
        startCameraThread();
    }

    /**
     *  初始配置,和拍照之后图片的图像数据回调
     * @param width 预览相机宽
     * @param height  预览相机高
     * @param fileName 图片保存的文件名
     */
    public void setupCamera(int width, int height, final String fileName) {
        final CameraManager cameraManager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
        try {
            if(cameraManager!=null) {
                for (String id : cameraManager.getCameraIdList()) {
                    CameraCharacteristics characteristics = cameraManager.getCameraCharacteristics(id);
                    if (characteristics.get(CameraCharacteristics.LENS_FACING) == CameraCharacteristics.LENS_FACING_FRONT) {//判断前后摄像头,这里使用后摄像头
                        continue;
                    }
                    StreamConfigurationMap map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
                    mPreviewSize = getCloselyPreSize(width, height, map.getOutputSizes(SurfaceTexture.class));
                    mCameraId = id;
                }
            }
            // 创建一个ImageReader对象,用于获取摄像头的图像数据
            mImageReader = ImageReader.newInstance(mPreviewSize.getWidth(),mPreviewSize.getHeight(),
                    ImageFormat.JPEG, 1);
            mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
                        // 当照片数据可用时激发该方法
                        @Override
                        public void onImageAvailable(final ImageReader reader) {
                            // 获取捕获的照片数据
                            Image image = reader.acquireNextImage();
                            ByteBuffer buffer = image.getPlanes()[0].getBuffer();
                            final byte[] bytes = new byte[buffer.remaining()];
                            buffer.get(bytes);
                            Handler handler=new Handler(Looper.getMainLooper());
                            handler.post(new Runnable() {
                                @Override
                                public void run() {
                                    BitmapFactory.Options options = new BitmapFactory.Options();
                                    //只保存图片尺寸大小,不保存图片到内存
                                    options.inJustDecodeBounds = false;
                                    //缩放比例
                                    options.inSampleSize = 2;
                                    // 根据拍照所得的数据创建位图
                                    Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0,
                                            bytes.length, options);
                                    int width = options.outWidth;
                                    Matrix matrix = new Matrix();
                                    matrix.setRotate(90);//旋转90度,照出来的图片初始是横着的
                                    float rate = (float)bitmap.getHeight() / 1080;//适配设置
                                    float marginRight = (float) 292 * rate;
                                    float marginTop= (float) 142 * rate;
                                    float cropBitmapWidth = (float) 1240 * rate;
                                    float cropBitmapHeight = (float) 796 * rate;
                                    //创建裁剪之后的bitmap,原图片不能直接操作
                                    bitmap = Bitmap.createBitmap(bitmap, (int)(width-marginRight-cropBitmapWidth)
                                            , (int)(marginTop)
                                            , (int)(cropBitmapWidth)
                                            , (int)(cropBitmapHeight), matrix, true);
                                    //这里是对照相得到的图片进行处理 黑白效果
                                    //Canvas canvas=new Canvas(bitmap);
                                    //Paint paint=new Paint();
                                    //ColorMatrix colorMatrix=new ColorMatrix(new float[]{
                                    //    0.213f, 0.715f,0.072f,0,0,
                                    //    0.213f, 0.715f,0.072f,0,0,
                                    //    0.213f, 0.715f,0.072f,0,0,
                                    //    0,0,0,1,0,
                                    //});
                                    //paint.setColorFilter(new ColorMatrixColorFilter(colorMatrix));
                                    //canvas.drawBitmap(bitmap,0,0,paint);
                                    //746px // 1198px
                                    mCallBack.photoData(bitmap);
                                }
                            });
                            if(fileName==null){
                                image.close();
                                return;
                            }
                            // 使用IO流将照片写入指定文件
                            File file = new File(fileName);
                            try (FileOutputStream output = new FileOutputStream(file)) {
                                output.write(bytes);
                            } catch (Exception e) {
                                e.printStackTrace();
                            }
                            finally {
                                image.close();
                            }
                        }
                    }, null);//最后一个参数可控制结果执行在哪个线程因为保存图片需在子线程执行,并没有指定线程
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 通过对比得到与宽高比最接近的尺寸(如果有相同尺寸,优先选择)
     *
     * @param surfaceWidth
     *            需要被进行对比的原宽
     * @param surfaceHeight
     *            需要被进行对比的原高
     * @param preSizeList
     *            需要对比的预览尺寸列表
     * @return 得到与原宽高比例最接近的尺寸
     */
    private Size getCloselyPreSize(int surfaceWidth, int surfaceHeight,
                                     Size[] preSizeList) {

        int reqTmpWidth;
        int reqTmpHeight;
        // 当屏幕为垂直的时候需要把宽高值进行调换,保证宽大于高
        if (mIsPortrait) {
            reqTmpWidth = surfaceHeight;
            reqTmpHeight = surfaceWidth;
        } else {
            reqTmpWidth = surfaceWidth;
            reqTmpHeight = surfaceHeight;
        }
        //先查找preview中是否存在与surfaceview相同宽高的尺寸
        for(Size size : preSizeList){
            if((size.getWidth() == reqTmpWidth) && (size.getHeight() == reqTmpHeight)){
                return size;
            }
        }

        // 得到与传入的宽高比最接近的size
        float reqRatio = ((float) reqTmpWidth) / reqTmpHeight;
        float curRatio, deltaRatio;
        float deltaRatioMin = Float.MAX_VALUE;
        Size retSize = null;
        for (Size size : preSizeList) {
            curRatio = ((float) size.getWidth()) / size.getHeight();
            deltaRatio = Math.abs(reqRatio - curRatio);
            if (deltaRatio < deltaRatioMin) {
                deltaRatioMin = deltaRatio;
                retSize = size;
            }
        }

        return retSize;
    }

    /**
     * 创建照相机执行线程
     */
    private void startCameraThread() {
        mCameraThread = new HandlerThread("CameraThread");
        mCameraThread.start();
        mCameraHandler = new Handler(mCameraThread.getLooper());
    }

    public boolean openCamera() {
        CameraManager cameraManager = (CameraManager) mActivity.getSystemService(Context.CAMERA_SERVICE);
        try {
            if (ActivityCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
                return false;
            }
            if(cameraManager!=null) {
                cameraManager.openCamera(mCameraId, mStateCallback, mCameraHandler);
            }
        } catch (CameraAccessException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    private CameraDevice.StateCallback mStateCallback = new CameraDevice.StateCallback() {
        @Override
        public void onOpened(@NonNull CameraDevice camera) {
            mCameraDevice = camera;
        }

        @Override
        public void onDisconnected(@NonNull CameraDevice camera) {
            camera.close();
            mCameraDevice = null;
        }

        @Override
        public void onError(@NonNull CameraDevice camera, int error) {
            camera.close();
            mCameraDevice = null;
        }
    };

    /**
     * 设置预览显示的surfaceTexture
     */
    public void setPreviewTexture(SurfaceTexture surfaceTexture) {
        mSurfaceTexture = surfaceTexture;
    }

    /**
     * 预览显示
     */
    public void startPreview() {
        mSurfaceTexture.setDefaultBufferSize(mPreviewSize.getWidth(), mPreviewSize.getHeight());
        Surface surface = new Surface(mSurfaceTexture);
        try {
            mCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
            mCaptureRequestBuilder.addTarget(surface);
            mCameraDevice.createCaptureSession(Arrays.asList(surface,mImageReader.getSurface()), new CameraCaptureSession.StateCallback() {
                @Override
                public void onConfigured(@NonNull CameraCaptureSession session) {
                    try {
                        mCaptureRequest = mCaptureRequestBuilder.build();
                        mCameraCaptureSession = session;
                        mCameraCaptureSession.setRepeatingRequest(mCaptureRequest, null, mCameraHandler);
                    } catch (CameraAccessException e) {
                        e.printStackTrace();
                    }
                }

                @Override
                public void onConfigureFailed(@NonNull CameraCaptureSession session) {
//                    ToastUtils.showLong("配置失败");
                }
            }, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    public void capture(CaptureCallBack callBack) {
        mCallBack=callBack;
        try {
            //首先我们创建请求拍照的CaptureRequest
            final CaptureRequest.Builder mCaptureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
            //设置CaptureRequest输出到mImageReader
            mCaptureBuilder.addTarget(mImageReader.getSurface());
            //设置拍照方向 默认竖屏
//            mCaptureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(ScreenUtils.getScreenRotation(ActivityUtils.getTopActivity())));
            //这个回调接口用于拍照结束时重启预览,因为拍照会导致预览停止
            mCameraCaptureSession.capture(mCaptureRequestBuilder.build(), new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
                    super.onCaptureCompleted(session, request, result);
                }
            }, mCameraHandler);
            CameraCaptureSession.CaptureCallback mImageSavedCallback = new CameraCaptureSession.CaptureCallback() {
                @Override
                public void onCaptureCompleted(@NonNull CameraCaptureSession session,@NonNull CaptureRequest request,@NonNull TotalCaptureResult result) {

                }
            };
            //停止预览
            mCameraCaptureSession.stopRepeating();
            //开始拍照,然后回调上面的接口重启预览,因为mCaptureBuilder设置ImageReader作为target,所以会自动回调ImageReader的onImageAvailable()方法保存图片
            mCameraCaptureSession.capture(mCaptureBuilder.build(), mImageSavedCallback, null);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    /**
     * 重新开启预览
     */
    public void restartPreview() {
        try {
            //执行setRepeatingRequest方法就行了,注意mCaptureRequest是之前开启预览设置的请求
            mCameraCaptureSession.setRepeatingRequest(mCaptureRequest, null, mCameraHandler);
        } catch (CameraAccessException e) {
            e.printStackTrace();
        }
    }

    public interface CaptureCallBack{
        void photoData(Bitmap bytes);
    }

    public void closeCamera(){
        mCameraDevice.close();
        mCameraDevice = null;
        mCameraThread.interrupt();
        mCameraHandler=null;
    }
}

自定义的GlSurfaceView,和GlSurfaceView渲染器

import android.content.Context;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;

/**
 * 显示界面
 */

public class CameraV2GLSurfaceView extends GLSurfaceView {

    public CameraV2GLSurfaceView(Context context) {
        super(context);
    }

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

    public void init(CameraV2 camera, boolean isPreviewStarted, Context context) {
        setEGLContextClientVersion(2);

        CameraV2Renderer cameraV2Renderer = new CameraV2Renderer();
        cameraV2Renderer.init(this, camera, isPreviewStarted, context);

        setRenderer(cameraV2Renderer);
    }
}
import android.content.Context;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11Ext;
import android.opengl.GLSurfaceView;
import java.nio.FloatBuffer;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import static android.opengl.GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
import static android.opengl.GLES20.GL_FLOAT;
import static android.opengl.GLES20.GL_FRAMEBUFFER;
import static android.opengl.GLES20.GL_TRIANGLES;
import static android.opengl.GLES20.glActiveTexture;
import static android.opengl.GLES20.glBindFramebuffer;
import static android.opengl.GLES20.glBindTexture;
import static android.opengl.GLES20.glClearColor;
import static android.opengl.GLES20.glDrawArrays;
import static android.opengl.GLES20.glEnableVertexAttribArray;
import static android.opengl.GLES20.glGenFramebuffers;
import static android.opengl.GLES20.glGetAttribLocation;
import static android.opengl.GLES20.glGetUniformLocation;
import static android.opengl.GLES20.glUniform1i;
import static android.opengl.GLES20.glUniformMatrix4fv;
import static android.opengl.GLES20.glVertexAttribPointer;
import static android.opengl.GLES20.glViewport;

/**
 * 渲染器
 */
public class CameraV2Renderer implements GLSurfaceView.Renderer {

    private Context mContext;
    private CameraV2GLSurfaceView mCameraV2GLSurfaceView;
    private CameraV2 mCamera;
    private boolean bIsPreviewStarted;
    private int mOESTextureId = -1;
    private SurfaceTexture mSurfaceTexture;
    private float[] transformMatrix = new float[16];
    private FloatBuffer mDataBuffer;
    private int mShaderProgram = -1;
    private int[] mFBOIds = new int[1];

    public void init(CameraV2GLSurfaceView surfaceView, CameraV2 camera, boolean isPreviewStarted, Context context) {
        mContext = context;
        mCameraV2GLSurfaceView = surfaceView;
        mCamera = camera;
        bIsPreviewStarted = isPreviewStarted;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        mOESTextureId = RawUtils.createOESTextureObject();
        FilterEngine filterEngine = new FilterEngine(mContext);
        mDataBuffer = filterEngine.getBuffer();
        mShaderProgram = filterEngine.getShaderProgram();
        glGenFramebuffers(1, mFBOIds, 0);
        glBindFramebuffer(GL_FRAMEBUFFER, mFBOIds[0]);
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        glViewport(0, 0, width, height);
    }

    @Override
    public void onDrawFrame(GL10 gl) {
        if (mSurfaceTexture != null) {
            mSurfaceTexture.updateTexImage();
            mSurfaceTexture.getTransformMatrix(transformMatrix);
        }

        if (!bIsPreviewStarted) {
            bIsPreviewStarted = initSurfaceTexture();
            bIsPreviewStarted = true;
            return;
        }

        //glClear(GL_COLOR_BUFFER_BIT);
        glClearColor(1.0f, 0.0f, 0.0f, 0.0f);

        int aPositionLocation = glGetAttribLocation(mShaderProgram, FilterEngine.POSITION_ATTRIBUTE);
        int aTextureCoordLocation = glGetAttribLocation(mShaderProgram, FilterEngine.TEXTURE_COORD_ATTRIBUTE);
        int uTextureMatrixLocation = glGetUniformLocation(mShaderProgram, FilterEngine.TEXTURE_MATRIX_UNIFORM);
        int uTextureSamplerLocation = glGetUniformLocation(mShaderProgram, FilterEngine.TEXTURE_SAMPLER_UNIFORM);

        glActiveTexture(GL_TEXTURE_EXTERNAL_OES);
        glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, mOESTextureId);
        glUniform1i(uTextureSamplerLocation, 0);
        glUniformMatrix4fv(uTextureMatrixLocation, 1, false, transformMatrix, 0);

        if (mDataBuffer != null) {
            mDataBuffer.position(0);
            glEnableVertexAttribArray(aPositionLocation);
            glVertexAttribPointer(aPositionLocation, 2, GL_FLOAT, false, 16, mDataBuffer);

            mDataBuffer.position(2);
            glEnableVertexAttribArray(aTextureCoordLocation);
            glVertexAttribPointer(aTextureCoordLocation, 2, GL_FLOAT, false, 16, mDataBuffer);
        }

        //glDrawElements(GL_TRIANGLE_FAN, 6,GL_UNSIGNED_INT, 0);
        //glDrawArrays(GL_TRIANGLE_FAN, 0 , 6);
        glDrawArrays(GL_TRIANGLES, 0, 6);
        //glDrawArrays(GL_TRIANGLES, 3, 3);
        glBindFramebuffer(GL_FRAMEBUFFER, 0);
    }

    /**
     * 初始化SurfaceTexture
     */
    private boolean initSurfaceTexture() {
        if (mCamera == null || mCameraV2GLSurfaceView == null) {
            return false;
        }
        mSurfaceTexture = new SurfaceTexture(mOESTextureId);
        mSurfaceTexture.setOnFrameAvailableListener(new SurfaceTexture.OnFrameAvailableListener() {
            @Override
            public void onFrameAvailable(SurfaceTexture surfaceTexture) {
                mCameraV2GLSurfaceView.requestRender();

            }
        });
        mCamera.setPreviewTexture(mSurfaceTexture);
        mCamera.startPreview();
        return true;
    }
}
import android.content.Context;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import static android.opengl.GLES20.GL_FRAGMENT_SHADER;
import static android.opengl.GLES20.GL_VERTEX_SHADER;
import static android.opengl.GLES20.glAttachShader;
import static android.opengl.GLES20.glCompileShader;
import static android.opengl.GLES20.glCreateProgram;
import static android.opengl.GLES20.glCreateShader;
import static android.opengl.GLES20.glGetError;
import static android.opengl.GLES20.glLinkProgram;
import static android.opengl.GLES20.glShaderSource;
import static android.opengl.GLES20.glUseProgram;

public class FilterEngine {

    private FloatBuffer mBuffer;

    private int mShaderProgram;

    public FilterEngine( Context context) {
        mBuffer = createBuffer(vertexData);
        int vertexShader = loadShader(GL_VERTEX_SHADER, RawUtils.readShaderFromResource(context, R.raw.base_vertex_shader));
        int fragmentShader = loadShader(GL_FRAGMENT_SHADER, RawUtils.readShaderFromResource(context, R.raw.base_fragment_shader));
        mShaderProgram = linkProgram(vertexShader, fragmentShader);
    }

    private static final float[] vertexData = {
            1f, 1f, 1f, 1f,
            -1f, 1f, 0f, 1f,
            -1f, -1f, 0f, 0f,
            1f, 1f, 1f, 1f,
            -1f, -1f, 0f, 0f,
            1f, -1f, 1f, 0f
    };

    public static final String POSITION_ATTRIBUTE = "aPosition";
    public static final String TEXTURE_COORD_ATTRIBUTE = "aTextureCoordinate";
    public static final String TEXTURE_MATRIX_UNIFORM = "uTextureMatrix";
    public static final String TEXTURE_SAMPLER_UNIFORM = "uTextureSampler";

    private FloatBuffer createBuffer(float[] vertexData) {
        FloatBuffer buffer = ByteBuffer.allocateDirect(vertexData.length * 4)
                .order(ByteOrder.nativeOrder())
                .asFloatBuffer();
        buffer.put(vertexData, 0, vertexData.length).position(0);
        return buffer;
    }

    private int loadShader(int type, String shaderSource) {
        int shader = glCreateShader(type);
        if (shader == 0) {
            throw new RuntimeException("Create Shader Failed!" + glGetError());
        }
        glShaderSource(shader, shaderSource);
        glCompileShader(shader);
        return shader;
    }

    private int linkProgram(int verShader, int fragShader) {
        int program = glCreateProgram();
        if (program == 0) {
            throw new RuntimeException("Create Program Failed!" + glGetError());
        }
        glAttachShader(program, verShader);
        glAttachShader(program, fragShader);
        glLinkProgram(program);

        glUseProgram(program);
        return program;
    }

    public int getShaderProgram() {
        return mShaderProgram;
    }

    public FloatBuffer getBuffer() {
        return mBuffer;
    }
}

其中的base_fragment_shader这种资源文件需要在如图所示的地方创建:


7B0FE788-D018-4ccf-B1C4-390ABA9597F0.png

内容分别为:
base_fragment_shader(就是在这里处理预览的显示特殊效果):

#extension GL_OES_EGL_image_external : require
 precision mediump float;
 uniform samplerExternalOES uTextureSampler;
 varying vec2 vTextureCoord;
 void main()
 {
   vec4 vCameraColor = texture2D(uTextureSampler, vTextureCoord);
   float fGrayColor = (0.3*vCameraColor.r + 0.59*vCameraColor.g + 0.11*vCameraColor.b);//黑白滤镜
   gl_FragColor = vec4(vCameraColor.r, vCameraColor.g, vCameraColor.b, 1.0);
 }

base_vertex_shader:

attribute vec4 aPosition;
uniform mat4 uTextureMatrix;
attribute vec4 aTextureCoordinate;
varying vec2 vTextureCoord;
void main()
{
  vTextureCoord = (uTextureMatrix * aTextureCoordinate).xy;
  gl_Position = aPosition;
}

工具类:

import android.content.Context;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.microedition.khronos.opengles.GL10;

public class RawUtils {
    public static int createOESTextureObject() {
        int[] tex = new int[1];
        GLES20.glGenTextures(1, tex, 0);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, tex[0]);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_S, GL10.GL_CLAMP_TO_EDGE);
        GLES20.glTexParameterf(GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
                GL10.GL_TEXTURE_WRAP_T, GL10.GL_CLAMP_TO_EDGE);
        GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, 0);
        return tex[0];
    }

    public static String readShaderFromResource(Context context, int resourceId) {
        StringBuilder builder = new StringBuilder();
        InputStream is = null;
        InputStreamReader isr = null;
        BufferedReader br = null;
        try {
            is = context.getResources().openRawResource(resourceId);
            isr = new InputStreamReader(is);
            br = new BufferedReader(isr);
            String line;
            while ((line = br.readLine()) != null) {
                builder.append(line + "\n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (is != null) {
                    is.close();
                    is = null;
                }
                if (isr != null) {
                    isr.close();
                    isr = null;
                }
                if (br != null) {
                    br.close();
                    br = null;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return builder.toString();
    }
}

这里还需要一个显示中间框的自定义view,这里的大小位置直接写死了,可以根据需求更改:

/**
 * 显示照相机界面
 */
public class PreviewBorderView extends SurfaceView implements SurfaceHolder.Callback, Runnable {
    private int mScreenH;
    private int mScreenW;
    private Canvas mCanvas;
    private Paint mPaint;
    private SurfaceHolder mHolder;
    private Thread mThread;

    public PreviewBorderView(Context context) {
        this(context, null);
    }

    public PreviewBorderView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public PreviewBorderView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    /**
     * 初始化绘图变量
     */
    private void init() {
        this.mHolder = getHolder();
        this.mHolder.addCallback(this);
        this.mHolder.setFormat(PixelFormat.TRANSPARENT);
        setZOrderOnTop(true);
        this.mPaint = new Paint();
        this.mPaint.setAntiAlias(true);
        this.mPaint.setColor(Color.WHITE);
        this.mPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        this.mPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.CLEAR));
        setKeepScreenOn(true);
    }

    /**
     * 绘制取景框
     */
    private void draw() {
        try {
            this.mCanvas = this.mHolder.lockCanvas();
            this.mCanvas.drawARGB(100, 0, 0, 0);
            float rate = (float)mScreenW / 1080;
            this.mCanvas.drawRect(new RectF(162*rate
                    ,  mScreenH-312*rate-1200*rate, mScreenW-162*rate
                    , mScreenH-312*rate), this.mPaint);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (this.mCanvas != null) {
                this.mHolder.unlockCanvasAndPost(this.mCanvas);
            }
        }
    }


    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        //获得宽高,开启子线程绘图
        this.mScreenW = getWidth();
        this.mScreenH = getHeight();
        this.mThread = new Thread(this);
        this.mThread.start();
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {

    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        //停止线程
        try {
            mThread.interrupt();
            mThread = null;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        //子线程绘图
        draw();
    }
}

照相显示界面:

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.os.Bundle;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import java.io.ByteArrayOutputStream;

import butterknife.BindView;
import butterknife.ButterKnife;

public class CameraActivity extends Activity implements View.OnClickListener {

    @BindView(R.id.camera_take_photo_text_view)
    ImageView tradingCameraTakePhotoTextView;
    @BindView(R.id.camera_retry_text_view)
    TextView tradingCameraRetryTextView;
    @BindView(R.id.camera_sure_text_view)
    ImageView tradingCameraSureTextView;
    @BindView(R.id.camera_bottom_relative_layout)
    RelativeLayout tradingCameraBottomRelativeLayout;
    @BindView(R.id.camera_middle_image_view)
    ImageView tradingCameraMiddleImageView;
    @BindView(R.id.camera_hint_text_view)
    TextView tradingCameraHintTextView;
    @BindView(R.id.camera_close_image_view)
    ImageView tradingCameraCloseImageView;
    @BindView(R.id.trading_record_surface_view)
    CameraV2GLSurfaceView tradingRecordSurfaceView;
    @BindView(R.id.camera_bg_view)
    View tradingCameraBgView;
    private CameraV2 mCamera;
    private Bitmap mPhotoBitmap;
    private String mPath;

    public static Intent newIntent(Context context,String path){
        Intent intent=new Intent(context,CameraActivity.class);
        intent.putExtra("path",path);
        return intent;
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        this.requestWindowFeature(Window.FEATURE_NO_TITLE);
        getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,  WindowManager.LayoutParams.FLAG_FULLSCREEN);
        hideBottomUIMenu();
        super.onCreate(savedInstanceState);
        mPath = getIntent().getStringExtra("path");
        setContentView(R.layout.activity_camera);
        ButterKnife.bind(this);
        initView();
    }

    private void initView() {
        tradingCameraTakePhotoTextView.setOnClickListener(this);
        tradingCameraRetryTextView.setOnClickListener(this);
        tradingCameraSureTextView.setOnClickListener(this);
        tradingCameraCloseImageView.setOnClickListener(this);
        mCamera = new CameraV2(this);
        mCamera.setupCamera(ScreenUtils.getScreenWidth(), ScreenUtils.getScreenHeight(),mPath);
        if (!mCamera.openCamera()) {
            return;
        }
        tradingRecordSurfaceView.init(mCamera, false, this);
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.camera_close_image_view:
                finish();
                break;
            case R.id.camera_take_photo_text_view:
                mCamera.capture(new CameraV2.CaptureCallBack() {
                    @Override
                    public void photoData(Bitmap bitmap) {
                        mPhotoBitmap = bitmap;
                        tradingCameraMiddleImageView.setImageBitmap(bitmap);
                        tradingCameraBgView.setVisibility(View.VISIBLE);
                        tradingCameraRetryTextView.setVisibility(View.VISIBLE);
                        tradingCameraSureTextView.setVisibility(View.VISIBLE);
                        tradingCameraTakePhotoTextView.setVisibility(View.GONE);
                    }
                });
                break;
            case R.id.camera_retry_text_view:
                tradingCameraMiddleImageView.setImageBitmap(null);
                tradingCameraRetryTextView.setVisibility(View.GONE);
                tradingCameraSureTextView.setVisibility(View.GONE);
                tradingCameraTakePhotoTextView.setVisibility(View.VISIBLE);
                tradingCameraBgView.setVisibility(View.GONE);
                mCamera.restartPreview();
                break;
            case R.id.camera_sure_text_view://bitmap不能直接回传图片过大,导致卡在页面
                Intent intent=new Intent();
                ByteArrayOutputStream baos = new ByteArrayOutputStream();
                mPhotoBitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos);
                byte[] datas = baos.toByteArray();
                intent.putExtra("photo",datas);
                setResult(RESULT_OK,intent);
                finish();
                break;
        }
    }

    @Override
    protected void onDestroy() {
        mCamera.closeCamera();
        super.onDestroy();
    }

    /**

     * 隐藏虚拟按键,并且全屏
     */
    protected void hideBottomUIMenu() {
        //for new api versions.
        View decorView = getWindow().getDecorView();
        int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY | View.SYSTEM_UI_FLAG_FULLSCREEN;
        decorView.setSystemUiVisibility(uiOptions);
    }
}

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

    <com.zqb.camera2.CameraV2GLSurfaceView
        android:id="@+id/trading_record_surface_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    <com.zqb.camera2.PreviewBorderView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
    <View
        android:visibility="gone"
        android:id="@+id/camera_bg_view"
        android:background="#000"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>
    <ImageView
        android:tint="#fff"
        android:id="@+id/camera_close_image_view"
        android:src="@mipmap/global_close_btn"
        android:layout_margin="72px"
        android:layout_width="60px"
        android:layout_height="60px" />

    <RelativeLayout
        android:id="@+id/camera_bottom_relative_layout"
        android:layout_marginBottom="48px"
        android:layout_marginTop="74px"
        android:layout_alignParentBottom="true"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <ImageView
            android:id="@+id/camera_take_photo_text_view"
            android:layout_centerHorizontal="true"
            android:scaleType="fitXY"
            android:src="@drawable/selector_btn_take_photo"
            android:layout_width="188px"
            android:layout_height="188px" />
        <TextView
            android:id="@+id/camera_retry_text_view"
            android:visibility="gone"
            android:layout_marginLeft="264px"
            android:textSize="44px"
            android:text="重新拍照"
            android:layout_centerVertical="true"
            android:textColor="#fff"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
        <ImageView
            android:id="@+id/camera_sure_text_view"
            android:visibility="gone"
            android:layout_marginRight="380px"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:scaleType="fitXY"
            android:src="@drawable/selector_btn_take_photo_sure"
            android:layout_width="160px"
            android:layout_height="160px" />
    </RelativeLayout>

    <ImageView
        android:scaleType="fitXY"
        android:layout_centerHorizontal="true"
        android:layout_alignParentBottom="true"
        android:layout_marginBottom="312px"
        android:id="@+id/camera_middle_image_view"
        android:layout_width="756px"
        android:layout_height="1200px"
        android:background="@mipmap/global_camera_bg"/>
    <TextView
        android:id="@+id/camera_hint_text_view"
        android:layout_above="@id/camera_middle_image_view"
        android:layout_marginBottom="58px"
        android:layout_centerHorizontal="true"
        android:textSize="44px"
        android:text="请对准相框"
        android:textColor="#4448ed"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</RelativeLayout>

最开始调用显示界面(布局就不贴了,就一个简单的imageview):

import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;

import butterknife.BindView;
import butterknife.ButterKnife;

public class MainActivity extends AppCompatActivity {

    @BindView(R.id.image_view)
    ImageView imageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        ButterKnife.bind(this);
        imageView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = CameraActivity.newIntent(MainActivity.this,null);
                startActivityForResult(intent,1);
            }
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode==1 && resultCode==RESULT_OK){
            Bundle extras = data.getExtras();
            if(extras!=null) {
                byte[] photos = data.getByteArrayExtra("photo");
                Bitmap bitmap = BitmapFactory.decodeByteArray(photos, 0, photos.length);
                imageView.setImageBitmap(bitmap);
            }
        }
    }
}

注:
ColorMatrix和gl中RGB相加=1就为黑白,矩阵那可以随意更改只要保证RGB的三个横排相加等于1就可以,可以在这里自由设置显示不同效果。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,802评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,109评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,683评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,458评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,452评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,505评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,901评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,550评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,763评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,556评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,629评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,330评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,898评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,897评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,140评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,807评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,339评论 2 342

推荐阅读更多精彩内容