ImageMagick で BMP処理


# 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フォーマット概要

- Bitmap Storage
    - https://msdn.microsoft.com/en-us/library/dd183391(v=vs.85).aspx
 
元々、Windowsで画像を処理する際のメモリ内部構造(いわゆる DIB)なので、ファイル形式の仕様もC言語の構造体で書かれています。あと、数値はリトルエンディアンです。

- BITMAPFILEHEADER structure
    - https://msdn.microsoft.com/en-us/library/dd183374(v=vs.85).aspx

```c
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コンテナ図

![bmp-container.png](https://qiita-image-store.s3.amazonaws.com/0/1798/9af896d0-2784-ab70-5f14-dfd6c795908a.png)

## バージョン毎の解説

### BMPv3

BMP はよく未圧縮の画像フォーマットとして認識される事が多いのですが、図でいう Info に相当する BITMAPINFOHEADER (BMP v4 の一つ前という意味で BMPv3) で Compression (圧縮) 方式を指定できます。

- BITMAPINFOHEADER structure
    - https://msdn.microsoft.com/en-us/library/dd183376(v=vs.85).aspx

### BMPv4

また、BMP の特徴的な機能として BMPv4 で追加された RGBAMask があります、これは R,G,B,A のビット長をバラバラに持つ事ができる素晴らしい機能です。

BMPv4 は Windows95 / Windows NT からの仕様です。

- BITMAPV4HEADER structure
    - https://msdn.microsoft.com/en-us/library/dd183380(v=vs.85).aspx

### BMPv5

最近、HDR が一般に少しずつ広まってきています。古いモニタに引きずられた sRGB 以外にも、DPI-P3 や Adobe RGB といった広い色域で鮮やかな色に対応するのに、ICC プロファイルで切り替える方法がメジャーですが、実は BMPv5 で対応しています。
BMPv5 は Windows 98 / Windows2000 からの仕様です。

- BITMAPV5HEADER structure
    - https://msdn.microsoft.com/en-us/library/dd183381(v=vs.85).aspx

ただし、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 |
|---|---|
|![Opaopa-transparent.bmp.png](https://qiita-image-store.s3.amazonaws.com/0/1798/76c4dd3d-01cb-1fe8-91a5-ec8cea7f0cb7.png) | ![Opaopa-transparent-bmp3.bmp.png](https://qiita-image-store.s3.amazonaws.com/0/1798/24561a5b-704d-86f1-15be-a1f5839e30c6.png)|

## bmp:subtype (string)

BMPv4 で追加された RGBAMask を活用して、柔軟に RGBA の深度を指定できます。特によく使われる以下の４形式を 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 |
|---|---|
![26040.bmp.png](https://qiita-image-store.s3.amazonaws.com/0/1798/9d5a63d7-81ce-da84-adbb-199fdfebc7ee.png) | ![26040-ARGB4444.bmp.png](https://qiita-image-store.s3.amazonaws.com/0/1798/0cb597b2-fd80-8e1b-b06d-9f56ab278c57.png)|

ディザをかけずに、色の値を足切りする減色で微妙です。。
減色の良い仕方は別エントリで解説する予定ですが、とりあえずは以下の解説が参考になります。

- RGB444 変換で劣化しない画像を ImageMagick で作る
  - https://blog.awm.jp/2016/07/21/rgb444/

## 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|
|---|
|![Opaopa-transparent-bmp3-alpha.bmp.png](https://qiita-image-store.s3.amazonaws.com/0/1798/26e6b228-d6c3-9e75-c17c-efc4393d1b36.png)|

とはいえ、X を透明度として処理するのは公式仕様にないので、例外として考えた方がよいです。(例えば、macOS のプレビューは対応していません)

# 追記

## BMPv4 Gamma の注意 (追記)

こちらでガンマ値について言及されていたので、折角なので解説します。

- https://qiita.com/yuji38kwmt/items/ae04b861b1958df02212

BMPv3 では無かったガンマ値フィールドが BMPv4 で追加されています。
ところが、実はこのガンマ値フィールドは滅多に使われません。

その少し上に CSType フィールドがありまして、これに LCS_CALIBRATED_RGB が指定された時だけ、ガンマ値フィールドが参照される決まりです。
実際には LCS_sRGB が入る事が多いので、ガンマ値フィールドには 0 が埋まります。(仕様では ignore としか書いてないので 0 が埋まる保証はなさそうですが。。)

- 2.1.1.14 LogicalColorSpace Enumeration
  - https://msdn.microsoft.com/en-us/library/cc250396.aspx

```c
 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 の仕様を見ると。

- https://msdn.microsoft.com/en-us/library/dd183380(v=vs.85).aspx

```
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
```
- https://www.imagemagick.org/script/formats.php

実際そういう意図をもってコードが書かれていますが、実際には BMPv5 (相当)が生成される事が多いです。
ImageMagick はデフォルトで rendering intent を 2(Perceptual)とするので、この条件にひっかかるからです。

```c
if ((image->rendering_intent != UndefinedIntent) ||
    (profile != (StringInfo *) NULL))
  {
    bmp_info.size=124;
    extra_size+=16;
  }
```
- https://github.com/ImageMagick/ImageMagick/blob/7.0.7-35/coders/bmp.c#L1806

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 ヘッダをバイナリ分解するツールがあるので、どうぞ。 <del>v4 まで対応してます。</del>

- http://app.awm.jp/image.js/format.html
 -  レポジトリ: https://github.com/yoya/image.js

<del>v5 も簡単なので時間があったら対応します。</del> <ins> v5 も対応しました </ins>

# 参考

- https://imagemagick.org/script/formats.php#supported
- http://www.fileformat.info/format/bmp/egff.htm