ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Javascript] 이미지 리사이징으로 용량 줄이기 (Canvas API)
    Java Script 2024. 3. 27. 13:43

    최근에 대한민국 철봉 지도라는 프로젝트를 진행하면서
    사용자가 지도에 철봉 위치를 등록하면서 주변 사진을 이미지로 업로드하고,
    업로드한 이미지를 다른 사용자들이 확인할 수 있는 기능을 구현하였습니다.

     

    이때 저희 서비스 특성상 고화질 이미지를 제공할 필요성이 크지 않고,

    거리뷰를 통해 확인할 수 있는 기능을 제공함으로써 서버의 부담을 줄이고, 로딩 속도를 올리기 위해

    이미지를 리사이징 하여 용량을 줄이기로 결정하였습니다.

    | react-image-file-resizer

    먼저 이미지를 리사이징 react-image-file-resizer라는 라이브러리를 활용해 보기로 했습니다.

    https://www.npmjs.com/package/react-image-file-resizer

     

    react-image-file-resizer

    React module that can rescaled local images. You can change image's width, height, format, rotation and quality. It returns resized image's new base64 URI or Blob. The URI can be used as the source of an <Image> component.. Latest version: 0.4.8, last publ

    www.npmjs.com

    해당 라이브러리를 사용하면 간단하게 아래 코드와 같이 이미지를 리사이징 할 수 있습니다.

    import Resizer from "react-image-file-resizer";
    
    const resizeFile = (file) =>
      new Promise((resolve) => {
        Resizer.imageFileResizer(
          file,
          300, // 리사이징 적용할 최대 넓이
          300, // 리사이징 적용할 최대 높이
          "JPEG", // 리사이징 이미지 포맷
          100, // 리사이징 품질
          0, // 이미지 회전 정도
          (uri) => {
            resolve(uri); // 리사이징 된 이미지 URI
          },
          "base64" // 출력 타입
        );
      });

     

    그리고 해당 함수를 이미지가 업로드될 때 다음과 같이 적용하면 됩니다.

    const onChange = async (event) => {
      try {
        const file = event.target.files[0];
        const image = await resizeFile(file);
        console.log(image);
      } catch (err) {
        console.log(err);
      }
    };

     

    다만 지금 프로젝트에서는 최대 넓이와 높이를 정해서 리사이징 하는 것이 아닌,

    현재 이미지 크기의 80%로 줄이는 방식으로 구현하고 싶었습니다.

    해당 라이브러리에는 이러한 기능이 없는 거 같아서 직접 구현해 보기로 결정했습니다.

    | Canvas API를 통한 이미지 리사이징

    먼저 축소하고자 할 비율과 파일을 받아 Promse를 리턴하는 resizeFile함수를 만들어 줍니다.

    const resizeFile = async (file: File, scale: number): Promise<File> => {
      return new Promise((resolve, reject) => {
        ...
      });
    };
    
    export default resizeFile;

     

    이제 리턴하는 해당 프로미스의 콜백함수 안에서
    Canvas API를 사용하여 이미지를 축소하고 내보내면 됩니다.

    const resizeFile = async (file: File, scale: number): Promise<File> => {
      return new Promise((resolve, reject) => {
        // 파일의 내용을 읽을 수 있도록 파일 리더 객체를 생성합니다.
        const reader = new FileReader();
        reader.onload = (e) => {
          // 새 이미지 객체를 생성합니다.
          const img = new Image();
          img.onload = () => {
            // 원하는 스케일을 적용하여 새로운 너비와 높이를 계산합니다.
            const width = img.width * scale;
            const height = img.height * scale;
    
            // 캔버스를 생성하고 크기를 설정합니다.
            const canvas = document.createElement("canvas");
            canvas.width = width;
            canvas.height = height;
    
            // 캔버스의 2D 컨텍스트를 가져옵니다.
            const ctx = canvas.getContext("2d");
            // 컨텍스트를 사용하여 새로 계산된 크기로 이미지를 조정합니다.
            ctx?.drawImage(img, 0, 0, width, height);
            // 캔버스의 내용을 바탕으로 파일로 변환할 Blob 객체를 생성합니다.
            canvas.toBlob((blob) => {
              if (blob) {
                // 새 파일 객체를 생성합니다. 파일 이름과 유형은 원본 파일에서 가져옵니다.
                const resizedFile = new File([blob], file.name, {
                  type: file.type,
                  lastModified: Date.now(),
                });
                // 생성된 파일 객체를 반환합니다.
                resolve(resizedFile);
              } else {
                reject(new Error("실패"));
              }
            }, file.type);
          };
          // FileReader 객체가 로드한 파일 데이터를 이미지 소스로 설정합니다.
          img.src = e.target?.result as string;
        };
        // FileReader에게 파일의 내용을 Data URL 형태로 읽도록 요청합니다.
        reader.readAsDataURL(file);
      });
    };
    
    export default resizeFile;

     

    이제 해당 함수를 사용하여 이미지를 업로드하고,
    파일 형식을 확인하여 미리 보기를 보여주는 기능을 아래와 같이 만들 수 있습니다.

    const handleImageChange = async (e: ChangeEvent<HTMLInputElement>) => {
        const suppertedFormats = [
          "image/jpeg",
          "image/png",
          "image/svg+xml",
          "image/webp",
        ];
    
        if (!e.target.files) return;
    
        if (!suppertedFormats.includes(e.target.files[0]?.type)) {
          setErrorMessage(
            "지원되지 않은 이미지 형식입니다. JPEG, PNG, webp형식의 이미지를 업로드해주세요."
          );
          return;
        }
    
        if (images.length + e.target.files.length > 5) {
          setErrorMessage("최대 5개 까지 등록 가능합니다!");
          return;
        }
    
        let file: File = await resizeFile(e.target.files[0], 0.8);
        let reader = new FileReader();
    
        reader.onloadend = () => {
          const imageData = {
            file: file,
            previewURL: reader.result as string,
            id: nanoid(),
          };
          setImages((prev) => [...prev, imageData]);
          formState.setImageForm(imageData);
        };
    
        if (file.size / (1024 * 1024) > 10) {
          setErrorMessage("이미지는 최대 10MB까지 가능합니다.");
          return;
        }
    
        setErrorMessage("");
    
        reader.readAsDataURL(file);
      };

    | 결과

    확실히 이미지의 크기를 줄이면 로딩 속도가 눈에 보이게 차이가 납니다.

    또한 용량이 줄어들기 때문에 서버의 부담도 줄일 수 있어 좋은 거 같습니다.

    리사이징 적용
    리사이징 미적용

     

Designed by Tistory.