環境
- go version go1.10.2 windows/amd64
- Windows10
- Windows Paint
- ImageMagick 7.0.4-10 Q16 x64 2017-02-18
背景
画像フォーマットをgo言語で確認したいです。
package main
import (
"fmt"
"image"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"os"
_ "golang.org/x/image/bmp"
)
func main() {
imageFile := os.Args[1]
f, _ := os.Open(imageFile)
defer f.Close()
//画像フォーマットを取得
_, format, err := image.DecodeConfig(f)
if err != nil {
fmt.Println(err)
return
}
//png, jpg, bmp, etc
fmt.Println(format)
}
$ go build
$ ReadImage.exe rena.png
png
rena.png
は以下の画像です。
問題
BMPファイルを読み込んだら、「bmp: unsupported BMP image」というエラーが発生しました。
- 読み込んだBMPファイル(
rena-convert.bmp
):ImageMagickのconvertコマンドで変換 - 変換元のBMPファイル(
rena.bmp
):Windows Paintで保存
$ # OriginalのBMPファイル
$ ReadImage.exe rena.bmp
bmp
$ # ImageMagickのconvertコマンドで変換(後でdiffを確認するため、何も変換しない)
$ convert rena.bmp rena-convert.bmp
$ # 変換したBMPファイル
$ ReadImage.exe rena-convert.bmp
bmp: unsupported BMP image
原因調査
identifyコマンドで、画像情報を確認
Imagemagickのidentify -verbose
で、画像の情報を確認します。
$ # OriginalのBMPファイルのidentify
$ identify --verbose rena.bmp > rena.bmp.identify
$ # 変換したBMPファイルのidentify
$ identify --verbose rena-convert.bmp > rena-convert.bmp.identify
# diffを出力
$ diff -u rena.bmp.identify rena-convert.bmp.identify > diff.txt
--- rena.bmp.identify 2018-05-24 11:14:45.086983300 +0900
+++ rena-convert.bmp.identify 2018-05-24 11:30:34.944869600 +0900
@@ -1,5 +1,5 @@
-Image: rena.bmp
- Format: BMP3 (Microsoft Windows bitmap image (V3))
+Image: rena-convert.bmp
+ Format: BMP (Microsoft Windows bitmap image)
Class: DirectClass
Geometry: 400x300+0+0
Resolution: 56.69x56.69
@@ -49,7 +49,6 @@
skewness: 0.725172
entropy: 0.917511
Rendering intent: Perceptual
- Gamma: 0.454545
Chromaticity:
red primary: (0.64,0.33)
green primary: (0.3,0.6)
@@ -68,15 +67,15 @@
Compression: None
Orientation: Undefined
Properties:
- date:create: 2018-05-24T11:12:30+09:00
- date:modify: 2018-05-24T11:12:30+09:00
+ date:create: 2018-05-24T11:30:11+09:00
+ date:modify: 2018-05-24T11:30:11+09:00
signature: eb75c46523cfb88d54ff8ca96b10ef9ea8fba88bd52f849f76bf70818977ac48
Artifacts:
verbose: true
Tainted: False
Filesize: 360KB
Number pixels: 120K
- Pixels per second: 59.98MB
+ Pixels per second: 120.1MB
User time: 0.000u
- Elapsed time: 0:01.002
+ Elapsed time: 0:01.000
Version: ImageMagick 7.0.4-10 Q16 x64 2017-02-18 http://www.imagemagick.org
分かったこと
- 変換前のBMP(rena.bmp)
- format: BMP3 (Microsoft Windows bitmap image (V3))
- Gamma あり
- 変換後のBMP(rena-convert.bmp)
- format: BMP (Microsoft Windows bitmap image)
- Gamma なし
Image: rena.bmp
Format: BMP3 (Microsoft Windows bitmap image (V3))
Class: DirectClass
Geometry: 400x300+0+0
Resolution: 56.69x56.69
Print size: 7.05592x5.29194
Units: PixelsPerCentimeter
Type: TrueColor
Endianess: Undefined
Colorspace: sRGB
Depth: 8-bit
Channel depth:
Red: 8-bit
Green: 8-bit
Blue: 8-bit
Channel statistics:
Pixels: 120000
Red:
min: 0 (0)
max: 255 (1)
mean: 131.226 (0.514612)
standard deviation: 57.9187 (0.227132)
kurtosis: -1.03751
skewness: -0.166315
entropy: 0.956321
Green:
min: 0 (0)
max: 228 (0.894118)
mean: 79.6108 (0.312199)
standard deviation: 41.0085 (0.160818)
kurtosis: -0.19308
skewness: 0.604492
entropy: 0.919659
Blue:
min: 0 (0)
max: 243 (0.952941)
mean: 64.5745 (0.253233)
standard deviation: 33.3705 (0.130865)
kurtosis: 1.05109
skewness: 1.09448
entropy: 0.876554
Image statistics:
Overall:
min: 0 (0)
max: 255 (1)
mean: 91.8038 (0.360015)
standard deviation: 53.5228 (0.209893)
kurtosis: -0.391929
skewness: 0.725172
entropy: 0.917511
Rendering intent: Perceptual
Gamma: 0.454545
Chromaticity:
red primary: (0.64,0.33)
green primary: (0.3,0.6)
blue primary: (0.15,0.06)
white point: (0.3127,0.329)
Matte color: grey74
Background color: white
Border color: srgb(223,223,223)
Transparent color: none
Interlace: None
Intensity: Undefined
Compose: Over
Page geometry: 400x300+0+0
Dispose: Undefined
Iterations: 0
Compression: None
Orientation: Undefined
Properties:
date:create: 2018-05-24T11:12:30+09:00
date:modify: 2018-05-24T11:12:30+09:00
signature: eb75c46523cfb88d54ff8ca96b10ef9ea8fba88bd52f849f76bf70818977ac48
Artifacts:
verbose: true
Tainted: False
Filesize: 360KB
Number pixels: 120K
Pixels per second: 59.98MB
User time: 0.000u
Elapsed time: 0:01.002
Version: ImageMagick 7.0.4-10 Q16 x64 2017-02-18 http://www.imagemagick.org
BMPのバージョン
BMP(のヘッダ)には、いくつかの種類があります。
https://qiita.com/yoya/items/3d588687a30175601885 引用
「INFO」がBMP v3に対応します。
(v4の一つ前という点でv3らしい)
ヘッダのサイズ(dibHeaderSize)は、バージョンの判定に使われます。
- Windows BMP v2
- Info header size: 12
- Info header name: BITMAPCOREHEADER
- Windows BMP v3
- Info header size: 40
- Info header name: BITMAPINFOHEADER
- Windows BMP v4
- Info header size: 108
- Info header name: BITMAPV4HEADER
- Windows BMP v5
- Info header size: 124
- Info header name: BITMAPV5HEADER
http://fileformats.archiveteam.org/wiki/BMP#Well-known_versions 参考
ちなみに、BITMAPINFOHEADER
(BMP v3)が標準のようです。
With OS/2 no longer supported after Windows 2000, for now the common Windows format is the BITMAPINFOHEADER header.
https://en.wikipedia.org/wiki/BMP_file_format 引用
ImageMagickの対応フォーマット
ImageMagickに対応しているフォーマットは、BMP, BMP2, BMP3の3種類です。
$ identify -list format
Format Module Mode Description
------------------------------------------
...
BMP* BMP rw- Microsoft Windows bitmap image
BMP2* BMP -w- Microsoft Windows bitmap image (V2)
BMP3* BMP -w- Microsoft Windows bitmap image (V3)
...
By default the BMP format is version 4. Use BMP3 and BMP2 to write versions 3 and 2 respectively.
https://www.imagemagick.org/script/formats.php 引用
- デフォルトはv4?(後述の疑問点参照)
go言語のBMP読み込み処理
// ErrUnsupported means that the input BMP image uses a valid but unsupported
// feature.
var ErrUnsupported = errors.New("bmp: unsupported BMP image")
//...
func decodeConfig(r io.Reader) (config image.Config, bitsPerPixel int, topDown bool, err error) {
// We only support those BMP images that are a BITMAPFILEHEADER
// immediately followed by a BITMAPINFOHEADER.
const (
fileHeaderLen = 14
infoHeaderLen = 40
)
var b [1024]byte
if _, err := io.ReadFull(r, b[:fileHeaderLen+infoHeaderLen]); err != nil {
return image.Config{}, 0, false, err
}
if string(b[:2]) != "BM" {
return image.Config{}, 0, false, errors.New("bmp: invalid format")
}
offset := readUint32(b[10:14])
//【yuji38kwmt追記】ヘッダサイズを取得して、判定
if readUint32(b[14:18]) != infoHeaderLen {
return image.Config{}, 0, false, ErrUnsupported
}
width := int(int32(readUint32(b[18:22])))
height := int(int32(readUint32(b[22:26])))
//.....
}
- ヘッダサイズ(
readUint32(b[14:18])
)が40
でなければ、ErrUnsupported
- ⇒BMP v3しか対応していない
- 変換前(
rena.bmp
)のヘッダサイズ:40
⇒ BMP v3 - 変換後(
rena-convert.bmp
)のヘッダサイズ:124
⇒ BMP v5?
【補足】ファイルサイズの比較
$ #ファイルサイズをbyte単位で表示
$ du -ba rena.bmp rena-convert.bmp
360054 rena.bmp
360138 rena-convert.bmp
- 変換したファイルの方が、84byte大きい。
84 = 124 -40
- ⇒ 変換前と後で、ヘッダサイズのみ増加
解決
-define
オプションで、BMPのフォーマットを指定します。
$ convert -define bmp:format=bmp3 rena.bmp rena-bmp3.bmp
valid values are bmp2, bmp3, and bmp4. This option can be useful when the method of prepending "BMP2:" to the output filename is inconvenient or is not available, such as when using the mogrify utility.
https://www.imagemagick.org/script/command-line-options.php#define 引用
補足
-
-format
オプションでは、BMP3を指定できない - 出力先のファイル名の先頭に、
BMP3:
を指定できる
# NG: BMP3に変換されない
$ convert -format bmp3 rena.bmp rena-convert.bmp
# OK: BMP3に変換できる
$ convert rena.bmp BMP3:rena-convert.bmp
まとめ
- Windows Bitmapファイル(のヘッダ)には、いくつかのバージョンがある
- ImageMagickでBMPに変換すると、デフォルトBMP v4(v5?)
- go言語の標準ライブラリは、BMP v3のみ対応している
参考にしたサイト
【バイナリファイル入門】Bitmapファイルを手書きで作って遊んでみる
Microsoft Windows Bitmap File Format Summary
Command-line Tools: Identify @ ImageMagick
Command-line Tools: Convert @ ImageMagick
画像フォーマットをバイナリ解析するツール
補足
なぜフォーマットが複数あるのか?
- BMPはポータブルのフォーマットではない
- OSのAPIが変わると、BMPフォーマットも変わる
The structure of BMP format files is closely tied to the API of both Windows and OS/2. In this regard, BMP was never meant to be a portable format or used for bitmap data interchange between different operating systems. As each of these operating system APIs has changed, the BMP format has changed along with it.
The reason that there are different headers is that Microsoft extended the DIB format several times. The new extended headers can be used with some GDI functions instead of the older ones, providing more functionality.
go言語はモノクロBMPもサポートしていない
ソースコードには、以下のコメントが記載されています。
We only support 1 plane, 8 or 24 bits per pixel and no compression.
モノクロBMPはbpp(bits per pixel)が"1"なので、サポート対象外です。
// We only support 1 plane, 8 or 24 bits per pixel and no compression.
planes, bpp, compression := readUint16(b[26:28]), readUint16(b[28:30]), readUint32(b[30:34])
if planes != 1 || compression != 0 {
return image.Config{}, 0, false, ErrUnsupported
}
switch bpp {
case 8:
//...
case 24:
//...
case 32:
//...
}
リトルエンディアン
全ての整数値は、リトルエンディアンで格納されます。
All of the integer values are stored in little-endian format (i.e. least-significant byte first).
https://en.wikipedia.org/wiki/BMP_file_format 引用
したがって、go言語のコードでは、以下のようなリトルエンディアンに対応させた関数が定義されています。
func readUint16(b []byte) uint16 {
return uint16(b[0]) | uint16(b[1])<<8
}
func readUint32(b []byte) uint32 {
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
}
分からなかったこと
@yoya さんが、解説してくれました。
https://qiita.com/yoya/items/3d588687a30175601885#bmpv4-gamma-%E3%81%AE%E6%B3%A8%E6%84%8F-%E8%BF%BD%E8%A8%98
BMP v4?v5?
- ドキュメントには、「デフォルトBMP v4」と記載されている
- ヘッダサイズを確認すると、「124」でBMP v5
gamma値が消えた理由
- BMP v3には、ガンマ値を格納する場所がない。
- BMP v4には、ガンマ値を格納する場所がある。
- ImageMagickの
identify
コマンドで表示されたガンマ値は、どのように取得した? - ヘッダを拡張した際に、ゼロ埋めしたからガンマ値が消えた?
「第4回名古屋若手Webエンジニア交流会」でLTしました。