本篇内容主要解决屏幕旋转和Surface旋转之间的关系,使相机的预览画面与屏幕旋转方向一致。
在官方demo的开始,定义了一个SparseIntArray
,用来保存屏幕旋转的key和value:
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
接着是static语句块,用来初始化这个SparseIntArray
:
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
可以说一开始就懵逼了,这还咋看下去啊!不急,有请Log和大神来帮忙。
在进行capture
的时候有这样一段代码:
// ①
int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, getOrientation(rotation));
// ②
private int getOrientation(int rotation) {
// Sensor orientation is 90 for most devices, or 270 for some devices (eg. Nexus 5X)
// We have to take that into account and rotate JPEG properly.
// For devices with orientation of 90, we simply return our mapping from ORIENTATIONS.
// For devices with orientation of 270, we need to rotate the JPEG 180 degrees.
return (ORIENTATIONS.get(rotation) + mSensorOrientation + 270) % 360;
}
// ③
mSensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
其中getRotation()
在API文档中的描述如下:
Returns the rotation of the screen from its "natural" orientation. The returned value may be Surface.ROTATION_0 (no rotation), Surface.ROTATION_90, Surface.ROTATION_180, or Surface.ROTATION_270. For example, if a device has a naturally tall screen, and the user has turned it on its side to go into a landscape orientation, the value returned here may be either Surface.ROTATION_90 or Surface.ROTATION_270 depending on the direction it was turned. The angle is the rotation of the drawn graphics on the screen, which is the opposite direction of the physical rotation of the device. For example, if the device is rotated 90 degrees counter-clockwise, to compensate rendering will be rotated by 90 degrees clockwise and thus the returned value here will be Surface.ROTATION_90.
简单翻译一下:
该方法返回的是当前屏幕相对于“自然方向的旋转角度”,所谓的自然方向就是:手机竖屏,平板横屏。返回值是上面列举的四种。例如,如果一个设备自然方向是竖屏,当将其横向放置的时候,返回值要么是Surface.ROTATION_90
,或者Surface.ROTATION_270
,取决于顺逆时针的转动。
此外,屏幕上绘图的角度旋转与物理设备的旋转方向正好相反,例如,如果设备相对于自然方向逆时针旋转90度,那么绘图需要顺时针旋转90度才能保证弥补渲染的改变,即返回值是Surface.ROTATION_90
。
以上这点大家可以打开相册,旋转屏幕后对比前后两张图片,就能看出旋转方向的差异。比如我将手机屏幕竖立正对着我,通过Log可以看出,当向自己的左边旋转手机时,rotation
返回为1,也即Surface.ROTATION_90
,向右边旋转就是3,对应Surface.ROTATION_270
。
在①代码中,获得了rotation
就可以对预览的图片进行旋转,旋转方法位于②方法中。
在②中,mSensorOrientation
变量是由③代码获得的,代表的是当前图像传感器(Image Sensor)的取景方向,有90度和270度两种,如果是90度,那么代码化简以后就是ORIENTATIONS.get(rotation)
,如果是270度,那么化简以后的效果就是将JPEG旋转180度。
下面主要分析ORIENTATIONS.get(rotation)
,这里是让屏幕旋转方向和相机预览方向一致的关键。
由于Android厂商的底层改动不同,各个机器的版本配置也不全相同,所以上述的90度和270度不能一一列举,下面仅借MI3的机器来举例子。以下内容借鉴自Android相机角度问题。
前面提到的Sensor被安装到手机上,就有一个默认的取景方向了,并且不会改变,坐标原点位于手机横放时的左上角,可以通过cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)
获取Sensor的安装角度,如下图:
比如MI3手机的屏幕“自然”方向和后置相机的图像传感器方向如下图:
可以看出两个坐标系旋转90度就能变成了对方。
当手机为横屏时,两坐标系重合,此时相机的预览方向和最终的拍照方向一致,图片是“正确”的,但是若手机为竖屏,两坐标系相差90度,而拍照方向依然为Sensor的方向,也就是横屏方向,由于手机上的照相应用做了处理,如果是未处理的状态,照出的相片会显示成横屏的模样,等价于逆时针旋转了90度,如果要正常显示就要顺时针旋转90度,如下图:
再附上两张实物照片,左边是横屏拍摄,右边是竖屏拍摄后未通过相机处理的照片:
通过查阅文档可知,CaptureRequest.JPEG_ORIENTATION
的值是针对顺时针旋转而言的,所以恰好满足顺时针旋转90度的要求。
到这里也就知道了为什么要在静态语句块中设置不同的旋转方向了,按照上面的分析举个简单的例子加深印象:
比如先将手机面向自己逆时针旋转90度,代码块①中的rotation
值为Surface.ROTATION_90
,进入代码块②,由于是后置摄像头,Sensor的安装方向为自然方向逆时针旋转90度,mSensorOrientation
的值为90,也就直接调用ORIENTATIONS.get(rotation)
,获取的值为0,也就是相片不需要旋转,其他情况类似。