Skip to content

Commit

Permalink
Merge pull request #7956 from Cirras/obscure-bitmap-headers
Browse files Browse the repository at this point in the history
Add support for reading `BITMAPV2INFOHEADER` and `BITMAPV3INFOHEADER`
  • Loading branch information
radarhere committed Apr 13, 2024
2 parents 3037dea + 94fe670 commit 22705d3
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 11 deletions.
Binary file added Tests/images/bmp/q/rgb32h52.bmp
Binary file not shown.
Binary file added Tests/images/bmp/q/rgba32h56.bmp
Binary file not shown.
3 changes: 3 additions & 0 deletions Tests/test_bmp_reference.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ def test_questionable() -> None:
"pal8os2sp.bmp",
"pal8rletrns.bmp",
"rgb32bf-xbgr.bmp",
"rgba32.bmp",
"rgb32h52.bmp",
"rgba32h56.bmp",
]
for f in get_files("q"):
try:
Expand Down
25 changes: 24 additions & 1 deletion Tests/test_file_bmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import pytest

from PIL import BmpImagePlugin, Image
from PIL import BmpImagePlugin, Image, _binary

from .helper import (
assert_image_equal,
Expand Down Expand Up @@ -128,6 +128,29 @@ def test_load_dib() -> None:
assert_image_equal_tofile(im, "Tests/images/clipboard_target.png")


@pytest.mark.parametrize(
"header_size, path",
(
(12, "g/pal8os2.bmp"),
(40, "g/pal1.bmp"),
(52, "q/rgb32h52.bmp"),
(56, "q/rgba32h56.bmp"),
(64, "q/pal8os2v2.bmp"),
(108, "g/pal8v4.bmp"),
(124, "g/pal8v5.bmp"),
),
)
def test_dib_header_size(header_size, path):
image_path = "Tests/images/bmp/" + path
with open(image_path, "rb") as fp:
data = fp.read()[14:]
assert _binary.i32le(data) == header_size

dib = io.BytesIO(data)
with Image.open(dib) as im:
im.load()


def test_save_dib(tmp_path: Path) -> None:
outfile = str(tmp_path / "temp.dib")

Expand Down
33 changes: 23 additions & 10 deletions src/PIL/BmpImagePlugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ def _accept(prefix: bytes) -> bool:


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


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

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

# --------------------------------------------- Windows Bitmap v2 to v5
# v3, OS/2 v2, v4, v5
elif file_info["header_size"] in (40, 64, 108, 124):
# --------------------------------------------- Windows Bitmap v3 to v5
# 40: BITMAPINFOHEADER
# 52: BITMAPV2HEADER
# 56: BITMAPV3HEADER
# 64: BITMAPCOREHEADER2/OS22XBITMAPHEADER
# 108: BITMAPV4HEADER
# 124: BITMAPV5HEADER
elif file_info["header_size"] in (40, 52, 56, 64, 108, 124):
file_info["y_flip"] = header_data[7] == 0xFF
file_info["direction"] = 1 if file_info["y_flip"] else -1
file_info["width"] = i32(header_data, 0)
Expand All @@ -117,10 +123,13 @@ def _bitmap(self, header=0, offset=0):
file_info["palette_padding"] = 4
self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
if file_info["compression"] == self.BITFIELDS:
if len(header_data) >= 52:
for idx, mask in enumerate(
["r_mask", "g_mask", "b_mask", "a_mask"]
):
masks = ["r_mask", "g_mask", "b_mask"]
if len(header_data) >= 48:
if len(header_data) >= 52:
masks.append("a_mask")
else:
file_info["a_mask"] = 0x0
for idx, mask in enumerate(masks):
file_info[mask] = i32(header_data, 36 + idx * 4)
else:
# 40 byte headers only have the three components in the
Expand All @@ -132,7 +141,7 @@ def _bitmap(self, header=0, offset=0):
# location, but it is listed as a reserved component,
# and it is not generally an alpha channel
file_info["a_mask"] = 0x0
for mask in ["r_mask", "g_mask", "b_mask"]:
for mask in masks:
file_info[mask] = i32(read(4))
file_info["rgb_mask"] = (
file_info["r_mask"],
Expand Down Expand Up @@ -175,9 +184,11 @@ def _bitmap(self, header=0, offset=0):
32: [
(0xFF0000, 0xFF00, 0xFF, 0x0),
(0xFF000000, 0xFF0000, 0xFF00, 0x0),
(0xFF000000, 0xFF00, 0xFF, 0x0),
(0xFF000000, 0xFF0000, 0xFF00, 0xFF),
(0xFF, 0xFF00, 0xFF0000, 0xFF000000),
(0xFF0000, 0xFF00, 0xFF, 0xFF000000),
(0xFF000000, 0xFF00, 0xFF, 0xFF0000),
(0x0, 0x0, 0x0, 0x0),
],
24: [(0xFF0000, 0xFF00, 0xFF)],
Expand All @@ -186,9 +197,11 @@ def _bitmap(self, header=0, offset=0):
MASK_MODES = {
(32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX",
(32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR",
(32, (0xFF000000, 0xFF00, 0xFF, 0x0)): "BGXR",
(32, (0xFF000000, 0xFF0000, 0xFF00, 0xFF)): "ABGR",
(32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA",
(32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA",
(32, (0xFF000000, 0xFF00, 0xFF, 0xFF0000)): "BGAR",
(32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
(24, (0xFF0000, 0xFF00, 0xFF)): "BGR",
(16, (0xF800, 0x7E0, 0x1F)): "BGR;16",
Expand Down
24 changes: 24 additions & 0 deletions src/libImaging/Unpack.c
Original file line number Diff line number Diff line change
Expand Up @@ -790,6 +790,17 @@ ImagingUnpackBGRX(UINT8 *_out, const UINT8 *in, int pixels) {
}
}

static void
ImagingUnpackBGXR(UINT8 *_out, const UINT8 *in, int pixels) {
int i;
for (i = 0; i < pixels; i++) {
UINT32 iv = MAKE_UINT32(in[3], in[1], in[0], 255);
memcpy(_out, &iv, sizeof(iv));
in += 4;
_out += 4;
}
}

static void
ImagingUnpackXRGB(UINT8 *_out, const UINT8 *in, int pixels) {
int i;
Expand Down Expand Up @@ -1090,6 +1101,17 @@ unpackBGRA16B(UINT8 *_out, const UINT8 *in, int pixels) {
}
}

static void
unpackBGAR(UINT8 *_out, const UINT8 *in, int pixels) {
int i;
for (i = 0; i < pixels; i++) {
UINT32 iv = MAKE_UINT32(in[3], in[1], in[0], in[2]);
memcpy(_out, &iv, sizeof(iv));
in += 4;
_out += 4;
}
}

/* Unpack to "CMYK" image */

static void
Expand Down Expand Up @@ -1584,6 +1606,7 @@ static struct {
{"RGB", "RGBA;L", 32, unpackRGBAL},
{"RGB", "RGBA;15", 16, ImagingUnpackRGBA15},
{"RGB", "BGRX", 32, ImagingUnpackBGRX},
{"RGB", "BGXR", 32, ImagingUnpackBGXR},
{"RGB", "XRGB", 32, ImagingUnpackXRGB},
{"RGB", "XBGR", 32, ImagingUnpackXBGR},
{"RGB", "YCC;P", 24, ImagingUnpackYCC},
Expand Down Expand Up @@ -1624,6 +1647,7 @@ static struct {
{"RGBA", "BGRA", 32, unpackBGRA},
{"RGBA", "BGRA;16L", 64, unpackBGRA16L},
{"RGBA", "BGRA;16B", 64, unpackBGRA16B},
{"RGBA", "BGAR", 32, unpackBGAR},
{"RGBA", "ARGB", 32, unpackARGB},
{"RGBA", "ABGR", 32, unpackABGR},
{"RGBA", "YCCA;P", 32, ImagingUnpackYCCA},
Expand Down

0 comments on commit 22705d3

Please sign in to comment.