一、前言
Android自定义相机开发中,常常会有通过手势放大或缩小相机预览画面的需求,即数码变焦DigitalZoom。
二、接口说明
1. 获取最大的放大倍数
float maxZoom = mCameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
怎么理解这个值呢?
假设正常预览画面(即没有缩放)矩形为 activity_rect,放大后的预览画面矩形为 crop_rect,那么它们宽高的比值最大就只能为 maxZoom ,例如我测试中获取的该值为 10.0。(PS:activity_rect的宽高为分子,crop_rect的宽高是分母)
2. 获取未缩放的正常预览画面大小
Rect rect = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
这个大小要用来计算我们最终放大后显示的画面大小。
3. 相机预览请求构造者设置显示的画面区域
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomRect);
计算得到的 zoomRect,通过上述接口设置给预览。
三、代码实现
1. 相机View处理触碰事件
private float mOldDistance;
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getPointerCount() == 2) {
switch (event.getAction() & MotionEvent.ACTION_MASK) {
case MotionEvent.ACTION_POINTER_DOWN:
mOldDistance = getFingerSpacing(event);
break;
case MotionEvent.ACTION_MOVE:
float newDistance = getFingerSpacing(event);
if (newDistance > mOldDistance) {
mCameraProxy.handleZoom(true);
} else if (newDistance < mOldDistance) {
mCameraProxy.handleZoom(false);
}
mOldDistance = newDistance;
break;
default:
break;
}
}
return super.onTouchEvent(event);
}
private static float getFingerSpacing(MotionEvent event) {
float x = event.getX(0) - event.getX(1);
float y = event.getY(0) - event.getY(1);
return (float) Math.sqrt(x * x + y * y);
}
上面我们只处理了触碰事件,预览放大或缩小的逻辑由 mCameraProxy 的 handleZoom(boolean isZoomIn) 实现。
2. handleZoom() 实现
private int mZoom = 0;
public void handleZoom(boolean isZoomIn) {
if (mCameraDevice == null || mCameraCharacteristics == null || mPreviewRequestBuilder == null) {
return;
}
float maxZoom = mCameraCharacteristics.get(CameraCharacteristics.SCALER_AVAILABLE_MAX_DIGITAL_ZOOM);
Log.d(TAG, "handleZoom: maxZoom: " + maxZoom);
int factor = 100;
if (isZoomIn && mZoom < factor) {
mZoom++;
} else if (mZoom > 0) {
mZoom--;
}
Log.d(TAG, "handleZoom: mZoom: " + mZoom);
Rect rect = mCameraCharacteristics.get(CameraCharacteristics.SENSOR_INFO_ACTIVE_ARRAY_SIZE);
int minW = (int) ((rect.width() - rect.width() / maxZoom) / (2 * factor));
int minH = (int) ((rect.height() - rect.height() / maxZoom) / (2 * factor));
int cropW = minW * mZoom;
int cropH = minH * mZoom;
Log.d(TAG, "handleZoom: cropW: " + cropW + ", cropH: " + cropH);
Rect zoomRect = new Rect(rect.left + cropW, rect.top + cropH, rect.right - cropW, rect.bottom - cropH);
mPreviewRequestBuilder.set(CaptureRequest.SCALER_CROP_REGION, zoomRect);
mPreviewRequest = mPreviewRequestBuilder.build();
startPreview();
}
再简单说明一下上面的代码,因为本身maxZoom的值并不会很大,如果直接使用 1~maxZoom 的 int 值去放大缩小,画面变化就很剧烈,所以我加了一个 int factor = 100 去把这个过程划分成了100份,这个值可以自己设定。所以 mZoom 的判定范围也就变成了 0~factor。
再说计算 minW 和 minH 的代码。原Rect的宽是 rect.width(),放大到最大时 zoomRect 的宽是 rect.width() / maxZoom,因为有左右两边,所以它们的差值需要除以2,然后划分成 factor 份,需要再除以 factor。
最后,放大时我们让 mZoom 自增1,缩小时让 mZoom 自减1,根据 mZoom 就可以得到剪裁的宽高大大小了。
四、工程地址
完整的代码可见: https://github.com/afei-cn/CameraDemo/blob/master/app/src/main/java/com/afei/camerademo/camera/Camera2Proxy.java
相关使用实例可见:
自定义Camera系列之:SurfaceView + Camera2
自定义Camera系列之:TextureView + Camera2
自定义Camera系列之:GLSurfaceView + Camera2
|