Android多种方式实现相机圆形预览的示例代码
效果图如下:
示意图
示例代码
//将预览控件和预览尺寸比例保持一致,避免拉伸 { 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使用流程
渲染YUV数据的Renderer
2. 具体实现
坐标系介绍
OpenGL世界坐标系
如图所示,和Android的View坐标系不同,OpenGL的坐标系是笛卡尔坐标系。
Android View的坐标系以左上角为原点,向右x递增,向下y递增;
而OpenGL坐标系以中心为原点,向右x递增,向上y递增。
着色器编写
/** * 顶点着色器 */ 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代表单个片元的颜色
其他变量解释
ySampler、uSampler、vSampler
分别代表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; }
其中createGLProgram用于创建OpenGL Program并关联着色器代码中的变量
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); } }
即可完成绘制。
四、加一层边框
有时候需求并不仅仅是圆形预览这么简单,我们可能还要为相机预览加一层边框
以上是来客网为你收集整理的Android多种方式实现相机圆形预览的示例代码全部内容,希望文章能够帮你解决Android多种方式实现相机圆形预览的示例代码所遇到的程序开发问题。
如果觉得来客网网站内容还不错,欢迎将来客网网站推荐给程序员好友。