Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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