前回書いた通り、PNG/JPEG間にはメタデータ互換性がまったく無いことがわかった。となると、変換が必須だ。そして、自前で書くのは間違いなく難しい。そこで、車輪の再発明はせずに既存のものを使うことにする。候補は2つ。
- GDI+のImageクラスおよびその派生
Windows標準ライブラリなので使うのは楽。C++との相性は良い。ディスク上のファイルを介さずに使えそう(後述)。 - exiftools( https://exiftool.org/ )
Perlで書かれており、C++との連携はちょっと大変かも。起動に時間がかかるらしい(公式ドキュメントより)。ディスクを介さずにやり取りできるか不明。
メタデータの扱いはexiftoolsのほうが優秀そうだけど、起動に時間がかかるというのはMozJpegGUIのデザインと合わない。一方、GDI+を使う方はその点大丈夫そうだ。また、IStreamを介したLoad/Saveができるのも大きい。これならメモリ上の操作だけで済み、高速になるからだ。すなわち、以下の手順とすれば一時ファイルを使わずに操作できる。
- 読み込み用メモリストリーム(memory stream)の作成
MozJpegがメモリ上に作成した画像データを、SHCreateMemStream()を使ってそのままメモリストリーム化する。
SHCreateMemStream 関数 (shlwapi.h) - Win32 apps | Microsoft Learn - Bitmapオブジェクトの生成
Bitmapオブジェクトのコンストラクタに、先程生成したメモリストリームを渡す。Bitmap::Bitmap(IN IStream,IN BOOL) (gdiplusheaders.h) - Win32 apps | Microsoft Learn
- メタデータ(実際はExif関連のみ?)の書き込み
SetPropertyItem()を使ってメタデータをBitmapオブジェクトに設定する。Image::SetPropertyItem (gdiplusheaders.h) - Win32 apps | Microsoft Learn
- 書き込み用メモリストリームの作成
SHCreateMemStream()を使って書き込み用の空っぽメモリストリームを作る。 - Jpeg画像の保存
BitmapオブジェクトのSave()を使って、書き込み用メモリストリームにJpeg画像を保存する。これはメモリストリームなので、ファイル書き込みは発生しない。 - 書き込み用メモリストリームからのデータ読み出し
Seek()にてアクセス位置をストリーム先頭に移動したあと、Read()にてストリームから書き込み用配列へデータをコピーする。IStream::Seek (objidl.h) - Win32 apps | Microsoft Learn
ISequentialStream::Read (objidl.h) - Win32 apps | Microsoft Learn
- 読み込み用メモリストリームや書き込み用メモリストリームの廃棄
読み込み用メモリストリームや書き込み用メモリストリームは古式ゆかしいCOMオブジェクトなので、Release()する。
一方、GDI+には懸念点もあった。保存時に画像データの再エンコードをしないか?だ。もし再エンコードしてしまうようなら、MozJpegGUIの意味がない。そこで、MozJpegのデフォルト挙動(元画像のメタデータを全コピー)と、それに加えGDI+によりメタデータを複写した場合とでファイルのバイナリ値を比較した。
何しろバイナリデータなので直接の比較が困難なので、テキストデータ(値を16進文字表記にしたものを1バイト1行で書いたもの)に変換し、それをWinMergeでテキスト比較した。結果は以下の左側に見える通り、合計3領域で食い違い(細い3本のオレンジ線)はあったものの大半では一致していた。
Jpegのファイル構造を解析したところ、最初の領域における差分はエンディアンの違いによるものだった。その後そこそこ大きな一致領域があるが、これはタグID: 0x927Cのメーカーノート領域。なんと20kバイトもありやがります。敵だ。そして2番目の差分領域はやはりエンディアンの違いによるメタデータ差異。最後の差分領域はGDI+では存在しない部分で、サムネイルデータの直後に存在した。この領域はどこからも参照されておらず、ゴミデータに見える。試しにASCII変換したらRalpha,32.3.+.0.403_0_f500という文字列が出てきた。これはタグ0x131:Softwareと同じ内容だが、なぜここにあるのかは不明。
まとめると、出てきた差分はエンディアン起因か、ゴミデータ起因のもので画像は完全一致していた。うん、これならGDI+をつかってメタデータをコピーしても大丈夫だな。
それにしても、今回は補助ツールを2個も作ってしまった(画像のメタデータ解析ツール、バイナリ->ASCII変換ツール)。思ったよりもおおごとになってしまった・・・