사이드 프로젝트 WebP 도입 후기

sightstudio

·

2021. 5. 5. 11:38

배경

 

사이드 프로젝트를 진행하던 중 이미지 파일의 용량이 너무 커서 로딩시간이 지연되던 문제가 있었습니다.  

 

스마트 폰에서 촬영된 고해상도, 고화소 이미지가 주 원인이였고, 

이를 별다른 처리 없이 클라이언트에 내려줄 경우, 클라이언트는 이 고용량 이미지를 그대로 다운받게됩니다. 

 

모바일이라면 데이터와 배터리가 낭비될 수 밖에 없습니다. 

물론 이 경우 thunbnail 처리를 하는 것만으로 끝낼 수 있지만, 예전에 우연찮게 webp를 알게되서  

서버에서 전달받은 이미지를 webp포맷으로 손실 압축하여 내려주도록 구현해보았습니다.  

 

서버는 Spring을 사용하였습니다. 

 

 

별다른 처리가 없다면 클라이언트는 12M 이미지를 데이터로 그대로 받는다...모바일이라면?

WebP란?  

 

WebP는 2010년 Google에서 발표한 이미지 포맷입니다. JPEG를 대체하기 위해 발표되었으며,  

현재는 JPEG, GIF, PNG 모두 대체할 수 있습니다.

 

WebP는 PNG 대비 26%, JPEG 대비 25~34%의 압축효율을 가지고 있습니다. 

 

자세한 내용은 [공식문서]나 제 블로그의 [webp 작동원리]로 대체합니다.

 

 

JVM 계열에서 Webp 적용

 

WebP는 C로 쓰여져 있는 [libwebp 코덱]과 이를 래핑한 [바이너리]로 제공됩니다.

그렇기 때문에 자바에서는 webp를 적용하려면 두가지 선택지가 있습니다.

 

 

1. JNI로 직접 구현

 

JNI를 통해 libwebp 코덱을 사용하는 C 코드를 직접 로드하여 사용 할  수 있습니다. 

 

완전하게 모든 옵션을 쓸 수 있고, 세부 옵션까지 완전히 조절할 수 있습니다.

 

다만 소스의 수정이 필요할 경우, 각 플랫폼마다 다시 컴파일해줘야하는 번거로움이 있습니다. 

이 경우는 다른 분이 이미 적용한 예제가 있어 링크를 남깁니다. 

 

예시: github.com/LeeKyoungIl/webp-java-sample

 

 

2. 바이너리 래퍼 사용

 

1번이 부담 되면 구글에서 제공하는 바이너리를 래핑하여 사용할 수 있습니다. 

 

각 역할을 하는 바이너리들이 나누어져 있으며, libwebp에 있는 기능 중 일부는 사용할 수 없습니다.  

 

예시) 바이너리만으로 animated webp를 animated gif로 변경 할 수 없습니다. [구글 그룹]

 

저는 2번으로 진행하였습니다. 2번으로 진행하기 전에 이미지를 리사이징할 일이 필요해서

webp를 지원하는 [scrimage]라는 이미지 라이브러리를 사용하였습니다. 

 

sksamuel/scrimage

Java, Scala and Kotlin image processing library. Contribute to sksamuel/scrimage development by creating an account on GitHub.

github.com

 

[scrimage] 는 현재 Animated Gif에서 Animated WebP로의 변환을 지원하지 않기 때문에

이 부분 래퍼를 따로 [구현]하였습니다. 

 

[실제 구현]은 디자인 패턴 중 하나인 [전략패턴]을 이용하여 구현하였습니다. 

이를 통해 [서비스레이어]에서 가독성이 낮은 분기문을 사용할 필요가 없습니다. 

 

 

와 디자인 패턴! 

 

적용 완료 후 결과

 

아래는 webp 손실압축 결과입니다.  

 

webp 변환에는 0~6 으로 압축 정도를 정할 수 있는데요. 저는 기본값인 4를 사용했습니다. (cwebp의 m 옵션)

 

이를 통해 클라이언트는 webp를 지원하는 플랫폼을 사용할 경우 이미지에 대한 네트워크 사용을 줄일 수 있었습니다. 

 

용량이 꽤 줄었다.. 오..? 너무 많이 줄었는데?

 

 

웹에서 적용

 

webp 포맷의 경우 하위 호환성을 생각해야하기 때문에 어느정도 분기처리나 fall-back 처리가 필요합니다. 

아래는 서버측과 클라이언트(web) 측에서 어떻게 대응할 수 있는지를 기록하였습니다. 

 

서버

 

accept 헤더를 통한 분기처리 

서버의 경우는 간단합니다. webp 이미지를 지원하는 브라우저나 기기에서

HTTP 헤더 중 accept에 위에 같이 image/webp 를 명시적으로 보내줍니다. 

 

Accecpt 헤더를 보고 nginx나 기타 서버에서 분기처리를 해주면 webp를 지원하는 클라이언트들에 대해

서만 webp 포맷을 보내주고, 나머지에 대해서는 원본 url을 보내 줄 수 있습니다.

 

클라이언트 (web)

 

js의 경우 modernizr.com/ 를 사용하면 해당 브라우저가 webp를 지원하는지 확인 할 수 있습니다.

이를 통해 분기처리를 하거나 webp -> 원본 이미지 순으로 fallback 처리를 할 수 있습니다. 

 

진행했던 사이드 프로젝트의 경우 이 방법을 사용하였습니다. 

 

겪었던 이슈

 

원본 파일보다 변환된 WebP의 용량이 더 큰문제 

 

?? 왜 증가요?

[구글 WebP FAQ]

 

처음에 이것저것 실험하다보니 원본 파일보다 WebP 파일의 용량이 증가하는 상황이 있었습니다. 

공식문서를 확인해보니 아래와 같은 경우에는 WebP 변환이 이미지 용량이 더 커질 수 있었습니다. 

 

1. 원본 이미지 무손실 ARGB 포맷일 경우 

 

이 경우 webp는 이미지를 블록으로 다운샘플링[1] 하기위해 YUV420으로 변환하는 과정에서 

원본을 압축하기 힘들어서 발생한다고 합니다.

 

(주로 색이 별로 없는 단색 PNG 파일에서 webp로 변환하는 과정)

 

2. 손실 압축된 이미지를 무손실 압축 webp로 변환하는 경우

 

대표적으로 JPEG를 무손실 압축으로 변환하면 발생합니다. ( 쓰고보니 당연한 말이네요 ㅋㅋ  )

 

3. 기존 이미지를 더 높은 화질의 이미지의 WebP로 변환할때 

 

이것도 당연한 말이군요... 

 

WebP 변환이 너무 오래걸려요.. 

 

[관련 이슈]

 

 

이미지 하나에 14초..

 

일부 용량이 큰 이미지의 변환시간이 약 10초 정도 걸리는 이슈가 있었습니다. 

( 특히 Animated GIF ) 

 

이를 처리하기 위해  webp 변환에 사용되는 별도의 [스레드풀]을 만들어 비동기로 처리하였습니다. 

 

webp 파일이 없을 경우 원본이미지로 fall-back 시키기 때문에

변환되는 동안 사용자는 원본이미지를 보도록 구현하였습니다. 

 

오픈 소스 컨트리뷰션 

webp를 사용하기 위해 오픈소스인 [scrimage] 를 사용하며 일부 자잘한 문제들을 제가 스스로 해결하여 

컨트리뷰션을 하였습니다. 메인테이너와 의사소통도 하고 재미있었네요 ㅎㅎ 

 

( 와 나도 오픈소스 컨트리뷰터! )

작고 귀여운 컨트리뷰션이지만 실제 코드를 컨트리뷰트해서 만족합니다 ㅎㅎ

뜬금 없지만 메인테이너분이 kotest 만드신 분이네요 ㅋㅋ 

 

작고 귀여운 컨트리뷰션

 

마치며

 

WebP를 적용하면서 여러가지 많은 삽질을 했었는데 막상 만들고 나니 뿌듯하네요

WebP는 아직은 넘어야 할 산이 많아보이지만 이는 시간이 해결해줄 문제일것 같네요.

 

앞으로 더 열씸히 공부해야겠습니다. 

 

 

[1] 다운샘플링 : 하나의 영상을 더 작은 표본들로 표현하는 과정

'사이드 프로젝트' 카테고리의 다른 글

사이드 프로젝트 Pinpoint 도입 후기  (7) 2021.04.03