들어가며
‘요즘카페’ 서비스를 배포 하고 설문을 통해 사용자들로부터 피드백을 받았다. 피드백의 내용으로는 이미지 로딩 속도 및 용량 관련된 것이었다.
우리는 보여지는 사진의 이미지의 화질이 좋아야 한다고 생각했다. 그래서 원본 데이터를 그대로 전달하고 있었는데 이것이 문제가 되었다. 그래서 이미지 로딩 시간이 길며 사용성을 해치고 있었다.
이를 위해 이미지를 리사이징하여 S3에 저장하고, 필요시 서빙하도록 했다.
리사이즈
툴
- marvin
- thumbnailator
- Imgscalr
- 2DGraphics
- getScaledInstance
과 같은 리사이즈를 위한 도구들이 존재한다. 처음 3개는 외부 라이브러리이며, 나머지 두개는 java 내부 라이브러리이다.
외부 라이브러리에 의존하지 않고 java 내부 라이브러리를 사용해보기로 하였다. 단순하게 크기 조절만 하면 되기 때문에 getScaledInstance를 사용하기로 하였다.
리사이즈
현재 서비스에서 이미지가 필요한 대표적인 부분이다.
API를 통해 전달받은 이미지 파일을 원하는 사이즈로 리사이즈 하고 S3에 업로드 하기로 하였다.
웹이지만 모바일 기기 특화이기 때문에 위의 사진 각각 500px, 100px 로 업로드 후 사용하기로 하였다.
코드
public List<String> resizeToAllSizesAndUpload(final List<MultipartFile> files) {
final List<ImageResizer> imageResizers = files.stream()
.map(this::multipartfileToImageResizer)
.toList();
imageResizers.parallelStream()
.forEach(imageResizer -> imageResizer.resizeImageToAllSizes()
.forEach(s3Client::upload));
return imageResizers.stream()
.map(ImageResizer::getFileName)
.toList();
}
이미지가 요청되면 ImageResizer로 변경한다. 이과정에서 검증을 거치고 각각에 대해서 사이즈별로 리사이즈를 진행하고 업로드를 한다.
public List<MultipartFile> resizeImageToAllSizes() {
return Arrays.stream(Size.values())
.map(this::resizeToFixedSize)
.toList();
}
이미지의 이름을 새로 붙이는 이유는 같은 이름의 이미지 업로드 방지를 의미한다.
public class ImageName {
private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyyMMddHHmmssSSSSSS");
private static final String EXTENSION_DELIMITER = ".";
public static ImageName from(String originalFileName) {
final String fileName = FORMATTER.format(LocalDateTime.now());
final String extension = getExtension(originalFileName);
return new ImageName(fileName + extension);
}
}
현재일자 + 확장자로 변경하여 처리한다.
public ImageResizer(final MultipartFile image, final String fileName) {
validate(image);
this.image = image;
this.fileName = fileName;
}
private void validate(final MultipartFile image) {
if (isNull(image.getContentType()) || isNotImage(image)) {
throw new BadRequestException(NOT_IMAGE);
} if (image.getSize() > MAX_IMAGE_SIZE) {
throw new BadRequestException(INVALID_IMAGE_SIZE);
}
}
ImageResizer를 생성할 때, 생성자의 인자로 원본파일을 받는데, 이 파일이 이미지가 맞는지 validate를 진행한 후 생성한다.
다음은 ImageResizer의 리사이즈기능을 구현한 코드이다.
public MultipartFile resizeToFixedSize(final Size size) {
final BufferedImage bufferedImage = getBufferedImage();
final int width = size.getWidth();
final int height = getResizedHeight(width, bufferedImage);
final BufferedImage scaledImage = resize(bufferedImage, width, height);
final byte[] bytes = toByteArray(scaledImage);
return toMultipartFile(bytes, size);
}
private BufferedImage getBufferedImage() {
try {
return ImageIO.read(image.getInputStream());
} catch (final IOException e) {
throw new RuntimeException(e);
}
}
private int getResizedHeight(final int resizedWidth, final BufferedImage bufferedImage) {
final double ratio = (double) resizedWidth / bufferedImage.getWidth();
return (int) (bufferedImage.getHeight() * ratio);
}
리사이즈 할 Size를 받아서 리사이즈를 진행한다. 원본의 비율은 유지하면서 리사이즈를 진행한다.
이때 옵션을 지정해줄 수 있다.
private BufferedImage resize(final BufferedImage image, final int width, final int height) {
final BufferedImage canvas = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
final Graphics graphics = canvas.getGraphics();
graphics.drawImage(image.getScaledInstance(width, height, Image.SCALE_SMOOTH), 0, 0, null)
graphics.dispose();
return canvas;
}
옵션은 다음과 같다.
- SCALE_DEFAULT - 기본
- SCALE_FAST - 빠른 이미지 스케일링
- SCALE_SMOOTH - 부드러운 이미지 스케일링
- SCALE_REPLICATE - 이미지를 확대할 때 주로 사용
- SCALE_AREA_AVERAGINGREPLICATE - 이미지를 확대 또는 축소할 때 사용
SCALE_AREA_AVERAGING - 이미지를 축소할 때 사용
속도
FAST > DEFAULT > SMOOTH
품질
SMOOTH > DEFAULT > FAST
우리 서비스는 사용자에게 시각적으로 매력적인 카페를 보여준다. 이 과정에서 이미지의 화질이 중요하다고 생각하기 때문에 속도보다는 품질이 우선시되는 SMOOTH를 사용하여 구현하였다.
이렇게 리사이즈를 진행한 결과 전체 리소스를 확인하는데 546MB에서 250MB로 약 45%가량 개선된 것을 확인할 수 있었다. 또한 사용자가 체감하지 못할 정도의 화질 저하만 이루어졌다.
'코코코딩공부' 카테고리의 다른 글
공간데이터와 공간인덱스로 지도 개선하기 (0) | 2023.11.03 |
---|---|
Batch Insert에 대한 고민(feat. 2배 개선) (1) | 2023.11.01 |
필터를 사용해 API 성능 로그 만들기 (2) | 2023.10.31 |
테스트를 더 빠르게 진행시켜보자 (0) | 2023.10.21 |
TestContainer 사용기 & 테스트 격리 (1) | 2023.10.16 |