今天客戶提了一個需求: 需要結合攝像頭將拍照的圖片和自己的產品(圖片)整合到一張圖片上面,如下圖:

針對這個功能需要做自定義相機,根據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://www.xuebuyuan.com/1223069.html 該文章介紹了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
();
具體實現代碼:
-
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); } }
布局文件:
-
<?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" />