# 역진행(Regressive) JPEG

> Clean Markdown view of GeekNews topic #31565. Use the original source for factual precision when an external source URL is present.

## Metadata

- GeekNews HTML: [https://news.hada.io/topic?id=31565](https://news.hada.io/topic?id=31565)
- GeekNews Markdown: [https://news.hada.io/topic/31565.md](https://news.hada.io/topic/31565.md)
- Type: GN+
- Author: [xguru](https://news.hada.io/@xguru)
- Published: 2026-07-19T07:57:41+09:00
- Updated: 2026-07-19T07:57:41+09:00
- Original source: [maurycyz.com](https://maurycyz.com/projects/bad_jpeg/)
- Points: 1
- Comments: 1

## Topic Body

- 점진적 JPEG의 **다중 스캔 구조**를 변형하면 다운로드가 진행될수록 화질이 좋아지는 대신, 이미 표시된 이미지가 다른 이미지로 계속 바뀌게 할 수 있음
- 각 스캔은 색상 채널·DCT 주파수 범위·정밀도를 지정하며, 같은 해상도의 JPEG에서 일부 마커를 제거해 이어 붙이면 **기존 렌더링 데이터를 덮어쓸 수 있음**
- 디코더는 압축 폭탄과 유사한 문제를 막기 위해 처리할 스캔 수를 제한하며, Chrome은 **약 90프레임**까지 렌더링하고 Firefox 등은 더 많이 처리함
- 프레임마다 DC 전용 스캔 하나만 사용하면 프레임 수를 늘리면서 잔상을 피할 수 있지만, DCT 블록 특성상 결과 해상도는 원본의 **1/16**로 낮아짐
- 단일 JPEG에 영상처럼 여러 프레임을 담을 수 있으나 타이밍 정보가 없어 재생 속도가 네트워크 지연에 좌우되며, 실용적 영상보다는 **부분 렌더링을 활용한 HTML·단일 페이지 앱 실험**에 적합함

---

### 점진적 JPEG가 이미지를 표시하는 방식
- JPEG는 **저주파 성분**을 먼저 저장해 파일을 일부만 다운로드한 상태에서도 잘린 이미지 대신 저해상도 미리보기를 표시할 수 있음
- 압축 데이터는 여러 **스캔(scan)** 으로 나뉘며 각 스캔 앞에는 헤더가 붙음
  - `FF DA`는 스캔 시작 마커임
  - 이어서 길이 필드, 포함된 채널 수와 ID, Huffman 테이블 인덱스가 들어감
  - 시작·종료 DCT 빈과 정밀도를 지정한 뒤 Huffman 부호화된 DCT 계수를 저장함
- 첫 스캔에는 세 색상 채널에서 가장 낮은 **DC 주파수 빈**이 포함됨

### YCbCr와 단계별 품질 개선
- JPEG의 세 색상 채널은 일반적인 RGB 대신 **YCbCr**를 사용함
  - `Y`는 휘도이므로 높은 품질이 필요함
  - `Cb`, `Cr`은 색차 성분이어서 품질을 낮춰도 시각적 영향이 크지 않음
  - 매우 거칠게 나타내면 `Y = G`, `Cb = B - G`, `Cr = R - G`임
- 예제 JPEG는 스캔 0부터 9까지 데이터를 단계적으로 채움
  - 스캔 0: `Y Cb Cr`, DCT 빈 `0–0`을 절반 정밀도로 저장해 매우 낮은 해상도의 미리보기를 제공함
  - 스캔 1: `Y`, 빈 `1–5`를 1/4 정밀도로 추가해 휘도 세부 정보를 보강함
  - 스캔 2·3: `Cb`, `Cr`의 빈 `1–63`을 절반 정밀도로 추가함
  - 스캔 4: `Y`의 빈 `6–63`을 1/4 정밀도로 채워 스캔 1이 남긴 범위를 보완함
  - 스캔 5: `Y`의 빈 `1–63`을 절반 정밀도로 개선함
  - 스캔 6–9: 각 채널에 마지막 비트를 추가해 완전한 품질로 만듦
- 색차 데이터가 휘도보다 먼저 완성되지만, 색차는 **절반 해상도**, 즉 1/4 픽셀 수로 저장되므로 `Cr + Cb` 전체 용량도 휘도의 절반에 불과함

### 다운로드 중 이미지 바꾸기
- 각 스캔이 적용할 주파수 범위를 명시하므로 이후 스캔으로 이미 렌더링된 이미지 데이터를 **덮어쓰는 JPEG**를 만들 수 있음
- 같은 해상도의 이미지 여러 장을 이어 붙이되 다음 마커를 걸러내는 방식으로 구현함
  - 이미지 시작(start-of-image)
  - 프레임 시작(start-of-frame)
  - 이미지 종료(end-of-image)
- 16진수 편집기로도 만들 수 있지만 실제 생성에는 간단한 **C 프로그램**을 사용함
- 파일을 느린 네트워크로 전송하면 다운로드가 진행되는 동안 여러 이미지가 차례로 전환됨

### 디코더의 스캔 제한
- 대부분의 JPEG 디코더는 일정 개수의 스캔을 처리하면 중단함
- 압축 폭탄과 유사한 문제를 피하기 위한 제한으로 추정되며, 단순 결합 방식으로는 **9프레임 이상** 구현하기 어려움
- 더 긴 애니메이션을 만들려면 프레임마다 필요한 스캔 수를 최소화해야 함

### Baseline JPEG를 사용할 수 없는 이유
- 스캔 하나만 사용하는 Baseline JPEG에서 시작하는 방식은 작동하지 않음
- 점진적 모드에서는 한 스캔에 **DC 데이터**인 빈 0과 **AC 데이터**인 빈 1 이상을 함께 넣을 수 없음
- Baseline 모드에는 이 제한이 없지만, Baseline 디코더가 첫 스캔 뒤 처리를 중단함
- AC 데이터는 DC 데이터 뒤에 와야 하므로 가장 작은 점진적 JPEG 프레임은 DC 전용 스캔 하나로 구성됨

### DC 전용 프레임의 구성과 해상도
- DCT가 **16×16 블록** 단위로 처리되므로 DC 데이터만 포함해도 단색이 아니라 원본의 1/16 해상도 이미지를 만들 수 있음
- 최소 프레임은 `Y Cb Cr` 세 채널의 DCT 빈 `0–0`을 완전한 정밀도로 저장하는 스캔 하나만 사용함
- 이 구성에서는 Chrome이 처리를 중단하기 전까지 약 **90프레임**을 렌더링함
  - Firefox 같은 다른 브라우저는 더 많은 스캔을 처리함
  - 90개 스캔으로 구성된 이미지는 거의 모든 브라우저에서 작동함

### 잔상 없는 프레임 전환
- 단순 결합 방식의 잔상은 AC 스캔이 기존 데이터를 **정밀화**하도록 설계된 데서 발생함
- 일반적인 점진적 JPEG에서는 파일 크기를 크게 늘리지 않고 여러 정밀도 단계를 담을 수 있지만, 이미지를 교체하는 방식에는 맞지 않음
- 실제 점진적 개선 없이 DC 전용 스캔만 사용하면 이전 AC 데이터의 정밀화가 일어나지 않아 잔상을 피할 수 있음
- DC 전용 프레임은 **표준을 준수하는 JPEG 이미지**여서 특수한 인코더가 필요하지 않음
  - 스캔 명세에는 `0,1,2:0-0,0,0;`처럼 세 채널의 DC 빈만 지정함

### 단일 JPEG 영상의 한계
- DC 전용 프레임을 이어 붙이면 JPEG 파일 하나에 **전체 영상**을 담을 수 있음
- JPEG 스캔에는 프레임 타이밍을 추가할 방법이 없어 재생 속도가 전적으로 네트워크 지연에 좌우됨
- 비전통적인 Rickroll이나 장난 외에는 실용적인 응용 분야가 없음

### 부분 렌더링을 확장한 실험
- 부분 렌더링을 활용하면 JPEG 프레임 교체 외의 실험도 가능함
- [`&lt;dialog&gt;` 태그만 사용한 순수 HTML 영상](http://badapple.rose.systems/)으로 Bad Apple 영상을 구현함
- 데이터를 파일에 하드코딩할 필요 없이 CSS나 JavaScript가 없는 [**대화형 단일 페이지 애플리케이션**](https://maurycyz.com/fun/chat)도 구현함
- 이미지 생성에는 `merge.c` C 코드를 사용함

## Comments



### Comment 62013

- Author: neo
- Created: 2026-07-19T07:57:42+09:00
- Points: 1

###### [Hacker News 의견들](https://news.ycombinator.com/item?id=48954851) 
- **Adam7 인터레이스 PNG**로 매우 비슷한 작업을 해봤음: [https://www.da.vidbuchanan.co.uk/adamation/image.png](<https://www.da.vidbuchanan.co.uk/adamation/image.png>)  
  재생이 결국 네트워크 지연에 좌우되긴 하지만, 서버가 각 프레임을 일정한 간격으로 따로 보내도록 구성했음. 프레임이 작아서 네트워크가 유난히 느리지 않다면 서버가 재생 시점을 결정함
  - HTTP 서버를 제어할 수 있다면 응답 헤더에 **`Refresh`** 를 설정해 클라이언트가 새 애니메이션 프레임을 주기적으로 가져오게 할 수도 있음[1]. 2013년 IOCCC 출품작도 이 기법으로 PNG로 렌더링한 시계를 계속 갱신함[2]  
    [1] [https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/...](<https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Refresh>)  
    [2] [https://www.ioccc.org/2013/mills/index.html](<https://www.ioccc.org/2013/mills/index.html>)  
    다음 줄에는 `Refresh` 헤더가 거꾸로 인코딩돼 있음: [https://github.com/ioccc-src/winner/blob/619f554bbdb19e5003a...](<https://github.com/ioccc-src/winner/blob/619f554bbdb19e5003aac5cbb87ea4d53dcee92f/2013/mills/mills.c#L30>)
  - **무한 GIF**로 실시간 영상을 스트리밍하는 비슷한 작업도 해봤음: [https://github.com/jbochi/gifstreaming#live-video-streaming-...](<https://github.com/jbochi/gifstreaming#live-video-streaming-using-animated-gifs>)
  - PNG를 애니메이션으로 만들려고 편법을 쓰는 동안, **APNG**는 옆에서 “나도 존재해!”라고 외치는 듯함
  - 20년 전 직장의 지도 애플리케이션에 실시간 대기질 레이어를 추가하려고 **애니메이션 GIF**로 같은 작업을 했음. 이미지는 Java2D로 렌더링했고, 점군 대신 그라데이션 덩어리를 만들려고 JOGL 버전도 개발했지만 서버 관리자가 OpenGL 드라이버 설치를 허용하지 않아 완성하지 못함

- 저주받은 기법이지만 확실히 여기에 어울림
  - 대형 기술 기업들은 이걸 **브라우저 지문 채취**에 어떻게 활용할지 고민할 듯함

- 이 기법으로 데이터를 눈앞에 숨기는 **스테가노그래피**가 가능한지 궁금함. 자동 이미지 분석 프로그램 대부분은 마지막 이미지만 검사할 가능성이 높아, 학생들이 학교의 콘텐츠 필터를 우회하는 데 쓸 수도 있어 보임
  - 사람에게 보낼 메시지는 첫 프레임에, AI에게 보낼 메시지는 마지막 프레임에 넣는 **AI 교란 기법**으로 쓸 수 있음. 고양이 사진을 서로 보내 Skynet을 물리치는 셈임
  - 일반적인 스테가노그래피보다 나을 방법은 없어 보임

- 같은 네트워크에서 병렬로 불러오는 작업의 **진행 표시줄**로 활용해 사용자가 남은 지연 시간을 가늠하게 할 수 있지 않을까 싶음
  - 4chan 같은 곳에서는 불쾌하거나 불법적인 이미지를 숨기는 데 악용돼 이런 동작을 완전히 차단한 것으로 기억함

- 웹 서버가 즉석에서 “JPEG”를 생성해 **시간 간격을 둔 조각**으로 클라이언트에 보내면 재생 시점을 어느 정도 제어할 수 있음. 웹캠을 소스로 삼으면 끝없이 이어지는 “JPEG”도 가능함
  - 이미 일부 웹캠이 **`multipart/x-mixed-replace`** 라는 MIME 기법으로 이를 구현함. 서버가 방금 보낸 데이터를 새 데이터로 교체하라고 클라이언트에 지시하는 방식이며, JavaScript 없이 일반 HTTP와 JPEG만으로 작동함
  - 많은 IP 카메라가 이미 [https://en.wikipedia.org/wiki/Motion_JPEG](<https://en.wikipedia.org/wiki/Motion_JPEG>)으로 이를 구현함
  - GIF로도 가능함. 한 번은 GIF를 프레임 단위로 보내고 이미지 맵으로 클릭을 처리해, JavaScript 없이 **원격 데스크톱**처럼 쓸 수 있는 장난감 CGI를 만들었음. JPEG가 더 나았을 수도 있어 다시 시도해볼 만함

- **Service Worker**로 느린 연결을 흉내 내면 재생 속도를 제어할 수 있음

- 환경에 따라 동작이 다른 점이 이상함. 데스크톱 Firefox에서는 제대로 재생되지만 iOS 모바일에서 “JPEG 안의 전체 영상”은 거의 단색인 갈색→주황색→빨간색 프레임 3개와 흐릿한 고양이 윤곽만 보임  
  색이 프레임마다 바뀌어 작동 중인 건 알 수 있지만 영상이라 부르기는 어려움. 데스크톱에서는 실제 영상처럼 재생돼 놀랐고, **iOS 이미지 디코더의 특이한 경계 조건**을 건드리는 듯함

- 최근 OpenGL과 jpeg-turbo로 이미지를 빠르게 표시하는 작업을 했는데, JPEG의 **프로그레시브 모드**를 켜면 디코딩이 크게 느려졌음. 프로그레시브 JPEG가 있으면 좋다는 옛 조언은 이제 유효하지 않을 가능성이 큼  
  수십 년간 이미지가 단계적으로 선명해지는 모습을 거의 보지 못했으므로 실질적인 가치도 없어 보임
  - 페이지가 로드된 직후 이미지가 약간 흐렸다가 다음 단계에서 선명해지는 모습을 놓쳤을 수도 있음. 예전처럼 첫 단계가 알아보기 어려울 정도는 아니지만, 전체 파일 크기의 약 **30%만으로 저해상도 미리보기**를 거의 공짜로 제공하는 가치는 여전히 있음
  - 인코더로 **cjpegli**를 쓰는데, 기본값인 프로그레시브와 전체 4:4:4 조합에서 압축률이 가장 좋으므로 단순한 부가 기능만은 아님
  - 프로그레시브로 저장한 JPEG 사진은 보통 **약 5% 더 작고**, JPEG를 프로그레시브 형식으로 무손실 변환하는 것도 가능함. JPEG XL로 무손실 변환하면 공간을 더 절약할 수 있음
  - 프로그레시브 디코딩은 디코딩 자체를 빠르게 하려는 게 아니라, 특히 느린 모바일 연결에서 **큰 이미지가 더 빨리 표시되게** 하려는 기능임  
    예시: [https://youtube.com/watch?v=UphN1_7nP8U](<https://youtube.com/watch?v=UphN1_7nP8U>)

- 첫 이미지의 “잘못된” 계수에서 고주파 계수를 어렵게 계산하는 방법부터 떠올렸는데, 그냥 두 이미지를 합치는 방식이 영리함. 한 이미지의 **저주파 성분** 뒤에 다른 이미지의 **고주파 성분**을 이어 붙이는 것만으로 놀랄 만큼 잘 작동함

- 카메라로도 **저주파 성분**을 직접 획득할 수 있는지 궁금함. k-공간에서 저주파를 먼저 획득하는 MRI 방식도 떠오름
