2011年7月25日 星期一

Android 處理圖像的方法 - (一) 避免記憶體不足的異常

近日的一個案件需要處理相片,包含了將原始圖片縮小, 黑白效果, 相片合成, 濾鏡的效果。在網路上找了相當多的文章,當然....大部份都是失敗的方法,在東撿撿、西撿撿後,終於完成了需要的效果,大致的說明如下。

避免記憶體不足的異常

因為相機所拍出的相片畫質如果比較高,在處理圖片縮小時很有可能會遇到VM記憶體不足的問題。而Android本身有提供幾個參數。

首先是

inSampleSize

If set to a value > 1, requests the decoder to subsample the original image, returning a smaller image to save memory. The sample size is the number of pixels in either dimension that correspond to a single pixel in the decoded bitmap. For example, inSampleSize == 4 returns an image that is 1/4 the width/height of the original, and 1/16 the number of pixels. Any value <= 1 is treated the same as 1. Note: the decoder will try to fulfill this request, but the resulting bitmap may have different dimensions that precisely what has been requested. Also, powers of 2 are often faster/easier for the decoder to honor. 

主要是說,如果將值設定為 1 以上的數值,系統便會回傳一個比原始圖像還要小的圖像,目的是為了減少記憶體的使用,相關的邏輯在原文中也有述說。

另一個是

inJustDecodeBounds

If set to true, the decoder will return null (no bitmap), but the out... fields will still be set, allowing the caller to query the bitmap without having to allocate the memory for its pixels.

如果將此值設為 true 的話,系統在decode時不會直接回傳一個Bitmap的物件,但我們可以取得原始圖像的高度和寬度等相關資訊。這邊是為了防止記憶體不足,利用取回的資訊,去計算出 inSampleSize 的數值。
Bitmap recycle

Free the native object associated with this bitmap, and clear the reference to the pixel data.
這個相當的重要。如果你所處理的圖像很多,記得把已經需要用到的 Bitmap 物件 recycle ,以釋放記憶體。


實際用法如下
public class CameraRunActivity extends Activity implements Callback, OnClickListener {
 private static final String TAG = CameraRunActivity.class.getSimpleName();
 private SurfaceView surfaceview;
 private SurfaceHolder surfaceholder;
 private Camera myCamera;
 private ImageView camera_action;
 private Bitmap finalBMP;
 private final int HANDLER_OF_PROCESS = 0;

 /** Called when the activity is first created. */
 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  requestWindowFeature(Window.FEATURE_NO_TITLE);
  setContentView(R.layout.camera_run);
  setRequestedOrientation(1);
  // 定義 Layout 中的 SurfaceView
  surfaceview = (SurfaceView) findViewById(R.id.surfaceview);
  surfaceholder = surfaceview.getHolder();
  // 定義 SurfaceView Holder 相關的處理
  surfaceholder.addCallback(this);
  surfaceholder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
  // 實際觸發拍照的物件
  camera_action = (ImageView) findViewById(R.id.camera_action);
  camera_action.setOnClickListener(this);
 }

 @Override
 public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
  Camera.Parameters parameters = myCamera.getParameters();
  // 開啟自動對焦的模式
  parameters.setFocusMode("auto");
  // 設定輸出的圖像格式
  parameters.setPictureFormat(PixelFormat.JPEG);
  myCamera.setParameters(parameters);
  // 同步 SurfaceView 與 Camera
  myCamera.startPreview();
  isCameraOpen = true;
 }
 @Override
 public void surfaceCreated(SurfaceHolder holder) {
  try {
   myCamera = Camera.open();
   myCamera.setPreviewDisplay(surfaceholder);
   // 將相機轉 90 度,因為手機要保持直立
   myCamera.setDisplayOrientation(90);
  } catch (IOException e) {
   myCamera.release();
   myCamera = null;
  }
 }
 @Override
 public void surfaceDestroyed(SurfaceHolder holder) {
  myCamera.stopPreview();
  isCameraOpen = false;
  myCamera.release();
  myCamera = null;
 }

 @Override
 public void onClick(View v) {
  switch (v.getId()) {
   case R.id.camera_action :
    isCameraOpen = false;
    // 先觸發自動對焦,然後再拍照
    myCamera.autoFocus(autoFacusCallback);
    break;
  }
 }

 private ShutterCallback shutterCallback = new ShutterCallback() {
  public void onShutter() {
   // Shutter has closed
  }
 };

 private PictureCallback rawCallback = new PictureCallback() {
  public void onPictureTaken(byte[] _data, Camera _camera) {
   // TODO Handle RAW image data
  }
 };

 private PictureCallback jpegCallback = new PictureCallback() {
  public void onPictureTaken(byte[] _data, Camera _camera) {
   // 設定 inJustDecodeBounds = true
   BitmapFactory.Options options = new BitmapFactory.Options();
   options.inJustDecodeBounds = true;
   Bitmap bitmap_def = BitmapFactory.decodeByteArray(_data, 0, _data.length, options);
   // 取得圖像的長寬
   int width = bitmap_def.getWidth();
   int height = bitmap_def.getHeight();
  }
 };

 AutoFocusCallback autoFacusCallback = new AutoFocusCallback() {

  @Override
  public void onAutoFocus(boolean focused, Camera camera) {
   // 當相機捉到焦點時,再啟動相機拍照
   if (focused == true) {
    myCamera.takePicture(shutterCallback, rawCallback, jpegCallback);
   }
  }
 };

}

沒有留言: