☃️❄️개발일지, 트러블슈팅❄️☃️

[개발일지] webp 확장자를 통한 이미지 제공 최적화 작업

대기업 가고 싶은 공돌이 2025. 1. 3. 13:32

공식 홈페이지 개발 중 프론트엔드에서 이미지를 띄우는 데 시간이 너무 오래 걸린다는 문제점을 확인했다.

 

이미지의 해상도가 너무 높아, 이미지의 크기가 큰 것이 문제였다.

이렇게 큰 크기의 이미지가 동시에 8개가 제공되니, UX가 낮아질 것이 분명했고 대책을 찾고자 했다.

 

이를 해결하기 위해 처음엔 해상도를 낮추는 방식을 생각했었다.

 

여러 가지 해결책을 찾아보던 중 webp라는 확장자를 알게 되었고, 이를 프로젝트에 적용한 내용을 정리해보려 한다.

 

Webp 확장자란?

WebP는 구글에서 개발한 이미지 파일 형식으로, JPEG, PNG, GIF와 같은 기존 이미지 형식에 비해 더 높은 압축률을 제공한다.

WebP는 두 가지의 압축 방식을 대표적으로 제공한다.

  1. 손실 압축 (Lossy Compression): JPEG와 유사한 방식으로, 파일 크기를 줄이기 위해 이미지의 일부 데이터를 손실시킨다. 하지만, 품질 손실을 최소화하면서 훨씬 작은 파일 크기를 유지할 수 있다.
  2. 무손실 압축 (Lossless Compression): PNG와 유사한 방식으로, 압축 과정에서 이미지 품질이 손실되지 않으며, 파일 크기를 줄이는 데 도움을 준다.

WebP는 또한 애니메이션을 지원하여 GIF 대신 사용할 수 있다. 이 형식은 더 적은 용량으로 애니메이션을 구현할 수 있어, 웹사이트의 로딩 속도를 개선하는 데 유리하다.

WebP는 대부분의 최신 웹 브라우저에서 지원되며, 특히 웹 성능 최적화를 위한 인기 있는 형식이다.

 

따라서, jpg나 png의 형식으로 들어온 이미지를 webp 형식으로 변환하여 저장하면 훨씬 빠른 이미지 제공 속도를 얻을 수 있을 것이라 판단했고 이를 프로젝트에 적용했다.

 

Gradle

// Image Processing
implementation "com.sksamuel.scrimage:scrimage-core:4.0.32"
implementation "com.sksamuel.scrimage:scrimage-webp:4.0.32"

 

Service

public File convertToWebp(String fileName, MultipartFile multipartFile) {
    File tempFile = null;
    try {
        // MultipartFile을 File로 변환
        tempFile = convertMultipartFileToFile(multipartFile);

        // WebP로 변환
        return ImmutableImage.loader() // 라이브러리 객체 생성
                .fromFile(tempFile) // .jpg or .png File 가져옴
                .output(WebpWriter.DEFAULT, new File(fileName + ".webp")); // 손실 압축 설정, fileName.webp로 파일 생성
    } catch (Exception e) {
        throw new S3UploadFailException();
    } finally {
        // 임시 파일 삭제
        if (tempFile != null && tempFile.exists()) {
            tempFile.delete();
        }
    }
}

public File convertToWebpWithLossless(String fileName, MultipartFile multipartFile) {
    File tempFile = null;
    try {
        // MultipartFile을 File로 변환
        tempFile = convertMultipartFileToFile(multipartFile);

        // WebP로 변환
        return ImmutableImage.loader() // 라이브러리 객체 생성
                .fromFile(tempFile) // .jpg or .png File 가져옴
                .output(WebpWriter.DEFAULT.withLossless(),
                        new File("무손실" + fileName + ".webp")); // 무손실 압축 설정, fileName.webp로 파일 생성
    } catch (Exception e) {
        throw new S3UploadFailException();
    } finally {
        // 임시 파일 삭제
        if (tempFile != null && tempFile.exists()) {
            tempFile.delete();
        }
    }
}

 

제공되는 라이브러리를 통해 간편하게 jpg, png 파일을 webp 형식으로 변환할 수 있다.

 

WebWriter

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package com.sksamuel.scrimage.webp;

import com.sksamuel.scrimage.AwtImage;
import com.sksamuel.scrimage.metadata.ImageMetadata;
import com.sksamuel.scrimage.nio.ImageWriter;
import com.sksamuel.scrimage.nio.PngWriter;
import java.io.IOException;
import java.io.OutputStream;

public class WebpWriter implements ImageWriter {
    public static final WebpWriter DEFAULT = new WebpWriter();
    public static final WebpWriter MAX_LOSSLESS_COMPRESSION;
    private final CWebpHandler handler = new CWebpHandler();
    private final int z;
    private final int q;
    private final int m;
    private final boolean lossless;

    public WebpWriter() {
        this.z = -1;
        this.q = -1;
        this.m = -1;
        this.lossless = false;
    }

    public WebpWriter(int z, int q, int m, boolean lossless) {
        this.z = z;
        this.q = q;
        this.m = m;
        this.lossless = lossless;
    }

    public WebpWriter withLossless() {
        return new WebpWriter(this.z, this.q, this.m, true);
    }

    public WebpWriter withQ(int q) {
        if (q < 0) {
            throw new IllegalArgumentException("q must be between 0 and 100");
        } else if (q > 100) {
            throw new IllegalArgumentException("q must be between 0 and 100");
        } else {
            return new WebpWriter(this.z, q, this.m, this.lossless);
        }
    }

    public WebpWriter withM(int m) {
        if (m < 0) {
            throw new IllegalArgumentException("m must be between 0 and 6");
        } else if (m > 6) {
            throw new IllegalArgumentException("m must be between 0 and 6");
        } else {
            return new WebpWriter(this.z, this.q, m, this.lossless);
        }
    }

    public WebpWriter withZ(int z) {
        if (z < 0) {
            throw new IllegalArgumentException("z must be between 0 and 9");
        } else if (z > 9) {
            throw new IllegalArgumentException("z must be between 0 and 9");
        } else {
            return new WebpWriter(z, this.q, this.m, this.lossless);
        }
    }

    public void write(AwtImage image, ImageMetadata metadata, OutputStream out) throws IOException {
        byte[] bytes = this.handler.convert(image.bytes(PngWriter.NoCompression), this.m, this.q, this.z, this.lossless);
        out.write(bytes);
    }

    static {
        MAX_LOSSLESS_COMPRESSION = DEFAULT.withZ(9);
    }
}

 

WebpWriter 구현체는 손실 여부, 압축률, 이미지 품질 등의 설정을 파라미터로 제공한다.

  • Q (Quality)
    • 이미지의 품질을 설정하는 매개변수로, 0에서 100사이의 값을 가진다.
      값이 높으면 품질이 높아지고 파일 크기가 커지며, 값이 낮으면 품질이 낮아지고 파일 크기가 작아진다.
  • M (Method)
    • 이미지 압축의 방법을 설정하는 매개변수로, 0에서 6사이의 값을 가진다.
    • 값이 낮을 수록 빠른 속도로 압축되지만 압축률은 낮아진다.
    • 값이 높을 수록 압축 속도는 느려지지만 압축률은 높아진다.
  • Z (Compression level)
    • 이미지의 압축 수준을 설정하는 매개변수로, 0에서 9사이의 값을 가진다.
    • 값이 높을 수록 더 높은 압축률을 제공하지만 압축 속도도 느려진다.

압축 진행 및 실험 결과

우선 테스트에 사용한 이미지의 크기는 4.4MB이다.

해당 이미지의 다운로드 및 이미지 제공 속도는 40.48ms로 측정됐다.

 

손실 압축 방식

손실 압축을 통해 이미지를 변환한 결과 이미지의 크기는 1.1MB가 되었다.

해당 이미지의 다운로드 및 이미지 제공 속도는 1.15ms로 측정됐다.

무손실 압축 방식

무손실 압축을 통해 이미지를 변환한 결과 이미지의 크기는 8.3MB가 되었다.

 

테스트에 사용한 이미지의 확장자는 jpeg였는데, jpeg는 기본적으로 손실 압축방식이다. 세부 사항을 제거해서 용량을 낮추는 반면

webp 확장자는 jpeg보다 많은 세부 사항을 지니고 있기 때문에, 이미지를 변환하며 오히려 이미지의 크기가 커진 것으로 판단된다.

 

png 파일로 테스트 해본 결과, 무손실 압축 방식에서도 약 40%의 유의미한 압축이 보이는 것을 확인했다.

 

이미지의 크기가 오히려 더 커졌기 때문에 콘텐츠 다운로드 속도는 측정하지 않았다.

 

결론

  • 손실 압축 방식
    • jpg 파일의 경우: 약 75%의 이미지 압축률을 보여준다.
    • png 파일의 경우: 약 90%의 이미지 압축률을 보여준다.
    • 4.4MB 이미지 기준, 이미지 제공 속도는 다음과 같다.
      • 기존 이미지 제공 속도 10회 실행 평균: 30.38ms
      • 손실 압축 이미지 제공 속도 10회 실행 평균: 1.58ms
    • 약, 94.8%의 성능 개선이 이뤄졌다.
  • 무손실 압축 방식 
    • jpg 파일의 경우: 이미지의 크기가 증가한다.
    • png 파일의 경우: 약 40%의 이미지 압축률을 보여준다.
  • 이와 같은 결론을 바탕으로 jpg 파일의 경우 무손실 압축으로 확장자 변환을 시도하면 오히려 성능이 악화될 수 있다는 판단하에, 손실 압축 방식을 채택했다.

느낀점

파일의 확장자만 변경했을 뿐인데 성능이 약 94.8%나 향상한 것을 보고, 모든 선택에 있어 항상 최선의 방식을 찾도록 노력해야겠다는 생각을 하게 되었다.

 

참고 블로그

https://velog.io/@dongjae0803/Webp%EB%A1%9C-%EC%9D%B4%EB%AF%B8%EC%A7%80-%ED%8C%8C%EC%9D%BC-%EC%B5%9C%EC%A0%81%ED%99%94%ED%95%98%EA%B8%B0