科学・技術
後退的なJPEG
Regressive JPEGs (maurycyz.com)
要約
この記事では、JPEGファイルが持つ「低周波成分を先に保存する」という特徴を利用した、画像表示のトリックについて解説しています。この機能は、部分的にダウンロードされた画像が低解像度で表示されるようにするものですが、複数のスキャンを巧妙に組み合わせることで、ファイルが読み込まれるたびに異なる画像が表示されるように「後退的(Regressive)」なJPEGを作成できることを示しています。この技術は、主にデモンストレーションやいたずら目的で利用され、実用的な応用は限定的です。
全文翻訳
後退的なJPEG:2026-07-17
JPEGファイルのクールな機能の一つに、低周波成分を先に保存するオプションがあることがあります。
これは、部分的にダウンロードされた画像が途中で切れるのではなく、低解像度で表示されることを意味します。
ファイル内では、これは圧縮データを複数の「スキャン」に分割することで機能し、各スキャンはヘッダーで区切られます。
以下は、代表的な画像の最初(ファースト)のスキャンです。
FF DA - 「スキャン開始」マーカー
00 0C - ビッグエンディアン長フィールド(12バイト)(自身を含む)
03 - スキャン内のチャンネル数(3)
01 - 最初に含まれるチャンネルのグローバルID
00 - ハフマンテーブルインデックス #1(DC: 0、AC: 0)
02 - 2番目に含まれるチャンネルのグローバルID
10 - ハフマンテーブルインデックス #2(DC: 1、AC: 0)
03 - 3番目に含まれるチャンネルのグローバルID
10 - ハフマンテーブルインデックス #2(DC: 0、AC: 0)
00 - 開始DCTビン(DC)
00 - 終了DCTビン(DCでもある)
01 - 精度:ハーフ、既存データなし。
fbab 512d d3f1 cd96 - ハフマン符号化DCT係数
bcb0 58df 53d5 5d97 [...その他多数]
...これは、3つのカラーチャンネルすべてについて、最も低い(DC)フーリエビンを含んでいます。
3つのカラーチャンネルは、通常のRGBではなくYCbCrです。
輝度(Y)は、高品質である必要があるため分離されていますが、色はかなりごまかしても問題なく見えるでしょう。
非常に大まかに言うと:Y = G、Cb = B - G、Cr = R - G
その後、ファイルには残りのデータを埋めるために8つの追加スキャンが含まれています。
スキャン番号 | チャンネル | DCTビン範囲 | 精度
0 | Y Cb Cr | 0 - 0 | ハーフ(-1ビット)
1 | Y | 1 - 5 | クォーター(-2ビット)
2 | Cb | 1 - 63 | ハーフ
3 | Cr | 1 - 63 | ハーフ
4 | Y | 6 - 63 | クォーター
5 | Y | 1 - 63 | ハーフ
6 | Y Cr Cb | 0 - 0 | フル
7 | Cr | 1 - 63 | フル
8 | Cb | 1 - 63 | フル
9 | Y | 1 - 63 | フル
スキャン #0には、画像の非常に低解像度のプレビューが含まれています。
スキャン #1は輝度にいくつかの詳細を追加します。
スキャン番号2から5には、完全な低精度データが含まれています。
スキャン4は、#1によって残されたギャップを埋めるため、異常なスペクトル範囲を持っています。
そのため、番号5は、構築するための完全なクォーター精度データを持っています。
スキャン6から9は、画像をフルクオリティにするための最後の欠けているビットを追加します。
色情報が重要ではないと言ったことを考えると、私の例でカラーデータが最初に来るのは奇妙に思えるかもしれません。
これは、色差信号(クロミナンス)がハーフ解像度(クォーターピクセル数)で保存されているため機能します。
その結果、完全な色差信号データ(Cr + Cb)は、輝度の半分しか重さがありません。
各スキャンは明示的にスペクトル範囲を設定するため、後続のスキャンが既にレンダリングされた画像データを上書きするJPEGファイルを作成できるはずです。
実際、これを実行するのは非常に簡単です。
同じ解像度の複数の画像を連結し、画像開始、フレーム開始、画像終了マーカーを除外します。
これはヘックスエディタで行うことができますが、私はクイックアンドダーティなCプログラムを使用しました。
遅いネットワークで提供されると、この連結されたファイルは複数の画像の間で切り替わります。
クリックして新規タブで開く
しかし、ほとんどのデコーダーは、ある数のスキャン後にギブアップします。
これはジップ爆弾スタイルの問題を回避するために行われているのだと思いますが、これは9フレーム以上にわたってこれを機能させることを妨げます。これは適切なアニメーションには十分ではありません。
そうするためには、各フレームのスキャン数を最小限にする必要があります。
最も簡単なアイデアは、単一スキャンのみを持つベースラインJPEGから始めることです。
...しかし、それは機能しません。
プログレッシブモードでは、スキャンは同時にAC(ビン0より上)とDC(ビン0)のデータを含むことはできません。
この制限はベースラインモードには存在しませんが、ベースラインデコーダーは最初(ファースト)のスキャンの後に停止します。
ACデータはDCデータの後に続く必要があるため、可能な限り小さい「プログレッシブ」JPEGにはDCのみのスキャンが1つ含まれます。
DCTは16x16ブロックで実行されるため、そのような画像は単色にはなりません。元の解像度の1/16になります。
スキャン番号 | チャンネル | DCTビン | 精度
0 | Y Cb Cr | 0 - 0 | フル
これを実行することで、Chromeは約90フレームをレンダリングしてからギブアップさせることができます。
Firefoxのような他のブラウザはより忍耐力がありますが、90スキャン画像はほとんどどこでも機能するようです。
ボーナスとして、これは単純な試みで発生したゴースト(残像)を回避します。
これは、ACスキャンが古いデータを洗練することを目的としているため発生しました。
通常、これにより画像はファイルサイズを増やさずに複数の精度レベルを含めることができますが、私のトリックとはうまく連携しません。
ファイルにDCのみのスキャンが含まれ、実際のプログレッションがない場合、これは問題ではありません。
「DCのみ」のフレームは標準規格に準拠した画像であるため、それを作成するために特別なことは何も必要ありません。
cat > frame.scans<<EOF
# DCのみのスキャン:0,1,2:0-0,0,0;
# それ以外は何もない
EOF
jpegtran -scans frame.scans -outfile out.jpg in.jpg
これらを使用すると、単一の画像内にビデオ全体をパックすることが可能です。
クリックして新規タブで開く
非従来的なリックロールやその他のいたずらを除けば、これには実用的な応用はありません。
タイミング情報(timing information)を追加する方法がないため、再生は完全にネットワーク遅延に依存します。
...ただし、部分的なレンダリングを使用して楽しむことはたくさんあります。
これは<dialog>タグを使用した純粋なHTMLビデオです。
badapple.rose.systems
もちろん、データがハードコーディングされている必要はありません。
ここに、CSSやJavaScriptを一切使用しないインタラクティブなシングルページアプリケーションがあります。
関連:/projects/bad_jpeg/merge.c: これらの画像を生成するために使用されたコード