2012年6月22日 星期五

打造具備自動對焦、閃光燈、儲存照片的 Android 程式


        經過無盡的搜尋資料,參考國內外的程式高手們分享的程式碼,我總算融會貫通後,成功地寫出來完整功能的相機程式了....

可能有人會質疑:利用 Intent.setAction(MediaStore.ACTION_IMAGE_CAPTURE) ;
直接呼叫內建的相機功能就好啦,各種功能樣樣齊全呢!



但是之所以要自己寫相機程式必定有其特殊需求,例如想設計類似大頭貼的攝影程式,就是在相機預覽畫面中先疊上一張圖,像這樣:

         是的,那只能要自己去 Layout 、自己寫camera方法去呼叫相機了,先從Layout開始說明:要如何將圖檔放在相機預覽畫面上呢?答案就是通常我們要放相機預覽畫面,必須要使用 SurfaceView,所以只要再SurfaceView的Background屬性中指定圖檔就可以了。

        另外,Android的拍照、錄影,都是預設橫式的,但是我需要的是直式的拍照啊?這部分我也嘗試了很久,例如使用 camera.setDisplayOrientation(90); 嘗試將顯示畫面選轉90度,但是實際手機預覽畫面卻明顯失真、比例都扁掉了...所以最終,我就將直式的Layout更改成橫式的就好囉,對程式而言是標準的橫式,但是人拿起手機拍照就像是直式拍照囉:

另外一個大缺點,就是儲存後的照片也是橫式的,所以想要轉成直式的照片,就需要利用程式在照片存檔之前先選轉乘90度再儲存,如此一來,實際手機畫面看到的就是直式的照片,所以拍照的部分,除了是要指定拍照後為JPEG檔案,品質參數以及拍完照後的儲存方式,使用獨立的副程式處理:

  1. private final class TakePictureCallback implements PictureCallback {
  2. @Override
  3. public void onPictureTaken(byte[] data, Camera camera) {
  4. try {
  5. Bitmap bitmap = BitmapFactory.decodeByteArray(data, 0,data.length);
  6.          Matrix matrix = new Matrix();   //
  7.          matrix.setRotate(90);   //
  8.          bitmap = Bitmap.createBitmap(bitmap, 0, 0,   //將相片旋轉90度後回存
  9.          bitmap.getWidth(),  //
  10.          bitmap.getHeight(),matrix, true);   //         
  11. String Path = createFilePath(); //載入儲存路徑
  12.                                                     //createFilePath() 為處理儲存檔案、儲存路徑的副程式
  13. FileOutputStream outStream = new FileOutputStream(Path);
  14. bitmap.compress(CompressFormat.JPEG, 100, outStream);
  15. outStream.close();
  16. camera.stopPreview();
  17. camera.startPreview();//開始預覽
  18. } catch (Exception e) {
  19. Log.e(TAG, e.toString());
  20. }
  21. }
  22. }
以下是處理檔案的副程式:

  1. private String createFilePath() {
  2. File dir = Environment.getExternalStorageDirectory();
  3. File appDir = new File(dir, "GSC/downline"); //指定資料夾
  4. if(!appDir.exists()) appDir.mkdir();
  5. String name = System.currentTimeMillis() + ".jpg"; //檔案以系統時間來命名
  6. return new File(appDir, name).getAbsolutePath();
  7. }


        自動對焦功能,我希望在拍照前可以進行自動對焦,因此,我將自動對焦部分包成一個副程式,副程式當中才去呼叫拍照的程式,因此拍照前都會先自動對焦了,因此我在處理"拍照按鈕"的部分,呼叫的是autoFoucs這支副程式,而不是TalkPictureCallback,在autoFoucs裡面才呼叫TalkPictureCallback:

  1. Camera.AutoFocusCallback autoFocus = new Camera.AutoFocusCallback() {
  2. @Override
  3. public void onAutoFocus(boolean success, Camera camera) 
  4. {// TODO Auto-generated method stub
  5. if(success){
  6. camera.takePicture(null, null, new TakePictureCallback()); 
  7.                                                                        //TakePictureCallback()是另外處理拍照的副程式
  8. }else{
  9. Toast.makeText(camera_downline.this, R.string.focusError, Toast.LENGTH_LONG).show();
  10. }
  11. }
  12. };

      當然,相機最主要的程式碼,包含一剛開始在開始構圖預覽、轉換、結束以及相關重要的參數如下:

  1. private SurfaceHolder.Callback surfaceholdercallback = new SurfaceHolder.Callback(){
  2. @Override
  3. public void surfaceChanged(SurfaceHolder holder, int format, int width,
  4. int height) {
  5. }
  6. @Override
  7. public void surfaceCreated(SurfaceHolder holder) {
  8. try{
  9. if(camera==null){
  10. camera = Camera.open();
  11. }else{
  12. Toast.makeText(camera_downline.this, "相機正在使用中", 1).show();
  13. }
  14. WindowManager wm = (WindowManager)  getSystemService(Context.WINDOW_SERVICE);
  15. Display display = wm.getDefaultDisplay();//設定這變數為預設的螢幕大小
  16. parameters = camera.getParameters();
  17. parameters.setPreviewSize(display.getWidth(), display.getHeight());
  18.                                  //設置預覽照片的大小
  19. parameters.setPreviewFrameRate(3);//每秒三張
  20. parameters.setPictureFormat(PixelFormat.JPEG);//照片的輸出格式
  21. parameters.set("jpeg-quality", 85);//照片質量
  22. parameters.setPictureSize(display.getWidth(), display.getHeight());
  23.                                 //設置照片的Size, 這邊也可以指定Pixel: Ex: 768, 1024
  24. camera.setParameters(parameters);
  25. camera.setPreviewDisplay(surfaceview.getHolder());
  26.                                 //透過Surface View顯示預覽畫面
  27. camera.startPreview();//開始預覽
  28. preview = true;
  29. }catch(IOException e){
  30. e.printStackTrace();
  31. }
  32. }
  33. @Override
  34. public void surfaceDestroyed(SurfaceHolder holder) {
  35. // TODO Auto-generated method stub
  36. if (camera != null) {
  37. if (preview)
  38. camera.stopPreview();
  39. camera.release();
  40. }
  41. }
  42. };


閃光燈的部分,放在按鈕裡面處裡就可以了,可以使用ToggleButton來去開啟或者關閉,因為我的ToggleButton是用自己設計的底圖,因此希望按下去的時候可以更換另外一個底圖,以下是 flash按鈕的程式碼:



  1.     flash.setOnClickListener(new OnClickListener(){
  2.          //定義返回按鍵後動作
  3.      public void onClick(View arg0){
  4.      if (flash.isChecked()){     
  5.                                flash.setBackgroundDrawable(
  6.                                        getResources().getDrawable(R.drawable.record_flesh_on));
  7.                                        //點選後更換底圖    
  8.      parameters.setFlashMode(Parameters.FLASH_MODE_ON);//開啟閃光燈
  9.      camera.setParameters(parameters);
  10.      Toast.makeText(camera_downline.this, R.string.fleshOn, 1).show();    
  11.      }else { 
  12.                                 flash.setBackgroundDrawable(
  13.                                    getResources().getDrawable(R.drawable.record_flesh_off));
  14.                                     //點選後更換底圖   
  15.      parameters.setFlashMode(Parameters.FLASH_MODE_OFF); 關閉閃光燈
  16.      camera.setParameters(parameters);
  17.      Toast.makeText(camera_downline.this, R.string.fleshOff, 1).show();    
  18.      }
  19.      }
  20.      });



整體的程式碼大概就是如此了,另外,通常拍照的時候,希望是全螢幕、沒有軟體抬頭、或者保持螢幕開啟,可以在onCreate程式開頭加上以下這段程式碼:


  1. public void onCreate(Bundle savedInstanceState){
  2. super.onCreate(savedInstanceState); 
  3. Window window = getWindow();
  4. requestWindowFeature(Window.FEATURE_NO_TITLE);//取消軟體抬頭
  5. window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
  6. WindowManager.LayoutParams.FLAG_FULLSCREEN);//全螢幕
  7. window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
  8.                                                                                                            //螢幕保持開啟


以上,如果有甚麼更好方式,歡迎與我討論...

1 則留言:

Unknown 提到...
作者已經移除這則留言。