이전 포스트 '[JavaScript] 이미지 리사이징, 이미지 용량 줄이기 (1) (feat. HTML5 canvas)'를 읽고 오시면 좋습니다!
Canvas의 리사이징
5575 x 3923, 2.37MB 이미지가 있다고 가정해보겠습니다. 한 번 다운로드 시에는 무리가 없는 이미지 용량으로 보이지만 수십 번 이미지를 다운로드하게 되면 더 많은 리소스가 요구됩니다. 해당 리소스를 줄이기 위하여 이미지 리사이징을 시도해보겠습니다. Canvas는 가로, 세로 px를 정하고 그 위에 이미지를 그리는 방식이기 때문에 px 기준으로 이미지가 리사이징됩니다. 보통 최대 px를 기준으로 비례하여 이미지를 리사이징합니다. 예를 들어, 위 이미지를 최대 px 400px기준으로 이미지를 리사이징한다고 가정하고 코드를 작성해보겠습니다.
//위 소스코드 생략
const maxSize = 400; //최대px 400px 기준
let width = image.width; //5575px
let height = image.height; //3923px
if (width > height) {
if (width > maxSize) {
height *= maxSize / width;
width = maxSize;
}
} else {
if (height > maxSize) {
width *= maxSize / height;
height = maxSize;
}
}
canvas.width = width;
canvas.height = height;
canvas.getContext('2d').drawImage(image, 0, 0, width, height);
해당 로직을 타게 되면 비례식에 의하여 width는 400px, height는 281px (3923*400/5575)이 됩니다.
canvas로 압축된 이미지의 가로는 5575px에서 400px이 되고 세로는 3923px에서 281px이 되었습니다. 2.37MB였던 이미지의 용량은 몇이 되었을까요? 가로세로가 각각 약 1/14만큼 줄었고 제곱으로 치면 약 1/196가 됩니다. 2.37MB에서 약 1/196 정도 용량이 줄었을 것으로 예상이 됩니다.
KB로 계산하면 12.38KB가 됩니다. 하지만 실제로 canvas를 활용하여 이미지를 줄였을 때 가로세로 px은 400 x 281 맞게 리사이징되었지만 용량은 30.8KB가 되었습니다. 왜 그럴까요? 이는 dpi(해상도)와 각 확장자 별 압축방식의 차이로 발생합니다. 이미지의 압축방식 등에 대한 내용은 주제에 벗어나는 내용이니 차후에 좀 더 알아보면 좋을 것 같습니다. 일단, 최대 px을 기준으로 canvas 리사이징 할 때 용량을 충분히 줄일 수 있다는 것에 대하여 알게 되었습니다. 하지만 좀 더 생각해보아야 할 부분이 있습니다.
어떤 기준으로 리사이징해야 할까요?
절대적인 기준으로 리사이징하는 과제라면 고려해보지 않아도 될 문제이지만 썸네일 처리와 같이 다양한 이미지의 용량을 줄이는 것이 목적인 경우 고려해볼 부분들이 좀 더 있습니다.
Q. 만약 가로세로가 400px보다 작은 이미지가 있다면 어떠할까요?
A. 굳이 리사이징할 필요가 없어 보입니다.
Q. 만약 기준과 근접한 크기의 401 x 401 이미지가 있다면 어떠할까요?
A. 가로세로가 400px보다 크니까 리사이징 해도 좋을 것 같습니다만 정작 리사이징을 해보면 이미지가 심각하게 깨지는 경우를 만나게 됩니다.
Q. 만약 한쪽 비율이 과하게 높은 1600 x 20 이미지가 있다면 어떠할까요?
A. 400px 기준으로 줄였다가는 400 x 5 이미지를 만나게 되고 역시 이미지가 심각하게 깨지는 경우를 만나게 됩니다.
Q. 만약 1000 x 800 이미지가 용량이 20KB밖에 안 되는 용량이 적은 이미지가 있다면 어떠할까요?
A. 400px 기준으로 줄여서 더 용량이 작아지는 이미지를 만들어낼 것입니다.
Q. 만약 399 x 399 이미지가 용량이 1.5MB 정도 되는 용량이 큰 이미지가 있다면 어떠할까요?
A. 400px 기준으로 줄여야 하니 리사이징이 적용되지 않을 것입니다.
이를 통해 알 수 있는 문제는 용량을 줄이는 것이 목적일 때 길이(px) 기준으로 줄이는 방법은 좋지 않다는 것입니다!
길이 기준을 용량 기준으로 바꾸는 방법
고민을 해보았습니다. 정확하게 용량을 맞출 수 있는 방법은 없지만 대략적으로는 맞출 수 있을 것이라고 생각했습니다. 5575 x 3923, 2.37MB 이미지를 다시 가져와보겠습니다. 썸네일 처리를 하기 위해 최대 px 400px이 아닌 용량 100KB로 기준을 잡아보겠습니다. 2.37MB(2426KB)가 100KB가 되기 위해서는 1/24.26만큼 줄여야 합니다.
단순하게 가로 세로 루트 값 약 4.9씩 줄이면 됩니다. 1137(5575/4.9) x 800(3923/4.9)을 기준으로 canvas 리사이징을 진행합니다. 이렇게 했을 때 2.37MB의 이미지는 100KB가 될까요? 실제로 진행했을 때 135KB의 이미지를 얻을 수 있습니다. 100KB 기준으로 길이 비례를 계산하여 canvas 리사이징을 진행하면 100KB에 가까운 이미지를 얻을 수 있습니다. 위에 말했던 이미지 압축방식과 dpi에 따라 용량은 정확하지 않을 수 있기 때문에 100KB 가깝게 압축했다는 것만으로 좋은 압축이 될 수 있을 것 같습니다.
추가로, 100KB 기준으로 리사이징 유무를 결정하게 되면 101KB 일 때 쓸데없이 리사이징하는 경우가 생길 수 있습니다. 또, 이때 이미지가 깨지는 경우가 생길 수 있습니다. 그래서 리사이징 유무의 기준이 되는 용량은 1MB로 하고 리사이징을 한다면 100KB로 리사이징을 시도하는 로직을 만들었습니다.
function getThumbImgFile(image, file){
const canvas = document.createElement("canvas");
const base_size = 1024000; //1MB (썸네일 작업 유무 기준 사이즈)
const comp_size = 102400; //100KB (썸네일 작업 결과물 사이즈, 50~200KB 수준으로 압축됨)
let width = image.width;
left height = image.height;
const size = file.size;
if(size <= base_size) return file;
const ratio = Math.ceil(Math.sqrt((size / comp_size), 2));
width = image.width / ratio,
height = image.height / ratio;
canvas.width = width;
canvas.height = height;
canvas.getContext("2d").drawImage(image, 0, 0, width, height);
return dataURItoBlob(canvas.toDataURL("image/png"))); //dataURLtoBlob 부분은 이전 포스팅 참조
}
이와 같이 이미지 리사이징을 진행하면 좀 더 효과적으로 이미지 리사이징을 할 수 있습니다.
정확한 용량으로 리사이징하거나 더 좋은 기준으로 리사이징 할 수 있다면 방법을 공유해주세요. 방법은 다양하지만 간단하게 썸네일 파일을 만들고자 할 때 위와 같은 로직으로 접근한다면 좋은 썸네일을 얻을 수 있을 것 같습니다!
실제 서비스에도 적용해 잘 써왔던 부분이나 추후에는 이미지 프로세싱 서버를 별도로 구축하여 썸네일 처리할 수 있게 방식을 전환했습니다. 매우 간단하게 썸네일 문제를 해결하고자 할 때는 위의 방법이 괜찮을 것 같습니다!!
'Deep Dive Series > Image Resizing' 카테고리의 다른 글
[JavaScript] 이미지 리사이징, 이미지 용량 줄이기 (1) (feat. HTML5 canvas) (0) | 2021.05.27 |
---|