実際のJPEGファイルのDPI確認と編集について

rikuo kamadarikuo kamada

前回の続き

前回の記事『JPEGファイルの主にDPIに関わる情報について』では、そもそものJPEGファイルの形式についてまとめてきました。

その中でDPIに関わる情報を持つ APP0 と APP1 について詳しく確認し、
具体的にAPP0では「DensityUnit / XDensity / YDensity」の値を、
APP1では「XResolution / YResolution / ResolutionUnit」の値を確認することでその画像のDPIを知る事ができるとわかりました。

そこで今回は実際に自分が体験・直面した

  • 実際の画像のDPIをmacのプレビューで確認する方法
  • JPEG内部の APP0 と APP1 に異なるDPI値が入っていた場合、どちらが使われるのか
  • DPI変換アプリを試作した際、SwiftのImageIOではAPP0のDPI値が期待通りに書き換わらなかったこと
  • JPEGファイル形式のイレギュラー

などについて緩くまとめます。

画像のDPIを簡単に確認する(mac)

最初にmacで画像のDPIを簡単に確認する方法を確認します。

macに最初から入っている『プレビュー』で確認できます。
アプリを開いて左上の「ツール」→「インスペクタを表示」からこのように「画像のDPI」を確認できます。

dpi

macの「プレビュー」でのDPIの値の参照についての考察

前回の記事『JPEGファイルの主にDPIに関わる情報について』において、DPIの値がAPP0,APP1にそれぞれ格納されていること。
また、APP0,1は画像によって両方ある場合もどちらか片方しかない場合もあることについて説明してきました。

では、『プレビュー』に表示されているDPIはAPP0,APP1どちらを参照しているのでしょうか?

結論から言うと、今回の検証範囲では、macの「プレビュー」に表示されるDPIは、APP1、つまりExif/TIFF側の解像度情報を参照している可能性が高いと考えています。
後に説明しますが、ある画像ではAPP0(jfif)のDPIの値とAPP1(Exif/TIFF)のDPIの値がズレている状態にありました。

jfif

JFIF (DPI 96)

exif

EXIF (DPI 300)

このように、同じ画像でJFIFのDPIは「96」でEXIFのDPIは「300(300 / 1)」になっています。

そしてこの画像を、「プレビュー」で表示するとこのようになります。

img-1

このように300DPIとして判断されており、APP0,APP1の両方ある場合APP1のDPI値が参照されていることがわかりました。

また、これとは別でAPP0(JFIF)しかない画像ではJFIFのDPI値が参照されていました。
あくまでこれは「プレビュー」の話です。Photoshopもこのルールなのかもっと言えば印刷時がどうなのか(流石に印刷時はAPP1 EXIFを使っていると思いますが)断定するものではありません。

DPI変換アプリの試作にて ImageIOでAPP0のDPI値が期待通りに書き換わらなかった話

なぜ、このようにAPP0,APP1どちらを参照しているのかを確認する必要があったのかというと、DPIを確認・変更するアプリのプロトタイプを作成した際に、swiftのmageIOを使いプレビュー上のDPI表示は変更できたが、JPEG内部のAPP0/JFIF側のDPI値まで期待通りに書き換えられなかったためです。

そのときに作成したアプリが こちら です。

技術スタックとアプリ設計はこちらで考え、実際のコードはAIが書いています。
SwiftでJPEG画像のDPIを変更するアプリのプロトタイプで、
画像の読み込みと書き出しには、macOS標準の画像処理機能である ImageIO を使用しました。

READMEに書いている通り、具体的に言うと

app-structure
UI: SwiftUI
アプリ層 / 画像I/O: Swift
コア計算: C++
JPEG メタデータ処理: ImageIO

このように、画像ファイルの構造解析などの重くなりそうなコア計算部分はC++で作成し、UI部分にはSwiftUIを使い、ファイル選択や画面表示など、macアプリとしての操作部分はSwift側で担当しました。

最初は、「アプリ層 / 画像I/O: Swift」のImageIOの書き出し時にDPI関連のプロパティを指定すれば、JPEG内部のDPI情報もまとめて変更できるものと考えていました。

具体的には、APP1 のExif/TIFF側にある XResolution、YResolution、ResolutionUnit に加えて、APP0 のJFIF側にある XDensity、YDensity、DensityUnit も更新されることを期待していました。

しかし、実際に変換後のJPEGファイルをバイナリレベルで確認すると、APP1 のExif/TIFF側のDPI情報は変更されていたものの、APP0 のJFIF側のDPI情報は期待通りには変更されていませんでした。
詳しくは こちら にまとめられていますが、c++ 側で計算した値をswift層のImageIOで更新するのがうまくいかなかったようでした。

そして、原因をいろいろ調べたのですが今回は特定できませんでした。

『DPI変換アプリ』から『画像ファイル内部構造ビューアー』へ

swiftのImageIOを使ってデータの更新する構成では以上のような問題があった為、
「それならいっそのこと、画像のバイナリデータを確認できて必要な箇所を変更できるようにしよう」という考えに至りました。

それを得て、改良したのが こちら です。
画像のImageIOに任せていたJPEGメタデータ処理の一部を、C++側の自前パーサー/編集処理へ移していく方針にしたと言うことです。

前の『JPEGファイルの主にDPIに関わる情報について』で画像を使っていたのも、このアプリで解析したデータの画像でした。

pixcellens

おわりに -今後の課題も含む-

今回は、実際のJPEGファイルのDPIをmacの「プレビュー」で確認する方法から始まり、JPEG内部の APP0 と APP1 にあるDPI情報の違い、そしてSwiftのImageIOを使ったDPI変換アプリの試作から画像ファイル内部構造ビューアーへの改良についてまとめました。

最初は、「画像のDPIを300に変更する」という比較的単純な処理を想定していました。
しかし実際に調べてみると、JPEGファイルには APP0 のJFIF領域と APP1 のExif/TIFF領域に、それぞれDPIに関係する情報が存在する場合があります。
さらに、macの「プレビュー」に表示されるDPIが、JPEG内部のすべてのDPI情報を代表しているとは限らないことも分かりました。
ImageIOを使った試作では、Exif/TIFF側のDPI情報は変更できた一方で、APP0/JFIF側のDPI値は期待通りに書き換わらないという課題が見えました。
このことから、DPI情報を正確に扱うためには、画像ファイルを単なる画像としてではなく、バイナリ構造を持つファイルとして理解する必要があると感じました。
その結果、当初の「DPI変換アプリ」という方向から、JPEGやPNGなどの画像ファイル内部構造を確認できるビューアーへと方針を広げることにしました。

今後の課題として、JPEGだけでなくPNGやWebPなどにも対応したものを実装していきたいと考えています。
他の画像形式にも対応する場合、それぞれファイル構造が異なります。
PNGであればチャンク構造、WebPであればRIFFベースの構造を理解する必要があるので、今後他の画像形式についても学数を続けていこうと考えています。

正直、この『実際のJPEGファイルのDPI確認と編集について』の方については本来書く予定がなかったので構成がかなり緩いと思いますが何か参考になるものがあれば幸いです。

付録:JPEGファイルのイレギュラー形式

実際の画像ファイルを調べていると、以前の記事で書いたようなJEPGの基本構造だけでは説明しきれないJPEGに出会うことがあります。

それについて、軽く書いておこうと思います。

EOIの後ろにもデータが続くJPEG

JPEGファイルは通常、「FF D8」 の SOI から始まり、「FF D9」 の EOI で終わります。
そのため、バイナリデータを確認していると、「FF D9」 が出てきた時点で「ここでJPEGは終わり」と考えたくなります。
しかし、実際のJPEGファイルの中には、EOI の後ろにもデータが続いているものがあります。

eol

このようなファイルを見ると、破損したJPEGなのではないかと思うかもしれません。
または、画像本体の圧縮データにたまたま「FF D9」が続いただけだと思うかもしれません。
しかし、必ずしもそうとは限りません。

そもそも、実画像データないにたまたま「FF D9」が続くことはありません。
JPEGではこの問題を避けるための仕組みがあります。
JPEGのエントロピー符号化された画像データ内では、FF というバイトは特別な意味を持ち、JPEGの各種マーカーは、
FF D8 は SOI
FF D9 は EOI
FF DA は SOS
のように基本的に FF に続く1バイトで表されます。

そのため、圧縮画像データ内に通常のデータとして FF が現れる場合、そのまま FF だけを置くのではなく、後ろに 00 を挿入して 「FF 00」 のように表現します。
つまり、画像データ内に実データとして FF が出てきた場合はエスケープして 「FF 00」 として保存されるため、圧縮データの中に通常のデータとして 「FF D9」 が現れることは避けられます。
つまり、画像本体の圧縮データにたまたま「FF D9」が続苦という考えはあまり現実的ではないです

ではこれは何だと考えられるのでしょう?

MPFを含むJPEG

EOI の後ろにデータが続くJPEGと関係して、MPF、つまりMulti-Picture Formatを含むJPEGがあります。
MPFは、1つのJPEGファイルの中に複数の画像を持たせるための仕組みです。

たとえば、メイン画像とは別に、サムネイル、副画像、視差情報、深度情報、連写や特殊撮影に関係する画像などが含まれる場合があります。

先ほどの例として紹介したバイナリデータの画像では、メイン画像の後ろに別のJPEGらしきデータが続いており、副画像を取り出すと、主画像に似ているものの、色が薄く、モノクロに近い見え方をする画像が確認できました。
※個人の画像ではなかったので見せる事ができません。すみません。

このような場合、単純なJPEGビューアーではメイン画像だけが表示されるため、ファイル内部に副画像が存在していることに気づきにくいです。
しかし、バイナリレベルで解析すると、1つのファイルの中に複数の画像情報が含まれていることがあります。
DPI変換やメタデータ編集を行う場合、このような複合画像をどう扱うかは注意が必要です。

Author

rikuo kamadarikuo kamada

主にシステム面で学習したことをまとめています。 フロント、サーバー、インフラ、AIなど細かい分野に絞らずに広く発信していきます。