Android多种方式实现相机圆形预览 看这一篇就够了,Android开发面试书籍
}
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: 1920x1080
2019-08-02 13:16:08.669 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPreviewSize: 1280x720
2019-08-02 13:16:08.669 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPreviewSize: 1440x1080
2019-08-02 13:16:08.669 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPreviewSize: 1088x1088
2019-08-02 13:16:08.670 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPreviewSize: 1056x864
2019-08-02 13:16:08.670 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPreviewSize: 960x720
2019-08-02 13:16:08.670 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPreviewSize: 720x480
2019-08-02 13:16:08.670 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPreviewSize: 640x480
2019-08-02 13:16:08.670 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPreviewSize: 352x288
2019-08-02 13:16:08.670 16407-16407/com.wsy.glcamerademo I/CameraHelper: supportedPreviewSize: 320x240
2019-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: 4128x3096
2019-08-02 13:19:24.980 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 4128x2322
2019-08-02 13:19:24.980 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 3264x2448
2019-08-02 13:19:24.980 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 3264x1836
2019-08-02 13:19:24.980 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 3024x3024
2019-08-02 13:19:24.980 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2976x2976
2019-08-02 13:19:24.980 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2880x2160
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2592x1944
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2560x1920
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2560x1440
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2560x1080
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2160x2160
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2048x1536
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 2048x1152
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 1936x1936
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 1920x1080
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 1440x1080
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 1280x960
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 1280x720
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 960x720
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 720x480
2019-08-02 13:19:24.981 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 640x480
2019-08-02 13:19:24.982 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupport
edSize: 320x240
2019-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使用流程
其中的重点是渲染器(Renderer)的编写,Renderer的介绍如下:
/**
A generic renderer interface.
The renderer is responsible for making OpenGL calls to render a frame.
GLSurfaceView clients typically create their own classes that implement
this interface, and then call {@link GLSurfaceView#setRenderer} to
register the renderer with the GLSurfaceView.
Developer Guides
For more information about how to use OpenGL, read the
OpenGL developer guide.
Threading
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.
EGL Context Lost
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.
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.
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.
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.
@param gl the GL interface. Use
instanceof
totest 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.
Called after the surface is created and whenever
the OpenGL ES surface size changes.
Typically you will set your viewport here. If your camera
is fixed then you could also set your projection matrix here:
void onSurfaceChanged(GL10 gl, int width, int height) {
gl.glViewport(0, 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, 1, 10);
}
@param gl the GL interface. Use
instanceof
totest if the interface supports GL11 or higher interfaces.
@param width
@param height
*/
void onSurfaceChanged(GL10 gl, int width, int height);
/**
Called to draw the current frame.
This method is responsible for drawing the current frame.
The implementation of this method typically looks like this:
void onDrawFrame(GL10 gl) {
gl.glClear(GL10.GL_COLOR_BUFFER_BIT | GL10.GL_DEPTH_BUFFER_BIT);
//... other gl calls to render the scene ...
}
@param gl the GL interface. Use
instanceof
totest if the interface supports GL11 or higher interfaces.
*/
void onDrawFrame(GL10 gl);
}
void onSurfaceCreated(GL10 gl, EGLConfig config)
在Surface创建或重建的情况下回调
void onSurfaceChanged(GL10 gl, int width, int height)
在Surface的大小发生变化的情况下回调
void onDrawFrame(GL10 gl)
在这里实现绘制操作。当我们设置的renderMode
为RENDERMODE_CONTINUOUSLY
时,该函数将不断地执行;
当我们设置的renderMode
为RENDERMODE_WHEN_DIRTY
时,将只在创建完成和调用requestRender
后才执行。一般我们选择RENDERMODE_WHEN_DIRTY
渲染模式,避免过度绘制。
一般情况下,我们会自己实现一个Renderer,然后为GLSurfaceView设置Renderer,可以说,Renderer的编写是整个流程的核心步骤。以下是在void onSurfaceCreated(GL10 gl, EGLConfig config)
进行的初始化操作和在void onDrawFrame(GL10 gl)
进行的绘制操作的流程图:
2. 具体实现
- 坐标系介绍
如图所示,和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, 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,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, 1.0, 1.0,
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, GLES20.GL_LUMINANCE, uTexture);
createTexture(frameWidth / 2, frameHeight / 2, GLES20.GL_LUMINANCE, 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);
aderProgram();
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);
Android多种方式实现相机圆形预览 看这一篇就够了,Android开发面试书籍相关推荐
- android 圆形相机预览拍照_Android多种方式实现相机圆形预览
最终效果图如下: 一.为预览控件设置圆角 public RoundTextureView(Context context, AttributeSet attrs) { super(context, a ...
- android 圆形相机预览拍照_Android多种方式实现相机圆形预览的示例代码
效果图如下: 一.为预览控件设置圆角 public RoundTextureView(Context context, AttributeSet attrs) { super(context, att ...
- 设置webstorm实时预览看这一篇就够了,5分钟解决!!
1.安装插件Live Edit 这里学到了一个名词,捆绑,意思就是有的编辑器默认自带的,无须另外下载我这里是2020.3.2是自带的,检查是不是自带的也很简单,按照下图去看看是不是有Live Edit ...
- js小学生图区_多种方式实现js图片预览
js多种方式图片预览-持续更新 //设置自己的变量存储区 var Util = { file : $("#file"), image_show:$("#img_show& ...
- 双目、结构光、tof,三种深度相机的原理区别看这一篇就够了!
编辑:OAK中国 首发:oakchina.cn 喜欢的话,请多多
- android平台下OpenGL ES 3.0使用GLSurfaceView对相机Camera预览实时处理
OpenGL ES 3.0学习实践 android平台下OpenGL ES 3.0从零开始 android平台下OpenGL ES 3.0绘制纯色背景 android平台下OpenGL ES 3.0绘 ...
- Android Camera2 教程 · 第三章 · 预览
Android Camera2 教程 · 第三章 · 预览 DarylGo关注 Android Camera 上一章<Camera2 开启相机>我们学习了如何开启和关闭相机,接下来我们来学 ...
- 谷歌发布 Android 8.1 首个开发者预览版,优化内存效率
今晨,谷歌推出了 Android 8.1 首个开发者预览版,此次升级涵盖了针对多个功能的提升优化,其中包含对 Android Go (设备运行内存小于等于 1 GB)和加速设备上对机器学习的全新神经网 ...
- android p dp5,谷歌释出Android P第5个开发者预览版更新!
原标题:谷歌释出Android P第5个开发者预览版更新! [PConline资讯]今天凌晨,谷歌正式推出了AndroidP的第五个开发者预览版(DP5),这标志着AndroidP终于准备好在2018 ...
最新文章
- jmeter 非gui 模式跑jmx
- 【转发】响应式Web设计?怎样进行?
- Eclipse3.2下JFace和SWT工程环境配置方法
- [leetcode]Longest Palindromic Substring
- 带你领略Object.assign()方法的风骚操作
- Atitit 文档资料处理重要类库与工具 跨语言api和第三方api跨语言 类库工具	大概功能	功能 Curl	httpclient	文件上传下载 数据传输rest doctotext.exe
- 浅谈iOS开发中的锁
- 指针c语言有什么作用,c语言中指针有什么用?
- verbose=False(TensorFlow)
- Loadbalancer
- 暴露自己IP地址有危险吗?
- [CSP2020]儒略日
- CF1463F Max Correct Set(取小样法+状压 DP)
- mysql在触发器中调用存储过程_mysql 触发器中调用存储过程
- 在word文档中从第3页开始编页码的方法
- 物联网应用平台开发——项目总结报告
- Cocos2d-x2.1.1-ClippingNodeTest 深入分析
- 如何才能提高自己未来的竞争力?
- 百度搜索资源平台链接提交通道
- 数据中心机柜水冷系统中一次泵和二次泵哪个更好?