WebP: 웹페이지 압축 형식
(purplesyringa.moe)- GitHub Pages가 Brotli를 지원하지 않자, HTML을 무손실 WebP 이미지로 인코딩하고 브라우저의 이미지 디코더와 JavaScript로 복원해 전송량을 줄이는 실험임
- WASM Brotli 디코더는
71 KiB~200 KB추가 비용이 들고, Compression Streams API는gzip,deflate,deflate-raw만 지원해 Brotli 디코딩 우회로가 되기 어려움 - VP8L 무손실 WebP는 예측 변환, 16x16 블록별 Huffman 트리 재사용, color cache 덕분에 텍스트성 데이터에서도 gzip보다 작은 결과를 낼 수 있음
- 테스트 HTML
439,478바이트는 gzip94,683바이트, WebP43,182바이트까지 줄었고, Brotli37 KiB보다는 크지만 gzip 대비 약 2.2배 작았음 - Canvas 2D의 anti-fingerprinting 노이즈, WebGL
readPixels변경, 초기 빈 화면, 스크롤 복원 문제 때문에 실제 적용보다는 브라우저 제약을 드러낸 해킹에 가까움
GitHub Pages에서 Brotli를 쓸 수 없는 문제
- 페이지 로딩 시간을 줄일 때는 HTML minify보다 HTTP 압축이 더 큰 효과를 냄
- HTTP는
Content-Encoding헤더로 gzip과 Brotli를 지원함- gzip은 저렴해 흔히 기본 활성화됨
- Brotli는 보통 gzip보다 압축률이 좋지만 훨씬 느림
- GitHub Pages는 Brotli를 지원하지 않아, 사이트의 가장 긴 글인
Recovering garbled Bitcoin addresses가 Brotli라면37 KiB일 수 있는 대신 gzip으로92 KiB가 됨 - 이 차이 때문에 로드 시간이 불필요하게 2.5배 늘어남
처음 떠올린 대안과 막힌 지점
- GitHub가 사전 압축된 Brotli 파일 업로드와 서빙을 지원하면 문제를 피할 수 있지만, 그런 기능은 제공되지 않음
- 클라이언트에서 JavaScript로 직접 압축 해제하는 방안은 WASM 디코더 크기 때문에 이점이 줄어듦
- brotli-dec-wasm은 약
200 KB - tiny-brotli-dec-wasm은
71 KiB - 비교 대상이 gzip
92 KiB와 Brotli 본문37 KiB + 71 KiB가 되어 이득이 사라짐
- brotli-dec-wasm은 약
- 브라우저 HTTP 스택에는 Brotli 디코더가 있지만, Compression Streams API의 DecompressionStream은
gzip,deflate,deflate-raw만 받음 gzip을 Zopfli로 사전 압축해도86 KiB라서 Brotli보다 크게 남음
이미지 포맷을 압축 컨테이너처럼 쓰기
- 브라우저는 이미지를 디코딩할 수 있으므로, 데이터를 이미지 픽셀에 넣고 Canvas API로 다시 읽으면 압축 해제 로직을 새로 넣지 않아도 됨
- GIF는 row-major 순서로 데이터를 펼친 뒤 LZW를 적용하지만, gzip의 DEFLATE가 LZW를 대체하려고 설계된 방식이라 이득을 기대하기 어려움
- PNG는 DEFLATE를 쓰지만, 픽셀 원본이 아니라 이웃 픽셀과의 차이를 압축하는 예측 변환을 먼저 적용함
- 예:
[a, b, c, d]대신[a, b-a, c-b, d-c]를 압축 - 예측값과 실제값의 차이가 작을수록 Huffman 압축에 유리함
- 예:
- 실험의 핵심은 무손실 WebP인 VP8L을 일반 바이트 데이터 압축용으로 활용하는 것임
VP8L이 gzip과 다른 점
- WebP에는 손실 및 무손실 변형이 있으며, 여기서는 무손실 포맷인 VP8L만 다룸
- VP8L은 PNG처럼 예측 변환을 쓰지만, DEFLATE 대신 Google이 만든 DEFLATE 유사 방식을 사용함
- DEFLATE는 파일을 여러 조각으로 나누고 각 조각에 맞는 Huffman 트리를 둘 수 있음
- JavaScript, SVG, 마크업이 한 HTML에 섞이면 서로 다른 트리가 유리할 수 있음
- VP8L은 임의로 큰 Huffman 트리 테이블을 정의하고, 각 16x16 픽셀 블록마다 다른 트리를 쓸 수 있음
- JavaScript 다음 CSS, 다시 JavaScript가 나오는 경우 DEFLATE는 비슷한 트리를 여러 번 인코딩할 수 있음
- VP8L은 트리 재사용이 가능해 더 자주, 더 싸게 트리를 바꿀 수 있음
- VP8L의 color cache는 특정 속성을 가진 최근 픽셀을 복사하라는 식으로 값을 짧게 표현할 수 있음
첫 WebP 압축 실험
- 테스트 파일은
Recovering garbled Bitcoin addressesHTML임- 원본 크기:
439,478바이트 gzip --best:94,683바이트
- 원본 크기:
- Rust의 webp crate로 바이트를 grayscale RGB 이미지로 바꿔 무손실 WebP로 압축함
- grayscale을 쓴 이유는 WebP의
subtract green변환 때문임- grayscale에서는 G 채널을 R/B에서 빼면 R/B가 사실상 0이 됨
- WebP는 세 채널을 별도 Huffman 트리로 인코딩하므로 고정값 채널은
O(1)공간에 가까워짐
- 처음에는
1xN이미지로 만들려 했지만 WebP가 최대16383x16383까지만 지원해VP8_ENC_ERROR_BAD_DIMENSION오류가 발생함 16383xN형태로 맞추자 결과가45,604바이트가 됐고, gzip보다 2배 작고 bzip249,764바이트보다도 작았음
WebP 특화 조정
- 넓은 이미지에 row-major 순서를 쓰면 16x16 블록 안에 입력에서 멀리 떨어진 바이트가 섞임
- 이미지 형태를 세로로 긴
27x16383로 바꾸자 압축 결과가43,232바이트로 줄어듦 cwebp의 압축 성능 설정인 method를0~6까지 비교함- method 0:
48,902 - method 1:
43,546 - method 2:
43,442 - method 3:
43,292 - method 4:
43,232 - method 5:
43,182 - method 6:
43,182
- method 0:
- method
5는 method6과 같은 크기를 내면서 더 빠른 선택지로 삼음 - 이 상태에서 WebP는 gzip보다 2.2배 작고 Brotli보다 1.2배 큼
여러 파일 벤치마크
- 비교 대상은 snappy testdata, Canterbury Corpus와 Large Corpus, SVG 파일 2개였음
- 비교 포맷은
gzip --best,brotli --best,bzip2 --best, WebP 압축 스크립트임 - WebP는 아주 작은 파일인
grammar.lsp,xargs.1과 일부 예외를 제외하면 거의 항상 gzip보다 나았음 - 예외는
kennedy.xls와paper-100k.pdf였음paper-100k.pdf는19 KBXML 뒤에 압축 데이터가 있어 사실상 작은 데이터를 측정하는 상황임kennedy.xls는 Brotli/bzip2 상대 성능도 이상하며, 가까운 위치에 이질적인 데이터가 많아 압축기가 다루기 어려운 파일일 수 있음
- WebP는 bzip2보다 약간 나쁜 편이지만, 일부 경우에는 앞서기도 함
- WebP는
fireworks.jpeg처럼 거의 균일한 랜덤 blob에 가까운 예외를 빼면 항상 Brotli보다 나빴음 - 큰 plain-text 데이터에서는 gzip 대비 측정 가능한 개선을 냄
- SVG 파일에서도 개선이 있었음
html_x_4에서는 WebP가3.3%압축률을 기록했고, Brotli2.8%보다는 나쁘지만 gzip13%보다 크게 나았음
JavaScript로 복원하기
- WebP 디코딩 자체는
fetch,createImageBitmap,OffscreenCanvas,getImageData,TextDecoder로 구현할 수 있음 - 픽셀의 R 채널을 원본 HTML 바이트로 사용하고, 이를 UTF-8로 디코딩한 뒤
document.documentElement.innerHTML에 넣는 구조임 - Canvas API는 fingerprinting에 자주 쓰이므로 일부 브라우저가
getImageData결과에 노이즈를 추가함- Firefox의 strict tracking protection에서 1% 미만 픽셀이 영향을 받을 수 있음
- HTML에는 이 노이즈가 오타처럼 나타남
- WebGL의
readPixels를 사용하면 당시에는 노이즈 없이 동작했음- WebGL은 텍스처를 안정적으로
2048x2048까지만 지원하므로 크기 제한을 다시 맞춰야 함 - 이 복원 코드는 minify 후 약
550바이트였음
- WebGL은 텍스처를 안정적으로
- WebP와 코드까지 합쳐
44 KiB가 됐고, gzip92 KiB, Brotli37 KiB와 비교됨 - 2026년 4월 15일 이후 Firefox가
readPixels에도 anti-fingerprinting을 추가해, 이 방식은 그대로는 더 이상 동작하지 않음
화면 깜빡임과 스크롤 문제
await는 promise 기반으로 처리되므로, WebP 다운로드가 끝나기 전에 브라우저는 스크립트 실행이 끝났다고 판단함- DOM이 아직 비어 있어 사용자는 짧은 시간 빈 흰 화면을 보게 됨
- 완화책으로 스타일과 페이지 상단 약
8 KiB는 gzip HTML에 남기고, 뷰포트 아래 콘텐츠만 WebP로 압축할 수 있음 - 새로고침 때 스크롤 위치 복원도 문제가 됨
- 예를 들어
Y = 5000px위치에서 새로고침했는데 페이지 높이가0px이면 위치가 초기화됨 - 아주 큰 임시
div를 추가하면 도움이 됨
- 예를 들어
document.write대신document.documentElement.innerHTML에 할당해야 현재 문서를 새 문서로 교체하지 않고 갱신할 수 있음
WebP를 JavaScript에 직접 넣기
- 지연 시간을 조금 더 줄이기 위해 WebP를 JavaScript 안에 직접 임베딩할 수 있음
- 가장 단순한 방식은 base64 data URL임
- base64는 원래 크기를
1.33배늘리지만, gzip이 이 증가분을 거의 상쇄함compressed.webp를 base64로 만들면57,576바이트- 이를 gzip
--best로 압축하면43,519바이트
- WebP 같은 압축 blob은 거의 균일한 랜덤 데이터이고, base64의 8비트-to-6비트 변환 결과에 gzip의 Huffman 트리가 사실상 역변환처럼 작동함
- Unicode와 UTF-16을 쓸 수도 있지만, base64가 충분한 첫 해법으로 남음
실제 적용과 이후 상태
- 글이 작성된 시점에는 이 페이지 자체가 오래된 브라우저나 JavaScript 비활성 환경이 아니면 “Fool me twice” 섹션부터 WebP로 압축되어 있었음
- 페이지의 WebP 이미지는 실제 코드에서는 세로로 길고 좁지만, 보기 좋게 정사각형 WebP 예시도 제공됨
- 이미지에서 밝은 상단과 하단은 텍스트와 코드, 약 1/5 지점의 해칭 영역은 다이어그램, 대부분의 어두운 영역은 다이어그램 안의 텍스트였음
- 실제 절감폭은 제한적이었음
- 기존 gzip 페이지:
88 KiB - WebP 적용 후 gzip 페이지:
83 KiB - Brotli 예상:
69 KiB
- 기존 gzip 페이지:
- 2026년 4월 15일 이후 Firefox 방문자에게 깨진 콘텐츠가 보이지 않도록 현재는 압축하지 않은 페이지로 바뀜
- Rust 코드, 코퍼스, 기타 파일은 GitHub에 공개되어 있음
댓글과 토론
Hacker News 의견들
-
지연 시간을 무시하면 그렇겠지만, 실제로는 로드 시간이 0.001% 쯤 늘어나는 정도로 보임
크기 증가분은 왕복 지연 시간에 비하면 의미가 없고, 55KiB 덜 전송해서 아끼는 시간보다 압축 해제에 드는 시간이 더 클 수도 있음
재미있는 실험이긴 해도 이 경우 사용자 경험은 오히려 나빠질 가능성이 크고, 속도는 거의 같으면서 호환성만 떨어질 듯함- 로드 시간만 최적화하고 모두의 데이터 속도를 가정한다면 맞는 말이지만, 웹사이트나 앱 작성자가 내 대신 속도/데이터 절충을 너무 쉽게 결정하지 않았으면 할 때가 많음
문제는 TFA 작성자처럼 100KB를 50KB로 줄이려고 애쓰는 사람도 있지만, 로밍 데이터로 식당 영업시간만 보려는 나에게 이미지 수십 MB를 아무렇지 않게 보내는 곳도 있다는 것임
자원 의식은 존재하지만 안타깝게도 매우 불균등하게 퍼져 있음 - 압축 해제 시간만의 문제가 아님. 전체를 다운로드한 뒤에야 압축을 풀 수 있지만, 브라우저는 서버에서 스트리밍되는 HTML을 즉시 압축 해제하고 렌더링할 수 있음
연결이 끊기면 전부 잃고, 다운로드된 부분만이라도 읽는 것도 불가능해짐
보통 연결에서는 차이가 의미 없고, 50KB가 중요할 만큼 아주 느리거나 불안정한 연결에서는 이 방식이 확실히 더 나빠짐. 재미있는 실험이지만 사이트에 적용하진 말았으면 함 - 캐시에 없다면 850K짜리
Symbols-2048-em%20Nerd%20Font%20Complete.woff2파일이 있어서, 그 차이를 거의 덮어버림 - 그 정도 크기 차이는 필요한 왕복 횟수에 영향을 줄 만큼 큼. 합리적인 현대적 초기 혼잡 윈도우 값이라면 대략 왕복 1회는 줄어야 함
2.5배 차이는 아니겠지만 0.001%도 아님 - TCP 수신 윈도우 하나보다 덜 아끼는 수준이면 지연 시간에는 차이가 없을 것임
손실이 있는 네트워크에서는 차이가 날 수도 있겠지만 확신은 못 하겠음
- 로드 시간만 최적화하고 모두의 데이터 속도를 가정한다면 맞는 말이지만, 웹사이트나 앱 작성자가 내 대신 속도/데이터 절충을 너무 쉽게 결정하지 않았으면 할 때가 많음
-
readPixels가 왜 지문 방지 대상이 아닌지 모르겠음. 거의 안 보이는 오타를 페이지 전체에 뿌리지는 않으니 내게는 괜찮음
gzip된 HTML에는 스타일과 페이지 상단 약 8KiB만 두고, 뷰포트 아래 콘텐츠만 WebP로 압축한다는 부분을 보니 글이 임의의 문장 뒤에서 갑자기 끊기고 빈 페이지가 이어진 이유가 설명됨
LibreWolf를 쓰고 있어서 WebGL이 꺼져 있고, WebGL이 필요한 랜덤 웹 게임은 Chromium으로 씀. WebGL을 켜면 글이 잘 동작했고, 솔직히 꽤 깔끔한 기법임- 모든 현대 웹 브라우저에서, 심지어 지문 보호가 켜진 상태에서도 동작하지 않고 구형 브라우저용 폴백도 없다면 깔끔하다고 보기 어려움
WWW는 평범한 HTML에서 시작해 점진적으로 향상되는 방식으로 보편적으로 접근 가능해야 함
- 모든 현대 웹 브라우저에서, 심지어 지문 보호가 켜진 상태에서도 동작하지 않고 구형 브라우저용 폴백도 없다면 깔끔하다고 보기 어려움
-
웹 브라우저에서 Brotli를 직접 사용하는 것도 가능하긴 하지만 당연히 제약이 있음
2022년 JS1024 제출작 [1]이 이 개념의 최초 시연이라고 생각하고, 임의 압축용 개념 증명 코드도 있음. 아쉽게도 원래 목적이던 크기 코딩에는 맞지 않았음
핵심 제약은 사실상 ASCII 문자로 제한되고, 명백한 이유로 렌더링 스택에 매우 민감하다는 점임. 지금은 Firefox에서 동작하지 않는 것처럼 보임[1] https://js1024.fun/demos/2022/18/readme
[2] https://gist.github.com/lifthrasiir/1c7f9c5a421ad39c1af19a9c...
- 이 접근을 이해하는 핵심은, 실제로 어떻게 우겨 넣었는지까지 파고들지 않아도 되는 다음 부분임
Brotli가 원래 설계된 WOFF2 글꼴 파일 형식을 쓰는 것만 가능하지만, 이를 활용하려면 전체 글꼴 파일을 만들어야 함
최근 브라우저는 신뢰할 수 없는 글꼴 파일을 시스템에 직접 넣는 것이 매우 위험하므로 보통 OpenType Sanitizer(OTS)로 글꼴을 정리하고, 그래서 OTS가 받아들일 만큼 정상적이면서도 원하는 바이트열을 내부에 담고 추출 가능한 WOFF2 파일을 만들어야 함
많은 실패 끝에 거의 제약 없이 2바이트 부호 있는 정수열로 인코딩되는 글리프 폭, 즉 advance에 정착했다는 아이디어가 환상적임 - 정정하자면 Firefox에서도 아직 동작함. 단지 Firefox에서는 확대/축소 비율이 정확히 100% 여야 한다는 걸 잊고 있었음
- 이 기법은 정말 놀랍고 내 글보다 훨씬 멋짐. 찬사를 보냄
- 이 접근을 이해하는 핵심은, 실제로 어떻게 우겨 넣었는지까지 파고들지 않아도 되는 다음 부분임
-
Chromium 쪽이 오랫동안 막고 있었지만, zstd도 이제 웹에 들어오고 있음. 마침내 Chrome에 들어갔으니 이제 Safari만 따라오면 됨
- 전부 Zstandard로 가고 싶지만, 이 특정 경우에는 내가 알기로 압축 해제기 메모리 사용량이 같을 때 Brotli와 Zstandard가 거의 비슷함
- 적어도 할 일 목록에는 올라와 있는 듯함: https://webkit.org/standards-positions/#position-168
-
Batch Compress(https://batchcompress.com/en)를 만들고 있고, 최근 WebP 지원을 추가한 뒤 얼마 지나지 않아 기본값으로 바꿈
내가 알기로는 이미 웹 압축 도구 중 가장 작은 JPEG를 만들고 있었는데, WebP는 JPEG의 약 50% 크기밖에 안 나왔음. 지원 추가 후 곧 기본값으로 바꾸는 건 쉬운 결정이었음
사이트 사용자가 꽤 많아서 WebP를 기본값으로 바꾼 뒤 불만이 좀 있을 줄 알았지만, 한 달쯤 지난 지금 WebP 관련 문의나 불만은 하나뿐이었음
이제 거의 모든 도구와 브라우저가 WebP를 지원하는 듯함. 최근 WebP 이미지 업로드를 제대로 처리하지 못해 다음 단계가 막힌 웹사이트를 하나 봤을 뿐, 요즘은 거의 다 잘 지원함- WebP가 JPEG 대비 15~20% 이상 파일 크기를 줄여준다면, 그 절감은 압축 개선이 아니라 품질 저하에서 나온 것임
JPEG를 잘 압축하고 최적화하면 WebP에 크게 뒤처지지 않아야 함
JPEG보다 거의 같아 보이는 WebP를 만들어 언제든 파일 크기를 줄일 수 있지만, 거의 같아 보이는 JPEG로 다시 압축해도 마찬가지임
모든 손실 압축 코덱의 특성이고, 품질이 올라갈수록 파일 크기가 지수적으로 커지기 때문에, 사람들은 거의 보이지 않는 아주 작은 품질 저하만으로도 파일 크기가 크게 달라지는 데 늘 놀람 - WebP가 JPEG의 약 50% 크기라는 건 어떤 품질 비교 지표 기준인지 궁금함
WebP는 어두운 영역의 디테일을 망가뜨리는 끔찍한 기본값으로 악명이 있었음
- WebP가 JPEG 대비 15~20% 이상 파일 크기를 줄여준다면, 그 절감은 압축 개선이 아니라 품질 저하에서 나온 것임
-
소스를 살펴보다가 doctype 선언에 공백이 빠져 있는 걸 봄. 현재 형태는 잘못됐고 공백이 들어가야 함
-
DOCTYPE공백 생략은 더 짧게 만들 수 있음
엄밀히 말하면 유효한 HTML은 아니지만 그래도 표준 모드를 성공적으로 트리거함
참고: https://GitHub.com/kangax/html-minifier/pull/970 / https://HTML.spec.WHATWG.org/multipage/parsing.html#parse-er...
나도 https://FreeSolitaire.win에서 그 트릭을 씀 -
최대한 많이 제거하는 minifier에서 나온 결과 같음 [0]
0: https://github.com/KTibow/KTibow/issues/3#issuecomment-23367...
-
-
이 트릭을 예전에 써본 적이 있음. 이상하게도 무엇에 썼는지는 기억나지 않지만, 아마 가능 여부를 보려고 했던 듯하고 여기에도 코멘트를 남겼음: https://gist.github.com/gasman/2560551?permalink_comment_id=...
오래전 프로토타입도 찾았는데, 그냥 테스트였던 것 같음: https://retr0.id/stuff/bee_movie.webp.html- 그 페이지가 내 마우스 제스처 확장을 깨뜨림
이제는 애드온이 아니라 확장이라고 부르는 스크립트 주입 같은 것들이지만, 어쨌든 처음에는 “쓰레기”를 전달하고 뒤에 JS를 붙여 페이지로 되돌리는 접근이 흥미로움
내 안의 보안 덕후는 댓글 폼 같은 사용자 제공 데이터가 있을 때 공격이 가능해질지 궁금해짐
누군가 댓글에 넣을 바이트열을 찾아내고, 압축 후 내 스크립트보다 앞에 위치해 실행되는script태그로 바뀌게 만들 수도 있지 않을까 싶음 - 내 경험상 WebP는 이 기법이 실제로 유용한 일반 사례, 즉 10KB 미만 데이터에는 잘 맞지 않았음
WebP 무손실이 PNG에 더하는 대부분은 인코딩이 아니라 모델링에 관한 것이고, 이런 텍스트 압축은 WebP의 인코딩 부분만 쓰게 되기 때문임
- 그 페이지가 내 마우스 제스처 확장을 깨뜨림
-
Google Fonts를 빼도 페이지 로드 시간이 조금 개선될 것임. 원격 서버에서 로드되고 추가 핸드셰이크가 필요하기 때문임
- 하지만 충분히 많은 다른 사이트도 그 글꼴을 쓰고 있다면 이미 로컬에 있을 수도 있음
-
이 페이지는 적어도 Sailfish OS 브라우저에서 깨짐. 다음 문단 뒤에 긴 빈 공간이 있음
“Alright, so we’re dealing with 92 KiB for gzip vs 37 + 71 KiB for Brotli. Umm…”
그렇다 해도 gzip과 Brotli HTML 압축의 오버헤드는 요즘 웹사이트가 쓰는 JS, 이미지, 비디오 양에 비하면 아무것도 아님- Orion, Safari, LibreWolf에서도 같음. 이거 Chrome 전용 페이지인가?
- Mull에서도 같음
-
개인적으로 이 형식을 별로 좋아하지 않음. 이미지를 저장했는데 WebP로 저장되면, 웹 브라우저 말고는 지원하는 곳이 없어서 편집하거나 의미 있게 쓰기 전에 변환해야 함
그냥 추가 단계를 강요하는 느낌임- 아이러니하게도 Google 제품인 Slides조차 WebP 이미지를 지원하지 않음
그래도 지원이 더 늘어난다면 괜찮을 듯함. 20년에 한 번 새 형식이 생기는 정도는 참을 수 있음
.webm은 사라져도 됨 - 변환은 2초면 됨. macOS에서는 말 그대로 오른쪽 클릭 메뉴에 있고, 크기도 더 작으니 딱히 문제는 아님
- 아이러니하게도 Google 제품인 Slides조차 WebP 이미지를 지원하지 않음