Android多种方式实现相机圆形预览

Android多种方式实现相机圆形预览,第1张

概述效果图如下: 一、为预览控件设置圆角 为控件设置ViewOutlineProvider public RoundTextureView(Context context, AttributeSet attrs) { super(context, attrs); setOutlineProvider(new ViewOutlineProvider() {

效果图如下:

一、为预览控件设置圆角

为控件设置ViewOutlineProvider

public RoundTextureVIEw(Context context,AttributeSet attrs) {        super(context,attrs);        setoutlineProvIDer(new VIEwOutlineProvIDer() {            @OverrIDe            public voID getoutline(VIEw vIEw,Outline outline) {                Rect rect = new Rect(0,vIEw.getMeasureDWIDth(),vIEw.getMeasuredHeight());                outline.setRoundRect(rect,radius);            }        });        setClipToOutline(true);    }

在需要时修改圆角值并更新

    public voID seTradius(int radius) {        this.radius = radius;    }    public voID turnRound() {        invalIDateOutline();    }

即可根据设置的圆角值更新控件显示的圆角大小。当控件为正方形,且圆角值为边长的一半,显示的就是圆形

二、实现正方形预览 1. 设备支持1:1预览尺寸

首先介绍一种简单但是局限性较大的实现方式:将相机预览尺寸和预览控件的大小都调整为1:1。

一般AndroID设备都支持多种预览尺寸,以Samsung Tab S3为例

在使用Camera API时,其支持的预览尺寸如下:
2019-08-02 13:16:08.669 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPrevIEwSize: 1920x10802019-08-02 13:16:08.669 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPrevIEwSize: 1280x7202019-08-02 13:16:08.669 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPrevIEwSize: 1440x10802019-08-02 13:16:08.669 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPrevIEwSize: 1088x10882019-08-02 13:16:08.670 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPrevIEwSize: 1056x8642019-08-02 13:16:08.670 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPrevIEwSize: 960x7202019-08-02 13:16:08.670 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPrevIEwSize: 720x4802019-08-02 13:16:08.670 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPrevIEwSize: 640x4802019-08-02 13:16:08.670 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPrevIEwSize: 352x2882019-08-02 13:16:08.670 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPrevIEwSize: 320x2402019-08-02 13:16:08.670 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPrevIEwSize: 176x144

其中1:1的预览尺寸为:1088x1088。

在使用Camera2 API时,其支持的预览尺寸(其实也包含了PictureSize)如下:
2019-08-02 13:19:24.980 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 4128x30962019-08-02 13:19:24.980 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 4128x23222019-08-02 13:19:24.980 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 3264x24482019-08-02 13:19:24.980 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 3264x18362019-08-02 13:19:24.980 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 3024x30242019-08-02 13:19:24.980 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2976x29762019-08-02 13:19:24.980 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2880x21602019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2592x19442019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2560x19202019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2560x14402019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2560x10802019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2160x21602019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2048x15362019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2048x11522019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 1936x19362019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 1920x10802019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 1440x10802019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 1280x9602019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 1280x7202019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 960x7202019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 720x4802019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 640x4802019-08-02 13:19:24.982 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 320x2402019-08-02 13:19:24.982 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 176x144

  

 

其中1:1的预览尺寸为:3024x3024、2976x2976、2160x2160、1936x1936。

只要我们选择1:1的预览尺寸,再将预览控件设置为正方形,即可实现正方形预览;
再通过设置预览控件的圆角为边长的一半,即可实现圆形预览。

2. 设备不支持1:1预览尺寸的情况

选择1:1预览尺寸的缺陷分析

分辨率局限性
上述说到,我们可以选择1:1的预览尺寸进行预览,但是局限性较高,
可选择范围都很小。如果相机不支持1:1的预览尺寸,这个方案就不可行了。 资源消耗
以Samsung tab S3为例,该设备使用Camera2 API时,支持的正方形预览尺寸都很大,在进行图像处理等 *** 作时将占用较多系统资源。

处理不支持1:1预览尺寸的情况

添加一个1:1尺寸的VIEwGroup 将TextureVIEw放入VIEwGroup 设置TextureVIEw的margin值以达到显示中心正方形区域的效果  

示意图

示例代码

   //将预览控件和预览尺寸比例保持一致,避免拉伸    {        FrameLayout.LayoutParams textureVIEwLayoutParams = (FrameLayout.LayoutParams) textureVIEw.getLayoutParams();        int newHeight = 0;        int newWIDth = textureVIEwLayoutParams.wIDth;        //横屏        if (displayOrIEntation % 180 == 0) {            newHeight = textureVIEwLayoutParams.wIDth * prevIEwSize.height / prevIEwSize.wIDth;        }        //竖屏        else {            newHeight = textureVIEwLayoutParams.wIDth * prevIEwSize.wIDth / prevIEwSize.height;        }        ////当不是正方形预览的情况下,添加一层VIEwGroup限制VIEw的显示区域        if (newHeight != textureVIEwLayoutParams.height) {           insertFrameLayout = new RoundFrameLayout(CoverByParentCameraActivity.this);           int sIDeLength = Math.min(newWIDth,newHeight);           FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(sIDeLength,sIDeLength);           insertFrameLayout.setLayoutParams(layoutParams);           FrameLayout parentVIEw = (FrameLayout) textureVIEw.getParent();           parentVIEw.removeVIEw(textureVIEw);           parentVIEw.addVIEw(insertFrameLayout);           insertFrameLayout.addVIEw(textureVIEw);           FrameLayout.LayoutParams newTextureVIEwLayoutParams = new FrameLayout.LayoutParams(newWIDth,newHeight);           //横屏           if (displayOrIEntation % 180 == 0) {               newTextureVIEwLayoutParams.leftmargin = ((newHeight - newWIDth) / 2);           }           //竖屏           else {               newTextureVIEwLayoutParams.topmargin = -(newHeight - newWIDth) / 2;           }           textureVIEw.setLayoutParams(newTextureVIEwLayoutParams);        }    }

  

 
三、使用GLSurfaceVIEw进行自定义程度更高的预览

使用上面的方法 *** 作已经可完成正方形和圆形预览,但是仅适用于原生相机,当我们的数据源并非是原生相机的情况时如何进行圆形预览?接下来介绍使用GLSurfaceVIEw显示NV21的方案,完全是自己实现预览数据的绘制。

1. GLSurfaceVIEw使用流程

OpenGL渲染YUV数据流程

其中的重点是渲染器(Renderer)的编写,Renderer的介绍如下:

   /**     * A generic renderer interface.     * <p>     * The renderer is responsible for making OpenGL calls to render a frame.     * <p>     * GLSurfaceVIEw clIEnts typically create their own classes that implement     * this interface,and then call {@link GLSurfaceVIEw#setRenderer} to     * register the renderer with the GLSurfaceVIEw.     * <p>     *     * <div >     * <h3>Developer GuIDes</h3>     * <p>For more information about how to use OpenGL,read the     * <a href="{@docRoot}guIDe/topics/graphics/opengl.HTML">OpenGL</a> developer guIDe.</p>     * </div>     *     * <h3>Threading</h3>     * The renderer will be called on a separate thread,so that rendering     * performance is decoupled from the UI thread. ClIEnts typically need to     * communicate with the renderer from the UI thread,because that‘s where     * input events are received. ClIEnts can communicate using any of the     * standard Java techniques for cross-thread communication,or they can     * use the {@link GLSurfaceVIEw#queueEvent(Runnable)} convenIEnce method.     * <p>     * <h3>EGL Context Lost</h3>     * There are situations where the EGL rendering context will be lost. This     * typically happens when device wakes up after going to sleep. When     * the EGL context is lost,all OpenGL resources (such as textures) that are     * associated with that context will be automatically deleted. In order to     * keep rendering correctly,a renderer must recreate any lost resources     * that it still needs. The {@link #onSurfaceCreated(GL10,EGLConfig)} method     * is a convenIEnt place to do this.     *     *     * @see #setRenderer(Renderer)     */    public interface Renderer {        /**         * Called when the surface is created or recreated.         * <p>         * Called when the rendering thread         * starts and whenever the EGL context is lost. The EGL context will typically         * be lost when the AndroID device awakes after going to sleep.         * <p>         * Since this method is called at the beginning of rendering,as well as         * every time the EGL context is lost,this method is a convenIEnt place to put         * code to create resources that need to be created when the rendering         * starts,and that need to be recreated when the EGL context is lost.         * Textures are an example of a resource that you might want to create         * here.         * <p>         * Note that when the EGL context is lost,all OpenGL resources associated         * with that context will be automatically deleted. You do not need to call         * the corresponding "glDelete" methods such as glDeleteTextures to         * manually delete these lost resources.         * <p>         * @param gl the GL interface. Use <code>instanceof</code> to         * test if the interface supports GL11 or higher interfaces.         * @param config the EGLConfig of the created surface. Can be used         * to create matching pbuffers.         */        voID onSurfaceCreated(GL10 gl,EGLConfig config);        /**         * Called when the surface changed size.         * <p>         * Called after the surface is created and whenever         * the OpenGL ES surface size changes.         * <p>         * Typically you will set your vIEwport here. If your camera         * is fixed then you Could also set your projection matrix here:         * <pre >         * voID onSurfaceChanged(GL10 gl,int wIDth,int height) {         *     gl.glVIEwport(0,wIDth,height);         *     // for a fixed camera,set the projection too         *     float ratio = (float) wIDth / height;         *     gl.glMatrixMode(GL10.GL_PROJECTION);         *     gl.glLoadIDentity();         *     gl.glFrustumf(-ratio,ratio,-1,1,10);         * }         * </pre>         * @param gl the GL interface. Use <code>instanceof</code> to         * test if the interface supports GL11 or higher interfaces.         * @param wIDth         * @param height         */        voID onSurfaceChanged(GL10 gl,int height);        /**         * Called to draw the current frame.         * <p>         * This method is responsible for drawing the current frame.         * <p>         * The implementation of this method typically looks like this:         * <pre >         * voID onDrawFrame(GL10 gl) {         *     gl.glClear(GL10.GL_color_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);         *     //... other gl calls to render the scene ...         * }         * </pre>         * @param gl the GL interface. Use <code>instanceof</code> to         * test if the interface supports GL11 or higher interfaces.         */        voID onDrawFrame(GL10 gl);    }

  

 
voID onSurfaceCreated(GL10 gl,EGLConfig config)
在Surface创建或重建的情况下回调 voID onSurfaceChanged(GL10 gl,int height)
在Surface的大小发生变化的情况下回调 voID onDrawFrame(GL10 gl)
在这里实现绘制 *** 作。当我们设置的renderModeRENDERMODE_CONTINUOUSLY时,该函数将不断地执行;
当我们设置的renderModeRENDERMODE_WHEN_DIRTY时,将只在创建完成和调用requestRender后才执行。一般我们选择RENDERMODE_WHEN_DIRTY渲染模式,避免过度绘制。

一般情况下,我们会自己实现一个Renderer,然后为GLSurfaceVIEw设置Renderer,可以说,Renderer的编写是整个流程的核心步骤。以下是在voID onSurfaceCreated(GL10 gl,EGLConfig config)进行的初始化 *** 作和在voID onDrawFrame(GL10 gl)进行的绘制 *** 作的流程图:

 

渲染YUV数据的Renderer

 

2. 具体实现 坐标系介绍

                                                AndroID VIEw坐标系                                                                                                                         OpenGL世界坐标系 着色器编写
   /**     * 顶点着色器     */    private static String VERTEX_SHADER =            "    attribute vec4 attr_position;\n" +                    "    attribute vec2 attr_tc;\n" +                    "    varying vec2 tc;\n" +                    "    voID main() {\n" +                    "        gl_position = attr_position;\n" +                    "        tc = attr_tc;\n" +                    "    }";    /**     * 片段着色器     */    private static String FRAG_SHADER =            "    varying vec2 tc;\n" +                    "    uniform sampler2D ySampler;\n" +                    "    uniform sampler2D uSampler;\n" +                    "    uniform sampler2D vSampler;\n" +                    "    const mat3 convertMat = mat3( 1.0,1.0,-0.001,-0.3441,1.772,1.402,-0.7141,-0.58060);\n" +                    "    voID main()\n" +                    "    {\n" +                    "        vec3 yuv;\n" +                    "        yuv.x = texture2D(ySampler,tc).r;\n" +                    "        yuv.y = texture2D(uSampler,tc).r - 0.5;\n" +                    "        yuv.z = texture2D(vSampler,tc).r - 0.5;\n" +                    "        gl_Fragcolor = vec4(convertMat * yuv,1.0);\n" +                    "    }";

 

内建变量解释

gl_position
VERTEX_SHADER代码里的gl_position代表绘制的空间坐标。由于我们是二维绘制,所以直接传入OpenGL二维坐标系的左下(-1,-1)、右下(1,-1)、左上(-1,1)、右上(1,1),也就是{-1,1} gl_Fragcolor
FRAG_SHADER代码里的gl_Fragcolor代表单个片元的颜色

其他变量解释

ySampleruSamplervSampler
分别代表Y、U、V纹理采样器 convertMat
根据以下公式:
R = Y + 1.402 (V - 128)G = Y - 0.34414 (U - 128) - 0.71414 (V - 128)B = Y + 1.772 (U - 128)
我们可得到一个YUV转RGB的矩阵
1.0,-0.344,1.77,1.403,-0.714,0 

 

部分类型、函数的解释

vec3、vec4
分别代表三维向量、四维向量。 vec4 texture2D(sampler2D sampler,vec2 coord)
以指定的矩阵将采样器的图像纹理转换为颜色值;如:
texture2D(ySampler,tc).r获取到的是Y数据,
texture2D(uSampler,tc).r获取到的是U数据,
texture2D(vSampler,tc).r获取到的是V数据。

在Java代码中进行初始化
根据图像宽高创建Y、U、V对应的ByteBuffer纹理数据;
根据是否镜像显示、旋转角度选择对应的转换矩阵;

public voID init(boolean isMirror,int rotateDegree,int frameWIDth,int frameHeight) {    if (this.frameWIDth == frameWIDth            && this.frameHeight == frameHeight            && this.rotateDegree == rotateDegree            && this.isMirror == isMirror) {        return;    }    datainput = false;    this.frameWIDth = frameWIDth;    this.frameHeight = frameHeight;    this.rotateDegree = rotateDegree;    this.isMirror = isMirror;    yArray = new byte[this.frameWIDth * this.frameHeight];    uArray = new byte[this.frameWIDth * this.frameHeight / 4];    vArray = new byte[this.frameWIDth * this.frameHeight / 4];    int yFrameSize = this.frameHeight * this.frameWIDth;    int uvFrameSize = yFrameSize >> 2;    yBuf = ByteBuffer.allocateDirect(yFrameSize);    yBuf.order(ByteOrder.nativeOrder()).position(0);    uBuf = ByteBuffer.allocateDirect(uvFrameSize);    uBuf.order(ByteOrder.nativeOrder()).position(0);    vBuf = ByteBuffer.allocateDirect(uvFrameSize);    vBuf.order(ByteOrder.nativeOrder()).position(0);    // 顶点坐标    squareVertices = ByteBuffer            .allocateDirect(glutil.SQUARE_VERTICES.length * float_SIZE_BYTES)            .order(ByteOrder.nativeOrder())            .asfloatBuffer();    squareVertices.put(glutil.SQUARE_VERTICES).position(0);    //纹理坐标    if (isMirror) {        switch (rotateDegree) {            case 0:                coordVertice = glutil.MIRROR_COORD_VERTICES;                break;            case 90:                coordVertice = glutil.ROTATE_90_MIRROR_COORD_VERTICES;                break;            case 180:                coordVertice = glutil.ROTATE_180_MIRROR_COORD_VERTICES;                break;            case 270:                coordVertice = glutil.ROTATE_270_MIRROR_COORD_VERTICES;                break;            default:                break;        }    } else {        switch (rotateDegree) {            case 0:                coordVertice = glutil.COORD_VERTICES;                break;            case 90:                coordVertice = glutil.ROTATE_90_COORD_VERTICES;                break;            case 180:                coordVertice = glutil.ROTATE_180_COORD_VERTICES;                break;            case 270:                coordVertice = glutil.ROTATE_270_COORD_VERTICES;                break;            default:                break;        }    }    coordVertices = ByteBuffer.allocateDirect(coordVertice.length * float_SIZE_BYTES).order(ByteOrder.nativeOrder()).asfloatBuffer();    coordVertices.put(coordVertice).position(0);}

  

 

在Surface创建完成时进行Renderer初始化

   private voID initRenderer() {        rendererReady = false;        createGLProgram();        //启用纹理        GLES20.glEnable(GLES20.GL_TEXTURE_2D);        //创建纹理        createTexture(frameWIDth,frameHeight,GLES20.GL_luminance,yTexture);        createTexture(frameWIDth / 2,frameHeight / 2,uTexture);        createTexture(frameWIDth / 2,vTexture);        rendererReady = true;    }  
其中用于创建OpenGL Program并关联着色器代码中的变量createGLProgram
 private voID createGLProgram() {      int programHandleMain = glutil.createShaderProgram();      if (programHandleMain != -1) {          // 使用着色器程序          GLES20.gluseProgram(programHandleMain);          // 获取顶点着色器变量          int glposition = GLES20.glGetAttribLocation(programHandleMain,"attr_position");          int textureCoord = GLES20.glGetAttribLocation(programHandleMain,"attr_tc");          // 获取片段着色器变量          int ySampler = GLES20.glGetUniformlocation(programHandleMain,"ySampler");          int uSampler = GLES20.glGetUniformlocation(programHandleMain,"uSampler");          int vSampler = GLES20.glGetUniformlocation(programHandleMain,"vSampler");          //给变量赋值          /**           * GLES20.GL_TEXTURE0 和 ySampler 绑定           * GLES20.GL_TEXTURE1 和 uSampler 绑定           * GLES20.GL_TEXTURE2 和 vSampler 绑定           *           * 也就是说 gluniform1i的第二个参数代表图层序号           */          GLES20.gluniform1i(ySampler,0);          GLES20.gluniform1i(uSampler,1);          GLES20.gluniform1i(vSampler,2);          GLES20.glEnabLevertexAttribarray(glposition);          GLES20.glEnabLevertexAttribarray(textureCoord);          /**           * 设置Vertex Shader数据           */          squareVertices.position(0);          GLES20.glVertexAttribPointer(glposition,glutil.COUNT_PER_SQUARE_VERTICE,GLES20.GL_float,false,8,squareVertices);          coordVertices.position(0);          GLES20.glVertexAttribPointer(textureCoord,glutil.COUNT_PER_COORD_VERTICES,coordVertices);      }  }

  

 

其中createTexture用于根据宽高和格式创建纹理

      private voID createTexture(int wIDth,int height,int format,int[] textureID) {          //创建纹理          GLES20.glGenTextures(1,textureID,0);          //绑定纹理          GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,textureID[0]);          /**           * {@link GLES20#GL_TEXTURE_WRAP_S}代表左右方向的纹理环绕模式           * {@link GLES20#GL_TEXTURE_WRAP_T}代表上下方向的纹理环绕模式           *           *  {@link GLES20#GL_REPEAT}:重复           *  {@link GLES20#GL_MIRRORED_REPEAT}:镜像重复           *  {@link GLES20#GL_CLAMP_TO_EDGE}:忽略边框截取           *           * 例如我们使用{@link GLES20#GL_REPEAT}:           *           *             squareVertices           coordVertices           *             -1.0f,-1.0f,1.0f,*             1.0f,0.0f,->          和textureVIEw预览相同           *             -1.0f,1.0f               0.0f,0.0f           *           *             squareVertices           coordVertices           *             -1.0f,2.0f,->          和textureVIEw预览相比,分割成了4 块相同的预览(左下,右下,左上,右上)           *             -1.0f,0.0f           */          GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_WRAP_S,GLES20.GL_REPEAT);          GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_WRAP_T,GLES20.GL_REPEAT);          /**           * {@link GLES20#GL_TEXTURE_MIN_FILTER}代表所显示的纹理比加载进来的纹理小时的情况           * {@link GLES20#GL_TEXTURE_MAG_FILTER}代表所显示的纹理比加载进来的纹理大时的情况           *           *  {@link GLES20#GL_NEAREST}:使用纹理中坐标最接近的一个像素的颜色作为需要绘制的像素颜色           *  {@link GLES20#GL_liNEAR}:使用纹理中坐标最接近的若干个颜色,通过加权平均算法得到需要绘制的像素颜色           */          GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MIN_FILTER,GLES20.GL_NEAREST);          GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D,GLES20.GL_TEXTURE_MAG_FILTER,GLES20.GL_liNEAR);          GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D,format,height,GLES20.GL_UNSIGNED_BYTE,null);      }

  

 
在Java代码中调用绘制

在数据源获取到时裁剪并传入帧数据

 @OverrIDe  public voID onPrevIEw(final byte[] nv21,Camera camera) {      //裁剪指定的图像区域      ImageUtil.cropNV21(nv21,this.squareNV21,prevIEwSize.wIDth,prevIEwSize.height,cropRect);      //刷新GLSurfaceVIEw      roundCameraGLSurfaceVIEw.refreshFrameNV21(this.squareNV21);  }

  

 

NV21数据裁剪代码

  /**   * 裁剪NV21数据   *   * @param originNV21 原始的NV21数据   * @param cropNV21   裁剪结果NV21数据,需要预先分配内存   * @param wIDth      原始数据的宽度   * @param height     原始数据的高度   * @param left       原始数据被裁剪的区域的左边界   * @param top        原始数据被裁剪的区域的上边界   * @param right      原始数据被裁剪的区域的右边界   * @param bottom     原始数据被裁剪的区域的下边界   */  public static voID cropNV21(byte[] originNV21,byte[] cropNV21,int left,int top,int right,int bottom) {      int halfWIDth = wIDth / 2;      int cropImageWIDth = right - left;      int cropImageHeight = bottom - top;      //原数据Y左上      int originalYlinestart = top * wIDth;      int targetYIndex = 0;      //原数据UV左上      int originalUVlinestart = wIDth * height + top * halfWIDth;      //目标数据的UV起始值      int targetUVIndex = cropImageWIDth * cropImageHeight;      for (int i = top; i < bottom; i++) {          System.arraycopy(originNV21,originalYlinestart + left,cropNV21,targetYIndex,cropImageWIDth);          originalYlinestart += wIDth;          targetYIndex += cropImageWIDth;          if ((i & 1) == 0) {              System.arraycopy(originNV21,originalUVlinestart + left,targetUVIndex,cropImageWIDth);              originalUVlinestart += wIDth;              targetUVIndex += cropImageWIDth;          }      }  }

  

 

传给GLSurafceVIEw并刷新帧数据

  /**   * 传入NV21刷新帧   *   * @param data NV21数据   */  public voID refreshFrameNV21(byte[] data) {      if (rendererReady) {          yBuf.clear();          uBuf.clear();          vBuf.clear();          putNV21(data,frameWIDth,frameHeight);          datainput = true;          requestRender();      }  }

  

 

其中putNV21用于将NV21中的Y、U、V数据分别取出

  /**   * 将NV21数据的Y、U、V分量取出   *   * @param src    nv21帧数据   * @param wIDth  宽度   * @param height 高度   */  private voID putNV21(byte[] src,int height) {      int ySize = wIDth * height;      int frameSize = ySize * 3 / 2;      //取分量y值      System.arraycopy(src,yArray,ySize);      int k = 0;      //取分量uv值      int index = ySize;      while (index < frameSize) {          vArray[k] = src[index++];          uArray[k++] = src[index++];      }      yBuf.put(yArray).position(0);      uBuf.put(uArray).position(0);      vBuf.put(vArray).position(0);  }

  

 

在执行requestRender后,onDrawFrame函数将被回调,在其中进行三个纹理的数据绑定并绘制

      @OverrIDe      public voID onDrawFrame(GL10 gl) {          // 分别对每个纹理做激活、绑定、设置数据 *** 作          if (datainput) {              //y              GLES20.glActiveTexture(GLES20.GL_TEXTURE0);              GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,yTexture[0]);              GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D,yBuf);              //u              GLES20.glActiveTexture(GLES20.GL_TEXTURE1);              GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,uTexture[0]);              GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D,frameWIDth >> 1,frameHeight >> 1,uBuf);              //v              GLES20.glActiveTexture(GLES20.GL_TEXTURE2);              GLES20.glBindTexture(GLES20.GL_TEXTURE_2D,vTexture[0]);              GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D,vBuf);              //在数据绑定完成后进行绘制              GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP,4);          }      }

  

 

即可完成绘制。

四、加一层边框

有时候需求并不仅仅是圆形预览这么简单,我们可能还要为相机预览加一层边框


 

边框效果

一样的思路,我们动态地修改边框值,并进行重绘。
边框自定义view中的相关代码如下:

  @OverrIDe    protected voID onDraw(Canvas canvas) {        super.onDraw(canvas);        if (paint == null) {            paint = new Paint();            paint.setStyle(Paint.Style.stroke);            paint.setAntiAlias(true);            SweepGradIEnt sweepGradIEnt = new SweepGradIEnt(((float) getWIDth() / 2),((float) getHeight() / 2),new int[]{color.GREEN,color.CYAN,color.BLUE,color.GREEN},null);            paint.setShader(sweepGradIEnt);        }        drawborder(canvas,6);    }    private voID drawborder(Canvas canvas,int rectThickness) {        if (canvas == null) {            return;        }        paint.setstrokeWIDth(rectThickness);        Path drawPath = new Path();        drawPath.addRoundRect(new RectF(0,getWIDth(),getHeight()),radius,Path.Direction.CW);        canvas.drawPath(drawPath,paint);    }    public voID turnRound() {        invalIDate();    }    public voID seTradius(int radius) {        this.radius = radius;    }

 

五、完整Demo代码:

https://github.com/wangshengyang1996/GLCameraDemo

使用Camera API和Camera2 API并选择最接近正方形的预览尺寸 使用Camera API并为其动态添加一层父控件,达到正方形预览的效果 使用Camera API获取预览数据,使用OpenGL的方式进行显示 最后,给大家推荐一个好用的AndroID免费离线人脸识别的sdk,可以和本文实现技术的完美结合:

https://ai.arcsoft.com.cn/ucenter/resource/openPlatform/index.html?cnblogs

总结

以上是内存溢出为你收集整理的Android多种方式实现相机圆形预览全部内容,希望文章能够帮你解决Android多种方式实现相机圆形预览所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/web/1123366.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-05-30
下一篇2022-05-30

发表评论

登录后才能评论

评论列表(0条)

    保存