Android多種方式實現(xiàn)相機圓形預(yù)覽的示例代碼
效果圖如下:

一、為預(yù)覽控件設(shè)置圓角
為控件設(shè)置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, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
outline.setRoundRect(rect, radius);
}
});
setClipToOutline(true);
}
在需要時修改圓角值并更新
public void setRadius(int radius) {
this.radius = radius;
}
public void turnRound() {
invalidateOutline();
}
即可根據(jù)設(shè)置的圓角值更新控件顯示的圓角大小。當控件為正方形,且圓角值為邊長的一半,顯示的就是圓形。
二、實現(xiàn)正方形預(yù)覽
1. 設(shè)備支持1:1預(yù)覽尺寸
首先介紹一種簡單但是局限性較大的實現(xiàn)方式:將相機預(yù)覽尺寸和預(yù)覽控件的大小都調(diào)整為1:1。
一般Android設(shè)備都支持多種預(yù)覽尺寸,以Samsung Tab S3為例
在使用Camera API時,其支持的預(yù)覽尺寸如下:
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的預(yù)覽尺寸為:1088x1088。
在使用Camera2 API時,其支持的預(yù)覽尺寸(其實也包含了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: getBestSupportedSize: 320x240 2019-08-02 13:19:24.982 16768-16768/com.wsy.glcamerademo I/Camera2Helper: getBestSupportedSize: 176x144
其中1:1的預(yù)覽尺寸為:3024x3024、2976x2976、2160x2160、1936x1936。
只要我們選擇1:1的預(yù)覽尺寸,再將預(yù)覽控件設(shè)置為正方形,即可實現(xiàn)正方形預(yù)覽;
再通過設(shè)置預(yù)覽控件的圓角為邊長的一半,即可實現(xiàn)圓形預(yù)覽。2. 設(shè)備不支持1:1預(yù)覽尺寸的情況
選擇1:1預(yù)覽尺寸的缺陷分析
分辨率局限性
上述說到,我們可以選擇1:1的預(yù)覽尺寸進行預(yù)覽,但是局限性較高,
可選擇范圍都很小。如果相機不支持1:1的預(yù)覽尺寸,這個方案就不可行了。
資源消耗
以Samsung tab S3為例,該設(shè)備使用Camera2 API時,支持的正方形預(yù)覽尺寸都很大,在進行圖像處理等操作時將占用較多系統(tǒng)資源。
處理不支持1:1預(yù)覽尺寸的情況
添加一個1:1尺寸的ViewGroup
將TextureView放入ViewGroup
設(shè)置TextureView的margin值以達到顯示中心正方形區(qū)域的效果

示意圖
示例代碼
//將預(yù)覽控件和預(yù)覽尺寸比例保持一致,避免拉伸
{
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;
}
////當不是正方形預(yù)覽的情況下,添加一層ViewGroup限制View的顯示區(qū)域
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進行自定義程度更高的預(yù)覽
使用上面的方法操作已經(jīng)可完成正方形和圓形預(yù)覽,但是僅適用于原生相機,當我們的數(shù)據(jù)源并非是原生相機的情況時如何進行圓形預(yù)覽?接下來介紹使用GLSurfaceView顯示NV21的方案,完全是自己實現(xiàn)預(yù)覽數(shù)據(jù)的繪制。
1. GLSurfaceView使用流程

OpenGL渲染YUV數(shù)據(jù)流程
其中的重點是渲染器(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" rel="external nofollow" >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)
在Surface創(chuàng)建或重建的情況下回調(diào)
void onSurfaceChanged(GL10 gl, int width, int height)
在Surface的大小發(fā)生變化的情況下回調(diào)
void onDrawFrame(GL10 gl)
在這里實現(xiàn)繪制操作。當我們設(shè)置的renderMode為RENDERMODE_CONTINUOUSLY時,該函數(shù)將不斷地執(zhí)行;
當我們設(shè)置的renderMode為RENDERMODE_WHEN_DIRTY時,將只在創(chuàng)建完成和調(diào)用requestRender后才執(zhí)行。一般我們選擇RENDERMODE_WHEN_DIRTY渲染模式,避免過度繪制。
一般情況下,我們會自己實現(xiàn)一個Renderer,然后為GLSurfaceView設(shè)置Renderer,可以說,Renderer的編寫是整個流程的核心步驟。以下是在void onSurfaceCreated(GL10 gl, EGLConfig config)進行的初始化操作和在void onDrawFrame(GL10 gl)進行的繪制操作的流程圖:

渲染YUV數(shù)據(jù)的Renderer
2. 具體實現(xiàn)
坐標系介紹

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" +
" }";
內(nèi)建變量解釋
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
根據(jù)以下公式:
R = Y + 1.402 (V - 128) G = Y - 0.34414 (U - 128) - 0.71414 (V - 128) B = Y + 1.772 (U - 128)
我們可得到一個YUV轉(zhuǎn)RGB的矩陣
1.0, 1.0, 1.0, 0, -0.344, 1.77, 1.403, -0.714, 0
部分類型、函數(shù)的解釋
vec3、vec4
分別代表三維向量、四維向量。
vec4 texture2D(sampler2D sampler, vec2 coord)
以指定的矩陣將采樣器的圖像紋理轉(zhuǎn)換為顏色值;如:
texture2D(ySampler, tc).r獲取到的是Y數(shù)據(jù),
texture2D(uSampler, tc).r獲取到的是U數(shù)據(jù),
texture2D(vSampler, tc).r獲取到的是V數(shù)據(jù)。
在Java代碼中進行初始化
根據(jù)圖像寬高創(chuàng)建Y、U、V對應(yīng)的ByteBuffer紋理數(shù)據(jù);
根據(jù)是否鏡像顯示、旋轉(zhuǎn)角度選擇對應(yīng)的轉(zhuǎn)換矩陣;
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創(chuàng)建完成時進行Renderer初始化
private void initRenderer() {
rendererReady = false;
createGLProgram();
//啟用紋理
GLES20.glEnable(GLES20.GL_TEXTURE_2D);
//創(chuàng)建紋理
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用于創(chuàng)建OpenGL Program并關(guān)聯(lián)著色器代碼中的變量
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的第二個參數(shù)代表圖層序號
*/
GLES20.glUniform1i(ySampler, 0);
GLES20.glUniform1i(uSampler, 1);
GLES20.glUniform1i(vSampler, 2);
GLES20.glEnableVertexAttribArray(glPosition);
GLES20.glEnableVertexAttribArray(textureCoord);
/**
* 設(shè)置Vertex Shader數(shù)據(jù)
*/
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用于根據(jù)寬高和格式創(chuàng)建紋理
private void createTexture(int width, int height, int format, int[] textureId) {
//創(chuàng)建紋理
GLES20.glGenTextures(1, textureId, 0);
//綁定紋理
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, textureId[0]);
/**
* {@link GLES20#GL_TEXTURE_WRAP_S}代表左右方向的紋理環(huán)繞模式
* {@link GLES20#GL_TEXTURE_WRAP_T}代表上下方向的紋理環(huán)繞模式
*
* {@link GLES20#GL_REPEAT}:重復(fù)
* {@link GLES20#GL_MIRRORED_REPEAT}:鏡像重復(fù)
* {@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預(yù)覽相同
* -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預(yù)覽相比,分割成了4 塊相同的預(yù)覽(左下,右下,左上,右上)
* -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}:使用紋理中坐標最接近的若干個顏色,通過加權(quán)平均算法得到需要繪制的像素顏色
*/
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);
}
在Java代碼中調(diào)用繪制
在數(shù)據(jù)源獲取到時裁剪并傳入幀數(shù)據(jù)
@Override
public void onPreview(final byte[] nv21, Camera camera) {
//裁剪指定的圖像區(qū)域
ImageUtil.cropNV21(nv21, this.squareNV21, previewSize.width, previewSize.height, cropRect);
//刷新GLSurfaceView
roundCameraGLSurfaceView.refreshFrameNV21(this.squareNV21);
}
NV21數(shù)據(jù)裁剪代碼
/**
* 裁剪NV21數(shù)據(jù)
*
* @param originNV21 原始的NV21數(shù)據(jù)
* @param cropNV21 裁剪結(jié)果NV21數(shù)據(jù),需要預(yù)先分配內(nèi)存
* @param width 原始數(shù)據(jù)的寬度
* @param height 原始數(shù)據(jù)的高度
* @param left 原始數(shù)據(jù)被裁剪的區(qū)域的左邊界
* @param top 原始數(shù)據(jù)被裁剪的區(qū)域的上邊界
* @param right 原始數(shù)據(jù)被裁剪的區(qū)域的右邊界
* @param bottom 原始數(shù)據(jù)被裁剪的區(qū)域的下邊界
*/
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;
//原數(shù)據(jù)Y左上
int originalYLineStart = top * width;
int targetYIndex = 0;
//原數(shù)據(jù)UV左上
int originalUVLineStart = width * height + top * halfWidth;
//目標數(shù)據(jù)的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并刷新幀數(shù)據(jù)
/**
* 傳入NV21刷新幀
*
* @param data NV21數(shù)據(jù)
*/
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數(shù)據(jù)分別取出
/**
* 將NV21數(shù)據(jù)的Y、U、V分量取出
*
* @param src nv21幀數(shù)據(jù)
* @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);
}
在執(zhí)行requestRender后,onDrawFrame函數(shù)將被回調(diào),在其中進行三個紋理的數(shù)據(jù)綁定并繪制
@Override
public void onDrawFrame(GL10 gl) {
// 分別對每個紋理做激活、綁定、設(shè)置數(shù)據(jù)操作
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);
//在數(shù)據(jù)綁定完成后進行繪制
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
}
}
即可完成繪制。
四、加一層邊框
有時候需求并不僅僅是圓形預(yù)覽這么簡單,我們可能還要為相機預(yù)覽加一層邊框

邊框效果
一樣的思路,我們動態(tài)地修改邊框值,并進行重繪。
邊框自定義View中的相關(guān)代碼如下:
@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;
}
五、完整Demo代碼:
https://github.com/wangshengyang1996/GLCameraDemo
使用Camera API和Camera2 API并選擇最接近正方形的預(yù)覽尺寸
使用Camera API并為其動態(tài)添加一層父控件,達到正方形預(yù)覽的效果
使用Camera API獲取預(yù)覽數(shù)據(jù),使用OpenGL的方式進行顯示最后,給大家推薦一個好用的Android免費離線人臉識別的sdk,可以和本文實現(xiàn)技術(shù)的完美結(jié)合: https://ai.arcsoft.com.cn/product/arcface.html
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android CameraX打開攝像頭預(yù)覽教程
- Android?CameraX?打開攝像頭預(yù)覽功能
- Android Camera2 實現(xiàn)預(yù)覽功能
- Android實現(xiàn)Camera2預(yù)覽和拍照效果
- Android camera實時預(yù)覽 實時處理,人臉識別示例
- Android編程中調(diào)用Camera時預(yù)覽畫面有旋轉(zhuǎn)問題的解決方法
- Android自定義相機、預(yù)覽區(qū)域裁剪
- Android實現(xiàn)圖片預(yù)覽與保存功能
- Android快速實現(xiàn)無預(yù)覽拍照功能
- Android?Camera1實現(xiàn)預(yù)覽框顯示
相關(guān)文章
使用SignalR推送服務(wù)在Android的實現(xiàn) SignalA
SignalA是老外寫的用于實現(xiàn).net端推送消息至安卓端的實現(xiàn),支持版本為android 2.3或以上2014-07-07
Android?Navigation重建Fragment問題分析及解決
這篇文章主要介紹了Android?Navigation重建Fragment問題分析及解決,文章通過圍繞主題展開詳細的內(nèi)容介紹,具有一定的參考價值,需要的小伙伴可以參考一下2022-09-09
Android 保存Fragment 切換狀態(tài)實例代碼
本文主要介紹Android Fragment的應(yīng)用,這里給大家用實例代碼詳細介紹了Android Fragment 切換狀態(tài),有需要的小伙伴可以參考下2016-07-07
android 網(wǎng)絡(luò)編程之網(wǎng)絡(luò)通信幾種方式實例分享
這篇文章主要介紹了android 網(wǎng)絡(luò)編程之網(wǎng)絡(luò)通信幾種方式,有需要的朋友可以參考一下2013-12-12
android?微信搶紅包工具AccessibilityService實現(xiàn)詳解
這篇文章主要為大家介紹了android?微信搶紅包工具AccessibilityService實現(xiàn)詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02

