클라이이언트에서 이미지 리사이징하기

이예빈·2022년 11월 19일
0
post-thumbnail

사용자가 이미지 파일을 업로드하면 클라이언트가 서버로 이미지 파일을 전송하고 서버는 S3 스토리지에 해당 이미지파일을 저장하는 로직을 프로젝트에서 진행중인데 파일의 용량이 그대로 전달되어 서버 운영상 리소스가 많이 차지하게 되어 비용부담이 커지는 문제가 있었다.

그래서 사용자가 등록한 이미지 파일을 리사이징하여 전송하는 방법을 고민해 보았다.

데스크탑 버전 도서 상세 조회 페이지에서 이미지가 가장 크게 나타나는데 max-width가 320px로 설정이 되어 있었다. 따라서 크기가 큰 이미지의 max size를 400px 정도로 설정하고 리사이징 하기로 결정했다.

이미지파일을 리사이징하기 위해서는 우선 FileReader를 통해 업로드된 파일의 dataURL을 읽어와야 한다.

const dataUriFromFormFile = (file: File) => {
	return new Promise(resolve => {
		const reader = new FileReader();

		reader.onload = () => {
			resolve(reader.result);
		};

		reader.readAsDataURL(file);
	});
};

사용자가 input에서 업로드 한 이미지는 이진 형식의 바이너리 파일인데 readAsDataURL메서드를 사용하여 base64로 이루어진 데이터를 반환 받을 수 있다.

readAsDataURL로 받은 문자열을 브라우저로 출력하면 업로드된 이미지를 동적으로 확인할 수 있다. 이미지 미리보기를 띄우기 위해 사용되기도 한다.

바이너리 파일(binary file)은 텍스트 파일과 구분되는, 즉 텍스트 파일이 아닌 파일을 말한다. non-text file이라고도 부른다. 바이너리 파일을 텍스트 모드로 열게 되면 위의 사진과 같이 깨져 보이게 된다.

위에서 얻은 이미지 URL을 src로 이미지 객체를 생성한 뒤 canvas 객체를 이용하여 리사이징을 진행한다.

const resizeImage = (imgEl: HTMLImageElement) => {
	const MAX_SIZE = 400;
	const canvas = document.createElement('canvas');
	let width = imgEl.width;
	let height = imgEl.height;
	if (width > height) {
		if (width > MAX_SIZE) {
			height *= MAX_SIZE / width;
			width = MAX_SIZE;
		}
	} else {
		if (height > MAX_SIZE) {
			width *= MAX_SIZE / height;
			height = MAX_SIZE;
		}
	}
	canvas.width = width;
	canvas.height = height;
	canvas.getContext('2d')?.drawImage(imgEl, 0, 0, width, height);
	return canvas.toDataURL('image/jpeg');
};

이미지의 가로가 세로보다 크고 가로 사이즈가 MAX_SIZE(400px)보다 큰 경우 가로 길이를 MAX_SIZE로 지정하고 그 비율에 맞게 세로 길이도 줄여준다. 세로가 가로보다 큰 경우에는 똑같은 로직을 반대로 수행한다.

리사이징한 크기를 캔버스에 적용해주고 캔버스에 이미지를 그려준 뒤 (drawImage) 그로부터 축소된 이미지의 URL을 얻을 수 있다.

이제 URL을 다시 파일로 만들어주어야 한다.

canvas에 toBlob이라는 메서드가 있으나 canvas에서 바로 blob 객체를 뽑았을 때 용량이 url을 blob으로 변환하는 것보다 10배 가까이 차이나는 경우가 있었다. 이유가 뭔지 더 알아봐야겠다.

const dataURItoBlob = (dataURI: string) => {
	let byteString;
	if (dataURI.split(',')[0].indexOf('base64') >= 0)
		byteString = atob(dataURI.split(',')[1]);
	else byteString = unescape(dataURI.split(',')[1]);
	const mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
	let ia = new Uint8Array(byteString.length);

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

	return new Blob([ia], { type: mimeString });
};

최종적으로 이미지 파일을 리사이징된 blob으로 변환해주는 함수를
선언해주었다.

const resizeImageToBlob: (file: File) => Promise<Blob> = file => {
	return new Promise(async resolve => {
		const result = await dataUriFromFormFile(file);
		const imgEl = document.createElement('img');
		imgEl.onload = () => {
			if (imgEl.width <= 400 && imgEl.height <= 400) {
				resolve(file);
			} else {
				const resizedDataUri = resizeImage(imgEl);
				resolve(dataURItoBlob(resizedDataUri));
			}
		};
		imgEl.src = result as string;
	});
};
profile
temporary potato

0개의 댓글