정상에서 IT를 외치다

[Android,GetImage] 카메라와 갤러리에서 이미지 가져오기 본문

안드로이드

[Android,GetImage] 카메라와 갤러리에서 이미지 가져오기

Black-Jin 2018. 10. 23. 11:49
반응형

안녕하세요. 블랙진입니다!!


이번에는 어마무시한 포스팅을 진행해 볼려고 합니다. 바로 '카메라와 갤러리에서 이미지 가져오기' 입니다. 단계 별로 포스팅을 진행하여 여러분 께서 쉽게 따라하고 이해할 수 있게 준비해 보았습니다. 포스팅 순서는 아래와 같이 진행하겠습니다.


1. 카메라와 갤러리에서 이미지 가져오기

2. Nougat(Android OS 7) 대응하기

3. 카메라회전 각도를 고려한 이미지 가져오기

4. 가져온 이미지 크롭(Crop) 하기



Chapter 1. 카메라와 갤러리에서 이미지 가져오기




만들어볼 모든 예제는 위와 같은 이미지 입니다. Gallery 와 Camera 버튼을 통해 이미지를 받아오고 이를 화면 중앙에 보이는 예제입니다.



0. Package

GetImageActivity 와 activity_get_image 로 1개의 Activity 로 이루어져 있습니다.



activiy_get_image.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">

<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@+id/llBottomNav"/>

<LinearLayout
android:id="@+id/llBottomNav"
android:layout_width="0dp"
android:layout_height="80dp"
android:background="#96000000"
android:orientation="horizontal"
android:gravity="center"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent">


<Button
android:id="@+id/btnGallery"
android:text="Gallery"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

<Button
android:id="@+id/btnCamera"
android:text="Camera"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

</LinearLayout>

</android.support.constraint.ConstraintLayout>


1. 카메라와 갤러리에 접근 할 수 있는 권한을 받아옵니다.


AnroidManifest.xml

<!-- camera, gallery -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

AnroidManifest 에 위 3가지 권한을 추가해 줍니다.



app/build.gradle


안드로이드 마시멜로우 버전 이상부터는 권한에 대한 허용을 사용자로부터 받아야 합니다. 이에 관해서는 Tedpermission 라이브러리를 사용했습니다.

//ted permission
implementation "gun0912.ted:tedpermission:2.1.0"

app 단계의 build.gradle 의 dependencies 에 tedpermission 을 추가해 줍니다.



GetImageActivity.class

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

tedPermission();

}

onCreate 에서 권한 요청을 해줍니다.

private void tedPermission() {

PermissionListener permissionListener = new PermissionListener() {
@Override
public void onPermissionGranted() {
// 권한 요청 성공

}

@Override
public void onPermissionDenied(ArrayList<String> deniedPermissions) {
// 권한 요청 실패
}
};

TedPermission.with(this)
.setPermissionListener(permissionListener)
.setRationaleMessage(getResources().getString(R.string.permission_2))
.setDeniedMessage(getResources().getString(R.string.permission_1))
.setPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.CAMERA)
.check();

}

TedPermission 이 동작되었을 때 나오는 안내 문구를 설정할 수가 있는데 저는 아래와 같은 안내 문구를 사용했습니다.


values/strings.xml

<resources>
<string name="app_name">MyImageSample</string>

<!-- Ted Permission -->
<string name="permission_1">[설정] > [권한] 에서 권한을 허용할 수 있습니다.</string>
<string name="permission_2">사진 및 파일을 저장하기 위하여 접근 권한이 필요합니다.</string>
</resources>



2. 앨범에서 이미지 가져오기


이제 권한을 설정해 주었으니 앨범에서 이미지를 가져오겠습니다.

private void goToAlbum() {

Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType(MediaStore.Images.Media.CONTENT_TYPE);
startActivityForResult(intent, PICK_FROM_ALBUM);
}

Intent 를 통해 앨범화면으로 쉽게 이동할 수 있습니다. 이 때 startActivityForResult 에 PICK_FROM_ALBUM 변수를 넣어주었습니다.

private static final int PICK_FROM_ALBUM = 1;

이 변수는 onActivityResult 에서 requestCode 로 반환되는 값입니다.


startActivityForResult 를 통해 다른 Activity 로 이동한 후 다시 돌아오게 되면 onActivityResult 가 동작되게 됩니다. 이때 startActivityForResult 의 두번 째 파라미터로 보낸 값 {여기서는 PICK_FROM_ALBUM 이겠죠?}이 requestCode 로 반환되는 동작을 합니다.

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

if (requestCode == PICK_FROM_ALBUM) {

Uri photoUri = data.getData();

Cursor cursor = null;

try {

/*
* Uri 스키마를
* content:/// 에서 file:/// 로 변경한다.
*/
String[] proj = { MediaStore.Images.Media.DATA };

assert photoUri != null;
cursor = getContentResolver().query(photoUri, proj, null, null, null);

assert cursor != null;
int column_index = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);

cursor.moveToFirst();

tempFile = new File(cursor.getString(column_index));

} finally {
if (cursor != null) {
cursor.close();
}
}

setImage();

}
}

onActivityResult 의 requestCode 값이 PICK_FROM_ALBUM 이면 해당 로직이 실행됩니다. 


data.getData() 를 통해 갤러리에서 선택한 이미지의 Uri 를 받아 옵니다. 이를 cursor 를 통해 스키마를 content:// 에서 file:// 로 변경해 줍니다. 이는 사진이 저장된 절대경로를 받아오는 과정입니다. 


전역변수로 File 타입의 tempFile 을 선언해 주세요. 이 tempFile 에 받아온 이미지를 저장할거에요.

private File tempFile;



3. 갤러리에서 받아온 이미지 넣기

private void setImage() {

ImageView imageView = findViewById(R.id.imageView);

BitmapFactory.Options options = new BitmapFactory.Options();
Bitmap originalBm = BitmapFactory.decodeFile(tempFile.getAbsolutePath(), options);

imageView.setImageBitmap(originalBm);

}

전역 변수 tempFile 의 경로를 불러와 bitmp 파일로 변형한 후 imageView 에 해당 이미지를 넣어줍니다. 짜잔! 여기까지가 앨범에서 이미지를 가져와 ImageView 에 넣는 과정이었습니다 .



4. 카메라에서 이미지 가져오기

private void takePhoto() {

Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);

try {
tempFile = createImageFile();
} catch (IOException e) {
Toast.makeText(this, "이미지 처리 오류! 다시 시도해주세요.", Toast.LENGTH_SHORT).show();
finish();
e.printStackTrace();
}
if (tempFile != null) {

Uri photoUri = Uri.fromFile(tempFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);
startActivityForResult(intent, PICK_FROM_CAMERA);
}
}

Intent 를 통해 카메라화면으로 이동할 수 있습니다. 이때 startAcitivtyResult 에는 PICK_FROM_CAMER 를 파라미터로 넣어줍니다.

private static final int PICK_FROM_CAMERA = 2;

근데 여기서 tempFile 의 Uri 경로를 intent 에 추가해 줘야 합니다. 이는 카메라에서 찍은 사진이 저장될 주소를 의미합니다. 예제에서는 tempFile 을 전역변수로 해서 사용하기 때문에 이 tempFile 에 카메라에서 촬연한 이미지를 넣어줄꺼에요!

 Uri photoUri = Uri.fromFile(tempFile);
intent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri);



5. 카메라에서 찍은 사진을 저장할 파일 만들기

private File createImageFile() throws IOException {

// 이미지 파일 이름 ( blackJin_{시간}_ )
String timeStamp = new SimpleDateFormat("HHmmss").format(new Date());
String imageFileName = "blackJin_" + timeStamp + "_";

// 이미지가 저장될 폴더 이름 ( blackJin )
File storageDir = new File(Environment.getExternalStorageDirectory() + "/blackJin/");
if (!storageDir.exists()) storageDir.mkdirs();

// 빈 파일 생성
File image = File.createTempFile(imageFileName, ".jpg", storageDir);

return image;
}

주석에 적어 놓은 것과 같이 파일 명과 폴더명을 직접 정할 수 있습니다. 그럼 카메라에서 찍은 사진을 우리가 생성한 파일에 저장하고 받아올 수 있게 됩니다.



6. onActivityResult 분기 처리

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {

if (requestCode == PICK_FROM_ALBUM) {

Uri photoUri = data.getData();

...

setImage()
;

} else if (requestCode == PICK_FROM_CAMERA) {

setImage()
;

}
}

onActivityResult 에서 requestCode 를 앨범에서 온 경우와 카메라에서 온 경우로 나눠서 처리해줍니다.



7. 예외 사항 처리하기

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != Activity.RESULT_OK) {

Toast.makeText(this, "취소 되었습니다.", Toast.LENGTH_SHORT).show();

if(tempFile != null) {
if (tempFile.exists()) {
if (tempFile.delete()) {
Log.e(TAG, tempFile.getAbsolutePath() + " 삭제 성공");
tempFile = null;
}
}
}

return;
}

if (requestCode == PICK_FROM_ALBUM) {

Uri photoUri = data.getData();


...


setImage()
;

} else if (requestCode == PICK_FROM_CAMERA) {

setImage()
;

}
}

예외 사항이란 앨범화면으로 이동 했지만 선택을 하지 않고 뒤로 간 경우 또는 카메라로 촬영한 후 저장하지 않고 뒤로 가기를 간 경우입니다. 이는 resultCode 값을 통해 확인할 수 있습니다. 이 경우 "취소 되었습니다.' 토스트를 보여줍니다.


우리가 카메라 촬영할 때 createImageFile() 을 통해 temFile 을 생성했었지요? 만약 사진 촬영 중 취소를 하게 되면 temFile 이 빈썸네일로 디바이스에 저장됩니다. 따라서 예외 사항에서 tempFIle 이 존재하면 이를 삭제해 주는 작업이 필요합니다.


여기까지가 안드로이드 Nougat(OS 7) 미만 버전에서 사용했던 방식입니다.


<주의>

위 예제 코드 카메라에서 이미지 가져오기를 Nougat(OS 7, api 24) 이상 버전에서 실행하면 android.os.FileUriExposedException 에러를 발생하게 됩니다. 원활한 테스트를 위해서는 Nougat 하위 버전에서 카메라에서 이미지 가져오기를 실행해 주시거나 Nougat OS에 대응한 이미지 가져오기 포스팅을 진행해주세요.


다음 시간에는 Nougat OS에 대응한 이미지 가져오기 포스팅을 진행하겠습니다.



Chapter1. 깃허브


관련 포스팅


1.  카메라와 갤러리에서 이미지 가져오기

2. Nougat(Android OS 7) 대응하기

3. 카메라회전 각도를 고려한 이미지 가져오기

4. 가져온 이미지 크롭(Crop) 하기

반응형
Comments