이미지 업로드시, 회전하는 이슈

May 30, 2023

0. 작성하게 된이유

  • AI로 이미지를 보냈는데…? 이미지가 돌아가서 왔다…
  • 이미지에는 여러가지의 정보가 들어있구나..

TL;DR

  • 모바일(or 디지털 카메라)로 찍은 이미지는 여러 Meta(촬영 일시 및 시간)정보가 들어있다.
    • Orientation 값으로 인해 이미지가 돌아가는것이다.
  • 이미지 MetaData를 바꿔주자
    • blueimp-load-image 이용하여 MetaData를 수정할 경우, Image 손실이 크다(약 50%이상)
    • File → Blob → File 같은 Flow Image 손실 적게 MetaData를 Reset 할 수 있다.

1. 이미지 Exif 정보란?

  • 정의 : Exif는 "Exchangeable Image File Format"의 약자로, 디지털 카메라와 다른 디지털 이미지 캡처 장치에서 이미지와 함께 추가 정보를 저장하기 위해 사용되는 표준 형식

  • Exif의 정보

    • 촬영 일시 및 시간
    • 카메라 모델 및 제조사
    • 렌즈 정보
    • 노출 시간 및 조리개 값
    • 촬영 모드 (자동, 수동 등)
    • 화이트 밸런스 설정
    • GPS 위치 정보
    • 이미지 해상도 및 크기
  • 문제의 Exif - orientation

    • 사진을 어떻게 찍던 모바일에서는 찰떡같이 바로 보여준다.
    • 이미지를 업로드하면, S3/Image 서버는 아래의 정보를 읽어서 이미지를 저장하는것같다…?

    orientation-info

2. 이미지 Exif(Orientation)를 수정하자

  • 이미지 MetaData데이터를 읽기 위해서는 라이브러리(blueimp-load-image)를 이용하자

  • 문제의 이미지 Exif 정보 (274 = Orientationn 정보)

    exif

  • 코드

    let file = e.target.files[0];
    
    loadImage(
      file,
      function (img, data) {
        if (data.imageHead && data.exif) {
    			// Orientation 정보를 Reset
          loadImage.writeExifData(data.imageHead, data, 'Orientation', 1)
          img.toBlob(function (blob) {
            loadImage.replaceHead(blob, data.imageHead, function (newBlob) {
    						// newBlob(Orientation 정보 reset)
            })
          }, 'image/jpeg', 1 // 이미지 Quality)
        }
      },
      { meta: true, orientation: true }
    )

3. Exif 메타정보가 변경된 이미지는 화질이 너무 떨어진다

  • 이미지 손실(2.7MB → 968KB)이 너무 심각해서.. 이미지를 다루는 서비스에서는 사용하기 어렵다.
  • 간단한 프로필 이미지같은 경우에는 충분히 사용가능

4. Meta 정보가 필요없다면, Reset

  • 이미지 Meta정보가 필요없다면, 아래와 같이 변경 할 수 도 있다.

  • File → Blob → File로 변경하면 손실 없이 Meta정보를 변경(Reset)할 수 도 있다.

    export const convertToNewFile = (file: File) => {
      return new Promise((resolve, reject) => {
        const reader = new FileReader();
        const image = new Image();
    
        const handleError = (error: unknown) => {
          reject(error);
        };
    
        const handleBlob: BlobCallback = (blob) => {
          if (!blob) return resolve(file);
    
          const newFile = new File([blob], file.name, { type: blob.type });
          resolve(newFile);
        };
    
        const handleLoad = () => {
          const canvas = document.createElement("canvas");
          const ctx = canvas.getContext("2d")!;
    
          canvas.width = image.width;
          canvas.height = image.height;
    
          ctx.drawImage(image, 0, 0);
    
          canvas.toBlob(handleBlob, file.type, 1);
        };
    
        reader.addEventListener("load", (event) => {
          image.addEventListener("load", handleLoad);
          image.addEventListener("error", handleError);
    
          image.src = event.target!.result as string;
        });
        reader.addEventListener("error", handleError);
    
        reader.readAsDataURL(file);
      });
    };

참고


© 2023, Customized by Joon