1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

JPEG (JFIF) ファイルの読み方まとめ

Posted at

これまでのJPEGシリーズ

今回やること

JPEG (JFIF) ファイルの各セグメントの読み方を確認し、JPEG ファイルからエンコードされた画像のデータを取得できるようにする。

JFIF とは

JFIF (JPEG File Interchange Format) とは、相互運用性を高めた JPEG のサブセットである。
以下の事項が定義されている。

  • JFIF であることを表すための APP0 セグメントの構造 (後述)
  • ピクセルの並び順 (上から下、同じ行では左から右)
  • 色空間 (1要素 (グレースケール) または3要素 (YCbCr))
  • RGB と YCbCr の変換方法
  • サブサンプリングされた要素の位置の解釈

Version 1.01 と Version 1.02 の仕様書が定義されており、Version 1.02 ではサムネイルのデータ構造の定義が追加されている。
JFIF で定義された形式のサムネイルを使用しない場合は、Version 1.01 を使用しておけばいいだろう。
ペイント・GIMP・ImageMagick は Version 1.01 で出力しているようである。

Version 1.01 でも、サムネイルの表現方法は定義されている。
Version 1.02 では、サムネイルのデータに使用可能なエンコード方式が追加されている。
また、世の中で用いられる画像のサムネイルは、Exif という別の形式で記録されていることがある。

JPEG ファイルの構造

JPEG ファイルは、大きく分けて

  1. SOI マーカー
  2. セグメント (列)
  3. EOI マーカー

からなる。

SOI マーカー

SOI マーカーは、0xFF 0xD8 の2バイトで、JPEG ファイルの最初に配置される。

セグメント (列)

セグメントは、以下のデータからなる。

  1. 1バイト以上0xFF
  2. セグメントの種類を表す1バイト
  3. セグメントのデータ

セグメントは、通常1個の 0xFF から始まる。
しかし、セグメントの開始を表す 0xFF の前に追加の 0xFF のバイトを挿入してもよい。

JPEG ファイルでは、SOI マーカーと EOI マーカーの間に、各セグメントを並べて記録する。

EOI マーカー

SOI マーカーは、0xFF 0xD9 の2バイトで、JPEG ファイルの最後に配置される。

各セグメントの構造

JPG ファイルフォーマットJPEG フォーマット辞典 - しいしせねっと
および TSXBIN のシンボル表示を参考に、各セグメントの構造を示す。

別途記載の無い場合、複数バイトの数値はビッグエンディアン (上位バイトを先に配置する) である。

APP0 (種類 0xE0)

アプリケーション固有のデータを格納する。

JFIF

JFIF では、以下の構造の APP0 セグメントを SOI マーカーの直後に配置する。

ファイル先頭から APP0 の冒頭にかけてのバイト列で JFIF かを判定することがあるため、この JFIF で定義された APP0 セグメントの前には余計な 0xFF のバイトは挿入しないべきである。

意味 バイト数 データ
APP0 である 2 0xFF 0xE0
このセグメントのサイズ 2 数値
JFIF である 5 0x4A 0x46 0x49 0x46 0x00 (JFIF)
バージョン 2 数値
解像度の単位 1 0x00: 単位なし (ピクセルの縦横比)
0x01: DPI
0x02: dot/cm
横方向の解像度 2 数値
縦方向の解像度 2 数値
サムネイルの横幅 1 数値
サムネイルの縦幅 1 数値
サムネイルデータ 0~ サムネイルの画像データ

「このセグメントのサイズ」は、最初の 0xFF 0xE0 以外の部分 (サイズを含む) のバイト数を格納する。
サムネイルが無い場合、これは 16 (0x00 0x10) になる。

バージョンは、上位バイトでメジャーバージョンを、下位バイトでマイナーバージョンを表す。
Version 1.01 であれば 0x0101 (0x01 0x01)、1.02 であれば 0x0102 (0x01 0x02) となる。

解像度の情報が無い場合は、単位を 0x00 (単位なし) とし、横方向と縦方向の解像度をそれぞれ 1 とする。(ピクセルが正方形の場合)
解像度を 0 にしてはいけない。

サムネイルの画像データは、1ピクセルを3バイト (RGB それぞれ1バイト) で表し、サムネイルに含まれるピクセルの数だけ繰り返す。
サムネイルが無い場合、横幅と縦幅をともに 0 とする。

JFIF の APP0 セグメントの例
002  ★APP[0]              FF E0
004  SizeOfThis[0]         00 10
006  JFIF[0]               4A 46 49 46 00
00B  MajorRev              01
00C  MinorRev              01
00D  Units                 01
00E  XDensity[0]           01 2C
010  YDensity[0]           01 2C
012  ThumbnailWidth        00
013  ThumbnailHeight       00

JFIF 拡張

JFIF Version 1.02 で定義された JFIF 拡張を用いる場合は、以下の構造の APP0 セグメントを上記 JFIF で定義された APP0 セグメントの直後に配置する。
このセグメントは、必要に応じて複数並べてもよい。

意味 バイト数 データ
APP0 である 2 0xFF 0xE0
このセグメントのサイズ 2 数値
JFIF 拡張である 5 0x4A 0x46 0x58 0x58 0x00 (JFXX)
拡張の種類 1 数値
拡張データ 可変 各拡張で定義されたデータ

「このセグメントのサイズ」は、最初の 0xFF 0xE0 以外の部分 (サイズを含む) のバイト数を格納する。

このセグメントは、JFIF のバージョンが 1.02 以上の場合のみ使用可能である。

SOF0 (種類 0xC0)

「通常」の JPEG 画像の幅・高さ・要素(チャンネル)数などを定義する。

意味 バイト数 データ
SOF0 である 2 0xFF 0xC0
このセグメントのサイズ 2 数値
サンプルの精度 1 数値 (通常は 8)
画像の高さ 2 数値 (縦方向のピクセル数)
画像の幅 2 数値 (横方向のピクセル数)
画像の要素(チャンネル)数 1 数値
各要素の情報 3×要素数 後述

「このセグメントのサイズ」は、最初の 0xFF 0xC0 以外の部分 (サイズを含む) のバイト数を格納する。

「各要素の情報」は、それぞれの要素について以下の構造の情報を並べて格納する。

意味 バイト数 データ
要素の識別子 1 通常は 1 から順に用いられる
サブサンプリング情報 1 上位4ビット:横方向の情報
下位4ビット:縦方向の情報
量子化テーブル情報 1 この要素で用いる量子化テーブルの識別子
SOF0 セグメントの例
350  ★SOF[0]                FF C0
352  SizeOfThis[0]           00 11
354  DataPrecision           08
355  PicHeight[0]            00 40
357  PicWidth[0]             00 40
359  Nf                      03
35A  Component0[0]           01 22 00
35D  Component1[0]           02 11 01
360  Component2[0]           03 11 01

その他の SOF 系セグメント

画像の形式によっては、SOF0 のかわりに以下のセグメントが用いられることがある。
これらについては、本記事では扱わない。

  • SOF2 (種類 0xC2):プログレッシブ JPEG で用いる
  • SOF9 (種類 0xC9):算術符号 (arithmetic coding) を用いる際に用いる
  • SOF10 (種類 0xCA):算術符号 (arithmetic coding) を用いたプログレッシブ JPEG で用いる

DQT (種類 0xDB)

量子化テーブルを格納する。
これは、圧縮データを展開して得られるデータの各要素に掛ける係数を表す。

意味 バイト数 データ
DQT である 2 0xFF 0xDB
このセグメントのサイズ 2 数値
精度と識別子 1 上位4ビット:精度 (0:8bit、1:16bit)
下位4ビット:量子化テーブルの識別子
係数のテーブル 64 or 128 それぞれの要素に対応する数値 64個
精度8bitなら1個1バイト、16bitなら2バイト

「このセグメントのサイズ」は、最初の 0xFF 0xDB 以外の部分 (サイズを含む) のバイト数を格納する。

それぞれの数値は、符号なしである。(たとえば 0xFF は 255 を表す)

DQT セグメントの例
014  ★DQT[0]              FF DB
016  SizeOfThis[0]         00 43
018  PqTq                  00
019  Quv[0]                03 02 02 02 02 02 03 02 02 02 03 03 03 03 04 06
029  Quv[16]               04 04 04 04 04 08 06 06 05 06 09 08 0A 0A 09 08
039  Quv[32]               09 09 0A 0C 0F 0C 0A 0B 0E 0B 09 09 0D 11 0D 0E
049  Quv[48]               0F 10 10 11 10 0A 0C 12 13 12 10 13 0F 10 10 10

DHT (種類 0xC4)

ハフマン符号の情報を格納する。

意味 バイト数 データ
DHT である 2 0xFF 0xC4
このセグメントのサイズ 2 数値
種類と識別子 1 上位4ビット:ハフマン符号テーブルの種類
       (0:DC成分、1:AC成分)
下位4ビット:ハフマン符号テーブルの識別子
符号長ごとの符号数 16 1~16ビットの符号の数を表す各1バイトの数値
各符号に対応するデータ 符号数 各符号に対応する各1バイトのデータ

「このセグメントのサイズ」は、最初の 0xFF 0xC4 以外の部分 (サイズを含む) のバイト数を格納する。

「符号長ごとの符号数」は、1ビットの符号の数、2ビットの符号の数、…、16ビットの符号の数、と順に配置する。

「各符号に対応するデータ」は、最初の符号に対応するデータから順に配置する。
(符号の順番については後述)

DHT セグメントの例
00E9  ★DHT[0]                      FF C4
00EB  SizeOfThis[0]                 00 1E
00ED  Th                            00
00EE  DCHaffman[0]                  00 01 04 03 01 01 01 00 00 00 00 00 00 00 00 00
00FE  DCHaffman[16]                 06 04 05 07 08 00 03 09 02 01 0A

DRI (種類 0xDD)

リスタートマーカーを使用するか、および使用する場合の間隔を定義する。

意味 バイト数 データ
DRI である 2 0xFF 0xDD
このセグメントのサイズ 2 数値
リスタート間隔 2 数値

「このセグメントのサイズ」は、最初の 0xFF 0xDD 以外の部分 (サイズを含む) のバイト数を格納する。

「リスタート間隔」は、MCU 何個を格納した後の MCU の前にリスタートマーカーを挟むか (すなわち、MCU 何個ごとにリスタートマーカーを挟むか) を定義する。
これが 0 のときは、リスタートマーカーを挟まない。

0 でなければリスタートマーカーを挟むとは言ってない。
実際、MCU の数がこの値以下の場合も、リスタートマーカーは挟まれない。

DRI セグメントの例
093  ★DRI[0]              FF DD
095  SizeOfThis[0]         00 04
097  RestartInterval[0]    00 03

SOS (種類 0xDA)

圧縮データの格納を開始する。

意味 バイト数 データ
SOS である 2 0xFF 0xDA
このセグメントのサイズ 2 数値
要素数 1 数値
各要素の情報 2×要素数 後述
スペクトル選択 (始点) 1 プログレッシブ JPEG で用いる情報
スペクトル選択 (終点) 1 プログレッシブ JPEG で用いる情報
逐次近似 1 プログレッシブ JPEG で用いる情報
圧縮データ 可変 画像の内容を表すデータ

「このセグメントのサイズ」は、最初の 0xFF 0xDA および圧縮データ以外の部分 (サイズを含む) のバイト数を格納する。

「各要素の情報」は、それぞれの要素について以下の構造の情報を並べて格納する。

意味 バイト数 データ
要素の識別子 1 SOF0 での定義と同じ順番でなければいけない?
ハフマン符号テーブル情報 1 上位4ビット:DC成分用ハフマン符号テーブルの識別子
下位4ビット:AC成分用ハフマン符号テーブルの識別子

プログレッシブ JPEG でないとき、最後の3項目は以下の値が用いられる。

項目
スペクトル選択 (始点) 0
スペクトル選択 (終点) 63 (0x3F)
逐次近似 0

「圧縮データ」は、次のマーカー (0xFF のバイトの後に 0x00 以外のバイトが来る場所) の手前までである。
圧縮データのビット列の中に値 0xFF のバイトが登場する際は、これを 0xFF 0x00 の2バイトで表現する。

SOS セグメントの例
261  ★SOS[0]          FF DA
263  SizeOfThis[0]     00 0C
265  CompCount         03
266  CompNumber0       01
267  TdTa0             00
268  CompNumber1       02
269  TdTa1             11
26A  CompNumber2       03
26B  TdTa2             11
26C  Maybe0x00         00
26D  Maybe0x3F         3F
26E  Maybe0x00         00
26F  圧縮データ(6B)[0] FD FC A2 8A 28 03

RST0~RST7 (種類 0xD0~0xD7)

圧縮データを区切って格納する。(リスタートマーカー)
SOS の後最初に用いるのは RST0 である。
その後、RST1、RST2、…、RST7、と順番に使い、RST7 の次はまた RST0 から順に使っていく。

プログレッシブ JPEG では、SOS が複数現れることがある。
それぞれの SOS の後、それぞれ RST0 から用いられるようである。

意味 バイト数 データ
RSTn である 2 0xFF 0xDn
圧縮データ 可変 画像の内容を表すデータ

圧縮データの表し方は、SOS と同じである。

その他 (未知・未対応) のセグメント

未知・未対応のセグメントであっても、セグメントの開始と種類を表すデータの後にセグメントのサイズが続く構造になっていることが多い。
そのため、この構造を仮定して読み飛ばすのがよいだろう。

意味 バイト数 データ
セグメントの開始と種類 2 0xFF 0x??
このセグメントのサイズ 2 数値
セグメントのデータ サイズ-2 セグメントによる

画像データの読み方

以下のようにすることで、「通常」の JPEG (JFIF) ファイルからエンコードされた画像データを取得できる。

この記事では、画像データのデコード (IDCTなど) については扱わない。
また、プログレッシブ JPEG、算術符号、可逆圧縮などについても扱わない。

各セグメントのデータを取得する

0xFF のバイトやセグメントのサイズ情報に注目し、ファイルをセグメントに分割する。

SOF0 セグメントから要素とMCUの情報を取得する

SOF0 セグメントから、以下の情報を取得する。

  • 画像の高さ (ピクセル数)
  • 画像の幅 (ピクセル数)
  • 画像の要素 (チャンネル) 数
  • 各要素のサブサンプリング情報
  • 各要素で用いる量子化テーブルの識別子

各要素のサブサンプリング情報から、MCU (前の記事でいう「まとまり」) のサイズを計算する。
縦および横それぞれについて、MCU のサイズ (8×8のブロック何個分か) は、各要素のサブサンプリング情報のうち最大のものである。

サブサンプリング情報が 3 の要素と 2 の要素があるなど、最大のサブサンプリング情報の約数でないサブサンプリング情報がある場合は、これは成り立たないかもしれない。
しかし、このようなケースはほとんど無いと考えられ、もし出会ったら非対応の形式とみなすのがよいだろう。

各要素のサブサンプリング情報は、その要素のブロックを1個の MCU 内に縦・横それぞれいくつ配置するかを表す。
1個の MCU のデータは、それぞれの要素のブロックを順に必要な数並べたものである。

たとえば、

  • 要素1:サブサンプリング情報 縦2、横2
  • 要素2:サブサンプリング情報 縦1、横1
  • 要素3:サブサンプリング情報 縦1、横1

の場合は、MCU の大きさは縦2ブロック、横2ブロック (16×16ピクセル) であり、そのデータは

  1. 要素1のブロック 4個
  2. 要素2のブロック 1個
  3. 要素3のブロック 1個

を順に配置したものになる。

画像の縦幅と横幅をそれぞれ MCU の縦サイズ・横サイズ (ピクセル数、すなわちブロック数の8倍) で割ることで、画像の縦と横にそれぞれ何個の MCU を配置するかが求まる。
割り切れない場合、端数は切り上げる

各 MCU は、上の行を表すものから下の行を表すものという順番で並べる。
同じ行の中では、左から右に並べる。
それぞれの MCU 内のブロックも、各要素内で同様に上から下、左から右に並べる。

相対的に小さいサブサンプリング情報を用いる、すなわち MCU のサイズに対して少ないブロック数を用いることは、縮小した画像のデータを記録する (デコード時は拡大する) ことに対応する。
そのため、データをそのまま繰り返すなどして、サブサンプリング情報を単純に (エンコード・デコードをせずに) 変換することはできない。

DHT セグメントの情報からハフマン符号を構築する

DHT セグメントには、ハフマン符号でそれぞれの長さの符号をそれぞれ何個用いるかと、各符号に対応するデータの表が記録されている。
そこで、これらの情報をもとに、deflate で用いられるのと同様のアルゴリズムで、ハフマン符号を構築する。

ハフマン符号の構築は、以下のように行う。

  1. 符号の長さの表に基づき、それぞれのデータに対応する符号の長さを求める
  2. 最初のデータから順に符号を割り当てる

符号の長さは、最初のデータから順に、短い長さからそれぞれ記録された数だけ割り当てていく。
符号の割り当ては、以下のように行う。

  1. 現在の符号を 0 に初期化する
  2. それぞれのデータについて、以下を行う
    1. 現在の符号の長さが現在のデータに対応する符号の長さより短い間、現在の符号の右端に 0 を追加する。(1ビット左シフトする)
    2. 現在の符号を現在のデータに割り当てる
    3. 現在の符号を2進数とみて、1を足す

たとえば、以下の DHT セグメントからは、以下のハフマン符号を構築できる。

DHT セグメントの例
00E9  ★DHT[0]                      FF C4
00EB  SizeOfThis[0]                 00 1E
00ED  Th                            00
00EE  DCHaffman[0]                  00 01 04 03 01 01 01 00 00 00 00 00 00 00 00 00
00FE  DCHaffman[16]                 06 04 05 07 08 00 03 09 02 01 0A
データ 符号の長さ 符号
06 2 00
04 3 010
05 3 011
07 3 100
08 3 101
00 4 1100
03 4 1101
09 4 1110
02 5 11110
01 6 111110
0A 7 1111110

deflate では、通常、「全部のビットが1」の符号までをデータに割り当てる。
一方、JPEG では、「全部のビットが1」の符号はデータに割り当てず、余らせておく。

DRI セグメントからリスタート間隔を取得する

DRI セグメントがあれば、そこからリスタート間隔を取得する。
リスタート間隔とは、「何個の MCU を記録したら、次の MCU の前にリスタートマーカーを挟むか」である。
リスタート間隔が 0 であるか、DRI セグメントが無い場合は、リスタートマーカーは挟まない。

MCU の個数がリスタート間隔で割り切れない場合、最後のリスタートマーカー (または、MCU が少ないためにリスタートマーカーが無い場合は SOS マーカー) の後の MCU の数はリスタート間隔より少なくなるが、パディングなどはせず、最後の MCU の後にそのまま EOI マーカーが置かれる。
MCU の個数がリスタート間隔で割り切れる場合も、最後の MCU の後にリスタートマーカーは置かれず、EOI マーカーが置かれる。

SOS セグメント・RSTn セグメントから圧縮データを展開する

まず、SOS セグメントの冒頭で各要素の展開に用いるハフマン符号 (テーブル) を確認する。
ハフマン符号 (テーブル) は、DC 成分 (各ブロックの最初の要素) と AC 成分 (各ブロックの最初以外の成分) で別のものを用いる。

SOF0 セグメントの情報をもとに計算した MCU の数、リスタート間隔、および MCU 内の各要素のブロックの数の情報をもとに、次に読むのがどの要素のブロックなのか (もしくは、EOI や RSTn マーカーが来るはずなのか) を把握し、対応したハフマン符号を用いて圧縮データの展開を行う。

圧縮データは、それぞれのブロックのデータを隙間なく並べたビット列である。
圧縮データを表す各バイトは、上位ビットから下位ビットに向かって読む。
圧縮データの最後 (リスタートマーカーや EOI マーカーの直前) のバイトの余った(下位)ビットは、1 で埋められている。

各ブロックのデータは、64 個の符号付き整数からなる配列に展開できる。
ブロックのデータの展開は、以下のようにして行う。

  1. 展開結果を空の配列に初期化する
  2. DC 成分用のハフマン符号を用いて符号を読む
  3. 読んだ符号に対応するデータの値のビット数だけ、続くビット列を読む
  4. 読んだビット列を後述の方法で数値に変換し、展開結果の配列の最後に追加する
  5. 展開結果の配列の要素数が 64 になるまで、以下を行う
    1. AC 成分用のハフマン符号を用いて符号を読み、対応するデータを取得する
    2. 取得したデータの上位4ビットが表す数の「0」を展開結果の配列の最後に追加する
    3. 取得したデータの下位4ビットが表すビット数だけ、続くビット列を読む
    4. 読んだビット列を後述の方法で数値に変換し、展開結果の配列の最後に追加する
    5. 取得したデータが 0x00 (EOB) であれば、展開結果の配列の要素数が 64 になるまで「0」を最後に追加する
  6. 展開結果の配列の各要素に、量子化テーブルの対応する位置の要素の値を掛ける

取得したデータが 0x00 (EOB) のとき、

  • 上位4ビットの数 (すなわち、0個) 「0」を追加する
  • 下位4ビットが表すビット数 (すなわち、0ビット) のビット列を読み、それが表す数値 (すなわち「0」) を追加する

処理を省略しても、結果は変わらない。

ビット列の数値への変換は、以下のように行う。

  • ビット列の長さが0ビットの場合、数値は 0 である
  • ビット列の最上位ビット (一番最初のビット)が 1 のときは、ビット列をそのまま数値として解釈する
  • ビット列の最上位ビットが 0 のときは
    1. ビット列よりも上位の部分に、整数型の幅いっぱいまで 1 のビットを詰める
    2. それを2の補数として解釈し、1を足す

例として、以下の圧縮データを展開してみる。

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
DHT
66  ★DHT[0]          FF C4
68  SizeOfThis[0]     00 14
6A  Th                00
6B  DCHaffman[0]      01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
7B  DCHaffman[16]     01
7C  ★DHT[0]          FF C4
7E  SizeOfThis[0]     00 18
80  Th                10
81  ACHaffman[0]      00 02 03 00 00 00 00 00 00 00 00 00 00 00 00 00
91  ACHaffman[16]     00 07 47 86 C7
圧縮データ
A0  圧縮データ(6B)[0] 14 51 B5 5B 31 3F

まず、DHT からハフマン符号を構築する。
DC 成分用は、符号 0 がデータ 0x01 を表すのみである。
AC 成分用は、以下のようになる。

データ 符号
0x00 00
0x07 01
0x47 100
0x86 101
0xC7 110

圧縮データをビット列で表現すると

0001 0100 0101 0001 1011 0101 0101 1011 0011 0001 0011 1111

となる。
これに対し、以下のような処理が行われる。

データ 処理
0 DC 成分用のハフマン符号として解釈する
データ 0x01 を得る
0 データを1ビット読み、数値として解釈する
結果の -1 を展開結果に追加する
01 AC 成分用のハフマン符号として解釈する
データ 0x07 を得る
0100010 データを7ビット読み、数値として解釈する
結果の -93 を展開結果に追加する
100 AC 成分用のハフマン符号として解釈する
データ 0x47 を得る
0 を4個展開結果に追加する
0110110 データを7ビット読み、数値として解釈する
結果の -73 を展開結果に追加する
101 AC 成分用のハフマン符号として解釈する
データ 0x86 を得る
0 を8個展開結果に追加する
010110 データを6ビット読み、数値として解釈する
結果の -41 を展開結果に追加する
110 AC 成分用のハフマン符号として解釈する
データ 0xC7 を得る
0 を12個展開結果に追加する
0110001 データを7ビット読み、数値として解釈する
結果の -78 を展開結果に追加する
00 AC 成分用のハフマン符号として解釈する
データ 0x00 を得る
展開結果が64要素になるまで 0 を追加する
111111 余ったデータであり、何もしない

その結果、以下の配列が得られる。

   -1  -93    0    0    0    0  -73    0    0    0    0    0    0    0    0  -41
    0    0    0    0    0    0    0    0    0    0    0    0  -78    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

この配列に DQT の対応する位置の要素を掛けると、以下のようになる。

   -3 -186    0    0    0    0 -219    0    0    0    0    0    0    0    0 -328
    0    0    0    0    0    0    0    0    0    0    0    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

ここで得られる DC 成分 (配列の最初の要素) は、同じ要素の前のブロックの DC 成分との差分 (前のブロックの DC 成分に足すことでこのブロックの DC 成分が得られる値) である。
そのため、この足し算を行うことで、画像の切り貼りに使いやすい、他のブロックから独立したデータが得られる。
SOS マーカーや RSTn マーカーの後最初のその要素のブロックでは、「前のブロックの DC 成分」は 0 とみなす。

まとめ

「通常」の JPEG ファイルからの (エンコードされた) 画像データの読み取りは

  1. SOF0 セグメントから画像のサイズ、および要素(チャンネル)の数とサブサンプリング情報を取得し、MCU (画像データの「まとまり」) の数を計算する
  2. SOS セグメントで指定されたハフマン符号テーブルを用いて圧縮データを読み取り、64要素の配列の列に展開する
  3. 得られた配列に、SOF0 セグメントで指定された量子化テーブルの要素を掛ける
  4. 前の配列との差分で表現されている DC 成分 (配列の最初の要素) を、独立した値に変換する

ことで行えることがわかった。
これにより、MCU 単位であれば、JPEG 画像の無劣化 (再圧縮なし) でのトリミングや結合が可能になるはずである。

ハフマン符号テーブルが違う画像同士の結合は、新しい画像のデータの分布に基づいて新しいハフマン符号テーブルを構築することで可能なはずである。

量子化テーブルが違う画像同士の結合は、一旦量子化テーブルの値を掛けた値を計算し、その最大公約数を用いて新しい量子化テーブルを構築することで可能なはずである。

一方、サブサンプリング情報が違う画像同士の結合は、ブロックの解釈 (何ピクセル×何ピクセルにデコードするか) が異なってしまい、再圧縮を行わない単純な処理での変換は一般には行えないと考えられるため、無劣化では行えないだろう。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?