[이슈해결노트] 이미지 최적화 작업

이나현·2022년 10월 27일
0

오라운드

목록 보기
10/18
post-thumbnail
  1. 문제상황

    아트워크(상품을 만들기위한 이미지)를 업로드 시킬 때, 이미지 사이즈를 maxwidth or height을 800으로 맞추다보니 용량이 크거나 이미지 사이즈가 컸던 이미지가 깨지는 현상 발견

  2. 해결방법 고안

    (1) 이미지 width나 height를 바로 바꾸는 것이 아닌 height나 width 중 큰 부분을 기준으로 삼아 해당 길이가 800될 때까지 반으로 쪼갠다.

    (2) 800이 된 길이의 비율에 맞춰서 나머지 한 쪽의 길이를 수정한다.

    (3) 해당 길이로 이미지의 사이즈를 바꾸는데, 이미지 해상도를 해치지 않고 줄이는 패키지를 사용해 이미지를 줄인다.

    (4) 이미지 태그로 만들어놓은 형식을 파일형식으로 만들어 api에 담아 보낸다.

  3. 기존 코드

const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const { files } = e.target;
    if (!files) return;
    const [file] = Array.from(files);
    if (!file) return;
    // if (file.size > 10485760 || !(file.type.includes('jpg') || file.type.includes('jpeg') || file.type.includes('png') || file.type.includes('svg'))) {
    if (!(file.type.includes('jpg') || file.type.includes('jpeg') || file.type.includes('png') || file.type.includes('svg'))) {
      Toast.show(t('selleb_upload:$MODAL_MESSAGE.FAIL.NO_FILE_FORM'));
    } else {

      let originalFile: undefined | File = file;

      if (!file.type.includes('svg')) { // exif의 Orientation을 반영한 이미지를 얻는다.
	     const originalOptions = {
	      useWebWorker: true
      };
      originalFile = await imageCompression(file, originalOptions);
      }
	     const options = {
	       maxSizeMB: 3,
		     maxWidthOrHeight: 800,
	       useWebWorker: true
      };
      const compressedFile = await imageCompression(newFile, options);

      const api = api
      try {
        const response = await api.upload(originalFile as RcFile,newCompressedFile);

        setSavedArtworkIndex(response.artworkIndex);

        alertModalStore1.setVisible(true);
        alertModalStore1.setMessage(
          <p>{t('selleb_upload:$MODAL_MESSAGE.SUCCESS.UPLOAD_IMG')}</p>
        );

        if (Toast.showResponse(response, t('아트워크 이미지가 등록되었습니다.'))) {
          handleShowEditor(response.artworkIndex);
         }
      } catch (error) {
        Toast.showError(error, t('selleb_upload:$MODAL_MESSAGE.FAIL.NOT_UPLOAD'));
      }
      }
    }
  };

⇒ 기존 코드에서는 아트워크의 이미지 사이즈를 바로 변경하여, 이미지 화질 저하의 이슈가 있다.

⇒ 또한, 기존에 사용한 imageCompression 패키지가 화질 저하를 방지하는 코드가 없는 것 같다.

  1. 변경한 코드

(1) 업로드 함수

  • 업로드 함수에 이미지 사이즈 확인용 이미지를 생성한다.
  • 이미지 에러가 뜰 때, 비정상적인 파일이라는 예외처리를 한다.
  • 이미지가 load될 때, 이미지 사이즈를 줄이는 함수를 추가하고 그 후에 사이즈 변경 함수를 넣어 처리한다.
const handleFileUpload = async (e: React.ChangeEvent<HTMLInputElement>) => {
    const { files } = e.target;
    if (!files) return;
    const [file] = Array.from(files);
    if (!file) return;
    // if (file.size > 10485760 || !(file.type.includes('jpg') || file.type.includes('jpeg') || file.type.includes('png') || file.type.includes('svg'))) {
    if (!(file.type.includes('jpg') || file.type.includes('jpeg') || file.type.includes('png') || file.type.includes('svg'))) {
      Toast.show(t('selleb_upload:$MODAL_MESSAGE.FAIL.NO_FILE_FORM'));
    } else {

      let originalFile: undefined | File = file;
      //이미지 리사이징_이미지 사이즈 확인용 이미지 생성
      let img = new Image();
      img.src = window?.URL.createObjectURL(file);

      img.onerror = function () {
        //에러 토스트 (비정상적인 파일입니다.)
        Toast.show(t('selleb_upload:$MODAL_MESSAGE.FAIL.NO_NORMAL_FILE'));
      };

      img.onload = async function () {

        //이미지 리사이징_이미지 사이즈 가로나 세로 중 긴거로 scale 변경
        const imgScaleSize = scaleIt(0.5, img?.width, img?.height);

        //이미지 리사이징 라이브러리 사용하여 이미지 자체 사이즈 변경
        const changedScaleImage = await Morpheus?.resize(file, {
          height: imgScaleSize?.resizeHeight,
          width: imgScaleSize?.resizeWidth
        });
        const newCompressedFile : File | undefined = makeNewFileForm(changedScaleImage, file);

        if (!file.type.includes('svg')) {
          const changedOriginalImage = await Morpheus?.resize(file, {
            height: img?.height,
            width: img?.width
          });
          originalFile = makeNewFileForm(changedOriginalImage, file);
        }

         const api = api
      try {
        const response = await api.upload(originalFile as RcFile, file.name, newCompressedFile);

        setSavedArtworkIndex(response.artworkIndex);

        alertModalStore1.setVisible(true);
        alertModalStore1.setMessage(
          <p>{t('selleb_upload:$MODAL_MESSAGE.SUCCESS.UPLOAD_IMG')}</p>
        );

        // if (Toast.showResponse(response, t('아트워크 이미지가 등록되었습니다.'))) {
        //   handleShowEditor(response.artworkIndex);
        // }
      } catch (error) {
        Toast.showError(error, t('selleb_upload:$MODAL_MESSAGE.FAIL.NOT_UPLOAD'));
      }
      }
    }
  };

(2) 이미지 사이즈 점진적 축소 함수

//스케일 줄이기(반씩 줄임)
  const scaleIt = (scaleFactor: number, width: number, height: number) => {
    const MAX_SIZE = 800;
    let resizeWidth = width;
    let resizeHeight = height;
    if (resizeWidth > resizeHeight) {
      while (resizeWidth > MAX_SIZE) {
        resizeWidth *= scaleFactor;
        if (resizeWidth < MAX_SIZE){
          resizeWidth = MAX_SIZE;
          resizeHeight *= MAX_SIZE / width;
          break;
        }
      }
    } else {
      while (resizeHeight > MAX_SIZE) {
        resizeHeight *= scaleFactor;
        if (resizeHeight < MAX_SIZE){
          resizeHeight = MAX_SIZE;
          resizeWidth *= MAX_SIZE / height;
          break;
        }
      }
    }

    return { resizeWidth, resizeHeight };

  };

(3) canvas파일을 data형식으로 변환하는 함수를 만들어 이미지를 파일로 변환한다.

//canvas파일을 data형식으로 변환
  const makeNewFileForm = (imgCanvas: HTMLCanvasElement, originalFile: File | undefined) => {

    if (!imgCanvas.toDataURL) return;

    let type : string;
    if (originalFile?.type === 'svg') {
      type = 'image/jpeg';
    } else {
      type = originalFile?.type || 'image/jpeg';
    }

    const dataURL = imgCanvas?.toDataURL(type, 1);
    const byteString = window.atob(dataURL?.split(',')[1]);
    const mimeString = dataURL?.split(',')[0].split(':')[1].split(';')[0];
    const ab = new ArrayBuffer(byteString.length);
    const ia = new Uint8Array(ab);

    for (let i = 0; i < byteString.length; i++) {
      ia[i] = byteString.charCodeAt(i);
    }

    return (new File([ab], originalFile?.name, { type: mimeString }))
  };
  1. Morpheus-image-resize 패키지
  • 2018년 이후 업데이트는 되지 않지만, 필요한 이미지 최적화 작업을 가장 잘해주는 패키지로 판단되어 사용했다

morpheus-image-resize

profile
technology blog

0개의 댓글