Tag Archives: 카메라

[Java/Android] 회전오류를 복구하는 카메라를 이용한 Bitmap 이미지 캡춰링

조금 순서가 반대로 되는 포스팅일것 같습니다만 이전에 [카메라 호출후 이미지 크롭하기] 글을 작성한 적이 있습니다. 간단하게 카메라만 임시로 호출하기 위해 사용하기엔 너무 복잡한 면이 있는듯 하니 이번에는 간단하게 카메라 사용에 관련된 포스팅을 하나 해보겠습니다.

하지만 여기서 중요한 기능을 하나 추가했는데요, 대부분의 기기가 사진 촬영시에 기기의 회전율을 고려하지 않고 바로 저장해 버린다는 문제를 재회전을 통해 복구하는 로직을 추가해보았습니다.

1. 카메라 호출하기

[code]// 임시로 사용할 파일 생성
File photo = new File(Environment.getExternalStorageDirectory(),
  “.camera.jpg”);
imageUri = Uri.fromFile(photo);

// 카메라를 호출합니다.
Intent i = new Intent(“android.media.action.IMAGE_CAPTURE”);
i.putExtra(MediaStore.EXTRA_OUTPUT, imageUri);
MainActivity.this.startActivityForResult(i,REQUEST_CAMERA);[/code]

2. 결과 처리 (onActivityResult)

[code]if(requestCode == REQUEST_CAMERA && resultCode == RESULT_OK)
{
  try
  {
    // 비트맵 이미지로 가져온다
    String imagePath = imageUri.getPath();
    Bitmap image = BitmapFactory.decodeFile(imagePath);
    
    // 이미지를 상황에 맞게 회전시킨다
    ExifInterface exif = new ExifInterface(imagePath);
    int exifOrientation = exif.getAttributeInt(
  ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
    int exifDegree = exifOrientationToDegrees(exifOrientation);
    image = rotate(image, exifDegree);
    
    // 변환된 이미지 사용
    imageView.setImageBitmap(image);
  }
  catch(Exception e)
  {
    Toast.makeText(this, “오류발생: ” + e.getLocalizedMessage(),
  Toast.LENGTH_LONG).show();
  }
}[/code]

3. 회전에 사용되는 추가 함수들

[code]/**
 * EXIF정보를 회전각도로 변환하는 메서드
 *
 * @param exifOrientation EXIF 회전각
 * @return 실제 각도
 */
public int exifOrientationToDegrees(int exifOrientation)
{
  if(exifOrientation == ExifInterface.ORIENTATION_ROTATE_90)
  {
    return 90;
  }
  else if(exifOrientation == ExifInterface.ORIENTATION_ROTATE_180)
  {
    return 180;
  }
  else if(exifOrientation == ExifInterface.ORIENTATION_ROTATE_270)
  {
    return 270;
  }
  return 0;
}

/**
 * 이미지를 회전시킵니다.
 *
 * @param bitmap 비트맵 이미지
 * @param degrees 회전 각도
 * @return 회전된 이미지
 */
public Bitmap rotate(Bitmap bitmap, int degrees)
{
  if(degrees != 0 && bitmap != null)
  {
    Matrix m = new Matrix();
    m.setRotate(degrees, (float) bitmap.getWidth() / 2,
    (float) bitmap.getHeight() / 2);
    
    try
    {
      Bitmap converted = Bitmap.createBitmap(bitmap, 0, 0,
      bitmap.getWidth(), bitmap.getHeight(), m, true);
      if(bitmap != converted)
      {
        bitmap.recycle();
        bitmap = converted;
      }
    }
    catch(OutOfMemoryError ex)
    {
      // 메모리가 부족하여 회전을 시키지 못할 경우 그냥 원본을 반환합니다.
    }
  }
  return bitmap;
}[/code]

4. AndroidManifest.xml 추가 설정

– 카메라 회전시에도 기존의 엑티비티를 제거하지 않도록 방지하기 위해 <activity..에 추가
[code]android:configChanges=”keyboardHidden|orientation”[/code]
– 카메라를 사용하기 위한 퍼미션 추가
[code]<uses-permission android:name=”android.permission.CAMERA”/>[/code]

* 결론

카메라를 이용하여 사진을 찍을때는 EXIF라는 메타 데이터가 이미지 파일에 추가로 기록이 됩니다. 이곳에서는 회전상태등이 저장이 되는데 이 값을 읽어온 후 회전된 상태만큼을 다시 원상복귀를 시키게 됩니다. exifOrientationToDegrees메서드를 사용하여 일반적으로 우리가 사용하는 정수형 회전각도값을 알아온후에 rotate메서드를 사용하여 이미지를 실제로 회전시킵니다.

1217742705.zip

[Android] Camera 호출 후 이미지 Crop하기 예제

안드로이드에서 카메라를 이용하여 이미지 촬영후 해당 이미지를 크롭하는 경우의 예제를 만들어 보았습니다. 이 예제에서는 카메라를 이용하는것 외에도 앨범에서 이미지를 가져오는 경우에도 마찬가지로 크롭을 할 수 있도록 하였습니다.

1. AndroidManifest.xml 에 권한 추가하기

<uses-permission
  android:name="android.permission.CAMERA" />
<uses-permission
  android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

2. main.xml 레이아웃 만들기

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<Button
android:id="@+id/button"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="이미지 가져오기"
/>
<ImageView
android:id="@+id/image"
android:layout_width="90dp"
android:layout_height="90dp"
android:layout_gravity="center"
android:layout_margin="50dp"
android:background="#eee"
/>
</LinearLayout>

3. 소스 코드 작성

package pe.kr.theeye.cameracrop;

import java.io.File;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;

public class CameraCropActivity extends Activity implements OnClickListener
{
  private static final int PICK_FROM_CAMERA = 0;
  private static final int PICK_FROM_ALBUM = 1;
  private static final int CROP_FROM_CAMERA = 2;

  private Uri mImageCaptureUri;
  private ImageView mPhotoImageView;
  private Button mButton;

  @Override
  public void onCreate(Bundle savedInstanceState)
  {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    mButton = (Button) findViewById(R.id.button);
    mPhotoImageView = (ImageView) findViewById(R.id.image);

    mButton.setOnClickListener(this);
  }

  /**
   * 카메라에서 이미지 가져오기
   */
  private void doTakePhotoAction()
  {
    /*
     * 참고 해볼곳
     * http://2009.hfoss.org/Tutorial:Camera_and_Gallery_Demo
     * http://stackoverflow.com/questions/1050297/how-to-get-the-url-of-the-captured-image
     * http://www.damonkohler.com/2009/02/android-recipes.html
     * http://www.firstclown.us/tag/android/
     */

    Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

    // 임시로 사용할 파일의 경로를 생성
    String url = "tmp_" + String.valueOf(System.currentTimeMillis()) + ".jpg";
    mImageCaptureUri = Uri.fromFile(new File(Environment.getExternalStorageDirectory(), url));

    intent.putExtra(android.provider.MediaStore.EXTRA_OUTPUT, mImageCaptureUri);
    // 특정기기에서 사진을 저장못하는 문제가 있어 다음을 주석처리 합니다.
    //intent.putExtra("return-data", true);
    startActivityForResult(intent, PICK_FROM_CAMERA);
  }

  /**
   * 앨범에서 이미지 가져오기
   */
  private void doTakeAlbumAction()
  {
    // 앨범 호출
    Intent intent = new Intent(Intent.ACTION_PICK);
    intent.setType(android.provider.MediaStore.Images.Media.CONTENT_TYPE);
    startActivityForResult(intent, PICK_FROM_ALBUM);
  }

  @Override
  protected void onActivityResult(int requestCode, int resultCode, Intent data)
  {
    if(resultCode != RESULT_OK)
    {
      return;
    }

    switch(requestCode)
    {
      case CROP_FROM_CAMERA:
      {
        // 크롭이 된 이후의 이미지를 넘겨 받습니다.
        // 이미지뷰에 이미지를 보여준다거나 부가적인 작업 이후에
        // 임시 파일을 삭제합니다.
        final Bundle extras = data.getExtras();

        if(extras != null)
        {
          Bitmap photo = extras.getParcelable("data");
          mPhotoImageView.setImageBitmap(photo);
        }

        // 임시 파일 삭제
        File f = new File(mImageCaptureUri.getPath());
        if(f.exists())
        {
          f.delete();
        }

        break;
      }

      case PICK_FROM_ALBUM:
      {
        // 이후의 처리가 카메라와 같으므로 일단  break없이 진행합니다.
        // 실제 코드에서는 좀더 합리적인 방법을 선택하시기 바랍니다.

        mImageCaptureUri = data.getData();
      }

      case PICK_FROM_CAMERA:
      {
        // 이미지를 가져온 이후의 리사이즈할 이미지 크기를 결정합니다.
        // 이후에 이미지 크롭 어플리케이션을 호출하게 됩니다.

        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setDataAndType(mImageCaptureUri, "image/*");

        intent.putExtra("outputX", 90);
        intent.putExtra("outputY", 90);
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);
        intent.putExtra("scale", true);
        intent.putExtra("return-data", true);
        startActivityForResult(intent, CROP_FROM_CAMERA);

        break;
      }
    }
  }

  @Override
  public void onClick(View v)
  {
    DialogInterface.OnClickListener cameraListener = new DialogInterface.OnClickListener()
    {
      @Override
      public void onClick(DialogInterface dialog, int which)
      {
        doTakePhotoAction();
      }
    };

    DialogInterface.OnClickListener albumListener = new DialogInterface.OnClickListener()
    {
      @Override
      public void onClick(DialogInterface dialog, int which)
      {
        doTakeAlbumAction();
      }
    };

    DialogInterface.OnClickListener cancelListener = new DialogInterface.OnClickListener()
    {
      @Override
      public void onClick(DialogInterface dialog, int which)
      {
        dialog.dismiss();
      }
    };

    new AlertDialog.Builder(this)
      .setTitle("업로드할 이미지 선택")
      .setPositiveButton("사진촬영", cameraListener)
      .setNeutralButton("앨범선택", albumListener)
      .setNegativeButton("취소", cancelListener)
      .show();
  }}

테스트 이미지는 생략하겠습니다. 위의 소스로 충분히 좋은 설명이 될 수 있을것이라 생각합니다. 두번의 Intent 호출을 통해 이미지를 촬영하고 크롭을 하는 과정을 거치게 됩니다. 테스트 해보니 잘 되는군요.

참고 : http://stackoverflow.com/questions/1973359/android-crop-an-image-after-taking-it-with-camera-with-a-fixed-aspect-ratio