最近公司需要做一个手机自拍照的功能,由于之前有做过类似手机拍照的功能,所以很快就实现了自定义手机拍的功能。但是后面发现部分手机出现预览照片拉伸和保存的图片拉伸的情况。然后百度了一下,发现原理很好理解,也有一堆demo,然而并没有解决拉伸的情况。下面总结一下我的解决方法,希望对你有用。
一、原理
我们首先来理解主要的三个大小:
1、SurfaceView的大小,屏幕上显示摄像头拍摄的图像的view
2、Camera中的Preview的大小,预览图像大小
3、Camera中的Picture的大小,最终保存的照片的大小
预览照片出现拉伸的原因就是SurfaceView的大小跟Preview的大小的宽高比率不一样,这个很容易理解,你摄像机拍出来的图片跟你用来显示这张图片的view的宽高比率不一样,那必然会出现拉伸的效果的。
保存的图片拉伸的原理跟上面的一样,明白原理也就有了解决方式了,那就是调用Camera.Params 的setPreviewSize方法和 setPictureSize方法,进行设置Preview和Picture的大小,让他跟SurfaceView的大小的宽高比率一致或者相似,就不会出现拉伸的情况了。
二、解决方式
由于直接调用parameters.setPreviewSize(size.width, size.height); 设置Preview的大小是不会生效的,必须先获取该设备支持的大小列表,再筛选合适的大小进行设置。由于parameters.setPreviewSize()和parameters.setPictureSize()方法里面的数值不能随便设置;所以我的解决方法是:
1、先调用parameters.getSupportedPreviewSizes();获取该手机支持的Preview的大小的集合,根据屏幕大小筛选出最佳的一个Preview的大小。
2、再根据Preview的大小设置SurfaceView的大小,这样Preview跟SurfaceView的宽高比率相似,则不会出现拉伸。
3、最后设置Picture大小,类似Preview,获取设备支持的大小列表,再筛选合适的大小进行设置。
三、代码,里面都有注释
Activity
package com.wzrylang.activity;
import android.Manifest;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.ImageFormat;
import android.graphics.Point;
import android.hardware.Camera;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.view.Display;
import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import com.wzrylang.camera.BitmapUtil;
import com.wzrylang.camera.CameraUtils;
import com.wzrylang.camera.FileUtil;
import com.wzrylang.camera.MySurfaceView;
import com.wzrylang.ui.UIActivity;
import com.wzrylang.util.ToastUtil;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
public class Camera3Activity extends UIActivity {
private FrameLayout camera_preview;
private MySurfaceView cameraSurfaceView;
private Camera camera;
private Point mScreenResolution;//屏幕分辨率
private Point previewSizeOnScreen;//相机预览尺寸
private Point pictureSizeOnScreen;//图片尺寸
private Bitmap bitmapCamera = null;
private ImageView img_camera;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_camera3);
camera_preview = (FrameLayout) findViewById(R.id.camera_preview);
img_camera = (ImageView) findViewById(R.id.img_camera);
findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if (camera != null) {
// 控制摄像头自动对焦后才拍摄
//关闭声音
CameraUtils.setCameraSound(true, mContext);
camera.takePicture(null, null, jpeg);
}
}
});
initCamera();
init();
}
private void initCamera() {
// 此处默认打开后置摄像头
// 通过传入参数可以打开前置摄像头
//判断系统版本大于23,即24(7.0)和以上版本提示打开权限
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
String[] permissions = {Manifest.permission.CAMERA};
int check = ContextCompat.checkSelfPermission(mContext, permissions[0]);
// 权限是否已经 授权 GRANTED---授权 DINIED---拒绝
if (check == PackageManager.PERMISSION_GRANTED) {
//调用相机
camera = CameraUtils.openFrontFacingCameraGingerbread();
} else {
requestPermissions(new String[]{Manifest.permission.CAMERA}, 1);
}
} else {
camera = CameraUtils.openFrontFacingCameraGingerbread();
}
if (camera == null) {
ToastUtil.MyToast(mContext, "摄像头被占用,摄像头权限没打开!");
return;
}
setCameraParameters(camera, camera.getParameters());
}
private void init() {
cameraSurfaceView = new MySurfaceView(mContext, camera);
//设置界面展示大小
Point point = CameraUtils.calculateViewSize(previewSizeOnScreen, mScreenResolution);
System.out.println(point.x + "," + point.y);
FrameLayout.LayoutParams layoutParams = new FrameLayout.LayoutParams(point.x, point.y);
layoutParams.gravity = Gravity.CENTER;
cameraSurfaceView.setLayoutParams(layoutParams);
camera_preview.addView(cameraSurfaceView);
}
private void setCameraParameters(Camera camera, Camera.Parameters parameters) {
WindowManager windowManager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point theScreenResolution = new Point();
display.getSize(theScreenResolution);//得到屏幕的尺寸,单位是像素
mScreenResolution = theScreenResolution;
previewSizeOnScreen = CameraUtils.findBestPreviewSizeValue(parameters, theScreenResolution);//通过相机尺寸、屏幕尺寸来得到最好的展示尺寸,此尺寸为相机的
parameters.setPreviewSize(previewSizeOnScreen.x, previewSizeOnScreen.y);
pictureSizeOnScreen = CameraUtils.findBestPictureSizeValue(parameters, theScreenResolution);//通过相机尺寸、屏幕尺寸来得到最好的展示尺寸,此尺寸为相机的
parameters.setPictureSize(pictureSizeOnScreen.x, pictureSizeOnScreen.y);
boolean isScreenPortrait = mScreenResolution.x < mScreenResolution.y;
boolean isPreviewSizePortrait = previewSizeOnScreen.x < previewSizeOnScreen.y;
if (isScreenPortrait != isPreviewSizePortrait) {//相机与屏幕一个方向,则使用相机尺寸
previewSizeOnScreen = new Point(previewSizeOnScreen.y, previewSizeOnScreen.x);//否则翻个
}
// 设置照片的格式
parameters.setPictureFormat(ImageFormat.JPEG);
CameraUtils.setFocus(parameters, true, false, true);//设置相机对焦模式
CameraUtils.setBarcodeSceneMode(parameters, Camera.Parameters.SCENE_MODE_BARCODE);//设置相机场景模式
CameraUtils.setBestPreviewFPS(parameters);//设置相机帧数
camera.setParameters(parameters);
// 系统相机默认是横屏的,我们要旋转90°
camera.setDisplayOrientation(90);
}
//创建jpeg图片回调数据对象
Camera.PictureCallback jpeg = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] data, Camera camera) {
// TODO Auto-generated method stub
try {
bitmapCamera = BitmapFactory.decodeByteArray(data, 0, data.length);
bitmapCamera = BitmapUtil.rotateImage(bitmapCamera);
zipBitmap();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
camera.stopPreview();//关闭预览 处理数据
CameraUtils.setCameraSound(false, mContext);
}
};
private void zipBitmap() {
try {
//bitmapCamera = BitmapUtil.zipBitmap(bitmapCamera, 100);
img_camera.setImageBitmap(bitmapCamera);
File file = FileUtil.saveImagePath(mContext, "et", "image");
if (file != null) {
FileOutputStream fileOutStream = null;
fileOutStream = new FileOutputStream(file);
//把位图输出到指定的文件中
bitmapCamera.compress(Bitmap.CompressFormat.JPEG, 100, fileOutStream);
fileOutStream.close();
System.out.println("**********图片保存成功***********");
}
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
protected void onDestroy() {
super.onDestroy();
//回收数据
if (camera != null) {
camera.stopPreview();
camera.release();
camera = null;
}
if (bitmapCamera != null) {
bitmapCamera.recycle();//回收bitmap空间
bitmapCamera = null;
}
}
}
布局
自定义MySurfaceView
/**
* Created by jin on 2018/9/20.
*/
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private SurfaceHolder mHolder;
private Camera mCamera;
public MySurfaceView(Context context, Camera camera) {
super(context);
mCamera = camera;
mHolder = getHolder();
mHolder.addCallback(this);
mHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
try {
//摄像头绑定view
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
if (mHolder.getSurface() == null){
// preview surface does not exist
return;
}
try {
mCamera.stopPreview();
} catch (Exception e){
// ignore: tried to stop a non-existent preview
}
try {
mCamera.setPreviewDisplay(mHolder);
mCamera.startPreview();
} catch (Exception e){
}
}
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
}
}
工具类CameraUtils,核心算法
package com.wzrylang.camera;
import android.content.Context;
import android.graphics.Point;
import android.hardware.Camera;
import android.media.AudioManager;
import android.util.Log;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
public final class CameraUtils {
private static final String TAG = "CameraUtils";
private static final int MIN_PREVIEW_PIXELS = 480 * 320; // normal screen
private static final double MAX_ASPECT_DISTORTION = 0.15;
private static final int MIN_FPS = 10;
private static final int MAX_FPS = 20;
private CameraUtils() {
}
public static Camera openFrontFacingCameraGingerbread() {
int cameraCount = 0;
Camera cam = null;
Camera.CameraInfo cameraInfo = new Camera.CameraInfo();
cameraCount = Camera.getNumberOfCameras();
System.out.println("cameraCount = " + cameraCount);
for (int camIdx = 0; camIdx < cameraCount; camIdx++) {
Camera.getCameraInfo(camIdx, cameraInfo);
if (cameraInfo.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) {
try {
cam = Camera.open(camIdx);
} catch (RuntimeException e) {
Log.e("", "Camera failed to open: " + e.getLocalizedMessage());
}
}
}
return cam;
}
public static void setCameraSound(final boolean isSound, final Context context) {
new Thread(
new Runnable() {
@Override
public void run() {
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
audioManager.setStreamMute(AudioManager.STREAM_SYSTEM, isSound);
}
}
).start();
}
/**
* set camera focus
*
* @param parameters
* @param autoFocus
* @param disableContinuous
* @param safeMode
*/
public static void setFocus(Camera.Parameters parameters,
boolean autoFocus,
boolean disableContinuous,
boolean safeMode) {
List supportedFocusModes = parameters.getSupportedFocusModes();
String focusMode = null;
if (autoFocus) {
if (safeMode || disableContinuous) {
focusMode = findSettableValue("focus mode",
supportedFocusModes,
Camera.Parameters.FOCUS_MODE_AUTO);
} else {
focusMode = findSettableValue("focus mode",
supportedFocusModes,
Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE,
Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO,
Camera.Parameters.FOCUS_MODE_AUTO);
}
}
// Maybe selected auto-focus but not available, so fall through here:
if (!safeMode && focusMode == null) {
focusMode = findSettableValue("focus mode",
supportedFocusModes,
Camera.Parameters.FOCUS_MODE_MACRO,
Camera.Parameters.FOCUS_MODE_EDOF);
}
if (focusMode != null) {
if (focusMode.equals(parameters.getFocusMode())) {// if camera already set focusMode equlas focusMode,no need to reset
} else {
parameters.setFocusMode(focusMode);
}
}
}
public static void setBestPreviewFPS(Camera.Parameters parameters) {
setBestPreviewFPS(parameters, MIN_FPS, MAX_FPS);
}
/**
* set camera fps(帧数)
*
* @param parameters
* @param minFPS
* @param maxFPS
*/
public static void setBestPreviewFPS(Camera.Parameters parameters, int minFPS, int maxFPS) {
List supportedPreviewFpsRanges = parameters.getSupportedPreviewFpsRange();
if (supportedPreviewFpsRanges != null && !supportedPreviewFpsRanges.isEmpty()) {
int[] suitableFPSRange = null;
for (int[] fpsRange : supportedPreviewFpsRanges) {
int thisMin = fpsRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX];
int thisMax = fpsRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX];
if (thisMin >= minFPS * 1000 && thisMax <= maxFPS * 1000) {
suitableFPSRange = fpsRange;
break;
}
}
if (suitableFPSRange == null) {
} else {
int[] currentFpsRange = new int[2];
parameters.getPreviewFpsRange(currentFpsRange);
if (Arrays.equals(currentFpsRange, suitableFPSRange)) {
} else {
parameters.setPreviewFpsRange(suitableFPSRange[Camera.Parameters.PREVIEW_FPS_MIN_INDEX],
suitableFPSRange[Camera.Parameters.PREVIEW_FPS_MAX_INDEX]);
}
}
}
}
/**
* set camera sceneMode
*
* @param parameters
* @param modes
*/
public static void setBarcodeSceneMode(Camera.Parameters parameters, String... modes) {
String sceneMode = findSettableValue("scene mode",
parameters.getSupportedSceneModes(),
modes);
if (sceneMode != null) {
parameters.setSceneMode(sceneMode);
}
}
/**
* find best previewSize value,on the basis of camera supported previewSize and screen size
*
* @param parameters
* @param screenResolution
* @return
*/
public static Point findBestPreviewSizeValue(Camera.Parameters parameters, Point screenResolution) {
List rawSupportedSizes = parameters.getSupportedPreviewSizes();
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);
}
// Sort by size, descending
List supportedPreviewSizes = new ArrayList<>(rawSupportedSizes);
Collections.sort(supportedPreviewSizes, new Comparator() {
@Override
public int compare(Camera.Size a, Camera.Size b) {
int aPixels = a.height * a.width;
int bPixels = b.height * b.width;
if (bPixels < aPixels) {
return -1;
}
if (bPixels > aPixels) {
return 1;
}
return 0;
}
});
if (Log.isLoggable(TAG, Log.INFO)) {//检查是否可以输出日志
StringBuilder previewSizesString = new StringBuilder();
for (Camera.Size supportedPreviewSize : supportedPreviewSizes) {
previewSizesString.append(supportedPreviewSize.width).append('x')
.append(supportedPreviewSize.height).append(' ');
}
Log.i(TAG, "Supported preview sizes: " + previewSizesString);
}
double screenAspectRatio;
if (screenResolution.x > screenResolution.y) {
screenAspectRatio = screenResolution.x / (double) screenResolution.y;//屏幕尺寸比例
} else {
screenAspectRatio = screenResolution.y / (double) screenResolution.x;//屏幕尺寸比例
}
// Remove sizes that are unsuitable
Iterator it = supportedPreviewSizes.iterator();
while (it.hasNext()) {
Camera.Size supportedPreviewSize = it.next();
int realWidth = supportedPreviewSize.width;
int realHeight = supportedPreviewSize.height;
if (realWidth * realHeight < MIN_PREVIEW_PIXELS) {//delete if less than minimum size
it.remove();
continue;
}
//camera preview width > height
boolean isCandidatePortrait = realWidth < realHeight;//width less than height
int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
double aspectRatio = maybeFlippedWidth / (double) maybeFlippedHeight;//ratio for camera
double distortion = Math.abs(aspectRatio - screenAspectRatio);//returan absolute value
if (distortion > MAX_ASPECT_DISTORTION) {//delete if distoraion greater than 0.15
it.remove();
continue;
}
if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) {//serceen size equal to camera supportedPreviewSize
Point exactPoint = new Point(realWidth, realHeight);
Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint);
return exactPoint;
}
}
if (!supportedPreviewSizes.isEmpty()) {//default return first supportedPreviewSize,mean largest
Camera.Size largestPreview = supportedPreviewSizes.get(0);
Point largestSize = new Point(largestPreview.width, largestPreview.height);
Log.i(TAG, "Using largest suitable preview size: " + largestSize);
return largestSize;
}
// If there is nothing at all suitable, return current preview size
Camera.Size defaultPreview = parameters.getPreviewSize();
if (defaultPreview == null) {
throw new IllegalStateException("Parameters contained no preview size!");
}
Point defaultSize = new Point(defaultPreview.width, defaultPreview.height);
Log.i(TAG, "No suitable preview sizes, using default: " + defaultSize);
return defaultSize;
}
/**
* find best pictureSize value,on the basis of camera supported pictureSize and screen size
*
* @param parameters
* @param screenResolution
* @return
*/
public static Point findBestPictureSizeValue(Camera.Parameters parameters, Point screenResolution) {
List rawSupportedSizes = parameters.getSupportedPictureSizes();
if (rawSupportedSizes == null) {
Log.w(TAG, "Device returned no supported preview sizes; using default");
Camera.Size defaultSize = parameters.getPictureSize();
if (defaultSize == null) {
throw new IllegalStateException("Parameters contained no preview size!");
}
return new Point(defaultSize.width, defaultSize.height);
}
// Sort by size, descending
List supportedPreviewSizes = new ArrayList<>(rawSupportedSizes);
Collections.sort(supportedPreviewSizes, new Comparator() {
@Override
public int compare(Camera.Size a, Camera.Size b) {
int aPixels = a.height * a.width;
int bPixels = b.height * b.width;
if (bPixels < aPixels) {
return -1;
}
if (bPixels > aPixels) {
return 1;
}
return 0;
}
});
if (Log.isLoggable(TAG, Log.INFO)) {//检查是否可以输出日志
StringBuilder previewSizesString = new StringBuilder();
for (Camera.Size supportedPreviewSize : supportedPreviewSizes) {
previewSizesString.append(supportedPreviewSize.width).append('x')
.append(supportedPreviewSize.height).append(' ');
}
Log.i(TAG, "Supported picture sizes: " + previewSizesString);
}
double screenAspectRatio;
if (screenResolution.x > screenResolution.y) {
screenAspectRatio = screenResolution.x / (double) screenResolution.y;//屏幕尺寸比例
} else {
screenAspectRatio = screenResolution.y / (double) screenResolution.x;//屏幕尺寸比例
}
// Remove sizes that are unsuitable
Iterator it = supportedPreviewSizes.iterator();
while (it.hasNext()) {
Camera.Size supportedPreviewSize = it.next();
int realWidth = supportedPreviewSize.width;
int realHeight = supportedPreviewSize.height;
if (realWidth * realHeight < MIN_PREVIEW_PIXELS) {//delete if less than minimum size
it.remove();
continue;
}
//camera preview width > height
boolean isCandidatePortrait = realWidth < realHeight;//width less than height
int maybeFlippedWidth = isCandidatePortrait ? realHeight : realWidth;
int maybeFlippedHeight = isCandidatePortrait ? realWidth : realHeight;
double aspectRatio = maybeFlippedWidth / (double) maybeFlippedHeight;//ratio for camera
double distortion = Math.abs(aspectRatio - screenAspectRatio);//returan absolute value
if (distortion > MAX_ASPECT_DISTORTION) {//delete if distoraion greater than 0.15
it.remove();
continue;
}
if (maybeFlippedWidth == screenResolution.x && maybeFlippedHeight == screenResolution.y) {//serceen size equal to camera supportedPreviewSize
Point exactPoint = new Point(realWidth, realHeight);
Log.i(TAG, "Found preview size exactly matching screen size: " + exactPoint);
return exactPoint;
}
}
if (!supportedPreviewSizes.isEmpty()) {//default return first supportedPreviewSize,mean largest
Camera.Size largestPreview = supportedPreviewSizes.get(0);
Point largestSize = new Point(largestPreview.width, largestPreview.height);
Log.i(TAG, "Using largest suitable preview size: " + largestSize);
return largestSize;
}
// If there is nothing at all suitable, return current preview size
Camera.Size defaultPreview = parameters.getPictureSize();
if (defaultPreview == null) {
throw new IllegalStateException("Parameters contained no preview size!");
}
Point defaultSize = new Point(defaultPreview.width, defaultPreview.height);
Log.i(TAG, "No suitable preview sizes, using default: " + defaultSize);
return defaultSize;
}
/**
* find seetable value from supportedValues for desiredValues
*
* @param name
* @param supportedValues
* @param desiredValues
* @return
*/
private static String findSettableValue(String name,
Collection supportedValues,
String... desiredValues) {
Log.i(TAG, "Requesting " + name + " value from among: " + Arrays.toString(desiredValues));
Log.i(TAG, "Supported " + name + " values: " + supportedValues);
if (supportedValues != null) {
for (String desiredValue : desiredValues) {
if (supportedValues.contains(desiredValue)) {
Log.i(TAG, "Can set " + name + " to: " + desiredValue);
return desiredValue;
}
}
}
Log.i(TAG, "No supported values match");
return null;
}
/**
* 根据相机预览尺寸、控件可展示最大尺寸来计算控件的展示尺寸,防止图像变形
*
* @param previewSizeOnScreen
* @param maxSizeOnView
* @return
*/
public static Point calculateViewSize(Point previewSizeOnScreen, Point maxSizeOnView) {
Point point = new Point();
float ratioPreview = (float) previewSizeOnScreen.x / (float) previewSizeOnScreen.y;//相机预览比率
float ratioMaxView = (float) maxSizeOnView.x / (float) maxSizeOnView.y;//控件比率
if (ratioPreview > ratioMaxView) {//x>y,以控件宽为标准,缩放高
point.x = maxSizeOnView.x;
point.y = (int) (maxSizeOnView.x / ratioPreview);
} else {
point.y = maxSizeOnView.y;
point.x = (int) (maxSizeOnView.y * ratioPreview);
}
return point;
}
}