逆進行(Regressive)JPEG
(maurycyz.com)- プログレッシブ JPEG のマルチスキャン構造を変形すると、ダウンロードが進むほど画質が向上する代わりに、すでに表示された画像を別の画像へ継続的に変化させられる
- 各スキャンは色チャンネル、DCT 周波数範囲、精度を指定し、同じ解像度の JPEG から一部のマーカーを取り除いて連結すると、既存のレンダリングデータを上書きできる
- デコーダーは圧縮爆弾に似た問題を防ぐため処理するスキャン数を制限しており、Chrome は約 90 フレームまでレンダリングし、Firefox などはさらに多く処理する
- フレームごとに DC 専用スキャンを 1 つだけ使えば、フレーム数を増やしつつ残像を避けられるが、DCT ブロックの特性上、結果の解像度は元画像の1/16に低下する
- 単一の JPEG に動画のように複数フレームを格納できるが、タイミング情報がないため再生速度はネットワーク遅延に左右され、実用的な動画よりも部分レンダリングを活用した HTML・シングルページアプリの実験に適している
プログレッシブ JPEG が画像を表示する仕組み
- JPEG は低周波成分を先に保存することで、ファイルを一部だけダウンロードした状態でも、切り欠けた画像ではなく低解像度のプレビューを表示できる
- 圧縮データは複数の**スキャン(scan)**に分けられ、各スキャンの前にはヘッダーが付く
FF DAはスキャン開始マーカーである- 続いて長さフィールド、含まれるチャンネル数と ID、Huffman テーブルのインデックスが入る
- 開始・終了 DCT ビンと精度を指定したあと、Huffman 符号化された DCT 係数を保存する
- 最初のスキャンには、3 つの色チャンネルにおける最も低いDC 周波数ビンが含まれる
YCbCr と段階的な品質改善
- JPEG の 3 つの色チャンネルは、一般的な 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: 各チャンネルに最後のビットを追加し、完全な品質にする
- スキャン 0:
- 色差データは輝度より先に完成するが、色差は半分の解像度、つまりピクセル数で 1/4 として保存されるため、
Cr + Cb全体の容量も輝度の半分にすぎない
ダウンロード中に画像を変える
- 各スキャンは適用する周波数範囲を明示するため、後続のスキャンで既にレンダリングされた画像データを上書きする JPEGを作れる
- 同じ解像度の画像を複数連結し、次のマーカーを除去する方式で実装する
- 画像開始(start-of-image)
- フレーム開始(start-of-frame)
- 画像終了(end-of-image)
- 16 進エディタでも作成できるが、実際の生成には簡単な C プログラムを使用する
- ファイルを遅いネットワークで転送すると、ダウンロードが進む間に複数の画像が順番に切り替わる
デコーダーのスキャン制限
- ほとんどの JPEG デコーダーは、一定数のスキャンを処理すると停止する
- 圧縮爆弾に似た問題を避けるための制限と推測され、単純な結合方式では9 フレーム以上を実現するのは難しい
- より長いアニメーションを作るには、フレームごとに必要なスキャン数を最小化する必要がある
Baseline JPEG を使えない理由
- スキャンを 1 つだけ使う Baseline JPEG から始める方式は動作しない
- プログレッシブモードでは、1 つのスキャンに DC データであるビン 0 と、AC データであるビン 1 以上を一緒に入れることができない
- Baseline モードにはこの制限がないが、Baseline デコーダーは最初のスキャンの後で処理を停止する
- AC データは DC データの後に来る必要があるため、最小のプログレッシブ JPEG フレームは DC 専用スキャン 1 つで構成される
DC 専用フレームの構成と解像度
- DCT は16×16 ブロック単位で処理されるため、DC データだけを含めても単色ではなく、元画像の 1/16 解像度の画像を作れる
- 最小フレームは、
Y Cb Cr3 チャンネルの DCT ビン0–0を完全な精度で保存するスキャンを 1 つだけ使う - この構成では、Chrome が処理を停止するまでに約90 フレームをレンダリングする
- Firefox のような他のブラウザは、より多くのスキャンを処理する
- 90 個のスキャンで構成された画像は、ほぼすべてのブラウザで動作する
残像のないフレーム切り替え
- 単純な結合方式で残像が出るのは、AC スキャンが既存データを精密化するよう設計されているためである
- 一般的なプログレッシブ JPEG では、ファイルサイズを大きく増やさずに複数の精度段階を含められるが、画像を置き換える方式には合わない
- 実際のプログレッシブ改善なしに DC 専用スキャンだけを使えば、以前の AC データの精密化が起こらないため、残像を避けられる
- DC 専用フレームは標準に準拠した JPEG 画像なので、特殊なエンコーダーは不要
- スキャン仕様では、
0,1,2:0-0,0,0;のように 3 チャンネルの DC ビンだけを指定する
- スキャン仕様では、
単一 JPEG 動画の限界
- DC 専用フレームを連結すれば、1 つの JPEG ファイルに動画全体を格納できる
- JPEG スキャンにはフレームタイミングを追加する方法がないため、再生速度は完全にネットワーク遅延に左右される
- 非伝統的な Rickroll やいたずら以外には、実用的な応用分野はない
部分レンダリングを拡張した実験
- 部分レンダリングを活用すれば、JPEG フレーム置換以外の実験も可能
<dialog>タグだけを使った純粋な HTML 動画として Bad Apple の動画を実装している- データをファイルにハードコードする必要なく、CSS や JavaScript を使わない対話型シングルページアプリケーションも実装している
- 画像生成には
merge.cの C コードを使用する
1件のコメント
Hacker News のコメント
Adam7 インターレース PNG でかなり似たことをやったことがある: https://www.da.vidbuchanan.co.uk/adamation/image.png
再生は結局ネットワーク遅延に左右されるが、サーバーが各フレームを一定間隔で別々に送るようにしていた。フレームが小さいので、ネットワークが極端に遅くなければ、サーバーが再生タイミングを決める
Refreshを設定して、クライアントが新しいアニメーションフレームを定期的に取りに来るようにもできる[1]。2013年の IOCCC 作品もこの手法で PNG で描画した時計を継続的に更新している[2][1] https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/...
[2] https://www.ioccc.org/2013/mills/index.html
次の行には
Refreshヘッダーが逆向きにエンコードされている: https://github.com/ioccc-src/winner/blob/619f554bbdb19e5003a...呪われた手法だが、たしかにここにはふさわしい
この手法でデータを目の前に隠す ステガノグラフィー が可能なのか気になる。自動画像解析プログラムの大半は最後の画像だけを見る可能性が高く、学生が学校のコンテンツフィルターを回避するのに使えそう
同じネットワークで並列に読み込む処理の 進捗バー として使って、ユーザーが残りの遅延時間を見積もれるようにできないだろうか
Web サーバーがその場で「JPEG」を生成し、時間を空けた断片 としてクライアントに送れば、再生タイミングをある程度制御できる。Web カメラをソースにすれば、終わりなく続く「JPEG」も可能
multipart/x-mixed-replaceという MIME の仕組みでこれを実現している。サーバーが、今送ったデータを新しいデータで置き換えるようクライアントに指示する方式で、JavaScript なしで通常の HTTP と JPEG だけで動くService Worker で遅い接続を擬似的に再現すれば、再生速度を制御できる
環境によって動作が違うのが奇妙。デスクトップ版 Firefox では正常に再生されるが、iOS モバイルでは「JPEG の中の動画全体」がほぼ単色の茶色→オレンジ→赤の 3 フレームと、ぼやけた猫の輪郭しか見えない
色がフレームごとに変わるので動いていることはわかるが、動画と呼ぶには厳しい。デスクトップでは本物の動画のように再生されて驚いたし、iOS の画像デコーダーの妙な境界条件 に触れているようだ
最近 OpenGL と jpeg-turbo で画像を高速表示する作業をしたが、JPEG の プログレッシブモード を有効にするとデコードが大幅に遅くなった。プログレッシブ JPEG があると良いという昔の助言は、もう有効ではない可能性が高い
何十年もかけて画像が段階的に鮮明になる様子をほとんど見ていないので、実用的な価値もなさそう
例: https://youtube.com/watch?v=UphN1_7nP8U
最初の画像の「間違った」係数から高周波係数を苦労して計算する方法をまず思いついたが、単に 2 枚の画像を結合するやり方が賢い。片方の画像の 低周波成分 の後ろに、もう片方の画像の 高周波成分 をつなぐだけで、驚くほどうまく動く
カメラでも 低周波成分 を直接取得できるのか気になる。k-space で低周波を先に取得する MRI の方式も思い出す