이 글의 코드는 img-compressor의 browser-image-processing.ts를 기준으로 읽었습니다. Canvas API의 일반적인 장점만 나열하지 않고, 이 구현이 파일을 어떤 순서로 바꾸는지에 집중합니다.

파일을 Canvas에 그리기까지의 네 단계

브라우저는 File을 바로 Canvas에 그릴 수 없습니다. 현재 함수는 FileReader.readAsDataURL로 파일을 읽고, 결과 Data URL을 Imagesrc로 넣은 뒤, 이미지가 로드되면 Canvas에 그립니다. 마지막으로 canvas.toBlob 결과를 다시 File로 감쌉니다.

const reader = new FileReader();
reader.onload = (event) => {
  const image = new Image();
  image.onload = () => {
    const canvas = document.createElement('canvas');
    const context = canvas.getContext('2d');
    context?.drawImage(image, 0, 0, canvas.width, canvas.height);
    canvas.toBlob((blob) => {
      if (!blob) return;
      resolve(new File([blob], 'result.webp', { type: 'image/webp' }));
    }, 'image/webp', quality);
  };
  image.src = event.target?.result as string;
};
reader.readAsDataURL(file);

각 비동기 경계에는 실패할 수 있는 지점이 있습니다. 파일 읽기 실패, 이미지 디코딩 실패, Canvas context 미획득, toBlob 결과 없음은 각각 다른 상태입니다. 구현은 이 경우 Promise를 reject해 호출부가 실패를 감지하도록 합니다. 압축 도구에서는 “파일이 안 된다”는 결과보다 어느 단계에서 멈췄는지를 분리하는 구조가 먼저 필요합니다.

비율 계산은 확대를 막는 쪽으로 고정한다

리사이즈 함수는 원본을 목표 크기보다 크게 만들지 않습니다. 가로와 세로 각각의 비율 중 더 작은 값을 고르고, 1보다 커지지 않게 제한합니다. 이 조건이 없으면 작은 이미지를 큰 Canvas에 그리는 순간 용량과 처리량이 불필요하게 커질 수 있습니다.

const scale = Math.min(
  1,
  maxWidth / image.width,
  maxHeight / image.height
);

canvas.width = Math.max(1, Math.round(image.width * scale));
canvas.height = Math.max(1, Math.round(image.height * scale));

최소값을 1로 두는 것도 작은 입력이나 극단적인 옵션에서 Canvas 크기가 0이 되는 것을 막기 위한 방어입니다. 이런 조건은 눈에 띄는 기능은 아니지만, 파일 처리 코드가 예상 밖 입력에서 멈추지 않게 합니다.

변환 결과가 항상 더 작지는 않다

WebP 변환 함수는 새 파일을 만들 수 있지만, 호출부는 그 파일이 언제나 더 낫다고 가정하지 않습니다. 직접 WebP 모드에서도 변환된 파일 크기가 리사이즈 파일보다 크거나 같으면 기존 파일을 유지합니다. 파일 포맷 선택을 UI 옵션으로 제공할 때 필요한 최소한의 안전장치입니다.

이 비교가 없으면 사용자는 “압축” 버튼을 눌렀는데 더 큰 파일을 받게 됩니다. 용량만 기준으로 한 판단이므로, 화질·투명도·색상 표현처럼 별도 요구가 있는 경우에는 다른 검증 기준을 추가해야 합니다.

리사이즈와 포맷 변환을 한 함수에 섞지 않은 이유

저장소에서는 resizeImageFileconvertToWebPFile을 분리합니다. 전자는 원본 형식을 유지하며 필요한 경우에만 가로·세로를 줄이고, 후자는 Canvas 결과를 image/webp 파일로 만듭니다. 호출부가 두 결과를 비교할 수 있으므로 “크기만 줄일지”, “포맷도 바꿀지”를 UI 옵션과 연결하기 쉬워집니다.

분리했다고 해서 모든 조건이 자동으로 해결되는 것은 아닙니다. 예를 들어 원본의 메타데이터를 보존해야 하는지, 투명 영역을 어떤 형식으로 전달해야 하는지, 애니메이션 파일을 허용할지는 제품 요구 사항입니다. 현재 글에서 확인한 것은 일반 이미지 파일을 Canvas로 다시 그려 출력하는 흐름이며, 지원 범위 밖 파일은 별도 테스트 항목으로 남겨야 합니다.

실패 경로를 테스트할 때의 기준

  • 손상된 파일 또는 브라우저가 디코딩하지 못하는 파일에서 이미지 로드 실패가 호출부로 전달되는지 확인합니다.
  • Canvas context를 얻지 못하는 환경에서 빈 파일을 만들지 않고 오류로 끝나는지 확인합니다.
  • toBlob이 null을 반환했을 때 결과 목록에 항목을 추가하지 않는지 확인합니다.
  • 처리 실패 후에도 이미 완료된 다른 파일의 다운로드 링크가 유지되는지 확인합니다.

이 구현을 다른 화면에 적용할 때 확인할 항목

  • 입력 파일 형식을 accept 속성만으로 신뢰하지 말고 실제 MIME type과 실패 흐름을 함께 다룹니다.
  • 최대 너비·높이 값은 0, 빈 값, 비정상 수치가 들어올 때의 정책을 정합니다.
  • 미리보기 Object URL은 삭제와 화면 이탈 시점에 정리합니다.
  • Canvas 변환이 유지해야 하는 이미지 특성이 있는지 실제 파일로 확인합니다.
  • 변환 성공 여부와 “더 작은 결과를 얻었는지”를 별도 정보로 보여 줍니다.

원본 코드

convertToWebPFile과 resizeImageFile 전체 보기