相机的分辨率

显示区域

如图所示,长、宽同一比例的分辨率显示的区域相同,不同比例对应圆中不同的最大的内接矩形。


预览分辨率

需求

1、全屏预览

2、预览尽可能没有拉伸

3、预览尽可能清晰

全屏预览

隐藏状态栏、标题栏、虚拟按键栏

1、在 activity 中加入代码

1
2
3
4
5
6
7
8
9
if (Build.VERSION.SDK_INT > 11 && Build.VERSION.SDK_INT < 19) {
getWindow().getDecorView().setSystemUiVisibility(View.GONE);
} else if (Build.VERSION.SDK_INT >= 19) {
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
| View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
}

获取屏幕分辨率

1、HardwareInfoUtil 的方法

1
2
3
4
5
6
7
8
9
10
11
12
public static DisplayMetrics getRealDisplayMetrics(Context context) {
DisplayMetrics displayMetrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
try {
Class clazz = Class.forName("android.view.Display");
Method method = clazz.getMethod("getRealMetrics", DisplayMetrics.class);
method.invoke(wm.getDefaultDisplay(), displayMetrics);
} catch (Exception e) {
e.printStackTrace();
}
return displayMetrics;
}

2、调用

1
2
3
4
DisplayMetrics displayMetrics = HardwareInfoUtil.getRealDisplayMetrics(context);
int height = displayMetrics.heightPixels;
int width = displayMetrics.widthPixels;
Log.i(TAG, "屏幕分辨率:(" + width + "," + height + ")");

预览尽可能没有拉伸

获取设备支持的预览分辨率

1
2
Camera.Parameters parameters = camera.getParameters();
List<Camera.Size> rawSupportedSizes = parameters.getSupportedPreviewSizes();

如果获取不到设备支持的预览分辨率,则获取默认的预览分辨率,再判断该分辨率是否存在,如果存在,则使用该分辨率作为预览分辨率。

1
2
3
4
5
6
7
8
if (rawSupportedSizes == null) {
Log.w(TAG, "Device returned no supported preview sizes; using default");
Camera.Size defaultSize = parameters.getPreviewSize();
if (defaultSize == null) {
throw new IllegalStateException("Parameters contained no preview size!");
}
return new Point(defaultSize.width, defaultSize.height);
}

如果能获取到设备支持的预览分辨率,则打印所有预览分辨率。

1
2
3
4
5
6
7
if (Log.isLoggable(TAG, Log.INFO)) {
StringBuilder previewSizesString = new StringBuilder();
for (Camera.Size size : rawSupportedSizes) {
previewSizesString.append(size.width).append('x').append(size.height).append(' ');
}
Log.i(TAG, "Supported preview sizes: " + previewSizesString);
}

选择和屏幕分辨率比例最接近的预览分辨率

最佳的预览分辨率就是屏幕分辨率,所以先判断设备支持的预览分辨率中是否包含屏幕分辨率,如果包含,则直接使用该分辨率。

1
2
3
4
Camera.Size bestSize = camera.new Size(screenResolution.x, screenResolution.y);
if (rawSupportedSizes.contains(bestSize)) {
return new Point(bestSize.width, bestSize.height);
}

如果设备支持的预览分辨率中不包含屏幕分辨率,则需要遍历预览分辨率,计算每个分辨率的长、宽比,选择其中和屏幕分辨率长、宽比最接近的。

1
2
3
4
5
6
7
8
9
10
11
bestSize = null;
float screenRatio = screenResolution.y * 1.0f / screenResolution.x;
float bestRatio = Integer.MAX_VALUE;
for (Camera.Size size : rawSupportedSizes) {
float aspectRatio = size.height * 1.0f / size.width;
float ratioSub = Math.abs(aspectRatio - screenRatio);
if (ratioSub < bestRatio) {
bestRatio = ratioSub;
bestSize = size;
}
}

预览尽可能清晰

选择比屏幕分辨率稍高或接近的预览分辨率

在上面的基础上添加处理,如果预览分辨率的长、宽比一样,且都是最接近屏幕分辨率的长、宽比时,比较预览分辨率宽距离屏幕宽的差,如果差不一样,选择差小的,如果差一样,选择预览分辨率大的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int maxHeight = 0;
bestSize = null;
float screenRatio = screenResolution.y * 1.0f / screenResolution.x;
float bestRatio = Integer.MAX_VALUE;
for (Camera.Size size : rawSupportedSizes) {
float aspectRatio = size.height * 1.0f / size.width;
float ratioSub = Math.abs(aspectRatio - screenRatio);
if (ratioSub < bestRatio) {
bestRatio = ratioSub;
maxHeight = size.height;
bestSize = size;
} else if (ratioSub == bestRatio) {
int sizeHeight2ScreenHeight = Math.abs(size.height - screenResolution.y);
int maxHeight2ScreenHeight = Math.abs(maxHeight - screenResolution.y);
if ((sizeHeight2ScreenHeight < maxHeight2ScreenHeight) || (sizeHeight2ScreenHeight == maxHeight2ScreenHeight && size.height > screenResolution.y)) {
maxHeight = size.height;
bestSize = size;
}
}
}

拍照分辨率

需求

1、裁剪图片在预览区域中指定方框中的区域,例如:

黑框表示全屏预览的区域,红框表示裁剪区域,红框边长为屏幕高度的 2/3,水平、垂直方向均居中。

2、裁剪区域的图片分辨率为 1080x1080

3、裁剪区域的图片尽量不失真

裁剪图片在预览区域中指定方框中的区域

获取设备支持的拍照分辨率

1
2
Camera.Parameters parameters = camera.getParameters();
List<Camera.Size> rawSupportedSizes = parameters.getSupportedPictureSizes();

如果获取不到设备支持的拍照分辨率,则获取默认的拍照分辨率,再判断该分辨率是否存在,如果存在,则使用该分辨率作为拍照分辨率。

1
2
3
4
5
6
7
8
if (rawSupportedSizes == null) {
Log.w(TAG, "Device returned no supported picture sizes; using default");
Camera.Size defaultSize = parameters.getPictureSize();
if (defaultSize == null) {
throw new IllegalStateException("Parameters contained no picture size!");
}
return new Point(defaultSize.width, defaultSize.height);
}

如果能获取到设备支持的拍照分辨率,则打印所有拍照分辨率。

1
2
3
4
5
6
7
if (Log.isLoggable(TAG, Log.INFO)) {
StringBuilder pictureSizesString = new StringBuilder();
for (Camera.Size size : rawSupportedSizes) {
pictureSizesString.append(size.width).append('x').append(size.height).append(' ');
}
Log.i(TAG, "Supported picture sizes: " + pictureSizesString);
}

裁剪图片在预览区域中指定方框中的区域

预览分辨率和拍照分辨率一致

1、BitmapUtil 的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static Bitmap createSquareCropBitmap(Bitmap bitmap) {
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int cropWidthIndex = 0, cropHeightIndex = 0;
int cropWidth = width;
int cropHeight = height;
if (width == height) {
return bitmap;
} else if (width > height) {
cropWidthIndex = (width - height) >> 1;
cropWidth = height;
} else {
cropHeightIndex = (height - width) >> 1;
cropHeight = width;
}
return Bitmap.createBitmap(bitmap, cropWidthIndex, cropHeightIndex, cropWidth, cropHeight);
}
public static Bitmap createCropBitmap(Bitmap bitmap, float cropXPercent, float cropYPercent) {
if (cropXPercent < 0) {
cropXPercent = 0;
}
if (cropYPercent < 0) {
cropYPercent = 0;
}
int width = bitmap.getWidth();
int height = bitmap.getHeight();
int cropWidthIndex = (int) (width * cropXPercent);
int cropHeightIndex = (int) (height * cropYPercent);
int cropWidth = width - (cropWidthIndex << 1);
int cropHeight = height - (cropHeightIndex << 1);
return Bitmap.createBitmap(bitmap, cropWidthIndex, cropHeightIndex, cropWidth, cropHeight);
}

2、调用

1
2
Bitmap squareCropBitmap = BitmapUtil.createSquareCropBitmap(bitmap);
Bitmap cropBitmap = BitmapUtil.createCropBitmap(squareCropBitmap, CameraActivity.RED_LINE_MARGIN, CameraActivity.RED_LINE_MARGIN);

预览分辨率和拍照分辨率不一致

1、获取预览分辨率,比如 previewRatio(在上面设置预览分辨率时保存,且值为宽:长,因为容易整除),拍照分辨率 pictureRatio

2、获取因拍照分辨率和预览分辨率不一致导致的自动被裁剪/添加的区域,比如预览分辨率是 16:9,拍照分辨率是 4:3

实际拍出的照片区域和在屏幕中预览的区域不一致,再按照原来的比例在图片中裁剪,裁剪所得的照片并不是预览时方框中显示的区域,垂直方向需要多裁剪 x,水平方向需要少裁剪 y。

3、x 计算

为了有助于计算,添加一个辅助矩形,该矩形和预览分辨率有相同长,但比例为 4:3,所以
$$lx’ = \frac{16a * pictureRatio}{2}$$
,如果不是因为相机传感器是圆形的,实际 x 应该是
$$x’ = \frac{16a * pictureRatio - 16a * previewRatio}{2 * 16a * pictureRatio} = \frac{pictureRatio - previewRatio}{2 * pictureRatio}$$

由于相机传感器是圆形的,所以 lx’ 缩小成 lx,x’ 缩小成 x

因为圆,所以
$$t1 = t4$$

$$t2^2 + t3^2 = t5^2 + t6^2$$
因为
$$t2 = t3 * pictureRatio,t5 = t6 * previewRatio$$
所以
$${(t3 * pictureRatio)}^2 + t3^2 = {(t6 * previewRatio)}^2 + t6^2$$

$${(1 + pictureRatio)}^2 * t3^2 = {(1 + previewRatio)}^2 * t6^2$$

$$\frac{t3}{t6} = \sqrt{\frac{(1 + previewRatio)}{(1 + pictureRatio)}}$$
因为三角形相似,所以
$$\frac{lx}{lx’} = \frac{t3}{t6} = \sqrt{\frac{(1 + previewRatio)}{(1 + pictureRatio)}}$$

$$x = x’ - \frac{lx’ - lx}{lx’} = x’ - 1 + \frac{lx}{lx’} = \frac{pictureRatio - previewRatio}{2 * pictureRatio} - 1 + \sqrt{\frac{(1 + previewRatio)}{(1 + pictureRatio)}}$$

4、y 计算

$$y = \frac{t6 - t3}{t3} = \frac{t6}{t3} - 1 = \frac{1}{\sqrt{\frac{(1 + previewRatio)}{(1 + pictureRatio)}}} - 1$$

5、BitmapUtil 的方法

1
2
3
4
5
6
7
8
9
10
public static AutoCropPercent getPreview2PictureAutoCropPercent(float pictureRatio, float previewRatio) {
if (pictureRatio == previewRatio) {
return new AutoCropPercent(0, 0);
} else {
float sqrt = (float) (Math.sqrt((1 + previewRatio) / (1 + pictureRatio)));
float ap = (pictureRatio - previewRatio) / (2 * pictureRatio) - 1 + sqrt;
float bp = 1.0f / sqrt - 1;
return new AutoCropPercent(-bp, ap);
}
}

6、调用

1
2
3
BitmapUtil.AutoCropPercent autoCropPercent = BitmapUtil.getPreview2PictureAutoCropPercent(aspectRatio, ratio);
Bitmap cropBitmap = BitmapUtil.createCropBitmap(bitmap, 0, CameraActivity.RED_LINE_MARGIN + autoCropPercent.getYp());
cropBitmap = BitmapUtil.createSquareCropBitmap(cropBitmap);

裁剪区域的图片分辨率为 1080x1080

1
Bitmap.createScaledBitmap(cropBitmap, CameraActivity.PICTURE_WIDTH, CameraActivity.PICTURE_WIDTH, false);

裁剪区域的图片尽量不失真

遍历设备支持的拍照分辨率,获取拍照分辨率相对于预览分辨率由于长、宽比例变化(如果有的话)导致的水平、垂直方向裁剪比例的变化,将该变化计算到图片裁剪后的高度,确保图片宽>=1080(因为长>=宽,所以不用考虑长)。在未找到能获取宽>=1080的分辨率时,保留能获取宽最大的拍照分辨率,已找到能获取宽>=1080的分辨率后,保留规格最小的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
int minLength = Integer.MAX_VALUE;
int maxY = 0;
bestSize = null;
boolean hasLegalBestSize = false;
HashMap<Float, BitmapUtil.AutoCropPercent> autoCropPercentMap = new HashMap<>();
StringBuilder pictureFitPreviewSizesString = new StringBuilder();
for (Camera.Size size : rawSupportedSizes) {
float aspectRatio = size.height * 1.0f / size.width;
BitmapUtil.AutoCropPercent autoCropPercent;
if (autoCropPercentMap.containsKey(aspectRatio)) {
autoCropPercent = autoCropPercentMap.get(aspectRatio);
} else {
autoCropPercent = BitmapUtil.getPreview2PictureAutoCropPercent(aspectRatio, ratio);
autoCropPercentMap.put(aspectRatio, autoCropPercent);
}
float yMargin = CameraActivity.RED_LINE_MARGIN + autoCropPercent.getYp();
int y = (int) (size.height * (1 - 2 * yMargin) + 0.5f);
if (hasLegalBestSize && y < CameraActivity.PICTURE_WIDTH) {
continue;
}
if (!hasLegalBestSize && y >= CameraActivity.PICTURE_WIDTH) {
hasLegalBestSize = true;
pictureFitPreviewSizesString.append(size.width).append('x').append(size.height).append(' ');
minLength = size.height * size.width;
bestSize = size;
mAutoCropPercent = autoCropPercent;
continue;
}
if (hasLegalBestSize) {
pictureFitPreviewSizesString.append(size.width).append('x').append(size.height).append(' ');
}
if (hasLegalBestSize) {
int length = size.height * size.width;
if (length < minLength) {
minLength = length;
bestSize = size;
mAutoCropPercent = autoCropPercent;
}
} else {
if (y > maxY) {
maxY = y;
bestSize = size;
mAutoCropPercent = autoCropPercent;
}
}
}
Log.i(TAG, "Supported picture fit preview sizes: " + pictureFitPreviewSizesString);
if (bestSize != null) {
return new Point(bestSize.width, bestSize.height);
}

本文标题:相机的分辨率

文章作者:魏超

发布时间:2017年10月22日 - 21:10

最后更新:2018年12月07日 - 19:12

原始链接:http://www.weichao.io/2017/10/22/相机的分辨率/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

---------------------本文结束---------------------