在看这篇文章之前,我建议先看
相机开发基础
针对这个功能需要做自定义相机,根据Camera相机类和SurfaceView类来实现自定义图形预览拍照功能。
但在实现过程中出现几个难点:
1.如何将自己产品图片(上图的台灯)和摄像头预览的图片结合成一张图片。
2.拍照的图片在有些手机上面出现旋转了90度的情况(Android兼容性问题)。
3.某些手机会出现在camera.setParameters(parameters)的调用时候出现设置失败的异常
java.lang.RuntimeException: setParameters failed
针对第一个问题:
开始的考虑是在布局中SurfaceView上加一层ImageView来显示产品图片,然后通过截取View的方法截取SurfaceView和ImageView的父布局来实现,但截取view的时候发现只能截取静态的方法,对于像surfaceview之类的动态的view是不能够截取的(具体的截取方法请参考下面的源码),后来才有的是绘图的方式将预览图片的bitmap和产品图片的bitmap绘制到一张图片实现图片的合并(具体方法请参考下面源码)。
针对第二个问题:
这个显然是android手机的兼容性问题,网上也有很大类似的网友提出此类问题,但给出的解决方法大都是先通过ExifInterface类来
读取bitmap的旋转角度,然后根据旋转角度旋转bitmap实现,我也用这个方法检验过,但实际上此方法并不靠谱,原因是读取bitmap的角度始终为0,即使我图片在拍照后出现的旋转了90度,也是读取不了的。
http://blog.csdn.net/qq_34850038/article/details/78761311 介绍了ExifInterface类在某些机型的有兼容性问题,并指出了问题产生的原因,我采用,拍照完成后,强制调用
setPictureDegreeZero方法(具体实现参考下面源码)来重新将图片的旋转角度设置为0,终于以次方解决了问题。终于松了口气,啊哈哈。
针对第三个问题:
某些机型出现了设置拍照参数失败,是因为某些参数设置对应该机型不支持,这里我需要做到的是需要确认哪些参数有兼容性问题的,经测试发现,
// parameters.setPreviewFrameRate(3);// 每秒3帧 每秒从摄像头里面获得3个画面, 此参数在小米2上面支持,在红米note2上面不支持,
//
parameters
.
setPreviewSize
(
PreviewWidth
,
PreviewHeight
);
// 获得摄像区域的大小
// parameters.setPictureSize(PreviewWidth,PreviewHeight);// 获得保存图片的大小 ----这个两个参数也会有兼容性问题。
目前发现这里3个参数有兼容性问题,其它若有发现在补充,同时如果网友们发现了其它参数存在兼容性问题也烦请指出,谢谢。
针对不兼容性问题,尽量避免去设置它,
对应setPreviewSize和setPictureSize可以通过以下方式取得合适的预览尺寸进行设置,具体方法请参考下面源代码。
// 选择合适的预览尺寸
List
<
Camera
.
Size
>
sizeList
=
parameters
.
getSupportedPreviewSizes
();
具体实现代码:
-
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550
package
com.example.eclipsetest;
import
java.io.BufferedOutputStream;
import
java.io.File;
import
java.io.FileOutputStream;
import
java.io.IOException;
import
java.lang.reflect.Method;
import
java.util.Iterator;
import
java.util.List;
import
android.app.Activity;
import
android.content.Context;
import
android.graphics.Bitmap;
import
android.graphics.Bitmap.Config;
import
android.graphics.BitmapFactory;
import
android.graphics.Canvas;
import
android.graphics.Matrix;
import
android.graphics.PixelFormat;
import
android.graphics.drawable.Drawable;
import
android.hardware.Camera;
import
android.hardware.Camera.AutoFocusCallback;
import
android.hardware.Camera.Parameters;
import
android.hardware.Camera.PictureCallback;
import
android.hardware.Camera.ShutterCallback;
import
android.hardware.Camera.Size;
import
android.media.ExifInterface;
import
android.os.Build;
import
android.os.Bundle;
import
android.os.Environment;
import
android.util.DisplayMetrics;
import
android.util.Log;
import
android.view.Display;
import
android.view.Gravity;
import
android.view.SurfaceHolder;
import
android.view.SurfaceView;
import
android.view.View;
import
android.view.View.MeasureSpec;
import
android.view.View.OnClickListener;
import
android.view.WindowManager;
import
android.widget.Button;
import
android.widget.FrameLayout;
import
android.widget.ImageView;
import
android.widget.ImageView.ScaleType;
import
android.widget.Toast;
/**
* 自定义拍照,将特定图片添加到预览图片中保存起来
*
* @ClassName: MyCameraDemo
* @Description:
* @author xiaoxiao
* @date modify by 2015-9-8 下午2:09:16
*
*/
public
class
MyCameraDemo
extends
Activity {
private
SurfaceView surface =
null
;
private
Button but =
null
;
private
SurfaceHolder holder =
null
;
private
Camera cam =
null
;
private
boolean
previewRunning =
true
;
private
Button but2;
private
ImageView iv_img;
private
FrameLayout flay_view;
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
super
.setContentView(R.layout.main);
this
.but = (Button)
super
.findViewById(R.id.but);
this
.but2 = (Button)
super
.findViewById(R.id.but2);
this
.surface = (SurfaceView)
super
.findViewById(R.id.surface);
iv_img = (ImageView) findViewById(R.id.iv_img);
flay_view = (FrameLayout) findViewById(R.id.flay_view);
this
.holder =
this
.surface.getHolder();
this
.holder.addCallback(
new
MySurfaceViewCallback());
this
.holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
this
.holder.setFixedSize(
500
,
350
);
this
.but.setOnClickListener(
new
OnClickListenerImpl());
but2.setOnClickListener(
new
OnClickListener() {
@Override
public
void
onClick(View v) {
iv_img.setVisibility(View.VISIBLE);
iv_img.setImageResource(R.drawable.taideng);
}
});
}
private
class
OnClickListenerImpl
implements
OnClickListener {
@Override
public
void
onClick(View v) {
if
(cam !=
null
) {
cam.autoFocus(
new
AutoFocusCallbackImpl());
}
}
}
private
class
MySurfaceViewCallback
implements
SurfaceHolder.Callback {
@Override
public
void
surfaceChanged(SurfaceHolder holder,
int
format,
int
width,
int
height) {
}
@Override
public
void
surfaceCreated(SurfaceHolder holder) {
if
(cam !=
null
) {
cam.stopPreview();
// 停掉原来摄像头的预览
cam.release();
// 释放资源
cam =
null
;
// 取消原来摄像头
}
try
{
cam = Camera.open(
0
);
// 取得第一个摄像头
}
catch
(Exception e) {
// TODO: handle exception
Toast.makeText(MyCameraDemo.
this
,
"摄像头打开失败"
,
0
).show();
return
;
}
cam = deal2(cam);
}
@Override
public
void
surfaceDestroyed(SurfaceHolder holder) {
if
(cam !=
null
) {
if
(MyCameraDemo.
this
.previewRunning) {
cam.stopPreview();
// 停止预览
MyCameraDemo.
this
.previewRunning =
false
;
}
cam.stopPreview();
cam.release();
cam =
null
;
}
}
}
private
class
AutoFocusCallbackImpl
implements
AutoFocusCallback {
@Override
public
void
onAutoFocus(
boolean
success, Camera camera) {
if
(success) {
// 成功
cam.takePicture(sc, pc, jpgcall);
}
}
}
private
PictureCallback jpgcall =
new
PictureCallback() {
@Override
public
void
onPictureTaken(
byte
[] data, Camera camera) {
// 保存图片的操作
Bitmap bmp = BitmapFactory.decodeByteArray(data,
0
, data.length);
// Bitmap bmp2 = new BitmapDrawable(iv_img).getDrawable();
// Bitmap bmp2 = BitmapFactory.decodeResource(getResources(),
// R.drawable.taideng);
// Bitmap bmp2 = drawableToBitamp(iv_img.getDrawable());
String fileName =
"test_"
+ System.currentTimeMillis() +
".jpg"
;
String filePath = Environment.getExternalStorageDirectory()
.toString()
+ File.separator
+
"xiao"
+ File.separator
+ fileName;
bmp = rotateBitmapByDegree(bmp,
90
);
save(bmp, filePath, fileName);
bmp = loadBitmap(filePath,
true
);
setPictureDegreeZero(filePath);
Bitmap bmp2 = getSmallBitmap(MyCameraDemo.
this
,
R.drawable.taideng, dip2px(MyCameraDemo.
this
,
200
),
dip2px(MyCameraDemo.
this
,
200
));
Bitmap bmp3 = combineBitmap(bmp, bmp2);
save(bmp3, filePath, fileName);
cam.stopPreview();
cam.startPreview();
}
};
private
ShutterCallback sc =
new
ShutterCallback() {
@Override
public
void
onShutter() {
// 按下快门之后进行的操作
}
};
private
PictureCallback pc =
new
PictureCallback() {
@Override
public
void
onPictureTaken(
byte
[] data, Camera camera) {
}
};
/**
* 开始考虑用剪切的方法,但是截取只适合静态界面,这里surfaceView是动态的(在不断重绘)不能剪切,后来考虑用绘图的方式将两个bitmap合在一起。
*
* @param view
* @return
*/
private
Bitmap cropView(View view) {
view.measure(MeasureSpec.makeMeasureSpec(
100
, MeasureSpec.UNSPECIFIED),
MeasureSpec.makeMeasureSpec(
100
, MeasureSpec.UNSPECIFIED));
view.layout(
0
,
0
, view.getMeasuredWidth(), view.getMeasuredHeight());
view.buildDrawingCache();
Bitmap bitmap = view.getDrawingCache();
return
bitmap;
}
/**
* 合并两张bitmap为一张
*
* @param background
* @param foreground
* @return Bitmap
*/
public
static
Bitmap combineBitmap(Bitmap background, Bitmap foreground) {
if
(background ==
null
) {
return
null
;
}
int
bgWidth = background.getWidth();
int
bgHeight = background.getHeight();
int
fgWidth = foreground.getWidth();
int
fgHeight = foreground.getHeight();
Bitmap newmap = Bitmap
.createBitmap(bgWidth, bgHeight, Config.ARGB_8888);
Canvas canvas =
new
Canvas(newmap);
canvas.drawBitmap(background,
0
,
0
,
null
);
canvas.drawBitmap(foreground, (bgWidth - fgWidth) /
2
,
(bgHeight - fgHeight) /
2
,
null
);
canvas.save(Canvas.ALL_SAVE_FLAG);
canvas.restore();
return
newmap;
}
private
void
save(Bitmap bitmap, String filePath, String fileName) {
File file =
new
File(filePath);
if
(!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
// 创建文件夹
}
try
{
BufferedOutputStream bos =
new
BufferedOutputStream(
new
FileOutputStream(file));
bitmap.compress(Bitmap.CompressFormat.JPEG,
80
, bos);
// 向缓冲区之中压缩图片
bos.flush();
bos.close();
Toast.makeText(MyCameraDemo.
this
,
"拍照成功,照片已保存在"
+ fileName +
"文件之中!"
, Toast.LENGTH_SHORT)
.show();
}
catch
(Exception e) {
Toast.makeText(MyCameraDemo.
this
,
"拍照失败!"
, Toast.LENGTH_SHORT)
.show();
}
}
private
Bitmap bitmap;
private
Bitmap drawableToBitamp(Drawable drawable) {
int
w = drawable.getIntrinsicWidth();
int
h = drawable.getIntrinsicHeight();
System.out.println(
"Drawable转Bitmap"
);
Bitmap.Config config = drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
: Bitmap.Config.RGB_565;
bitmap = Bitmap.createBitmap(w, h, config);
// 注意,下面三行代码要用到,否在在View或者surfaceview里的canvas.drawBitmap会看不到图
Canvas canvas =
new
Canvas(bitmap);
drawable.setBounds(
0
,
0
, w, h);
drawable.draw(canvas);
return
bitmap;
}
/**
* 读取图片的旋转的角度, 某些机型此方法无效
*
* @param path
* 图片绝对路径
* @return 图片的旋转角度
*/
private
int
getBitmapDegree(String path) {
int
degree =
0
;
try
{
// 从指定路径下读取图片,并获取其EXIF信息
ExifInterface exifInterface =
new
ExifInterface(path);
// 获取图片的旋转信息
int
orientation = exifInterface.getAttributeInt(
ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_NORMAL);
switch
(orientation) {
case
ExifInterface.ORIENTATION_ROTATE_90:
degree =
90
;
break
;
case
ExifInterface.ORIENTATION_ROTATE_180:
degree =
180
;
break
;
case
ExifInterface.ORIENTATION_ROTATE_270:
degree =
270
;
break
;
}
}
catch
(IOException e) {
e.printStackTrace();
}
return
degree;
}
/**
* 将图片按照某个角度进行旋转
*
* @param bm
* 需要旋转的图片
* @param degree
* 旋转角度
* @return 旋转后的图片
*/
public
static
Bitmap rotateBitmapByDegree(Bitmap bm,
int
degree) {
Bitmap returnBm =
null
;
// 根据旋转角度,生成旋转矩阵
Matrix matrix =
new
Matrix();
matrix.postRotate(degree);
try
{
// 将原始图片按照旋转矩阵进行旋转,并得到新的图片
returnBm = Bitmap.createBitmap(bm,
0
,
0
, bm.getWidth(),
bm.getHeight(), matrix,
true
);
}
catch
(OutOfMemoryError e) {
}
if
(returnBm ==
null
) {
returnBm = bm;
}
if
(bm != returnBm) {
bm.recycle();
}
return
returnBm;
}
// 控制图像的正确显示方向
private
void
setDispaly(Camera.Parameters parameters, Camera camera) {
if
(Integer.parseInt(Build.VERSION.SDK) >=
8
) {
setDisplayOrientation(camera,
90
);
}
else
{
parameters.setRotation(
90
);
}
}
// 实现的图像的正确显示
private
void
setDisplayOrientation(Camera camera,
int
i) {
Method downPolymorphic;
try
{
downPolymorphic = camera.getClass().getMethod(
"setDisplayOrientation"
,
new
Class[] {
int
.
class
});
if
(downPolymorphic !=
null
) {
downPolymorphic.invoke(camera,
new
Object[] { i });
}
}
catch
(Exception e) {
Log.e(
"Came_e"
,
"图像出错"
);
}
}
private
Camera deal2(Camera mCamera) {
// 设置camera预览的角度,因为默认图片是倾斜90度的
// mCamera.setDisplayOrientation(90);
int
PreviewWidth =
0
;
int
PreviewHeight =
0
;
WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
// 获取窗口的管理器
Display display = wm.getDefaultDisplay();
// 获得窗口里面的屏幕
Camera.Parameters parameters = mCamera.getParameters();
// parameters.setFlashMode(Parameters.FLASH_MODE_TORCH); //开启闪光灯,支持
setDispaly(parameters, mCamera);
// parameters.setRotation(90);
// parameters.setPreviewFrameRate(3);// 每秒3帧 每秒从摄像头里面获得3个画面,
// 某些机型(红米note2)不支持
parameters.setPictureFormat(PixelFormat.JPEG);
// 设置照片输出的格式
parameters.set(
"jpeg-quality"
,
100
);
// 设置照片质量
try
{
// 选择合适的预览尺寸
List<Camera.Size> sizeList = parameters.getSupportedPreviewSizes();
// 如果sizeList只有一个我们也没有必要做什么了,因为就他一个别无选择
if
(sizeList.size() >
1
) {
Iterator<Camera.Size> itor = sizeList.iterator();
while
(itor.hasNext()) {
Camera.Size cur = itor.next();
if
(cur.width >= PreviewWidth
&& cur.height >= PreviewHeight) {
PreviewWidth = cur.width;
PreviewHeight = cur.height;
break
;
}
}
}
parameters.setPreviewSize(PreviewWidth, PreviewHeight);
// 获得摄像区域的大小
parameters.setPictureSize(PreviewWidth, PreviewHeight);
// 获得保存图片的大小
// parameters.setPreviewSize(display.getWidth(),
// display.getWidth()); // 获得摄像区域的大小
// parameters.setPictureSize(display.getWidth(),
// display.getWidth());// 设置拍出来的屏幕大小
}
catch
(Exception e) {
Log.e(
"MyCameraDemo"
, e.toString());
}
try
{
cam.setPreviewDisplay(MyCameraDemo.
this
.holder);
}
catch
(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
mCamera.setParameters(parameters);
// 把上面的设置 赋给摄像头
mCamera.startPreview();
// 开始预览
mCamera.cancelAutoFocus();
// 2如果要实现连续的自动对焦,这一句必须加上
previewRunning =
true
;
return
mCamera;
}
/** 从给定路径加载图片 */
public
Bitmap loadBitmap(String imgpath) {
return
BitmapFactory.decodeFile(imgpath);
}
/** 从给定的路径加载图片,并指定是否自动旋转方向 */
public
Bitmap loadBitmap(String imgpath,
boolean
adjustOritation) {
if
(!adjustOritation) {
return
loadBitmap(imgpath);
}
else
{
Bitmap bm = loadBitmap(imgpath);
int
digree =
0
;
ExifInterface exif =
null
;
try
{
exif =
new
ExifInterface(imgpath);
}
catch
(IOException e) {
e.printStackTrace();
exif =
null
;
}
if
(exif !=
null
) {
// 读取图片中相机方向信息
// int ori = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
// ExifInterface.ORIENTATION_NORMAL);
int
ori = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,
ExifInterface.ORIENTATION_FLIP_VERTICAL);
// 计算旋转角度
switch
(ori) {
case
ExifInterface.ORIENTATION_ROTATE_90:
digree =
90
;
break
;
case
ExifInterface.ORIENTATION_ROTATE_180:
digree =
180
;
break
;
case
ExifInterface.ORIENTATION_ROTATE_270:
digree =
270
;
break
;
default
:
digree =
0
;
break
;
}
}
if
(digree !=
0
) {
// 旋转图片
Matrix m =
new
Matrix();
m.postRotate(digree);
bm = Bitmap.createBitmap(bm,
0
,
0
, bm.getWidth(),
bm.getHeight(), m,
true
);
}
return
bm;
}
}
/**
* 将图片的旋转角度置为0 ,此方法可以解决某些机型拍照后图像,出现了旋转情况
*
* @Title: setPictureDegreeZero
* @param path
* @return void
* @date 2012-12-10 上午10:54:46
*/
private
void
setPictureDegreeZero(String path) {
try
{
ExifInterface exifInterface =
new
ExifInterface(path);
// 修正图片的旋转角度,设置其不旋转。这里也可以设置其旋转的角度,可以传值过去,
// 例如旋转90度,传值ExifInterface.ORIENTATION_ROTATE_90,需要将这个值转换为String类型的
exifInterface.setAttribute(ExifInterface.TAG_ORIENTATION,
"no"
);
exifInterface.saveAttributes();
}
catch
(IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
/**
* 根据路径获取图片并压缩返回bitmap用于显示
*
* @param context
* @param id
* @return
*/
private
Bitmap getSmallBitmap(Context context,
int
id,
int
width,
int
height) {
final
BitmapFactory.Options options =
new
BitmapFactory.Options();
options.inJustDecodeBounds =
true
;
BitmapFactory.decodeResource(context.getResources(), id, options);
// 计算 缩略图大小为原始图片大小的几分之一 inSampleSize:缩略图大小为原始图片大小的几分之一
options.inSampleSize = calculateInSampleSize(options, width, height);
options.inJustDecodeBounds =
false
;
return
BitmapFactory.decodeResource(context.getResources(), id, options);
}
/**
* 计算图片的缩放值
*
* @param options
* @param reqWidth
* @param reqHeight
* @return
*/
private
int
calculateInSampleSize(BitmapFactory.Options options,
int
reqWidth,
int
reqHeight) {
// Raw height and width of image
final
int
height = options.outHeight;
final
int
width = options.outWidth;
int
inSampleSize =
1
;
if
(height > reqHeight || width > reqWidth) {
final
int
heightRatio = Math.round((
float
) height / (
float
) reqHeight);
final
int
widthRatio = Math.round((
float
) width / (
float
) reqWidth);
inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;
}
return
inSampleSize;
}
/**
* 根据手机的分辨率从 dp 的单位 转成为 px(像素)
*/
public
static
int
dip2px(Context context,
float
dpValue) {
final
float
scale = context.getResources().getDisplayMetrics().density;
return
(
int
) (dpValue * scale +
0
.5f);
}
/**
* 根据手机的分辨率从 px(像素) 的单位 转成为 dp
*/
public
static
int
px2dip(Context context,
float
pxValue) {
final
float
scale = context.getResources().getDisplayMetrics().density;
return
(
int
) (pxValue / scale +
0
.5f);
}
}
布局文件:
-
12345678910111213141516171819202122232425262728293031323334353637383940414243
<?xml version=
"1.0"
encoding=
"utf-8"
?>
<LinearLayout xmlns:android=
"http://schemas.android.com/apk/res/android"
android:layout_width=
"fill_parent"
android:layout_height=
"fill_parent"
android:orientation=
"vertical"
>
<FrameLayout
android:id=
"@+id/flay_view"
android:layout_width=
"fill_parent"
android:layout_height=
"fill_parent"
android:layout_weight=
"1000"
>
<SurfaceView
android:id=
"@+id/surface"
android:layout_width=
"fill_parent"
android:layout_height=
"fill_parent"
android:layout_weight=
"1000"
/>
<ImageView
android:id=
"@+id/iv_img"
android:layout_width=
"200dp"
android:layout_height=
"200dp"
android:layout_gravity=
"center"
android:visibility=
"gone"
android:src=
"@drawable/taideng"
android:scaleType=
"fitCenter"
/>
</FrameLayout>
<LinearLayout
android:layout_width=
"match_parent"
android:layout_height=
"wrap_content"
android:layout_weight=
"1"
android:orientation=
"horizontal"
>
<Button
android:id=
"@+id/but"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_weight=
"1"
android:text=
"照相"
/>
<Button
android:id=
"@+id/but2"
android:layout_width=
"wrap_content"
android:layout_height=
"wrap_content"
android:layout_weight=
"1"
android:text=
"添加图片"
/>
</LinearLayout>
</LinearLayout>
最后不要忘了权限:
-
<uses-feature android:name="android.hardware.camera" /> <uses-feature android:name="android.hardware.camera.autofocus" /> <uses-permission android:name="android.permission.CAMERA" /> <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />