效果图以下:css
为控件设置ViewOutlineProviderhtml
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, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
outline.setRoundRect(rect, radius);
}
});
setClipToOutline(true);
}
复制代码
在须要时修改圆角值并更新java
public void setRadius(int radius) {
this.radius = radius;
}
public void turnRound() {
invalidateOutline();
}
复制代码
便可根据设置的圆角值更新控件显示的圆角大小。当控件为正方形,且圆角值为边长的一半,显示的就是圆形。android
首先介绍一种简单可是局限性较大的实现方式:将相机预览尺寸和预览控件的大小都调整为1:1。git
通常Android设备都支持多种预览尺寸,以Samsung Tab S3为例github
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。算法
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: getBestSupportedSize: 320x240
2019-08-02 13:19:24.982 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 176x144
复制代码
其中1:1的预览尺寸为:3024x302四、2976x297六、2160x2160、1936x1936。canvas
只要咱们选择1:1的预览尺寸,再将预览控件设置为正方形,便可实现正方形预览;
再经过设置预览控件的圆角为边长的一半,便可实现圆形预览。ruby
选择1:1预览尺寸的缺陷分析app
处理不支持1:1预览尺寸的状况
示例代码
//将预览控件和预览尺寸比例保持一致,避免拉伸
{
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显示NV21的方案,彻底是本身实现预览数据的绘制。
其中的重点是渲染器(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 class="special reference"> * <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 class="prettyprint"> * 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); * } * </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 width, 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 class="prettyprint"> * 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)
void onSurfaceChanged(GL10 gl, int width, int height)
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)
进行的绘制操做的流程图:
![]()
Android View坐标系
|
![]()
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, 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
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
复制代码
部分类型、函数的解释
vec三、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);
/** * 设置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, GLES20.GL_FLOAT, false, 8, 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, * 1.0f, -1.0f, 1.0f, 0.0f, -> 和textureView预览相同 * -1.0f, 1.0f, 0.0f, 1.0f, * 1.0f, 1.0f 0.0f, 0.0f * * squareVertices coordVertices * -1.0f, -1.0f, 2.0f, 2.0f, * 1.0f, -1.0f, 2.0f, 0.0f, -> 和textureView预览相比,分割成了4 块相同的预览(左下,右下,左上,右上) * -1.0f, 1.0f, 0.0f, 2.0f, * 1.0f, 1.0f 0.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, 0, format, width, height, 0, format, GLES20.GL_UNSIGNED_BYTE, null);
}
复制代码
在数据源获取到时裁剪并传入帧数据
@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 width, int height, 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, cropNV21, 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 width, int height) {
int ySize = width * height;
int frameSize = ySize * 3 / 2;
//取份量y值
System.arraycopy(src, 0, yArray, 0, 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,
0,
0,
0,
frameWidth,
frameHeight,
GLES20.GL_LUMINANCE,
GLES20.GL_UNSIGNED_BYTE,
yBuf);
//u
GLES20.glActiveTexture(GLES20.GL_TEXTURE1);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, uTexture[0]);
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D,
0,
0,
0,
frameWidth >> 1,
frameHeight >> 1,
GLES20.GL_LUMINANCE,
GLES20.GL_UNSIGNED_BYTE,
uBuf);
//v
GLES20.glActiveTexture(GLES20.GL_TEXTURE2);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, vTexture[0]);
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D,
0,
0,
0,
frameWidth >> 1,
frameHeight >> 1,
GLES20.GL_LUMINANCE,
GLES20.GL_UNSIGNED_BYTE,
vBuf);
//在数据绑定完成后进行绘制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 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.CYAN, 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, 0, getWidth(), getHeight()), radius, radius, Path.Direction.CW);
canvas.drawPath(drawPath, paint);
}
public void turnRound() {
invalidate();
}
public void setRadius(int radius) {
this.radius = radius;
}
复制代码