JPEGファイルの主にDPIに関わる情報について
1. 導入
知人の仕事の手伝いをしている中ある時、
「この画像のDPIがなぜか96になっているから300にしてほしい」
と言われ、いくつかのJPEG形式のファイルが送られてきました。
特に考えずに承諾し持ち帰ってきたものの、詳しくDPIについて知らなかったので今回JPEGのDPIについてまとめることにしました。
よく色々なところで「ファイル名の末尾のファイル形式の記述を変えるだけでは画像形式は変わらない」という話を聞いていたので、画像ファイルの中に画像形式の情報やその他の情報が入っており、今回のDPIもおそらくその情報に含まれているのだろうと言うことはなんとなくわかっていました。
ただ、DPIが具体的に何かよくわかっていませんでしたし、JPEGのファイルについて調べてみるとDPI 解像度 JFIF EXIF TIFFタグが混在しており実際には少し複雑であることが改めてわかりました。
本記事では、主にJPEGのDPIについてのメタデータについて調べたことについてまとめていきます。
2. そもそも画像ファイルとは?
DPIやJPEGの話に入る前に、「画像ファイルとは何なのか」について軽く説明しておきます。
そもそも画像ファイルは、単なる16進数のバイト列です。
画像ビューアはこれらの情報を読み取り、人間が見える形に変換して表示しています。
00000000: FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 01 2C
00000010: 01 2C 00 00 FF E1 00 48 45 78 69 66 00 00 49 49
00000020: 2A 00 08 00 00 00 03 00 1A 01 05 00 01 00 00 00
00000030: 32 00 00 00 1B 01 05 00 01 00 00 00 3A 00 00 00
00000040: 28 01 03 00 01 00 00 00 02 00 00 00 FF DB 00 43
00000050: 00 08 06 06 07 06 05 08 07 07 07 09 09 08 0A 0C
00000060: 14 0D 0C 0B 0B 0C 19 12 13 0F 14 1D 1A 1F 1E 1D
00000070: 1A 1C 1C 20 24 2E 27 20 22 2C 23 1C 1C 28 37 29
00000080: 2C 30 31 34 34 34 1F 27 39 3D 38 32 3C 2E 33 34
00000090: 32 FF DA 00 0C 03 01 00 02 11 03 11 00 3F 00 ...
画像ファイルは簡単に言うと、画像データとメタデータでできています。
画像データとは、文字どおり写真そのものが入っているデータです。
メタデータとは、ファイルについての情報が入っているデータです。
例えば、
・撮影日時
・カメラ機種
・GPS情報
・向き情報
・解像度(DPI)
などが含まれます。
画像
├─ 画素データ
└─ メタデータ
├─ 撮影日時
├─ カメラ名
├─ DPI
├─ GPS
└─ ...
今回扱うDPIは、このメタデータの一種です。
3. JPEGのメタデータの内訳
JPEGファイルは複数のセグメントが並ぶことで構成されています。
概念的には主に次のようになります。
| セグメント | 用途 |
|---|---|
| SOI | JPEGファイルの開始を示す |
| APP0 (JFIF) | JFIFが格納されている |
| APP1 (EXIF) | EXIFが格納されている |
| DQT | JPEG圧縮の画質に関わる量子化テーブルの情報を記録 |
| DRI | 障害時の復旧ポイント設定 |
| SOF | 幅 高さ 色成分数などの画像の基本情報を記録 |
| DHT | JPEG圧縮の画質に関わるハフマン符号化テーブルの情報を記録 |
| SOS | 画像データの先頭につき後に続く画像データの成分などの情報を記録 |
| EOI | JPEGファイルの終了を示す |
他にも、APP3以上のセグメントやCOM(コメント)などのセグメントがありますが今回は扱いません。
この中でjpegとして実質必須になるセグメントは「SOI」「DQT」「SOF」「DHT」「SOS」「EOI」です。
今回はDPIについての記事なので、これに「APP0 (JFIF)」「APP1 (EXIF)」を加えて説明することにします。
SOI
SOI(Start Of Image)です。
JPEGファイルの開始を示し、以下の画像のように必ずjpegでは「FF D8」です。

これを見つけることで、「このファイルはJPEGである」と判断できます。
EOI
EOI(End Of Image)です。
JPEGファイルの終了を示し、ファイル末尾に「FF D9」で存在します。

DQT
DQT(Define Quantization Table)です。
jpegの圧縮工程の量子化に関わる情報を格納した量子化テーブルです。
「FF DB」から始まります。

今回は深く掘り下げませんが、jpegは非可逆圧縮形式で
色変換 RGB → YCbCr
↓
DCT変換
↓
量子化
↓
ハフマン符号化
と言う処理を経てJPEGファイルとして保存しています。
つまり、JPEGファイルには圧縮済み画像データと、それを復元するためのDQT・DHT・SOF・SOSなどの情報が入っていると言うことです。
このDQTは簡単に言うと量子化の表が入っています。
DHT
DHT(Define Huffman Table)です。
DQTと同じくjpegの圧縮に関する情報を扱っており、ここはハフマン符号化テーブルです。
「FF C4」から始まります。

※少し長いため最初の十数行のみ表示
SOF
SOF(Start Of Frame)です。
画像の基本構造を定義するセグメントで
画像の幅
画像の高さ
色成分数
サンプリング比率
量子化テーブル番号
などが入っています。
「量子化テーブル番号」が入っているとあるようにここからDQTを参照しています。

マーカーの種類が「FF C0」と「FF C2」の場合がありそれぞれ、
SOF0 = Baseline DCT(ベースライン方式)
SOF2 = Progressive DCT(プログレッシブ方式)
です。
よって、上の画像はベースライン方式です。
補足:ベースライン方式とプログレッシブ方式
ベースラインJPEGは、画像を一度の流れで復元し上から順番に読み込んで表示していきます。
昔のWebページで、画像が上から少しずつ表示されるような挙動を見たことがあるかもしれません。
一般的なJPEG画像の方式です。プログレッシブJPEGは、画像を段階的に表示できるJPEGです。
プログレッシブJPEGでは、最初に粗い画像を表示し、その後だんだん細部が追加されます。この構造から、ベースラインJPEGは基本的に SOS が1つであることが多いですが
プログレッシブJPEGでは荒い画像から少しづつ細部が表示される構造から SOS が複数出ることがあります。
DRI
DRI(Define Restart Interval)です。
JPEGは通常、前のデータを利用しながら復号します。
しかし、途中で1ビット壊れるとそれ以降の画像データが壊れる可能性があります。
そこで一定の間隔でRTSとという再開ポイントを定期的に挿入します。
これにより特定の場所でデータの破損があった場合は次のRSTマーカまで読み飛ばし以降の画像データの崩壊を防ぎます。

上の画像では、「FF DD 00 04 00 FC」とあり
「FF DD」がDRIマーカーで、そのあとはRSTマーカがどのくらいの間隔で置かれているかを定義しているセグメントです。
APPとは
ここからはAPPO、APP1なので少し詳しく書きます
まず前提として、APP0 や APP1 の APP は、Applicationの略です。
先ほどまで紹介してきた、画像を圧縮や復元するために必要なセグメントのほかに、アプリケーションが自由に情報を入れられる領域があります。
それが APP です。
APP0 ~ APP15 までありますが、今回は基本的に使われることの多い APP0 と APP1 をまとめます。
APP0 = FF E0
APP1 = FF E1
APP2 = FF E2
...
APP15 = FF EF
この中に、JFIF、Exif、XMP、ICC Profile、Photoshopの情報などが入ります。
APP0
APP0はマーカーで言うと「FF E0」です。

例えばこの画像の場合「FF E0 00 10 4A 46 49 46 00 ...」から始まっています。
「FF E0」 APP0マーカー
「00 10」 セグメント長
「4A 46 49 46 00」 "JFIF\0"
と言う意味合いで、特にこの4A 46 49 46 はASCIIで JFIF でありここの APP0 の場合はここの「JFIF」をアプリが探しにくることになります。
-DPIに関係するJFIF項目
今回の記事で特に重要なのはこの3つです。
「DensityUnit」
「XDensity」
「YDensity」
以上の画像では、"JFIF\0"から2バイト分のJFIFのバージョン情報「01 01」がある次からです。
「01 01 2C 01 2C ...」の箇所の
「01」 DensityUnit
「01 2C」 XDensity
「01 2C」 YDensity
です。
-APP0 DensityUnit XDensity YDensity
DensityUnitは密度の単位です。
0 = 単位なし
1 = dpi
2 = dpcm
と決まっており、今回の画像は「01」なのでdpiであることがわかります。
XDensity YDensityはそれぞれ「横方向の密度」「縦方向の密度」です。
今回はどちらも「01 2C」でDensityUnitの値と一緒に考えると、
「300dpi」であることがわかります。
ここで注意なのですが、JFIFの XDensity / YDensity は、画像のピクセル数を変えるものではありません。
例えば、
「3337 x 2421 px 96 dpi」
と
「3337 x 2421 px 300 dpi」
はピクセル数が同じなら画像データ自体は同じで、違いは主に印刷時の物理サイズの解釈です。
※画像によっては、このあと「サムネイル横サイズ(1バイト)」「サムネイル縦サイズ(1バイト)」「サムネイルイメージ(RGBビットマップイメージデータ 3バイト)」が入っている場合もあります。
APP1
APP1はマーカーでいうと、「FF E1」です。

-EXIF
例えば上の画像の場合「FF E1 00 80 45 78 69 66 00 ...」から始まっています。
「FF E1」 APP1マーカー
「00 80」 セグメント長
「45 78 69 66 00 00」 "EXIF\0"
と言う意味合いで、特にこの45 78 69 66 00 はASCIIで Exif でありここの APP1 の場合はここの「Exif」をアプリが探しにくることになります。
Exifの中身は、かなりざっくり言うとTIFF形式の構造を使っています。
上の画像の続きとして「... 4D 4D 00 2A 00 00 00 08 ...」 とあります。
「4D 4D」 Tiffヘッダ
「00 2A」 Tiff識別コード
「00 00 00 08」 IFDポインタ
となっています。
APP1
└─ Exif
└─ TIFF構造
├─ IFD0
├─ Exif IFD
├─ GPS IFD
└─ Thumbnail IFD
*あくまで主なTIFF構造の例でこれ以上に項目が多い場合や少ない場合があります。
TIFFはJPEGとは別の画像形式ですが、このExifではTIFFのタグ管理方式が使われています。
主な理由としてはTIFF構造特有のIFD(Image File Directory)により、画像の情報の管理が容易だからです。
ここには撮影日時、カメラ機種、画像の向き、DPIに関係するXResolution / YResolution / ResolutionUnitなどが保存されます。
特に、IFD0・Exif IFDの概念は重要で理由としては以下の情報が入っているからです。
TIFF構造
├─ IFD0
│ ├─ 画像全体に関する基本情報
│ ├─ XResolution (DPIに関係する値)
│ ├─ YResolution (DPIに関係する値)
│ └─ ResolutionUnit (DPIに関係する値)
│
└─ Exif IFD
├─ 撮影時の詳細情報
├─ DateTimeOriginal
├─ ExposureTime
├─ FNumber
├─ ISO
└─ FocalLength
ここは深入りするとキリがないので簡単にまとめると
「IFD0」は画像ファイル全体に関する基本情報が入る場所
「Exif IFD」はより詳細な情報が入る場所
そしてそれらは、TIFFタグ(IFD)としてタグの種類を整理して保存されていると言うことです。
jsonやyaml形式で管理されているようなものだと思うと分かりやすいです。
そして今回の記事の目的であったDPIは、Exifの場合だと「XResolution」「YResolution」「ResolutionUnit」の値の関係から算出できます。
XResolution は横方向の密度、YResolution は縦方向の密度、ResolutionUnit はその単位を表し、
ResolutionUnit が inch の場合(2 = inch / 3 = centimeter)、XResolution / YResolution は dpi として扱われます。
例として、XResolutionの値を確認してみます。

先ほどの、APP0のXDensity の2バイト「01 2C」と比べて欲しいのですが、なんとXResolutionの値は12バイトあります。
この値の内訳はこうです。
XResolution IFD Entry
「01 1A」 Tag ID = 0x011A (XResolution識別子)
「00 05」 Type = RATIONAL (5 = RATIONAL 分子 / 分母で格納)
「00 00 00 01」 Count = 1 (値は1個)
「00 00 00 3E」 Value/Offset = 値の場所へのオフセット (値本体はオフセット 0x3E にある)
そうです、IFDは値の情報を目次のように最初にリストのようにまとめ、そこには値のある場所だけを記し、実際の値は目次に後ろにまとめて保存しています。
値の場所へのオフセットとはこう言うことです。
今回は、「0x3E」なので TIFF ヘッダ開始位置から数えて 0x3Eの場所に値があることを示しています。
少しみにくいですが実際の値はここです。

「00 00 01 2C 00 00 00 01」と正しく 分子 / 分母 の関係で300dpiとなっています。
APP1
├─ FF E1
├─ length
├─ Exif識別子
│ └─ 45 78 69 66 00 00 = "Exif\0\0"
└─ TIFF構造
├─ TIFF Header
│ ├─ MM / II
│ ├─ 00 2A / 2A 00
│ └─ IFD0 offset
├─ IFD0
│ └─ XResolution entry
│ └─ value offset = 0x3E
└─ 0x3E の位置
└─ XResolution の実値
※その他の主な情報について
スマホで撮影した画像には、「make」や「model」、「Datetime」などのかなり個人情報によった情報も格納されています。
DPIを確認・編集する目的でExifを見る場合でも、画像によっては(全ての画像ではありません)同じ領域に撮影日時や端末情報、位置情報などが含まれていた場合がありました。
このことから普段の画像データの扱いは、これらの情報が漏れる可能性があることを意識しておいた方が良いです。最近の端末では、Apple Androidともに写真アプリから位置情報を消すことができます。
ただし、端末情報や撮影日時全てを消すことはできません。
最も確実な方法は、元の画像のスクリーンショットを使うことです。以下、主にスマホで撮影した画像にあったExifメタデータ一覧です。
基本情報
「Make」 => メーカー(appleなど)
「Model」 => 撮影機種(iPhone 12 など)
「Software」 => 画像生成時のOS/ソフトウェア系バージョン
「DateTime」 => TIFF 側の記録日時
「DateTimeOriginal」 => 実際の撮影日時
「DateTimeDigitized」 => デジタル化日時、通常は撮影日時と同じ
「OffsetTime / OffsetTimeOriginal / OffsetTimeDigitized」 => タイムゾーン、日本時間なら +9:00画像サイズ・表示系
「PixelXDimension / PixelYDimension 」 => 画像の実ピクセルサイズ
「XResolution / YResolution / ResolutionUnit」 => 説明済み、DPIメタデータ
「Orientation」 => 回転の向き
「ProfileName」 => 色域プロファイル、「Display P3」はApple系でよく使われる広色域です。撮影条件
「ExposureTime」 => 露光時間
「FNumber」 => F値、明るいレンズを使っているなど
「FocalLength」 => 実焦点距離
「FocalLenIn35mmFilm」 => 35mm換算
「ISOSpeedRatings」 => ISO感度、撮影場所の明るさがわかる
「Flash」 => フラッシュ「GPSIFD」 => 位置情報、あるならIFD0のまとまりの次にある
ほぼ入っているのを見た事がないなどの情報がありました。
-補足:XMP
また、APP1は必ずExifとは限らず、他にも「XMP(Extensible Metadata Platform)」と言うAdobe系のメタデータが格納されていることもあります。
なので必ずしもAPP1がひとつで「Exif」であるとは限らず、何ならAPP1そのものがないjpegファイルも少なくありません。
APP1はAPP0以上に注意して確認していく必要があります。
今回は詳しく解説しませんが、xmpの識別子は私が調べた感じだと「http://ns.adobe.com/xap/1.0/」であると思われます。

APP1
└─ XMP
├─ http://ns.adobe.com/xap/1.0/
└─ XMLデータ
4. おわりに
ここまで、JPEGファイルの構造とDPIに関わるAPP0 APP1を確認してきました。
基本的にDPIを編集する際、画像ファイルのどの箇所に注目して編集すれば良いのかわかったと思います。
次に書く予定の『実際のJPEGファイルのDPI確認と編集について』で
「既存の画像プレビューのどこで確認できるのか」や「APP1 APP0 双方あり値が異なる場合どちらの値が優先されるのか」、「JPEGパーサーアプリ作成中に躓いたこと」など実践的な内容についてまとめたいと思います。
参考
・画像解像度とdpi|印刷やWebサイトの目安と確認方法 - ネット印刷は【印刷通販@グラフィック】
・【画像処理】ヘッダ情報の確認とバイナリエディタからの画像編集(JPEG編) #jpg - Qiita
Author
主にシステム面で学習したことをまとめています。 フロント、サーバー、インフラ、AIなど細かい分野に絞らずに広く発信していきます。