中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

怎么在Android中實現相機圓形預覽

發布時間:2021-05-27 17:46:49 來源:億速云 閱讀:255 作者:Leah 欄目:移動開發

怎么在Android中實現相機圓形預覽?相信很多沒有經驗的人對此束手無策,為此本文總結了問題出現的原因和解決方法,通過這篇文章希望你能解決這個問題。

一、為預覽控件設置圓角

為控件設置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();
}

即可根據設置的圓角值更新控件顯示的圓角大小。當控件為正方形,且圓角值為邊長的一半,顯示的就是圓形。

二、實現正方形預覽

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: getBestSupportedSize: 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值以達到顯示中心正方形區域的效果

怎么在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使用流程

怎么在Android中實現相機圓形預覽

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 class="special reference">
 * <h4>Developer Guides</h4>
 * <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>
 *
 * <h4>Threading</h4>
 * 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>
 * <h4>EGL Context Lost</h4>
 * 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創建或重建的情況下回調

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)進行的繪制操作的流程圖:

怎么在Android中實現相機圓形預覽

渲染YUV數據的Renderer

2. 具體實現

坐標系介紹

怎么在Android中實現相機圓形預覽

Android View坐標系

怎么在Android中實現相機圓形預覽

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

分別代表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);

   /**
    * 設置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);
 }

在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 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);
   }
 }

即可完成繪制。

四、加一層邊框

有時候需求并不僅僅是圓形預覽這么簡單,我們可能還要為相機預覽加一層邊框

怎么在Android中實現相機圓形預覽

邊框效果

一樣的思路,我們動態地修改邊框值,并進行重繪。
邊框自定義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;
}

看完上述內容,你們掌握怎么在Android中實現相機圓形預覽的方法了嗎?如果還想學到更多技能或想了解更多相關內容,歡迎關注億速云行業資訊頻道,感謝各位的閱讀!

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

天柱县| 类乌齐县| 天长市| 旬阳县| 扎囊县| 恩施市| 望城县| 定州市| 津市市| 英超| 新野县| 中西区| 大悟县| 镇远县| 威宁| 哈尔滨市| 鄱阳县| 綦江县| 潜江市| 梧州市| 从江县| 钟祥市| 米易县| 罗田县| 宁都县| 中阳县| 军事| 丁青县| 灵川县| 盐城市| 密山市| 保山市| 苍山县| 鄯善县| 阜阳市| 遂宁市| 米林县| 兴城市| 麻城市| 贵南县| 江都市|