일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- Rxjava Observable
- rxjava cold observable
- 2022 플러터 안드로이드 스튜디오
- 서비스 vs 쓰레드
- 안드로이드 레트로핏 사용법
- 클래스
- jvm이란
- 스택 큐 차이
- 큐 자바 코드
- 서비스 쓰레드 차이
- 2022 플러터 설치
- 안드로이드 라이선스 종류
- 안드로이드 라이선스
- 안드로이드 유닛 테스트
- rxjava hot observable
- 자바 다형성
- rxjava disposable
- 안드로이드 레트로핏 crud
- 안드로이드 os 구조
- android ar 개발
- 스택 자바 코드
- jvm 작동 원리
- 객체
- 멤버변수
- 안드로이드 유닛 테스트 예시
- 안드로이드 유닛테스트란
- android retrofit login
- ANR이란
- 플러터 설치 2022
- ar vr 차이
- Today
- Total
나만을 위한 블로그
[Android] ExifInterface로 사진의 회전 각도 구하는 법 본문
사진 업로드를 구현하면 핸드폰을 세로나 가로로 들고 찍어서 테스트해볼 것이다. 이후 갤러리에서 사진 파일을 가져온 다음 서버로 업로드하고, 결과를 보면 가로로 찍은 사진은 피사체가 가로로 누워 있거나, 거꾸로 찍으면 180도 뒤집혀 보이는 경우가 발생할 수 있다.
그렇다면 사진이 회전되어 있다는 걸 알 수 있다면 얼마나 좋을까? 돌아간 만큼 거꾸로 돌린 후 업로드하면 회전되지 않은 정상적인 사진을 얻을 수 있을 것이다.
안드로이드에선 ExifInterface라는 클래스를 써서 사진 파일이 갖고 있는 메타데이터 중 회전 각도를 가져올 수 있다.
https://developer.android.com/reference/android/media/ExifInterface
여러 이미지 파일 형식의 Exif 태그를 읽고 쓰기 위한 클래스
참고 : 이 클래스는 일부 안드로이드 버전에서 알려진 문제가 있다. 이 클래스 메서드의 상위 집합을 제공하고 더 쉽게 업데이트할 수 있는 AndroidX ExifInterface 라이브러리를 쓰는 게 좋다. 이 클래스는 포함된 기능 외에도 노출, 데이터 압축 정보 같은 추가 메타데이터를 파싱하고 GPS의 날짜 / 시간 정보 같은 추가 메타데이터를 설정하는 기능을 지원한다
읽기 지원 : JPEG, PNG, WebP, HEIF, DNG, CR2,...
쓰기 지원 : JPEG, PNG, WebP
JPEG, HEIF 파일은 Exif 데이터 청크 내부 or 외부에 XMP 데이터를 포함할 수 있다. 이 클래스는 두 위치 모두에서 XMP 데이터를 검색하지만 XMP 데이터가 Exif 내부, 외부에 모두 존재한다면 내부의 XMP 데이터를 선호한다
https://www.geeksforgeeks.org/what-is-exifinterface-in-android/
일부 사진 관련 앱은 모든 이미지 정보가 필요하다. 그러나 이미지에서 항상 사진의 특징을 바로 읽을 순 없다. GPS 위치, 날짜/시간, 촬영 당시의 설정, 방향 같은 사진 정보를 알아야 할 수 있다. 이전에는 여러 기법으로 개별적으로 메타데이터를 추출하고 이를 판독해야 했다. GPS 태그를 삭제하거나 방향을 바꾸는 등 사진을 바꾸면 판독 작업은 어려워졌다. Exif 파일과 ExifInterface는 이 문제에 대한 해답이다
Exif는 교환 가능한 이미지 파일 형식(Exchangeable Image File Format)의 약자다. 카메라로 찍은 사진이나 기타 미디어에 대한 자세한 정보를 지정하는 표준이다. 카메라 노출, 이미지 촬영 날짜/시간, GPS 위치 같은 중요 정보를 알 수 있다...(중략)...이것은 안드로이드 7.1부터 존재했지만 UI에 제공되는 모든 가능성을 고려할 때 안드로이드 9 (파이, 28) 이상에서만 의미가 있었다
Exif 데이터에 대해 기억할 한 가지는 필수 태그는 없다는 것이다. 모든 태그는 선택 사항이고 일부 서비스에선 Exif 데이터를 완전 제거하기도 한다. 따라서 단일 속성에 대한 데이터가 없거나 이미지 형식이 악명 높은 WebP처럼 데이터를 전혀 연결할 수 없는 경우 등 Exif 데이터가 없는 경우는 항상 해결해야 한다
한마디로 사진 파일의 여러 정보를 확인하거나 수정하는 메서드들을 가진 클래스다.
이 문서를 보면 여러 상수들, 메서드들을 볼 수 있다. 그러나 제목대로 사진의 회전 각도를 확인하는 방법을 중심으로 확인한다.
ExifInterface로 사진의 회전 각도를 구하려면 3가지 상수를 활용할 수 있다.
- ORIENTATION_ROTATE_90
- ORIENTATION_ROTATE_180
- ORIENTATION_ROTATE_270
사진의 회전 각도를 구하는 로직에서 저 상수들을 활용할 경우, ExifInterface로부터 1번째 상수를 리턴받는다면 사진이 90도 회전되었다는 뜻이다. 나머지 상수는 180도, 270도 회전되었다는 뜻이다.
그럼 예제를 확인한다. 먼저 앱 gradle 파일에 의존성을 추가한다.
implementation "androidx.exifinterface:exifinterface:1.3.6"
오늘 날짜 기준으로 최신 버전은 1.3.7이지만 이 버전을 쓰더라도 큰 문제는 없다.
아래는 ExifInterface를 통해 사진의 회전 각도를 구하고 회전된 만큼 거꾸로 회전시킨 비트맵을 얻는 예시다.
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Matrix
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.core.net.toUri
import androidx.exifinterface.media.ExifInterface
import java.io.File
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun rotateBitmap(context: Context, file: File): Bitmap {
val bitmap = BitmapFactory.decodeFile(file.absolutePath)
val exifData = getExifData(context, file.toUri())
val exifOrientation = exifData.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL)
val rotationAngle = exifToRotationAngle(exifOrientation)
return if (rotationAngle != 0) {
// 사진이 90도, 180도, 270도 중 하나의 각도로 회전된 경우 이 부분이 호출
// 회전된 만큼 역방향으로 회전시켜서 정방향의 비트맵 파일을 리턴
rotateBitmap(bitmap, angle = rotationAngle.toFloat())
} else {
// 이미 정방향의 이미지라면 이 부분이 호출
// 회전시킬 필요가 없기 때문에 회전 없이 그대로 리턴
bitmap
}
}
fun rotateBitmap(bitmap: Bitmap, angle: Float): Bitmap {
val matrix = Matrix()
matrix.postRotate(angle)
return Bitmap.createBitmap(bitmap, 0, 0, bitmap.width, bitmap.height, matrix, true)
}
fun getExifData(context: Context, uri: Uri): ExifInterface =
ExifInterface(context.contentResolver.openInputStream(uri)!!)
fun exifToRotationAngle(exifOrientation: Int): Int = when (exifOrientation) {
ExifInterface.ORIENTATION_ROTATE_90 -> 90
ExifInterface.ORIENTATION_ROTATE_180 -> 180
ExifInterface.ORIENTATION_ROTATE_270 -> 270
else -> 0
}
}
이제 rotateBitmap()만 호출하면 사진이 90도, 180도, 270도 회전돼 있는 경우 그만큼 거꾸로 회전되어 정방향으로 표시되는 비트맵을 얻게 된다. 예시기 때문에 필요한 기타 예외처리는 하지 않았다.
예를 들어 ExifInterface 상수 3가지에 해당하지 않는 각도로 회전된 사진을 받은 경우, 예를 들어 105도나 200도 등으로 회전된 이미지를 받은 경우 이 로직은 제대로 작동하지 않을 것이다. 보통 기기를 비스듬하게 들고 찍으면 이 케이스에 걸릴 텐데, 이 경우까지 예외처리하는 일은 드물 것이라고 생각된다. 이건 사용자님이 나빠요 누가 사진 그렇게 찍으래요
그리고 Not-null assertion(!!)을 사용했는데, 이 글을 찾아보고 있다면 알다시피 절대로 널이 생길 수 없을 때만 사용해야 하는 연산자다. 위 코드는 예제 수준이라 그냥 !!을 사용했지만, 애매하다 싶으면 ?. 연산자 써서 안전하게 가자. 앱이 죽는 것만은 무슨 일이 있어도 막아야 하기 때문이다.
이외에도 리팩토링할 여지는 많이 있으니 각자 프로젝트에서 입맛대로 리팩토링하면 될 것이다.
'Android' 카테고리의 다른 글
[Android] SerializedName이란? (0) | 2024.02.08 |
---|---|
[Android] 라이브러리 커스텀 방법 (0) | 2024.02.04 |
[Android] 레트로핏으로 파일 업로드 중 java.io.FileNotFoundException: /external/video/media/1000000085: open failed: ENOENT (No such file or directory) 에러 해결 방법 (0) | 2024.01.21 |
[Android] TedImagePicker 사용법 (0) | 2023.12.22 |
[Android] Coroutine의 Job이란? (0) | 2023.12.18 |