Bleeding edge

Image date 다루기(data 변환에 관하여) 본문

Javascript

Image date 다루기(data 변환에 관하여)

codevil 2023. 12. 3. 16:29

Intro

이번에 카메라 앱에서 이미지를 crop하는 기능을 만들려고 canvas를 사용했다. canvas에서 이미지를 사용하려고 하니 input에서 받은 파일의 데이터 형식으로는 canvas에서 이미지를 바로 그릴 수 없다는 것을 알게 되었다.

우리가 웹을 만들면서 바로 받는 타입은 [URL, File] 두 가지이다.

이 두타입에 대해 간략하게 이야기하자면..

URL : 일반적으로 우리가 이미 알고 있거나 외부의 엔드포인트로부터 받은 이미지의 주소의 값을 말한다. 

File : Input type file을 사용하면 Input의 event.target.value로 들어오는 값이다.

 

Image data map

canvas의 이미지에 대해서 이해를 잘하기 위하여 구글링을 해보았더니 다음과 같은 관계도가 있었다. 

(출처 : https://observablehq.com/@ehouais/how-to-get-image-data-from-an-url-or-a-file)

 

머리가 질끈..

 

이미지 데이터 타입 통일

canvas에 이미지를 그릴 때도 image.src를 이용하고, canvas에서도 이미지를 수정하고 변환을 할 때 toDataURL 그리고 img에서도 src를 사용하기에 이미지는 url로 변환해서 사용하는 것이 가장 편안하다고 생각해서 파일 데이터가 들어오면 이미지 데이터를 url로 통일하도록 하겠다.

File 타입 변환하기

우선 파일이 한 개가 input에 들어온다고 가정하고 코드를 작성하겠다. 

  function handleFileChange(event: React.ChangeEvent<HTMLInputElement>){
    const file = event.target.files?.[0];
	//...
  };

 

위의 그래프에는 써있는 방법은 아니지만, File로 파일이 들어오면 다음과 같은 방법으로 base64 데이터타입으로 변환할 수 있다. 

function fileToBase64String(file: File) {
    return new Promise<string>((res, rej) => {
      const reader = new FileReader();
      reader.onload = () => {
        if (typeof reader.result === 'string') res(reader.result || '');
      };
      reader.readAsDataURL(file);
      reader.onerror = () => '';
    });
}
  
function handleFileChange(event: React.ChangeEvent<HTMLInputElement>){
    //현재 이미지의 데이터 타입은 File
    const file = event.target.files?.[0];
    if(!file) return
	//현재 이미지의 데이터 타입은 base64 string으로 인코딩 되었다.
    const base64String = await fileToBase64String(file);
	//...
};

우리가 이미지 혹은 canvas에서 바로 사용할 수 있도록 url을 만들어야한다. 이 때 우리는 base64 string에서 url로 바꾸기 위해서 Blob으로 변환을 해야한다.

 

async function base64StringToBlob(base64String: string) {
    const base64 = await fetch(base64String);
    return base64.blob();
}
function fileToBase64String(file: File) {
    return new Promise<string>((res, rej) => {
      const reader = new FileReader();
      reader.onload = () => {
        if (typeof reader.result === 'string') res(reader.result || '');
      };
      reader.readAsDataURL(file);
      reader.onerror = () => '';
    });
}
  
async function handleFileChange(event: React.ChangeEvent<HTMLInputElement>){
    //현재 이미지의 데이터 타입은 File
    const file = event.target.files?.[0];
    if(!file) return
	//현재 이미지의 데이터 타입은 base64
    const base64String = await fileToBase64String(file);
    //현재 이미지의 데이터 타입은 Blob
    const jpgBlob = await base64StringToBlob(base64String);
	//...
};

드디어 이미지의 그래프에 있는대로 URL.createObjectURL를 이용하여 데이터 url만들 수 있다. (위의 그래프가 정말 다양한 데이터 타입에 대해 이해를 할 수 있지만, 아마 저 그래프는 한 개의 메서드로 바로 변환할 수 있는 경우만 적어놓은 것 같다)

async function base64StringToBlob(base64String: string) {
    const base64 = await fetch(base64String);
    return base64.blob();
}
function fileToBase64String(file: File) {
    return new Promise<string>((res, rej) => {
      const reader = new FileReader();
      reader.onload = () => {
        if (typeof reader.result === 'string') res(reader.result || '');
      };
      reader.readAsDataURL(file);
      reader.onerror = () => '';
    });
}
  
async function handleFileChange(event: React.ChangeEvent<HTMLInputElement>){
    //현재 이미지의 데이터 타입은 File
    const file = event.target.files?.[0];
    if(!file) return
	//현재 이미지의 데이터 타입은 base64
    const base64String = await fileToBase64String(file);
    //현재 이미지의 데이터 타입은 Blob
    const jpgBlob = await base64StringToBlob(base64String);
    //현재 이미지의 데이터 타입은 URL
    const objectURL = URL.createObjectURL(jpgBlob);
	console.log(objectURL)
};

 

Retro

이전에 contentEditable이 가장 성가신 친구인 줄 알았는데 이미지나 canvas를 다루는 것도 이미지 데이터 타입부터 이미지를 변환하고나서의 화질저하와 같은 성가신 것들이 많았다. 간만에 공부할 것이 많아서 재밌었다.