Skip to content

Commit 22705d3

Browse files
authored
Merge pull request #7956 from Cirras/obscure-bitmap-headers
Add support for reading `BITMAPV2INFOHEADER` and `BITMAPV3INFOHEADER`
2 parents 3037dea + 94fe670 commit 22705d3

File tree

6 files changed

+74
-11
lines changed

6 files changed

+74
-11
lines changed

Tests/images/bmp/q/rgb32h52.bmp

31.8 KB
Binary file not shown.

Tests/images/bmp/q/rgba32h56.bmp

31.8 KB
Binary file not shown.

Tests/test_bmp_reference.py

+3
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ def test_questionable() -> None:
4444
"pal8os2sp.bmp",
4545
"pal8rletrns.bmp",
4646
"rgb32bf-xbgr.bmp",
47+
"rgba32.bmp",
48+
"rgb32h52.bmp",
49+
"rgba32h56.bmp",
4750
]
4851
for f in get_files("q"):
4952
try:

Tests/test_file_bmp.py

+24-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import pytest
77

8-
from PIL import BmpImagePlugin, Image
8+
from PIL import BmpImagePlugin, Image, _binary
99

1010
from .helper import (
1111
assert_image_equal,
@@ -128,6 +128,29 @@ def test_load_dib() -> None:
128128
assert_image_equal_tofile(im, "Tests/images/clipboard_target.png")
129129

130130

131+
@pytest.mark.parametrize(
132+
"header_size, path",
133+
(
134+
(12, "g/pal8os2.bmp"),
135+
(40, "g/pal1.bmp"),
136+
(52, "q/rgb32h52.bmp"),
137+
(56, "q/rgba32h56.bmp"),
138+
(64, "q/pal8os2v2.bmp"),
139+
(108, "g/pal8v4.bmp"),
140+
(124, "g/pal8v5.bmp"),
141+
),
142+
)
143+
def test_dib_header_size(header_size, path):
144+
image_path = "Tests/images/bmp/" + path
145+
with open(image_path, "rb") as fp:
146+
data = fp.read()[14:]
147+
assert _binary.i32le(data) == header_size
148+
149+
dib = io.BytesIO(data)
150+
with Image.open(dib) as im:
151+
im.load()
152+
153+
131154
def test_save_dib(tmp_path: Path) -> None:
132155
outfile = str(tmp_path / "temp.dib")
133156

src/PIL/BmpImagePlugin.py

+23-10
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ def _accept(prefix: bytes) -> bool:
5353

5454

5555
def _dib_accept(prefix):
56-
return i32(prefix) in [12, 40, 64, 108, 124]
56+
return i32(prefix) in [12, 40, 52, 56, 64, 108, 124]
5757

5858

5959
# =============================================================================
@@ -83,8 +83,9 @@ def _bitmap(self, header=0, offset=0):
8383
# read the rest of the bmp header, without its size
8484
header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
8585

86-
# -------------------------------------------------- IBM OS/2 Bitmap v1
86+
# ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1
8787
# ----- This format has different offsets because of width/height types
88+
# 12: BITMAPCOREHEADER/OS21XBITMAPHEADER
8889
if file_info["header_size"] == 12:
8990
file_info["width"] = i16(header_data, 0)
9091
file_info["height"] = i16(header_data, 2)
@@ -93,9 +94,14 @@ def _bitmap(self, header=0, offset=0):
9394
file_info["compression"] = self.RAW
9495
file_info["palette_padding"] = 3
9596

96-
# --------------------------------------------- Windows Bitmap v2 to v5
97-
# v3, OS/2 v2, v4, v5
98-
elif file_info["header_size"] in (40, 64, 108, 124):
97+
# --------------------------------------------- Windows Bitmap v3 to v5
98+
# 40: BITMAPINFOHEADER
99+
# 52: BITMAPV2HEADER
100+
# 56: BITMAPV3HEADER
101+
# 64: BITMAPCOREHEADER2/OS22XBITMAPHEADER
102+
# 108: BITMAPV4HEADER
103+
# 124: BITMAPV5HEADER
104+
elif file_info["header_size"] in (40, 52, 56, 64, 108, 124):
99105
file_info["y_flip"] = header_data[7] == 0xFF
100106
file_info["direction"] = 1 if file_info["y_flip"] else -1
101107
file_info["width"] = i32(header_data, 0)
@@ -117,10 +123,13 @@ def _bitmap(self, header=0, offset=0):
117123
file_info["palette_padding"] = 4
118124
self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
119125
if file_info["compression"] == self.BITFIELDS:
120-
if len(header_data) >= 52:
121-
for idx, mask in enumerate(
122-
["r_mask", "g_mask", "b_mask", "a_mask"]
123-
):
126+
masks = ["r_mask", "g_mask", "b_mask"]
127+
if len(header_data) >= 48:
128+
if len(header_data) >= 52:
129+
masks.append("a_mask")
130+
else:
131+
file_info["a_mask"] = 0x0
132+
for idx, mask in enumerate(masks):
124133
file_info[mask] = i32(header_data, 36 + idx * 4)
125134
else:
126135
# 40 byte headers only have the three components in the
@@ -132,7 +141,7 @@ def _bitmap(self, header=0, offset=0):
132141
# location, but it is listed as a reserved component,
133142
# and it is not generally an alpha channel
134143
file_info["a_mask"] = 0x0
135-
for mask in ["r_mask", "g_mask", "b_mask"]:
144+
for mask in masks:
136145
file_info[mask] = i32(read(4))
137146
file_info["rgb_mask"] = (
138147
file_info["r_mask"],
@@ -175,9 +184,11 @@ def _bitmap(self, header=0, offset=0):
175184
32: [
176185
(0xFF0000, 0xFF00, 0xFF, 0x0),
177186
(0xFF000000, 0xFF0000, 0xFF00, 0x0),
187+
(0xFF000000, 0xFF00, 0xFF, 0x0),
178188
(0xFF000000, 0xFF0000, 0xFF00, 0xFF),
179189
(0xFF, 0xFF00, 0xFF0000, 0xFF000000),
180190
(0xFF0000, 0xFF00, 0xFF, 0xFF000000),
191+
(0xFF000000, 0xFF00, 0xFF, 0xFF0000),
181192
(0x0, 0x0, 0x0, 0x0),
182193
],
183194
24: [(0xFF0000, 0xFF00, 0xFF)],
@@ -186,9 +197,11 @@ def _bitmap(self, header=0, offset=0):
186197
MASK_MODES = {
187198
(32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX",
188199
(32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR",
200+
(32, (0xFF000000, 0xFF00, 0xFF, 0x0)): "BGXR",
189201
(32, (0xFF000000, 0xFF0000, 0xFF00, 0xFF)): "ABGR",
190202
(32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA",
191203
(32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA",
204+
(32, (0xFF000000, 0xFF00, 0xFF, 0xFF0000)): "BGAR",
192205
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
193206
(24, (0xFF0000, 0xFF00, 0xFF)): "BGR",
194207
(16, (0xF800, 0x7E0, 0x1F)): "BGR;16",

src/libImaging/Unpack.c

+24
Original file line numberDiff line numberDiff line change
@@ -790,6 +790,17 @@ ImagingUnpackBGRX(UINT8 *_out, const UINT8 *in, int pixels) {
790790
}
791791
}
792792

793+
static void
794+
ImagingUnpackBGXR(UINT8 *_out, const UINT8 *in, int pixels) {
795+
int i;
796+
for (i = 0; i < pixels; i++) {
797+
UINT32 iv = MAKE_UINT32(in[3], in[1], in[0], 255);
798+
memcpy(_out, &iv, sizeof(iv));
799+
in += 4;
800+
_out += 4;
801+
}
802+
}
803+
793804
static void
794805
ImagingUnpackXRGB(UINT8 *_out, const UINT8 *in, int pixels) {
795806
int i;
@@ -1090,6 +1101,17 @@ unpackBGRA16B(UINT8 *_out, const UINT8 *in, int pixels) {
10901101
}
10911102
}
10921103

1104+
static void
1105+
unpackBGAR(UINT8 *_out, const UINT8 *in, int pixels) {
1106+
int i;
1107+
for (i = 0; i < pixels; i++) {
1108+
UINT32 iv = MAKE_UINT32(in[3], in[1], in[0], in[2]);
1109+
memcpy(_out, &iv, sizeof(iv));
1110+
in += 4;
1111+
_out += 4;
1112+
}
1113+
}
1114+
10931115
/* Unpack to "CMYK" image */
10941116

10951117
static void
@@ -1584,6 +1606,7 @@ static struct {
15841606
{"RGB", "RGBA;L", 32, unpackRGBAL},
15851607
{"RGB", "RGBA;15", 16, ImagingUnpackRGBA15},
15861608
{"RGB", "BGRX", 32, ImagingUnpackBGRX},
1609+
{"RGB", "BGXR", 32, ImagingUnpackBGXR},
15871610
{"RGB", "XRGB", 32, ImagingUnpackXRGB},
15881611
{"RGB", "XBGR", 32, ImagingUnpackXBGR},
15891612
{"RGB", "YCC;P", 24, ImagingUnpackYCC},
@@ -1624,6 +1647,7 @@ static struct {
16241647
{"RGBA", "BGRA", 32, unpackBGRA},
16251648
{"RGBA", "BGRA;16L", 64, unpackBGRA16L},
16261649
{"RGBA", "BGRA;16B", 64, unpackBGRA16B},
1650+
{"RGBA", "BGAR", 32, unpackBGAR},
16271651
{"RGBA", "ARGB", 32, unpackARGB},
16281652
{"RGBA", "ABGR", 32, unpackABGR},
16291653
{"RGBA", "YCCA;P", 32, ImagingUnpackYCCA},

0 commit comments

Comments
 (0)