BMP 画像
Windows の標準画像フォーマットです。
ImageMagick は、BMP2(v2)、BMP3(v3)、BMP(v4) に対応しています。尚、BMPv5 の読み書きも出来ますが、BMP(v4)の少しデータが大きい版として処理するだけで、ICCプロファイルの読み出しや埋め込みは出来ません。
(追記: 2019年2月中旬に ICCプロファイル読み書き対応しました)
% identify -list format | grep BMP
BMP* BMP rw- Microsoft Windows bitmap image
BMP2* BMP -w- Microsoft Windows bitmap image (V2)
BMP3* BMP -w- Microsoft Windows bitmap image (V3)
はじめに BMP ファイルの version 毎の違いを並べて、後半で ImageMagick の操作オプションを紹介します。
BMPフォーマット概要
元々、Windowsで画像を処理する際のメモリ内部構造(いわゆる DIB)なので、ファイル形式の仕様もC言語の構造体で書かれています。あと、数値はリトルエンディアンです。
- BITMAPFILEHEADER structure
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;
Win32 API で使われるデータ型で、WORD が 2バイト(16ビット)、DWORD が 4バイト(32ビット)なので、
- BITMAPFILEHEADER
bfType | bfSize | bfReserved1 | bfReserved2 | bfOffBits |
---|---|---|---|---|
WORD | DWORD | WORD | WORD | DWORD |
2バイト | 4バイト | 2バイト | 2バイト | 4バイト |
の並びを表現します。
BMPコンテナ図
バージョン毎の解説
BMPv3
BMP はよく未圧縮の画像フォーマットとして認識される事が多いのですが、図でいう Info に相当する BITMAPINFOHEADER (BMP v4 の一つ前という意味で BMPv3) で Compression (圧縮) 方式を指定できます。
- BITMAPINFOHEADER structure
BMPv4
また、BMP の特徴的な機能として BMPv4 で追加された RGBAMask があります、これは R,G,B,A のビット長をバラバラに持つ事ができる素晴らしい機能です。
BMPv4 は Windows95 / Windows NT からの仕様です。
- BITMAPV4HEADER structure
BMPv5
最近、HDR が一般に少しずつ広まってきています。古いモニタに引きずられた sRGB 以外にも、DPI-P3 や Adobe RGB といった広い色域で鮮やかな色に対応するのに、ICC プロファイルで切り替える方法がメジャーですが、実は BMPv5 で対応しています。
BMPv5 は Windows 98 / Windows2000 からの仕様です。
- BITMAPV5HEADER structure
ただし、ICCプロファイル埋め込みBMPファイルの読み書きに対応している画像ビューアはほぼ皆無なので、フィイルフォーマットとしては実質無視されている機能です。
-define bmp:〜
bmp オプションパラメータを解説します。
bmp:format (string)
BMP のバージョンを指示できます。
- bmp2
- bmp3
- bmp4
例えば、-define bmp:format=bmp3 のようにすると、透明度データが消えます。
BMPv4 で追加された RGBAMask で透明度に対応したので、BMPv3 では基本的(後でこの例外にも触れます)に透明度を持てないからです。
% convert Opaopa-transparent.gif Opaopa-transparent.bmp
% convert Opaopa-transparent.gif -define bmp:format=bmp3 Opaopa-transparent-bmp3.bmp
(BMP 画像は Qiita に貼れないので、PNG で貼り付けます)
Opaopa-transparent.bmp.png | Opaopa-transparent-bmp3.bmp.png |
---|---|
bmp:subtype (string)
BMPv4 で追加された RGBAMask を活用して、柔軟に RGBA の深度を指定できます。特によく使われる以下の4形式を subtype で指定できます。
- ARGB4444
- ARGB1555
- RGB555
- RGB565
ARGB4444 を適用すると、こんな感じです。(ちなみに透明度のない画像に ARGBxxxx 指定しても無視されるのでご注意を)
% convert 26040.png 26040.bmp
% convert 26040.png -define bmp:subtype=ARGB4444 26040-ARGB4444.bmp
26040.bmp.png | 26040-ARGB4444.bmp.png |
---|---|
ディザをかけずに、色の値を足切りする減色で微妙です。。
減色の良い仕方は別エントリで解説する予定ですが、とりあえずは以下の解説が参考になります。
- RGB444 変換で劣化しない画像を ImageMagick で作る
bmp3:alpha (boolean)
先ほど、bmp3 にすると透明度が消えましたが、実は裏技がありまして。
BMPv3 でも BitCount:32 で BGRX形式にすると、仕様では未使用の X に 0 以外の値が入っている時にそれを Alpha値として使うシステムがあるそうです。bmp3:alpha は BitCount:32 に強制する事で、そのような状況にも対応します。
% convert Opaopa-transparent.gif -define bmp:format=bmp3 -define bmp3:alpha=true Opaopa-transparent-bmp3-alpha.bmp
% convert Opaopa-transparent-bmp3-alpha.bmp Opaopa-transparent-bmp3-alpha.bmp.png
Opaopa-transparent-bmp3-alpha.bmp.png |
---|
とはいえ、X を透明度として処理するのは公式仕様にないので、例外として考えた方がよいです。(例えば、macOS のプレビューは対応していません)
追記
BMPv4 Gamma の注意 (追記)
こちらでガンマ値について言及されていたので、折角なので解説します。
BMPv3 では無かったガンマ値フィールドが BMPv4 で追加されています。
ところが、実はこのガンマ値フィールドは滅多に使われません。
その少し上に CSType フィールドがありまして、これに LCS_CALIBRATED_RGB が指定された時だけ、ガンマ値フィールドが参照される決まりです。
実際には LCS_sRGB が入る事が多いので、ガンマ値フィールドには 0 が埋まります。(仕様では ignore としか書いてないので 0 が埋まる保証はなさそうですが。。)
- 2.1.1.14 LogicalColorSpace Enumeration
typedef enum
{
LCS_CALIBRATED_RGB = 0x00000000,
LCS_sRGB = 0x73524742,
LCS_WINDOWS_COLOR_SPACE = 0x57696E20
} LogicalColorSpace;
Windows らしくリトルエンディアンで数値を埋めるので、BMP画像ファイルをテキストとして開くと、"BGRs" の4文字が見えるでしょう。もしかしたら " niW" の方かもしれません。
興味のある方はテキストエディタでご確認下さい。
identify で BMPv3 は gamma が表示され、BMPv4 だと gamma 無し。仕様と逆じゃない?
ImageMagick はガンマ値 1/2.2(=0.4545)がデフォルトで、BMPv3 のように画像ファイルにガンマ値を持たない場合、その 0.4545 を表示します。一方、BMPv4 で identify がガンマ値を表示しないのは、ほぼ不具合に近い微妙な動作でして。。
BMPv4 のガンマ値(&その隣のCIEXYZ-3刺激値)は、CSType が Calibrated RGB の時だけ使われるフィールドで、大抵 CSType は sRGB なので、その場合にはガンマ値に 0 が埋まってます(←お行儀がよければ)。ImageMagick はそれを場合分けせず本当にガンマ値が 0 だとして取り込んでしまい、0 は本来ありえない値なので、identify では表示しないという流れです。
恐らく不具合だと思いますが、今何故それでも問題ないの?というコードを一通り検証したくなるので、問題提起は後回しにしてます。
gamma:0 は異常な値でプログラムの通る場所によっては警告もでますし、特に 0 をデフォルト値扱いするようにも見えません。
ちなみに PNG だと gamma:0 の時は gAMA チャンクを埋めない条件が入ってる上に 0.75 以下だと sRGB として処理。JPEG はそもそも gamma は処理しない。という事でそれらへの変換に問題はなさそうです。多分、綱渡りかなと。。
こうなっている事情の推測
BMPv4 の仕様を見ると。
bV4CSType
The color space of the DIB. The following table lists the value for bV4CSType.
Value | Meaning |
---|---|
LCS_CALIBRATED_RGB | This value indicates that endpoints and gamma values are given in the appropriate fields. |
となってまして。v4 では LCS_CALIBRATED_RGB しか選べないと解釈するのが自然なので、ImageMagick はそれ前提でコードを書いてるのかなと。
そういえば、sRGB 規格自体 98年に出たので、BMPv4(Windows95)の頃には存在しない規格を指定しようもなく、BMPv5(Windows98)が出た頃に後付で BMPv4 にも sRGB を入れるベンダーが出てきたと考えると、MSのドキュメントがこうなっているのは自然な気がします。
BMP画像ファイル出力のデフォルトは v4 と v5 どっち?
マニュアルでは v4 です。
By default the BMP format is version 4
実際そういう意図をもってコードが書かれていますが、実際には BMPv5 (相当)が生成される事が多いです。
ImageMagick はデフォルトで rendering intent を 2(Perceptual)とするので、この条件にひっかかるからです。
if ((image->rendering_intent != UndefinedIntent) ||
(profile != (StringInfo *) NULL))
{
bmp_info.size=124;
extra_size+=16;
}
BMPv5 では rendering indent と icc profile のフィールドが追加されたので、こうしているのですが、それをわざわざ打ち消すような処理をしない限り、v5 を出力する結果になります。
convert in.png -intent Undefined out.bmp
このように明示的に intent を打ち消せば BMPv4 を出力します。サイズが勿体無い場合は、どうぞ。ただし、ICC Profile が入っていた時に事故りそうなので、そこは注意で。
あと、rendering intent は icc profile に従属する概念なので、条件式では profile だけ見たほうが良さそうですが、画像フォーマット的には今のが正しいので、変更しよという提案はちょっと面倒そうです。
そもそも BMP version について。
実は ImageMagick に明示的な v5 は無いです。単に v4 のサイズがちょっと大きな形式として処理します。
v2 は BMP2、v3 はBMP3 としてるのに、v4 も v5 もごっちゃに BMP として扱います。
なので、あまり拘っても意味がないかもしれません。
最後に
BMP ヘッダをバイナリ分解するツールがあるので、どうぞ。 v4 まで対応してます。
v5 も簡単なので時間があったら対応します。 v5 も対応しました