본문 바로가기

Android/정리 노트

[Android/안드로이드] 카메라로 사진 찍어 가져오기 (get photo from camera)

갤러리나 카메라로 사진을 가져오는 기능을 구현해야 할 때가 많다.


상당히 많이 쓰이는 기능이라 자료가 많지만, 각종 블로그들을 따라하다보면 Cursor 부분에서 NullPointerException이 뜬다.


뭘 덜 따라했는지 권한문제인지(퍼미션 주고 체크도 함) 이제는 막힌 방법인지 여튼 안됐다.


그러다 어떤 최신 블로그 포스팅 보고 구현에 성공하고 다른 방법있나 구글링을 하다가 개발자 문서에서 공식적인 방법(?)을 발견..


https://developer.android.com/training/camera/photobasics.html


이 포스팅은 위 글을 참고해 정리한 것이니 더 자세한 정보를 알고싶다면 꼭 꼭 들어가 읽어봅시다.




카메라로 사진 찍어 이미지 띄우기



private void sendTakePhotoIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}

사진을 찍을 액티비티에 위의 메소드를 선언한다.


그리고 버튼의 onClick등 사진을 찍어야 하는 시점에 해당 메소드를 호출한다.




@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");
((ImageView)findViewById(R.id.photo)).setImageBitmap(imageBitmap);
}
}

intent로 비트맵 이미지 자체를 불러와서 이미지뷰에 띄워주는 코드이다.


그리고 onActivityResult에 위와 같은 코드를 적어놓고 앱을 실행해 결과를 확인해보면..

(photo는 xml에 정의한 ImageView의 id)



코딱지만한 사진이 뜬다.


저래 뜨는 이유는 ImageVIew의 크기를 너비, 높이 wrap_content로 두었기 때문이다.


한마디로 그냥 원래 저 크기라는 얘기다.



이 이미지는 썸네일 이미지로 찍은 사진의 원본이 아니라 갤러리앱의 리스트에 보이는 작은 썸네일 이미지를 가져온 것이다.


그래서 위에 소개된 개발자 문서에도 Get the thumbnai이라는 챕터이름이 달려있다.


그럼이제 원본 이미지를 불러오는 방법을 알아보자.




원본 이미지 가져오기


먼저 xml resource파일을 만들어줘야 한다.


프로젝트 구조에서 res폴더에 커서를 놓고 우클릭을 하여 [New] - [Android resource directory]를 클릭한다.

위의 창이 뜨면 [Resource type]을 xml로 바꿔주고 [OK]를 누른다.


그럼 res 폴더 하위에 xml폴더가 생성되어 있다.


그리고 xml 폴더에 우클릭을 하고 [New] - [XML resource file]을 눌러 file_paths라는 이름의 파일을 생성한다.


file_paths말고 다른 이름으로 할경우 기억해두기.


그리고 아래의 코드를 [file_paths.xml]에 적어준다.


<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="Android/data/com.raonstudio.cameratest/files/Pictures" />
</paths>

저기서! 그대로 복붙하지 말고 프로젝트마다 바꿔줘야 할 것이 있다.


빨간 글씨로 되어있는 패키지명을 자기 프로젝트의 패키지명으로 바꿔줘야 한다.



그리고 [AndroidManifes.xml]의 application단에 Provider를 추가해준다.


<application

...

<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.raonstudio.cameratest"
android:exported="false"
android:grantUriPermissions="true">

<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>

</application>

마찬가지로 빨간 글씨로 되어있는 패키지명을 자기 프로젝트의 패키지명으로 바꿔줘야 한다.


패키지명은 [AndroidManifes.xml]상단에 

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.raonstudio.cameratest">

에 적혀있는 패키지명을 가져다 적으면 된다.



자 이제 액티비티로 돌아와서


private String imageFilePath;

private String photoUri;

private File createImageFile() throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "TEST_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
imageFilePath = image.getAbsolutePath();
return image;
}

이미지 파일을 생성하는 메소드를 선언해준다.


이 메소드는 이미지가 저장된 파일을 만드는게 아니라 이미지가 저장될 파일을 만드는 함수이다.


이름은 유일성을 주기 위해 ms를 이용한다.


imageFilePath는 액티비티 클래스의 멤버변수로 선언했다.



private Uri photoUri;


private void sendTakePhotoIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
// Error occurred while creating the File
}

if (photoFile != null) {
photoUri = FileProvider.getUriForFile(this, getPackageName(), photoFile);

takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
}

위에서 정의한 메소드를 위와 같이 고친다.


인텐트로 카메라로 사진을 찍으라는 요청을 보낸다.


흔히 나오는 예제와 다른점은 putExtra(MediaStroe.EXPRA_OUTPUT, photoUri)를 이용해 이미지가 저장될 uri를 같이 넘긴다.


photoUri는 위에서 정의한 createImageFile으로 만들어진 File 객체에서 나온다.


FileProvider는 AndroidManifest에서 추가했던 <Provider>요소를 이용해 uri를 불러오는 역할을 한다.


photoUri는 액티비티 클래스의 멤버변수로 선언했다.



@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
((ImageView)findViewById(R.id.photo)).setImageURI(photoUri);
}
}

그리고 onActivityResult를 위와 같이 다시 쓴다.


intent를 이용해 사진이나 uri를 가져오는게 아니라 그냥 사진을 찍었을때 불리는 시점이라서 onActivityResult에 썼다.


그러고 결과를 확인해보면...


잘 찍혔고 원본 이미지가 나온듯 하지만 회전이 되어있다.


여기까지만 정리하면


1. 파일을 만들고 

2. 그 파일을 타겟으로 사진을 찍어 저장하여 

3. 그 파일에 찍은 이미지가 저장되고

4, 그 파일의 uri를 이용해 이미지를 띄울수 있다!



그럼 이제 회전을 시켜보자.



private 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;
}

private Bitmap rotate(Bitmap bitmap, float degree) {
Matrix matrix = new Matrix();
matrix.postRotate(degree);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}

두 개의 메소드를 액티비티에 정의한다.


상수를 받아 각도로 변환시켜주는 메소드와 비트맵을 각도대로 회전시켜 결과를 반환해주는 메소드이다.



@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath);
ExifInterface exif = null;

try {
exif = new ExifInterface(imageFilePath);
} catch (IOException e) {
e.printStackTrace();
}

int exifOrientation;
int exifDegree;

if (exif != null) {
exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
exifDegree = exifOrientationToDegrees(exifOrientation);
} else {
exifDegree = 0;
}

((ImageView)findViewById(R.id.photo)).setImageBitmap(rotate(bitmap, exifDegree));
}
}

그리고 onActivityResult를 위와 같이 고쳐준다.


ExifInterface라는 클래스를 이용해 이미지가 회전되어있는 각도를 가져와 회전시켜 이미지 뷰에 띄우는 작업이다.


아래는 드디어 제대로 뜨는 결과



이제 imageFilePath나 photoUri를 이용해서 이미지를 불러올 수 있게 되었다.


위에서 이미지를 저장한 파일은 임시파일이다. 따라서 따로 권한 체크가 필요없다.


다음 포스팅에서는 이미지를 외부 저장소에 저장하고 갤러리에서 볼 수 있도록 하는 법을 알아보자




full source



import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore;
import android.support.v4.content.FileProvider;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ImageView;

import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class MainActivity extends AppCompatActivity {

private static final int REQUEST_IMAGE_CAPTURE = 672;
private String imageFilePath;
private Uri photoUri;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

findViewById(R.id.take).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
sendTakePhotoIntent();
}
});
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_IMAGE_CAPTURE && resultCode == RESULT_OK) {
Bitmap bitmap = BitmapFactory.decodeFile(imageFilePath);
ExifInterface exif = null;

try {
exif = new ExifInterface(imageFilePath);
} catch (IOException e) {
e.printStackTrace();
}

int exifOrientation;
int exifDegree;

if (exif != null) {
exifOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
exifDegree = exifOrientationToDegrees(exifOrientation);
} else {
exifDegree = 0;
}

((ImageView)findViewById(R.id.photo)).setImageBitmap(rotate(bitmap, exifDegree));
}
}

private 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;
}

private Bitmap rotate(Bitmap bitmap, float degree) {
Matrix matrix = new Matrix();
matrix.postRotate(degree);
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix, true);
}


private void sendTakePhotoIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
File photoFile = null;
try {
photoFile = createImageFile();
} catch (IOException ex) {
// Error occurred while creating the File
}

if (photoFile != null) {
photoUri = FileProvider.getUriForFile(this, getPackageName(), photoFile);
takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE);
}
}
}

private File createImageFile() throws IOException {
String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
String imageFileName = "TEST_" + timeStamp + "_";
File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES);
File image = File.createTempFile(
imageFileName, /* prefix */
".jpg", /* suffix */
storageDir /* directory */
);
imageFilePath = image.getAbsolutePath();
return image;
}
}