安卓开发——相机:拍照并处理图片

本次使用的是Camera API来实现的,并不是使用隐式Intent与照相机进行交互的。所以我们使用SurfaceView类和相机硬件来实现实时展示拍照界面,以及拍照后的图片处理。

  相机是一种独占性资源:也就是说一次只能有一个activity可以调用相机。因此,我们在使用相机硬件资源时需要时刻注意,使用完资源后要释放资源!!!
  SurfaceView实例可以用来充当相机的取景器。SurfaceView是一种特殊的视图,可直接将想要显示的内容渲染输出到设备上(也可以用来播放视频)。

拍照

获取权限

要使用相机,首先我们就要获取相关的权限:(AndroidMenifest.xml)

1
2
3
4
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.FLASHLIGHT"/>
<uses-feature android:name="android.hardware.Camera"/>
<uses-feature android:name="android.hardware.camera.autofocus" />

PS:屏幕显示方向可以在activity中的android:screenOrientation标签定义:

  1. landscape : 横屏
  2. portrait : 竖屏
1
2
3
<activity android:name=".CameraActivity"
android:screenOrientation="landscape">
</activity>

Camera API

这里使用的是android.hardware.Camera包中的Camera类来打开相机的。其中Camera操作主要包括以下三个方法:

  1. public static Camera open(int);
  2. public static Camera open();
  3. public final void release();

  open()可以打开一个Camera资源,含参数的open方法则是在API9以后加入的(推荐使用这个)。release()方法则用于释放获取的相机资源。一般情况下只有当activity视图为在前台可见时才需要使用相机,所以我们考虑在onResume()方法中申请使用相机,而当视图离开前台时,即在onPause()方法中释放获取的相机资源。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public void onResume() {
super.onResume();
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD)
mCamera = Camera.open(0);
else
mCamera = Camera.open();
}
@Override
public void onPause() {
super.onPause();
if(mCamera != null){
mCamera.release();
mCamera = null;
}
}

SurfaceView实现取景器

  1. SurfaceView:是我们用来显示图像的视图组件。
  2. Surface:可以看成是原始像素数据的缓冲区。
  3. SurfaceHolder:是和Surface对象连接的重要纽带。(可以理解为通过SurfaceHolder给Surface对象设置像素数据)。

  Surface对象是有生命周期的,即当SurfaceView在前台显示时会生成Surface对象,当SurfaceView消失时,Surface对象也会被销毁。因为当Surface不存在时我们需要保证Surface中没有需要绘制的对象。所以我们可以在Surface创建后把相机连接到SurfaceHolder上,当Surface被销毁后,我们把SurfaceHolder和相机的连接取消。为了实现这个我们需要使用SurfaceView.Callback接口中的三个方法。

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
mSurfaceView = (SurfaceView) v.findViewById(R.id.camera_surfaceView);
SurfaceHolder holder = mSurfaceView.getHolder();
//holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
holder.addCallback(new SurfaceHolder.Callback() {
/* 当SurfaceView创建时候调用 */
@Override
public void surfaceCreated(SurfaceHolder surfaceHolder) {
/* 设置当前surfaceview为相机的显示区域 */
try {
if (mCamera != null)
mCamera.setPreviewDisplay(surfaceHolder);
} catch (IOException e) {
Log.e(TAG_CAMERA, "surfaceView fail...");
e.printStackTrace();
}
}
/* 首次显示到屏幕是调用的方法 */
@Override
public void surfaceChanged(SurfaceHolder surfaceHolder, int i, int i1, int i2) {
if(mCamera == null)
return;
try {
/* 开始绘制帧 */
mCamera.startPreview();
} catch (Exception e) {
Log.e(TAG_CAMERA, "camera start preview fail...");
mCamera.release();
mCamera = null;
e.printStackTrace();
}
}
/* 当SurfaceView从屏幕上移除时 */
@Override
public void surfaceDestroyed(SurfaceHolder surfaceHolder) {
/* 设置相机不再在这个域显示 */
if (mCamera != null)
/* 停止绘制帧 */
mCamera.stopPreview();
}
});

实现相机的回调方法

捕获图像,并存为JPEG图片。

public final void takePicture(Camera.ShutterCallback shutter, Camera.PictureCallback raw, Camera.PictureCallback jpeg);

  1. ShutterCallback回调方法在相机捕获图片时调用。此时图像数据还没有完成。
  2. PictureCallback raw,原始数据可用时调用的回调函数。
  3. PicryreCallback jpeg,在jpeg版本的图像可用时调用的回调函数。

下面就是两个接口ShutterCallback,PictureCallbak的实现:

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
private Camera.ShutterCallback mShutterCallback = new Camera.ShutterCallback() {
@Override
public void onShutter() {
mProgressBarContainer.setVisibility(View.VISIBLE);
}
};
private Camera.PictureCallback mJpegCallback = new Camera.PictureCallback() {
@Override
public void onPictureTaken(byte[] bytes, Camera camera) {
/* 随机生成一个文件名 */
String fileName = UUID.randomUUID().toString() + ".jpg";
/* 创建一个文件输出流,生成jpeg文件 */
FileOutputStream os = null;
boolean success = true;
try {
os = getActivity().openFileOutput(fileName, Context.MODE_PRIVATE);
os.write(bytes);
} catch (IOException e) {
success = false;
e.printStackTrace();
} finally {
try {
if (os != null)
os.close();
} catch (IOException e) {
success = false;
e.printStackTrace();
}
}
if(success) {
//Log.d(TAG_CAMERA, "save picture " + fileName + " success.");
/* 返回生成的文件名给父Activity */
Intent i = new Intent();
i.putExtra(EXTRA_PHOTO, fileName);
getActivity().setResult(Activity.RESULT_OK, i);
}
else
getActivity().setResult(Activity.RESULT_CANCELED);
/* 关闭拍照界面 */
getActivity().finish();
}
};

拍照功能的实现是在拍照按钮的监听器事件中触发的:

1
2
3
4
5
6
7
8
9
mTakePhoto = (Button) v.findViewById(R.id.camera_take_photo);
mTakePhoto.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
if(mCamera != null)
/* 这里只处理生成jpeg图像后的事件 */
mCamera.takePicture(mShutterCallback, null, mJpegCallback);
}
});

图片处理与显示

(1)图片显示可以使用ImageView视图:

1
2
3
4
5
6
<ImageView
android:id="@+id/imageView"
android:layout_width="80dp"
android:layout_height="80dp"
android:scaleType="centerInside"
android:cropToPadding="true"/>

(2)由于现在的图片的尺寸都是比较大的,一般我们在显示图片之前都会对图片进行处理。接下来就是一个类来适当的缩放图片尺寸来适合在ImageView中的显示。

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
import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.view.Display;
import android.widget.ImageView;
public class PictureUtils {
/* 获取适当尺寸的图片大小,path为图片文字路径 */
public static BitmapDrawable getScaledDrawble(Activity a, String path){
Display display = a.getWindowManager().getDefaultDisplay();
float desWidth = display.getWidth();
float desHeight = display.getHeight();
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(path, options);
float srcWidth = options.outWidth;
float srcHeight = options.outHeight;
int scaleSize = 1;
if(srcWidth > desWidth || srcHeight > desHeight){
if(srcWidth > srcHeight)
scaleSize = Math.round(srcHeight/desHeight);
else
scaleSize = Math.round(srcWidth/desWidth);
}
options = new BitmapFactory.Options();
options.inSampleSize = scaleSize;
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
return new BitmapDrawable(a.getResources(), bitmap);
}
/* 回收ImageView对象中图像数据 */
public static void cleanImageView(ImageView imageView){
if(!(imageView.getDrawable() instanceof BitmapDrawable))
return;
BitmapDrawable b = (BitmapDrawable) imageView.getDrawable();
b.getBitmap().recycle();
imageView.setImageDrawable(null);
}
}

(3)然后就是把处理后的图片显示到ImageView上。

1
2
3
4
5
public void showPhoto(){
String path = ...;
BitmapDrawable b = PictureUtils.getScaledDrawble(getActivity(), path);
mImageView.setImageDrawable(b);
}