image/bmp: support v4 and v5 info header versions

Decode BITMAPV4INFOHEADER and BITMAPV5INFOHEADER in addition to
BITMAPINFOHEADER and check if any of their features are used. If this is
not the case, the bmp can be decoded as if it had the BITMAPINFOHEADER.
Otherwise an ErrUnsupported is returned.

The colormap.bmp and yellow_rose-small-v5.bmp files were generated using
imagemagick using the following conversions:

convert video-001.bmp -depth 8 -palette colormap.bmp
convert yellow_rose-small.bmp -format BMP5 yellow_rose-small-v5.bmp

The corresponding png files were created using imagemagick convert
without any arguments.

Fixes golang/go#27767

Change-Id: I5c0138b231c68132d39a29c71b61faa546921511
Reviewed-on: https://go-review.googlesource.com/c/141799
Reviewed-by: Nigel Tao <nigeltao@golang.org>
Run-TryBot: Nigel Tao <nigeltao@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/bmp/reader.go b/bmp/reader.go
index a0f2715..32bbda9 100644
--- a/bmp/reader.go
+++ b/bmp/reader.go
@@ -137,20 +137,26 @@
 	// We only support those BMP images that are a BITMAPFILEHEADER
 	// immediately followed by a BITMAPINFOHEADER.
 	const (
-		fileHeaderLen = 14
-		infoHeaderLen = 40
+		fileHeaderLen   = 14
+		infoHeaderLen   = 40
+		v4InfoHeaderLen = 108
+		v5InfoHeaderLen = 124
 	)
 	var b [1024]byte
-	if _, err := io.ReadFull(r, b[:fileHeaderLen+infoHeaderLen]); err != nil {
+	if _, err := io.ReadFull(r, b[:fileHeaderLen+4]); 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])
-	if readUint32(b[14:18]) != infoHeaderLen {
+	infoLen := readUint32(b[14:18])
+	if infoLen != infoHeaderLen && infoLen != v4InfoHeaderLen && infoLen != v5InfoHeaderLen {
 		return image.Config{}, 0, false, ErrUnsupported
 	}
+	if _, err := io.ReadFull(r, b[fileHeaderLen+4:fileHeaderLen+infoLen]); err != nil {
+		return image.Config{}, 0, false, err
+	}
 	width := int(int32(readUint32(b[18:22])))
 	height := int(int32(readUint32(b[22:26])))
 	if height < 0 {
@@ -161,12 +167,19 @@
 	}
 	// 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 compression is set to BITFIELDS, but the bitmask is set to the default bitmask
+	// that would be used if compression was set to 0, we can continue as if compression was 0
+	if compression == 3 && infoLen > infoHeaderLen &&
+		readUint32(b[54:58]) == 0xff0000 && readUint32(b[58:62]) == 0xff00 &&
+		readUint32(b[62:66]) == 0xff && readUint32(b[66:70]) == 0xff000000 {
+		compression = 0
+	}
 	if planes != 1 || compression != 0 {
 		return image.Config{}, 0, false, ErrUnsupported
 	}
 	switch bpp {
 	case 8:
-		if offset != fileHeaderLen+infoHeaderLen+256*4 {
+		if offset != fileHeaderLen+infoLen+256*4 {
 			return image.Config{}, 0, false, ErrUnsupported
 		}
 		_, err = io.ReadFull(r, b[:256*4])
@@ -181,12 +194,12 @@
 		}
 		return image.Config{ColorModel: pcm, Width: width, Height: height}, 8, topDown, nil
 	case 24:
-		if offset != fileHeaderLen+infoHeaderLen {
+		if offset != fileHeaderLen+infoLen {
 			return image.Config{}, 0, false, ErrUnsupported
 		}
 		return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 24, topDown, nil
 	case 32:
-		if offset != fileHeaderLen+infoHeaderLen {
+		if offset != fileHeaderLen+infoLen {
 			return image.Config{}, 0, false, ErrUnsupported
 		}
 		return image.Config{ColorModel: color.RGBAModel, Width: width, Height: height}, 32, topDown, nil
diff --git a/bmp/reader_test.go b/bmp/reader_test.go
index 91b4391..e54aafd 100644
--- a/bmp/reader_test.go
+++ b/bmp/reader_test.go
@@ -38,8 +38,10 @@
 // same pixel data.
 func TestDecode(t *testing.T) {
 	testCases := []string{
+		"colormap",
 		"video-001",
 		"yellow_rose-small",
+		"yellow_rose-small-v5",
 	}
 
 	for _, tc := range testCases {
diff --git a/testdata/colormap.bmp b/testdata/colormap.bmp
new file mode 100644
index 0000000..d5d394c
--- /dev/null
+++ b/testdata/colormap.bmp
Binary files differ
diff --git a/testdata/colormap.png b/testdata/colormap.png
new file mode 100644
index 0000000..c0dadc5
--- /dev/null
+++ b/testdata/colormap.png
Binary files differ
diff --git a/testdata/yellow_rose-small-v5.bmp b/testdata/yellow_rose-small-v5.bmp
new file mode 100644
index 0000000..b1d5ab4
--- /dev/null
+++ b/testdata/yellow_rose-small-v5.bmp
Binary files differ
diff --git a/testdata/yellow_rose-small-v5.png b/testdata/yellow_rose-small-v5.png
new file mode 100644
index 0000000..8dad370
--- /dev/null
+++ b/testdata/yellow_rose-small-v5.png
Binary files differ