これまでのJPEGシリーズ
- 【調査中】JPEG画像を無劣化(ロスレス)で切り貼りしたい【ハフマン符号】 #画像処理 - Qiita
- JPEG画像を無劣化(ロスレス)で切り貼りしたい ~ハフマン符号の読み方の答え~ #画像処理 - Qiita
- JPEG画像の圧縮データにおける各コンポーネントのデータ数と配置の求め方 #画像処理 - Qiita
今回やること
- JPEG画像の圧縮データで用いられる「追加データ」と、それが表す数値の相互変換方法を確認する
- JPEG画像全体における、圧縮データ内の8×8のブロックのデータの並び順を確認する
サンプルの用意
今回は、PNG生成を用いて観察用の画像データを作成した。
これは、JavaScript のコードで色情報を指定し、PNG画像を生成するツールである。
縦縞
縦方向に縞が入った8×8の画像を用意した。
これにより、AC成分を大きくすることを狙った。
function color_calc(img_data,width,height) {
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
img_data[y][x].r = x % 2 * 255;
img_data[y][x].g = x % 2 * 255;
img_data[y][x].b = x % 2 * 255;
}
}
}
以下が、この画像を8倍に拡大したものである。
市松模様
1要素が8×8の白黒の市松模様の画像 (全体のサイズは16×16) を用意した。
これにより、DC成分を大きくすることを狙った。
function color_calc(img_data,width,height) {
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
img_data[y][x].r = ((x >> 3) + (y >> 3)) % 2 * 255;
img_data[y][x].g = ((x >> 3) + (y >> 3)) % 2 * 255;
img_data[y][x].b = ((x >> 3) + (y >> 3)) % 2 * 255;
}
}
}
以下が、この画像を8倍に拡大したものである。
階段
8×8のブロックごとに、だんだん明るくしていく画像 (全体のサイズは32×32) を用意した。
また、今回はカラー画像にするため、青成分を他の成分とは少しずらした。
これにより、ブロックのデータの並び順を確認することを狙った。
function color_calc(img_data,width,height) {
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
img_data[y][x].r = (((x >> 3) + (y >> 3) * 4) * 255) >> 4;
img_data[y][x].g = (((x >> 3) + (y >> 3) * 4) * 255) >> 4;
img_data[y][x].b = ((((x >> 3) + (y >> 3) * 4) * 255) >> 4) ^ 1;
}
}
}
以下が、この画像を8倍に拡大したものである。
圧縮データとそれが表す数値の相互変換
以前の記事でも紹介した資料
も参考にしながら、圧縮データとそれが表す数値の相互変換方法を見ていく。
JPEGsnoopで圧縮データを読み取る設定
JPEGsnoop で解析を行う際、圧縮データを読み取ってその結果を出力させるため、
Options → Scan Segment で以下の設定を行う。
- Full ICDT (AC+DC - slow) を選択する
- Detailed Decode... から
- # MCUs = を十分大きな値 (10000 など) に設定する
- Enable detailed Scan Decode? にチェックを入れる
品質100の場合
作成したサンプル画像を以下のように ImageMagick で JPEG に変換し、JPEGsnoop で解析した。
magick convert sima8x8.png -quality 100 sima8x8.jpg
magick convert itimatu16x16.png -quality 100 itimatu16x16.jpg
すると、以下の結果が得られた。
圧縮データの解析結果
Lum (Tbl #0), MCU=[0,0]
[0x000000A0.0]: ZRL=[ 0] Val=[ -4] Coef=[00= DC] Data=[0x 35 1E 13 55 = 0b (0011---- -------- -------- --------)]
[0x000000A0.4]: ZRL=[ 0] Val=[ -184] Coef=[01..01] Data=[0x 35 1E 13 55 = 0b (----0101 000111-- -------- --------)]
[0x000000A1.6]: ZRL=[ 4] Val=[ -217] Coef=[02..06] Data=[0x 1E 13 55 D6 = 0b (------10 00010011 0------- --------)]
[0x000000A3.1]: ZRL=[ 8] Val=[ -325] Coef=[07..15] Data=[0x 55 D6 18 CF = 0b (-1010101 11010--- -------- --------)]
Scan Data encountered marker 0xFFD9 @ 0x000000A7.0
[0x000000A4.5]: ZRL=[12] Val=[ -924] Coef=[16..28] Data=[0x D6 18 CF FF = 0b (-----110 00011000 11------ --------)]
[0x000000A6.2]: ZRL=[ 0] Val=[ 0] Coef=[29..29] Data=[0x CF FF D9 00 = 0b (--00---- -------- -------- --------)] EOB
DCT Matrix=[ -4 -184 0 -217 0 -325 0 -924]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
Lum (Tbl #0), MCU=[0,0]
[0x0000009D.0]: ZRL=[ 0] Val=[-1024] Coef=[00= DC] Data=[0x 3F F3 FC 20 = 0b (00111111 1111---- -------- --------)]
[0x0000009E.4]: ZRL=[ 0] Val=[ 0] Coef=[01..01] Data=[0x F3 FC 20 03 = 0b (----0--- -------- -------- --------)] EOB
DCT Matrix=[-1024 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
Lum (Tbl #0), MCU=[1,0]
[0x0000009E.5]: ZRL=[ 0] Val=[ 2040] Coef=[00= DC] Data=[0x F3 FC 20 03 = 0b (-----011 11111100 0------- --------)]
Scan Data encountered marker 0xFFD9 @ 0x000000A3.0
[0x000000A0.1]: ZRL=[ 0] Val=[ 0] Coef=[01..01] Data=[0x 20 03 BF FF = 0b (-0------ -------- -------- --------)] EOB
DCT Matrix=[ 2040 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
Lum (Tbl #0), MCU=[0,1]
[0x000000A0.2]: ZRL=[ 0] Val=[ 0] Coef=[00= DC] Data=[0x 20 03 BF FF = 0b (--10---- -------- -------- --------)] EOB
[0x000000A0.4]: ZRL=[ 0] Val=[ 0] Coef=[01..01] Data=[0x 20 03 BF FF = 0b (----0--- -------- -------- --------)] EOB
DCT Matrix=[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
Lum (Tbl #0), MCU=[1,1]
[0x000000A0.5]: ZRL=[ 0] Val=[-2040] Coef=[00= DC] Data=[0x 20 03 BF FF = 0b (-----000 00000011 1------- --------)]
[0x000000A2.1]: ZRL=[ 0] Val=[ 0] Coef=[01..01] Data=[0x BF FF D9 00 = 0b (-0------ -------- -------- --------)] EOB
DCT Matrix=[-2040 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
ここから、エンコードされた追加データの長さ・追加データ・それが表す数値を抜き出すと、以下のようになった。
| 追加データの長さ | 追加データ | 数値 |
|---|---|---|
| 3 | 011 |
-4 |
| 8 | 01000111 |
-184 |
| 8 | 00100110 |
-217 |
| 9 | 010111010 |
-325 |
| 10 | 0001100011 |
-924 |
| 11 | 01111111111 |
-1024 |
| 11 | 11111111000 |
2040 |
| 11 | 00000000111 |
-2040 |
追加データ (長さを含む) から数値を読み取るのは、以下のようにできる。
- 追加データの最上位ビットが
0の場合- 追加データの長さを超える上位に、整数型の幅いっぱいまで
1のビットを詰める- たとえば、
011を8ビットに詰めると11111011
- たとえば、
- それを2の補数として解釈し、1 を足す
-
11111011は -5 を表すので、1 を足して -4 と読み取れる
-
- 追加データの長さを超える上位に、整数型の幅いっぱいまで
- 追加データの最上位ビットが
1の場合- 追加データをそのまま数値として解釈する (長さを超える上位に
0のビットを詰める)- たとえば、
11111111000はそのまま 2040 を表す
- たとえば、
- 追加データをそのまま数値として解釈する (長さを超える上位に
- 追加データが無い (0ビットの) 場合
- それが表す数値は 0 である
数値から追加データ (長さを含む) に変換するのは、以下のようにできる。
- 正の値の場合
- 数値をリーディングゼロなしの2進数で表した際の桁数を、追加データの長さとする
- すなわち、最上位の
1のビットが下から何ビット目 (1-origin) か
- すなわち、最上位の
- 数値を追加データとしてそのまま格納する
- 数値をリーディングゼロなしの2進数で表した際の桁数を、追加データの長さとする
- 0 の場合
- 追加データ無し (長さ0) で表す
- AC 成分 (ブロックの最初以外) では、明示的に表さない (後述)
- 負の値の場合
- 数値から 1 を引いた値を、2の補数で表す
- 最上位の
0のビットが下から何ビット目 (1-origin) かを、追加データの長さとする - その長さの分だけ下位のビット列を取り出し、追加データとする
品質を下げた場合
これまでは、品質の設定を 100 として JPEG に変換しており、DQT (Quv) の値は全て 1 であった。
品質を下げると、ここで記録される値が大きくなる。
これが圧縮データの値の扱いに与える影響を見ていく。
magick convert sima8x8.png -quality 90 sima8x8_q90.jpg
これにより得られたファイル sima8x8_q90.jpg を TSXBIN で見ると、DQT は以下のようになっていた。
14 ★DQT[0] FF DB
16 SizeOfThis[0] 00 43
18 PqTq 00
19 Quv[0] 03 02 02 03 02 02 03 03 03 03 04 03 03 04 05 08
29 Quv[16] 05 05 04 04 05 0A 07 07 06 08 0C 0A 0C 0C 0B 0A
39 Quv[32] 0B 0B 0D 0E 12 10 0D 0E 11 0E 0B 0B 10 16 10 11
49 Quv[48] 13 14 15 15 15 0C 0F 17 18 16 14 18 12 14 15 14
JPEGsnoop で見ると、以下のようになっていた。
Precision=8 bits
Destination ID=0 (Luminance)
DQT, Row #0: 3 2 2 3 5 8 10 12
DQT, Row #1: 2 2 3 4 5 12 12 11
DQT, Row #2: 3 3 3 5 8 11 14 11
DQT, Row #3: 3 3 4 6 10 17 16 12
DQT, Row #4: 4 4 7 11 14 22 21 15
DQT, Row #5: 5 7 11 13 16 21 23 18
DQT, Row #6: 10 13 16 17 21 24 24 20
DQT, Row #7: 14 18 19 20 22 20 21 20
Approx quality factor = 90.06 (scaling=19.88 variance=1.14)
そして、JPEGsnoop で見た圧縮データは、以下のようになっていた。
Lum (Tbl #0), MCU=[0,0]
[0x000000A0.0]: ZRL=[ 0] Val=[ -1] Coef=[00= DC] Data=[0x 14 51 B5 5B = 0b (00------ -------- -------- --------)]
[0x000000A0.2]: ZRL=[ 0] Val=[ -93] Coef=[01..01] Data=[0x 14 51 B5 5B = 0b (--010100 010----- -------- --------)]
[0x000000A1.3]: ZRL=[ 4] Val=[ -73] Coef=[02..06] Data=[0x 51 B5 5B 31 = 0b (---10001 10110--- -------- --------)]
Scan Data encountered marker 0xFFD9 @ 0x000000A6.0
[0x000000A2.5]: ZRL=[ 8] Val=[ -41] Coef=[07..15] Data=[0x B5 5B 31 3F = 0b (-----101 010110-- -------- --------)]
[0x000000A3.6]: ZRL=[12] Val=[ -78] Coef=[16..28] Data=[0x 5B 31 3F FF = 0b (------11 00110001 -------- --------)]
[0x000000A5.0]: ZRL=[ 0] Val=[ 0] Coef=[29..29] Data=[0x 3F FF D9 00 = 0b (00------ -------- -------- --------)] EOB
DCT Matrix=[ -3 -186 0 -219 0 -328 0 -936]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
圧縮データに記録された値と DCT Matrix での値の関係は、以下のようになっている。
| 位置 | 圧縮データ | DCT Matrix | 関係 |
|---|---|---|---|
| 0 | -1 | -3 | 3倍 |
| 1 | -93 | -186 | 2倍 |
| 6 | -73 | -219 | 3倍 |
| 15 | -31 | -328 | 8倍 |
| 28 | -78 | -936 | 12倍 |
DCT Matrix での値は、圧縮データに記録された値に DQT に記録された対応する位置の値を掛けた値となっていることが読み取れる。
AC 成分の 0 の表現
DC 成分 (ブロック内の最初の要素) では、0 はそのまま追加データの長さ 0 ビットのデータ (を表す符号) で表される。
一方、AC 成分 (ブロック内の最初以外の要素) では、0 を原則としてそのまま表さず、0 以外の値を表す際に「その値の前に 0 が何個並んでいるか」で表す。
この「その値の前に 0 が何個並んでいるか」は、ハフマン符号テーブルに記録された値の上位 4 ビットで表す。
この原則の例外 (0 を表す符号を用いる場合) として、以下の場合がある。
- このブロックの、この要素とその先の要素が全て 0 である事を表す場合 (EOB)
- 0 が 16 個連続した場合 (ZRL)
EOB が現れた場合、そのブロックの圧縮データはそこで終了し、(次のブロックがあれば) その直後のビットから次のブロックのデータが始まる。
ZRL は「この値の前に 0 が 15 個並び、この値も 0」という表現なので、デコード結果に 0 を 16 個追加し、引き続き同じブロックで次の符号のデコードに移る。
JPEG ファイルをバイナリエディタで書き換えて ZRL を含むようにし、観察を行ってみた。
このファイルでは、DQT として 1~64 の値を順に並べ、要素の位置がわかりやすいようにした。
00000000 ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 00 |......JFIF......|
00000010 00 00 00 00 ff db 00 43 00 01 02 03 04 05 06 07 |.......C........|
00000020 08 09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 |................|
00000030 18 19 1a 1b 1c 1d 1e 1f 20 21 22 23 24 25 26 27 |........ !"#$%&'|
00000040 28 29 2a 2b 2c 2d 2e 2f 30 31 32 33 34 35 36 37 |()*+,-./01234567|
00000050 38 39 3a 3b 3c 3d 3e 3f 40 ff c0 00 0b 08 00 08 |89:;<=>?@.......|
00000060 00 08 01 01 11 00 ff c4 00 14 00 01 00 00 00 00 |................|
00000070 00 00 00 00 00 00 00 00 00 00 00 03 ff c4 00 18 |................|
00000080 10 00 02 03 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000090 00 00 f0 01 11 ca ff da 00 08 01 01 00 00 3f 00 |..............?.|
000000a0 39 65 b3 ff d9 |9e...|
このファイルの圧縮データを JPEGsnoop で見ると、以下のようになった。
Lum (Tbl #0), MCU=[0,0]
[0x000000A0.0]: ZRL=[ 0] Val=[ -4] Coef=[00= DC] Data=[0x 39 65 B3 FF = 0b (0011---- -------- -------- --------)]
[0x000000A0.4]: ZRL=[ 0] Val=[ 1] Coef=[01..01] Data=[0x 39 65 B3 FF = 0b (----1001 -------- -------- --------)]
[0x000000A1.0]: ZRL=[15] Val=[ 0] Coef=[02..17] Data=[0x 65 B3 FF D9 = 0b (01------ -------- -------- --------)]
[0x000000A1.2]: ZRL=[ 0] Val=[ 1] Coef=[18..18] Data=[0x 65 B3 FF D9 = 0b (--1001-- -------- -------- --------)]
[0x000000A1.6]: ZRL=[15] Val=[ 0] Coef=[19..34] Data=[0x 65 B3 FF D9 = 0b (------01 -------- -------- --------)]
[0x000000A2.0]: ZRL=[ 1] Val=[ 1] Coef=[35..36] Data=[0x B3 FF D9 00 = 0b (1011---- -------- -------- --------)]
[0x000000A2.4]: ZRL=[ 0] Val=[ 0] Coef=[37..37] Data=[0x B3 FF D9 00 = 0b (----00-- -------- -------- --------)] EOB
DCT Matrix=[ -4 2 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 19 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 0 0 0 0 0 0 0]
[ 0 37 0 0 0 0 0 0]
まず、DQT の値 2 に対応する位置に、AC 成分の最初の要素がある。
その後、ZRL を1個挟んで、前に並ぶ0の数が 0 の要素を配置した。
この要素の DCT Matrix での値は 19 となっており、前の要素から 17 個進んでいることがわかる。
これは、ZRL により 16 個進み、そのさらに次の要素であるためである。
また ZRL を1個挟んで、今度は前に並ぶ0の数が 1 の要素を配置した。
この要素の DCT Matrix での値は 37 となっており、前の要素から 18 個進んでいることがわかる。
これは、ZRL により 16 個進み、そのあともう1個 0 を挟み、さらに次の要素であるためである。
ブロックの並び順
「階段」のサンプル stair32x32.png を用いて、JPEG 画像の圧縮データ全体において8×8のブロックがどのような順で格納されるのかをみていく。
このサンプルでは、
- 上の行から下の行
- 同じ行の中では、左のブロックから右のブロック
の順で明るくなっていく。
まとまりのサイズが1×1のとき
以下のように、サンプル画像を JPEG に変換した。
magick convert stair32x32.png -quality 100 stair32x32.jpg
変換結果の JPEGsnoop での解析結果のうち、圧縮データの Lum の DC 成分を抜き出すと、以下のようになった。
Lum (Tbl #0), MCU=[0,0]
[0x0000011A.0]: ZRL=[ 0] Val=[-1024] Coef=[00= DC] Data=[0x CF FC 17 84 = 0b (11001111 111111-- -------- --------)]
Lum (Tbl #0), MCU=[1,0]
[0x0000011C.3]: ZRL=[ 0] Val=[ 120] Coef=[00= DC] Data=[0x 17 84 E1 00 = 0b (---10111 1000---- -------- --------)]
Lum (Tbl #0), MCU=[2,0]
[0x0000011E.6]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x E1 00 04 00 = 0b (------01 0000000- -------- --------)]
Lum (Tbl #0), MCU=[3,0]
[0x00000120.4]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 04 00 10 00 = 0b (----0100 00000--- -------- --------)]
Lum (Tbl #0), MCU=[0,1]
[0x00000122.2]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 10 00 40 01 = 0b (--010000 000----- -------- --------)]
Lum (Tbl #0), MCU=[1,1]
[0x00000124.0]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 40 01 00 04 = 0b (01000000 0------- -------- --------)]
Lum (Tbl #0), MCU=[2,1]
[0x00000125.6]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 01 00 04 00 = 0b (------01 0000000- -------- --------)]
Lum (Tbl #0), MCU=[3,1]
[0x00000127.4]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 04 00 10 00 = 0b (----0100 00000--- -------- --------)]
Lum (Tbl #0), MCU=[0,2]
[0x00000129.2]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 10 00 40 01 = 0b (--010000 000----- -------- --------)]
Lum (Tbl #0), MCU=[0,2]
[0x00000129.2]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 10 00 40 01 = 0b (--010000 000----- -------- --------)]
Lum (Tbl #0), MCU=[2,2]
[0x0000012C.6]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 01 00 04 00 = 0b (------01 0000000- -------- --------)]
Lum (Tbl #0), MCU=[3,2]
[0x0000012E.4]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 04 00 10 00 = 0b (----0100 00000--- -------- --------)]
Lum (Tbl #0), MCU=[0,3]
[0x00000130.2]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 10 00 40 01 = 0b (--010000 000----- -------- --------)]
Lum (Tbl #0), MCU=[1,3]
[0x00000132.0]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 40 01 00 04 = 0b (01000000 0------- -------- --------)]
Lum (Tbl #0), MCU=[2,3]
[0x00000133.6]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 01 00 04 00 = 0b (------01 0000000- -------- --------)]
Lum (Tbl #0), MCU=[3,3]
[0x00000135.4]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 04 00 3F FF = 0b (----0100 00000--- -------- --------)]
最初のブロックは絶対値が大きな負の値で、その後は 128 (2番目のみ 120) になっている。
2番目以降の DC 成分の値は前のブロックとの差分を表しているため、これはブロックの格納順が今回のサンプルで明るさが上がる順と同じ
- 上の行から下の行
- 同じ行の中では、左のブロックから右のブロック
であることを示している。
まとまりのサイズが2×2のとき
以下のように、サンプル画像を JPEG に変換した。
magick convert stair32x32.png -sampling-factor 2x2,1x1,1x1 -quality 100 stair32x32_2x2.jpg
変換結果の JPEGsnoop での解析結果のうち、圧縮データの Lum の DC 成分を抜き出すと、以下のようになった。
Lum (Tbl #0), MCU=[0,0]
[0x00000120.0]: ZRL=[ 0] Val=[-1024] Coef=[00= DC] Data=[0x E7 FE DE 16 = 0b (11100111 1111111- -------- --------)]
Lum (Tbl #0), MCU=[0,0]
[0x00000122.0]: ZRL=[ 0] Val=[ 120] Coef=[00= DC] Data=[0x DE 16 00 80 = 0b (11011110 00------ -------- --------)]
Lum (Tbl #0), MCU=[0,0]
[0x00000123.3]: ZRL=[ 0] Val=[ 384] Coef=[00= DC] Data=[0x 16 00 80 63 = 0b (---10110 000000-- -------- --------)]
Lum (Tbl #0), MCU=[0,0]
[0x00000124.7]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 00 80 63 AD = 0b (-------0 10000000 -------- --------)]
Lum (Tbl #0), MCU=[1,0]
[0x0000012B.1]: ZRL=[ 0] Val=[ -384] Coef=[00= DC] Data=[0x 47 F2 01 60 = 0b (-1000111 1111---- -------- --------)]
Lum (Tbl #0), MCU=[1,0]
[0x0000012C.5]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x F2 01 60 08 = 0b (-----010 000000-- -------- --------)]
Lum (Tbl #0), MCU=[1,0]
[0x0000012D.7]: ZRL=[ 0] Val=[ 384] Coef=[00= DC] Data=[0x 01 60 08 04 = 0b (-------1 01100000 00------ --------)]
Lum (Tbl #0), MCU=[1,0]
[0x0000012F.3]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 08 04 84 01 = 0b (---01000 0000---- -------- --------)]
Lum (Tbl #0), MCU=[0,1]
[0x00000131.4]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 84 01 00 B0 = 0b (----0100 00000--- -------- --------)]
Lum (Tbl #0), MCU=[0,1]
[0x00000132.6]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 01 00 B0 04 = 0b (------01 0000000- -------- --------)]
Lum (Tbl #0), MCU=[0,1]
[0x00000134.0]: ZRL=[ 0] Val=[ 384] Coef=[00= DC] Data=[0x B0 04 00 23 = 0b (10110000 000----- -------- --------)]
Lum (Tbl #0), MCU=[0,1]
[0x00000135.4]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 04 00 23 F9 = 0b (----0100 00000--- -------- --------)]
Lum (Tbl #0), MCU=[1,1]
[0x00000137.2]: ZRL=[ 0] Val=[ -384] Coef=[00= DC] Data=[0x 23 F9 00 B0 = 0b (--100011 11111--- -------- --------)]
Lum (Tbl #0), MCU=[1,1]
[0x00000138.6]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x F9 00 B0 04 = 0b (------01 0000000- -------- --------)]
Lum (Tbl #0), MCU=[1,1]
[0x0000013A.0]: ZRL=[ 0] Val=[ 384] Coef=[00= DC] Data=[0x B0 04 00 3F = 0b (10110000 000----- -------- --------)]
Lum (Tbl #0), MCU=[1,1]
[0x0000013B.4]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 04 00 3F FF = 0b (----0100 00000--- -------- --------)]
今回は、2×2ブロック (16×16ピクセル) のまとまりごとに圧縮データが記録されている。
それぞれのまとまり内における3番目の DC 成分は全て 384 となっており、明るさが 3 段階上がることを示している。
それぞれのまとまり内における2番目と4番目の DC 成分は全て 128 または 120 となっており、明るさが 1 段階上がることを示している。
それぞれのまとまり内における1番目の DC 成分も考慮すると、これは以下のような順でブロックのデータが記録されていることを示している。
1 2 5 6
3 4 7 8
9 10 13 14
11 12 15 16
すなわち、各ブロックのデータは以下のような順で格納されていることが読み取れる。
- 上のまとまり行から下のまとまり行
- 同じまとまり行の中では、左のまとまりから右のまとまり
- 各まとまりの中で、
- 上の行から下の行
- 同じ行の中では、左のブロックから右のブロック
まとまりのサイズが2×4のとき
以下のように、サンプル画像を JPEG に変換した。
magick convert stair32x32.png -sampling-factor 2x4,1x1,1x1 -quality 100 stair32x32_2x4.jpg
変換結果の JPEGsnoop での解析結果のうち、圧縮データの Lum の DC 成分を抜き出すと、以下のようになった。
Lum (Tbl #0), MCU=[0,0]
[0x0000011E.0]: ZRL=[ 0] Val=[-1024] Coef=[00= DC] Data=[0x CF FD DE 16 = 0b (11001111 111111-- -------- --------)]
Lum (Tbl #0), MCU=[0,0]
[0x0000011F.7]: ZRL=[ 0] Val=[ 120] Coef=[00= DC] Data=[0x FD DE 16 00 = 0b (-------1 11011110 00------ --------)]
Lum (Tbl #0), MCU=[0,0]
[0x00000121.3]: ZRL=[ 0] Val=[ 384] Coef=[00= DC] Data=[0x 16 00 80 58 = 0b (---10110 000000-- -------- --------)]
Lum (Tbl #0), MCU=[0,0]
[0x00000122.7]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 00 80 58 02 = 0b (-------0 10000000 -------- --------)]
Lum (Tbl #0), MCU=[0,0]
[0x00000124.1]: ZRL=[ 0] Val=[ 384] Coef=[00= DC] Data=[0x 58 02 01 60 = 0b (-1011000 0000---- -------- --------)]
Lum (Tbl #0), MCU=[0,0]
[0x00000125.5]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 02 01 60 08 = 0b (-----010 000000-- -------- --------)]
Lum (Tbl #0), MCU=[0,0]
[0x00000126.7]: ZRL=[ 0] Val=[ 384] Coef=[00= DC] Data=[0x 01 60 08 06 = 0b (-------1 01100000 00------ --------)]
Lum (Tbl #0), MCU=[0,0]
[0x00000128.3]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 08 06 0A BA = 0b (---01000 0000---- -------- --------)]
Lum (Tbl #0), MCU=[1,0]
[0x0000012C.6]: ZRL=[ 0] Val=[-1408] Coef=[00= DC] Data=[0x CB 27 F2 01 = 0b (------11 00100111 1111---- --------)]
Lum (Tbl #0), MCU=[1,0]
[0x0000012E.5]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x F2 01 60 08 = 0b (-----010 000000-- -------- --------)]
Lum (Tbl #0), MCU=[1,0]
[0x0000012F.7]: ZRL=[ 0] Val=[ 384] Coef=[00= DC] Data=[0x 01 60 08 05 = 0b (-------1 01100000 00------ --------)]
Lum (Tbl #0), MCU=[1,0]
[0x00000131.3]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 08 05 80 20 = 0b (---01000 0000---- -------- --------)]
Lum (Tbl #0), MCU=[1,0]
[0x00000132.5]: ZRL=[ 0] Val=[ 384] Coef=[00= DC] Data=[0x 05 80 20 16 = 0b (-----101 10000000 -------- --------)]
Lum (Tbl #0), MCU=[1,0]
[0x00000134.1]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 20 16 00 80 = 0b (-0100000 00------ -------- --------)]
Lum (Tbl #0), MCU=[1,0]
[0x00000135.3]: ZRL=[ 0] Val=[ 384] Coef=[00= DC] Data=[0x 16 00 80 49 = 0b (---10110 000000-- -------- --------)]
Lum (Tbl #0), MCU=[1,0]
[0x00000136.7]: ZRL=[ 0] Val=[ 128] Coef=[00= DC] Data=[0x 00 80 49 7F = 0b (-------0 10000000 -------- --------)]
今回は、2×4ブロック (16×32ピクセル) のまとまりごとに圧縮データが記録されている。
各まとまり内の2番目以降の要素のうち、偶数番目は 128 または 120 なので 1 段階明るくなり、奇数番目は 384 なので 3 段階明るくなることを示している。
2番目のまとまりの最初の要素は -1408 であり、11 段階暗くなることを示している。
これは以下のような順でブロックのデータが記録されていることを示している。
1 2 9 10
3 4 11 12
5 6 13 14
7 8 15 16
よって、まとまりの大きさが2×2のときと同様の法則でブロックのデータが記録されていることがわかる。
まとめ
圧縮データと数値の関係
JPEG の圧縮データの追加データは、以下のようにすることで数値に変換できる。
- 追加データが無い (0ビットの) 場合
- 表す数値は 0 である
- 追加データの最上位ビットが
0の場合 (負の数値を表す)- 追加データより上位のビットはすべて
1であると仮定し、2の補数で整数として解釈する - それに 1 を足す
- 追加データより上位のビットはすべて
- 追加データの最上位ビットが
1の場合 (正の数値を表す)- 追加データをそのまま (符号なし) 整数として解釈する
このようにして数値に変換したあと、DQT に記録されている要素に対応する位置の値を掛けることで、DCT Matrix 上の数値が求まる。
0 以外の数値は、これの逆、すなわち
- DQT に記録されている対応する位置の値で割る
- 負の数値ならば、1 を引く
- 2の補数で表し、最下位ビットから、符号ビットと異なる最上位のビットまでを追加データとする
ことで圧縮データ用の追加データに変換できる。
0 は、以下のようにして表す。
- DC 成分 (各ブロックの最初の要素) では、追加データの長さ 0 ビットとして表す
- AC 成分 (各ブロックの最初以外の要素) では
- 原則として、次の 0 でない要素に「その前に値 0 の要素が何個あるか」の情報を乗せて表す
- それ以降の要素が全て 0 の場合は、EOB という要素で表す
- 0 でない要素の前に 0 が 16 個以上連続するときは、16 個の 0 を ZRL という要素で表す
EOB および ZRL についても、追加データの長さは 0 ビットである。
ブロックの並び順
各ブロックのデータは、サブサンプリング情報によって決まる大きさ (ブロック数) の「まとまり」を基準に、以下の順で圧縮データ内に格納される。
- 上のまとまり行から下のまとまり行
- 同じまとまり行の中では、左のまとまりから右のまとまり
- 各まとまりの中で、
- 上の行から下の行
- 同じ行の中では、左のブロックから右のブロック
コンポーネントが複数ある場合は
- まとまり1のコンポーネントA
- まとまり1のコンポーネントB
- まとまり1のコンポーネントC
- まとまり2のコンポーネントA
- まとまり2のコンポーネントB
- まとまり2のコンポーネントC
- まとまり3のコンポーネントA
- …
のように、各まとまりごとにそれに含まれるコンポーネントのデータを順に並べる。
各まとまり内のコンポーネント内のブロックの並び順、およびまとまりの並び順は、上の法則に従う。


