- 목차
개요
이미지(JPEG) 크기를 축소하면서 Exif 정보를 유지할 수 있는 방법을 알아보고 있었다. 안타깝게도 (안드로이드에서는) 이미지를 새로 만들면 (크기를 축소한다던가…) Exif가 없어지게된다.
안드로이드에서도 Exif를 다루는 클래스인 ExifInterface (androidx.exifinterface.media) 조차 Exif를 Copy해주는 기능은 없으며, 설상가상으로 대부분의 함수들이 Private로 처리되어 있다. 생성자 파라미터로 파일(Inputstream)로 받기 때문에 이미지 파일과 1:1 응집되어 있다.
어쨌든 방법을 찾기 위해 조사한 내용을 기록한다.
ExifInterface 클래스 분석
아… 코드 8000 줄이다…
Parsing 과정
- 생성자에서 파일관련 정보를 파라미터로 받는다.
- 결국 inputstream으로 변환한다.
- inputstream을 이용하여 loadAttribute 메소드 호출
- mAttribute 초기화 (HashMap 생성)
- 만일 Exif data only가 아닌 경우 getMimeType 메소드를 호출하여 이미지 포맷 확인
- 각 이미지 포맷에 맞는 Exif 속성을 가져오는 메소드를 호출
- 여기서는 JPEG 이미지를 활용하기 때문에 getJpegAttributes 메소드 활용
- 내부에 InputStream을 Extends한 ByteOrderedDataInputStream을 활용
- 일단 JPEG는 Exif를 기본적으로 Big endian으로 처리하기 때문에 ByteOrderedDataInputStream을 우선 Big endian으로 설정
- 파일을 읽으면서 중간에 readExifSegment 메소드 호출
- parseTiffHeaders 메소드 호출 (Exif가 없는 이미지면 호출 안되는 듯?)
- readByteOrder 메소드를 호출하여 byte 순서를 확인하여 ExifInterface와 ByteOrderedDataInputStream의 byte order를 반영한다. (Little endian, Big endian)
- 일부 생략…
- 기본적인 EXIF 정보가 없다면 (0 값으로) 추가한다.
- 날짜, 이미지 너비, 이미지 높이, 회전 정보, 조명 정보 (Light source)
Exif 유지 방법은?
없다… 편법이라면 있다.
원본이미지의 Exif를 복사한 후, 새로 만든 이미지에 덮어 씌우는 수밖에 없다.
과정은 아래와 같다.
- Gradle에 ExifInterface를 추가한다.
dependencies {
implementation "androidx.exifinterface:exifinterface:1.3.5"
}
- 새로운 클래스 (java 파일)을 만들고, ExifInterface 클래스의 모든 코드를 복사 붙여 넣기 한다.
- ExifInterface, ExifAttribute 클래스의 이름을 바꾼다. (본인의 경우 PublicExifInterface, PublicExifAttribute로 바꾸었다.)
- 패키지 이름에 오류가 뜰 것인데 이름을 새로 바꾼 ExifInterface와 ExifAttribute로 다시 매칭해주고, 일부 다른 내부 클래스나 static 클래스도 적절히 수정한다.
- ExifInterface 클래스의 대부분의 필드와 메소드들이 Private이기 때문에 접근을 위해서 불가피하다.
- mAttributes 라는 HashMap<String, ExifAttribute>[] 녀석에 원하는 TAG 정보를 넣어주면 된다.
- Java는 Deep copy가 지원되지 않기 때문에(?) 원본 Exif로 부터 일일이 복사해야된다.
- ByteOrder가 Big Endian인지 Little Endian인지 유의해야된다.
- JPEG 이미지는 Exif를 Big Endian으로 처리하지만, 그렇지 않은 경우도 있으니 유의. Exif를 복사 한 후, mExifByteOrder 변수 값을 원본 이미지의 mExifByteOrder와 같은 값으로 하면된다.
//대략적인 코드 구조
////// 외부 호출 하는 부분 //////
PublicExifInterface 원본_이미지 = new PublicExifInterface(원본 이미지 파일 정보);
PublicExifInterface 새로운_이미지 = new PublicExifInterface(새로운 이미지 파일 정보);
새로운_이미지.copyAllAttribute(원본_이미지.getExifByteOrder(), 원본_이미지.getAllAttribute());
// 이 메소드를 꼭 호출해야 Exif를 파일에 기록한다.
// 기존에 있는 데이터를 지운 후, 처음부터 다시 쓰는 구조이기 때문에
// Exif 편집을 모두 마친 후에 이 메소드 한 번만 호출하는 것이 좋다.
새로운_이미지.saveAttributes();
//////
////// PublicExifInterface 클래스 내부 //////
/**
* 원본 Exif 정보를 가져오시고요
*/
public HashMap<String, PublicExifAttribute>[] getAllAttribute() {
return mAttributes;
}
/**
* 원본 Exif의 byte order를 가져오시고요
*/
public ByteOrder getExifByteOrder() {
return mExifByteOrder;
}
/**
*
*/
public void copyAllAttribute(
ByteOrder srcByteOrder,
HashMap<String, PublicExifAttribute>[] sourceAttr
)
{
//HashMap을 복사하세요.
//ByteOrder를 바꿔 주세요.
//일부 EXIF 값을 조정해주세요.
}
유의할 점
- EXIF에 XMP 값 (TAG_XMP)은 복사하지 않도록 한다.
(원인은 아직 모르겠지만)이미지가 깨진다. - (이 부분은 좀 더 확인할 필요가 있음) 이미지 너비와 높이 값을 바꾼다면, TAG_PIXEL_X_DIMENSION (“PixelXDimension”)값과 TAG_PIXEL_Y_DIMENSION (“PixelYDimension”)값도 이미지 너비 높이 값과 일치 시켜야한다.
- 길이는 16 또는 64 배수를 선호하는 것 같음.
- 이미지 포맷관련 정보가 변화 할 수 있다. (
본인의 경우 YUV422에서 YUV420 변화해 버리는…)
EXIF TAG 정보
(JEITA CP-3451C Section 4.6.8 Tag Support Levels 부분을 참고)
- (Idx 0) IFD TIFF_TAGS : Primary image IFD TIFF tags
- (Idx 1) IFD_EXIF_TAGS : Primary image IFD Exif Private tags
- (Idx 2) IFD_GPS_TAGS : Primary image IFD GPS Info tags
- (Idx 3) IFD_INTEROPERABILITY_TAGS : Primary image IFD Interoperability tag
- (Idx 4) IFD_THUMBNAIL_TAGS : IFD Thumbnail tags
- (Idx 5) IFD TIFF_TAGS : (Preview?) image IFD TIFF tags
- (Idx 6) ORF_MAKER_NOTE_TAGS : ORF file tags
- (Idx 7) ORF_CAMERA_SETTINGS_TAGS : ORF file tags
- (Idx 8) ORF_IMAGE_PROCESSING_TAGS : ORF file tags
- (Idx 9) PEF_TAGS : PEF file tag
IFD_TIFF_TAGS
이미지의 기본적인 특성을 정의
TAG_IMAGE_WIDTH = “ImageWidth”
TAG_IMAGE_LENGTH = “ImageLength” (Height)
데이터 타입으로 usort 또는 ulong을 사용
해당 태그의 주석에 의하면 JPEG 이미지는 JPEG marker가 대신 사용되기 대문에 이 태그를 사용하지 말라고 한다.
setAttribute() 메소드 호출시 Primary 부분이 아닌 Preview 부분에 저장되는 문제가?..
TAG_IMAGE_WIDTH = “ImageWidth” => The number of columns of image data, equal to the number of pixels per row. In JPEG compressed data, this tag shall not be used because a JPEG marker is used instead of it.
TAG_IMAGE_LENGTH = “ImageLength”=> The number of rows of image data. In JPEG compressed data, this tag shall not be used because a JPEG marker is used instead of it.
TAG_IMAGE_DESCRIPTION = “ImageDescription”
이미지 타이틀. 2byte 문자 코드는 사용 불가. (2byte 문자 코드를 사용하고 싶으면 TAG_USER_COMMENT를 사용하면 된다.)
IFD_EXIF_TAGS
카메라 설정 값
TAG_PIXEL_X_DIMENSION = “PixelXDimension”
TAG_PIXEL_Y_DIMENSION = “PixelYDimension”
그냥 이미지 width, height와 값을 일치 시켰다. (다르면 이미지가 깨지는 듯?)
TAG_USER_COMMENT = “UserComment”
이미지에 대한 설명. TAG_IMAGE_DESCRIPTION 보다 사용가능한 문자에 대한 제한이 없음.
IFD_GPS_TAGS
GPS 정보
TAG_GPS_IMG_DIRECTION_REF = “GPSImgDirectionRef”
카메라 촬영 방향의 기준. TAG_GPS_IMG_DIRECTION 항목의 0 값의 기준을 정해준다. 아래 두 개의 값중 하나가 기록된다.
- T : (True North) 진북 - 지구 표면 지리적인 북극을 가리키는 방향
- M : (Magnetic North) 자북 - 지구 자기장이 가리키는 북극
TAG_GPS_IMG_DIRECTION = “GPSImgDirection”
카메라 촬영 방향을 각도로 표기 값의 범위는 60분법 0 ~ 359.99 이다. 파일에는 도, 분, 초를 Rational 타입으로 기록된다. 시계 방향(CW)으로 북, 동, 남, 서 순이다. (삼성 스마트폰의 나침반 기능을 생각하면 된다.)
IFD_INTEROPERABILITY_TAGS
IFD_THUMBNAIL_TAGS
이미지의 기본적인 특성을 정의 (썸네일 이미지)
IFD TIFF_TAGS
이미지의 기본적인 특성을 정의 (미리보기 이미지)
ORF_MAKER_NOTE_TAGS
ORF_CAMERA_SETTINGS_TAGS
ORF_IMAGE_PROCESSING_TAGS
Olympus 카메라 관련 태그
PEF_TAGS
출처 및 참고
JEITA CP-3451C Section 4.6.8 Tag Support Levels (85페이지, 문서에서는 페이지 번호 79로 표기됨)
https://home.jeita.or.jp/tsc/std-pdf/CP3451C.pdf
AndroidX ExifInterface
https://developer.android.com/jetpack/androidx/releases/exifinterface?hl=ko
ORF file tags
http://www.exiv2.org/tags-olympus.html
PEF file tag
기존 웹페이지가 exiftool.org로 리다이렉트됨
댓글
댓글 쓰기