显示区域
如图所示,长、宽同一比例的分辨率显示的区域相同,不同比例对应圆中不同的最大的内接矩形。
预览分辨率 需求 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
因为圆,所以
t 1 = t 4 t1 = t4 t 1 = t 4
即
t 2 2 + t 3 2 = t 5 2 + t 6 2 t2^2 + t3^2 = t5^2 + t6^2 t 2 2 + t 3 2 = t 5 2 + t 6 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
即
t 3 t 6 = ( 1 + p r e v i e w R a t i o ) ( 1 + p i c t u r e R a t i o ) \frac{t3}{t6} = \sqrt{\frac{(1 + previewRatio)}{(1 + pictureRatio)}} t 6 t 3 = ( 1 + p i c t u r e R a t i o ) ( 1 + p r e v i e w R a t i o )
因为三角形相似,所以
l x l x ′ = t 3 t 6 = ( 1 + p r e v i e w R a t i o ) ( 1 + p i c t u r e R a t i o ) \frac{lx}{lx'} = \frac{t3}{t6} = \sqrt{\frac{(1 + previewRatio)}{(1 + pictureRatio)}} l x ′ l x = t 6 t 3 = ( 1 + p i c t u r e R a t i o ) ( 1 + p r e v i e w R a t i o )
则
x = x ′ − l x ′ − l x l x ′ = x ′ − 1 + l x l x ′ = p i c t u r e R a t i o − p r e v i e w R a t i o 2 ∗ p i c t u r e R a t i o − 1 + ( 1 + p r e v i e w R a t i o ) ( 1 + p i c t u r e R a t i o ) x = x' - \frac{lx' - lx}{lx'} = x' - 1 + \frac{lx}{lx'} = \frac{pictureRatio - previewRatio}{2 * pictureRatio} - 1 + \sqrt{\frac{(1 + previewRatio)}{(1 + pictureRatio)}} x = x ′ − l x ′ l x ′ − l x = x ′ − 1 + l x ′ l x = 2 ∗ p i c t u r e R a t i o p i c t u r e R a t i o − p r e v i e w R a t i o − 1 + ( 1 + p i c t u r e R a t i o ) ( 1 + p r e v i e w R a t i o )
4、y 计算
y = t 6 − t 3 t 3 = t 6 t 3 − 1 = 1 ( 1 + p r e v i e w R a t i o ) ( 1 + p i c t u r e R a t i o ) − 1 y = \frac{t6 - t3}{t3} = \frac{t6}{t3} - 1 = \frac{1}{\sqrt{\frac{(1 + previewRatio)}{(1 + pictureRatio)}}} - 1 y = t 3 t 6 − t 3 = t 3 t 6 − 1 = ( 1 + p i c t u r e R a t i o ) ( 1 + p r e v i e w R a t i o ) 1 − 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); }