mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 09:57:43 +03:00 
			
		
		
		
	Merge pull request #7956 from Cirras/obscure-bitmap-headers
Add support for reading `BITMAPV2INFOHEADER` and `BITMAPV3INFOHEADER`
This commit is contained in:
		
						commit
						22705d3da5
					
				
							
								
								
									
										
											BIN
										
									
								
								Tests/images/bmp/q/rgb32h52.bmp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/bmp/q/rgb32h52.bmp
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 32 KiB  | 
							
								
								
									
										
											BIN
										
									
								
								Tests/images/bmp/q/rgba32h56.bmp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								Tests/images/bmp/q/rgba32h56.bmp
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 32 KiB  | 
| 
						 | 
					@ -44,6 +44,9 @@ def test_questionable() -> None:
 | 
				
			||||||
        "pal8os2sp.bmp",
 | 
					        "pal8os2sp.bmp",
 | 
				
			||||||
        "pal8rletrns.bmp",
 | 
					        "pal8rletrns.bmp",
 | 
				
			||||||
        "rgb32bf-xbgr.bmp",
 | 
					        "rgb32bf-xbgr.bmp",
 | 
				
			||||||
 | 
					        "rgba32.bmp",
 | 
				
			||||||
 | 
					        "rgb32h52.bmp",
 | 
				
			||||||
 | 
					        "rgba32h56.bmp",
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    for f in get_files("q"):
 | 
					    for f in get_files("q"):
 | 
				
			||||||
        try:
 | 
					        try:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,7 +5,7 @@ from pathlib import Path
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import pytest
 | 
					import pytest
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from PIL import BmpImagePlugin, Image
 | 
					from PIL import BmpImagePlugin, Image, _binary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
from .helper import (
 | 
					from .helper import (
 | 
				
			||||||
    assert_image_equal,
 | 
					    assert_image_equal,
 | 
				
			||||||
| 
						 | 
					@ -128,6 +128,29 @@ def test_load_dib() -> None:
 | 
				
			||||||
        assert_image_equal_tofile(im, "Tests/images/clipboard_target.png")
 | 
					        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:
 | 
					def test_save_dib(tmp_path: Path) -> None:
 | 
				
			||||||
    outfile = str(tmp_path / "temp.dib")
 | 
					    outfile = str(tmp_path / "temp.dib")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -53,7 +53,7 @@ def _accept(prefix: bytes) -> bool:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _dib_accept(prefix):
 | 
					def _dib_accept(prefix):
 | 
				
			||||||
    return i32(prefix) in [12, 40, 64, 108, 124]
 | 
					    return i32(prefix) in [12, 40, 52, 56, 64, 108, 124]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# =============================================================================
 | 
					# =============================================================================
 | 
				
			||||||
| 
						 | 
					@ -83,8 +83,9 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        # read the rest of the bmp header, without its size
 | 
					        # read the rest of the bmp header, without its size
 | 
				
			||||||
        header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4)
 | 
					        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
 | 
					        # ----- This format has different offsets because of width/height types
 | 
				
			||||||
 | 
					        # 12: BITMAPCOREHEADER/OS21XBITMAPHEADER
 | 
				
			||||||
        if file_info["header_size"] == 12:
 | 
					        if file_info["header_size"] == 12:
 | 
				
			||||||
            file_info["width"] = i16(header_data, 0)
 | 
					            file_info["width"] = i16(header_data, 0)
 | 
				
			||||||
            file_info["height"] = i16(header_data, 2)
 | 
					            file_info["height"] = i16(header_data, 2)
 | 
				
			||||||
| 
						 | 
					@ -93,9 +94,14 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            file_info["compression"] = self.RAW
 | 
					            file_info["compression"] = self.RAW
 | 
				
			||||||
            file_info["palette_padding"] = 3
 | 
					            file_info["palette_padding"] = 3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # --------------------------------------------- Windows Bitmap v2 to v5
 | 
					        # --------------------------------------------- Windows Bitmap v3 to v5
 | 
				
			||||||
        # v3, OS/2 v2, v4, v5
 | 
					        #  40: BITMAPINFOHEADER
 | 
				
			||||||
        elif file_info["header_size"] in (40, 64, 108, 124):
 | 
					        #  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["y_flip"] = header_data[7] == 0xFF
 | 
				
			||||||
            file_info["direction"] = 1 if file_info["y_flip"] else -1
 | 
					            file_info["direction"] = 1 if file_info["y_flip"] else -1
 | 
				
			||||||
            file_info["width"] = i32(header_data, 0)
 | 
					            file_info["width"] = i32(header_data, 0)
 | 
				
			||||||
| 
						 | 
					@ -117,10 +123,13 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            file_info["palette_padding"] = 4
 | 
					            file_info["palette_padding"] = 4
 | 
				
			||||||
            self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
 | 
					            self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"])
 | 
				
			||||||
            if file_info["compression"] == self.BITFIELDS:
 | 
					            if file_info["compression"] == self.BITFIELDS:
 | 
				
			||||||
 | 
					                masks = ["r_mask", "g_mask", "b_mask"]
 | 
				
			||||||
 | 
					                if len(header_data) >= 48:
 | 
				
			||||||
                    if len(header_data) >= 52:
 | 
					                    if len(header_data) >= 52:
 | 
				
			||||||
                    for idx, mask in enumerate(
 | 
					                        masks.append("a_mask")
 | 
				
			||||||
                        ["r_mask", "g_mask", "b_mask", "a_mask"]
 | 
					                    else:
 | 
				
			||||||
                    ):
 | 
					                        file_info["a_mask"] = 0x0
 | 
				
			||||||
 | 
					                    for idx, mask in enumerate(masks):
 | 
				
			||||||
                        file_info[mask] = i32(header_data, 36 + idx * 4)
 | 
					                        file_info[mask] = i32(header_data, 36 + idx * 4)
 | 
				
			||||||
                else:
 | 
					                else:
 | 
				
			||||||
                    # 40 byte headers only have the three components in the
 | 
					                    # 40 byte headers only have the three components in the
 | 
				
			||||||
| 
						 | 
					@ -132,7 +141,7 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                    # location, but it is listed as a reserved component,
 | 
					                    # location, but it is listed as a reserved component,
 | 
				
			||||||
                    # and it is not generally an alpha channel
 | 
					                    # and it is not generally an alpha channel
 | 
				
			||||||
                    file_info["a_mask"] = 0x0
 | 
					                    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[mask] = i32(read(4))
 | 
				
			||||||
                file_info["rgb_mask"] = (
 | 
					                file_info["rgb_mask"] = (
 | 
				
			||||||
                    file_info["r_mask"],
 | 
					                    file_info["r_mask"],
 | 
				
			||||||
| 
						 | 
					@ -175,9 +184,11 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
                32: [
 | 
					                32: [
 | 
				
			||||||
                    (0xFF0000, 0xFF00, 0xFF, 0x0),
 | 
					                    (0xFF0000, 0xFF00, 0xFF, 0x0),
 | 
				
			||||||
                    (0xFF000000, 0xFF0000, 0xFF00, 0x0),
 | 
					                    (0xFF000000, 0xFF0000, 0xFF00, 0x0),
 | 
				
			||||||
 | 
					                    (0xFF000000, 0xFF00, 0xFF, 0x0),
 | 
				
			||||||
                    (0xFF000000, 0xFF0000, 0xFF00, 0xFF),
 | 
					                    (0xFF000000, 0xFF0000, 0xFF00, 0xFF),
 | 
				
			||||||
                    (0xFF, 0xFF00, 0xFF0000, 0xFF000000),
 | 
					                    (0xFF, 0xFF00, 0xFF0000, 0xFF000000),
 | 
				
			||||||
                    (0xFF0000, 0xFF00, 0xFF, 0xFF000000),
 | 
					                    (0xFF0000, 0xFF00, 0xFF, 0xFF000000),
 | 
				
			||||||
 | 
					                    (0xFF000000, 0xFF00, 0xFF, 0xFF0000),
 | 
				
			||||||
                    (0x0, 0x0, 0x0, 0x0),
 | 
					                    (0x0, 0x0, 0x0, 0x0),
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
                24: [(0xFF0000, 0xFF00, 0xFF)],
 | 
					                24: [(0xFF0000, 0xFF00, 0xFF)],
 | 
				
			||||||
| 
						 | 
					@ -186,9 +197,11 @@ class BmpImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            MASK_MODES = {
 | 
					            MASK_MODES = {
 | 
				
			||||||
                (32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX",
 | 
					                (32, (0xFF0000, 0xFF00, 0xFF, 0x0)): "BGRX",
 | 
				
			||||||
                (32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR",
 | 
					                (32, (0xFF000000, 0xFF0000, 0xFF00, 0x0)): "XBGR",
 | 
				
			||||||
 | 
					                (32, (0xFF000000, 0xFF00, 0xFF, 0x0)): "BGXR",
 | 
				
			||||||
                (32, (0xFF000000, 0xFF0000, 0xFF00, 0xFF)): "ABGR",
 | 
					                (32, (0xFF000000, 0xFF0000, 0xFF00, 0xFF)): "ABGR",
 | 
				
			||||||
                (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA",
 | 
					                (32, (0xFF, 0xFF00, 0xFF0000, 0xFF000000)): "RGBA",
 | 
				
			||||||
                (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA",
 | 
					                (32, (0xFF0000, 0xFF00, 0xFF, 0xFF000000)): "BGRA",
 | 
				
			||||||
 | 
					                (32, (0xFF000000, 0xFF00, 0xFF, 0xFF0000)): "BGAR",
 | 
				
			||||||
                (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
 | 
					                (32, (0x0, 0x0, 0x0, 0x0)): "BGRA",
 | 
				
			||||||
                (24, (0xFF0000, 0xFF00, 0xFF)): "BGR",
 | 
					                (24, (0xFF0000, 0xFF00, 0xFF)): "BGR",
 | 
				
			||||||
                (16, (0xF800, 0x7E0, 0x1F)): "BGR;16",
 | 
					                (16, (0xF800, 0x7E0, 0x1F)): "BGR;16",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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
 | 
					static void
 | 
				
			||||||
ImagingUnpackXRGB(UINT8 *_out, const UINT8 *in, int pixels) {
 | 
					ImagingUnpackXRGB(UINT8 *_out, const UINT8 *in, int pixels) {
 | 
				
			||||||
    int i;
 | 
					    int i;
 | 
				
			||||||
| 
						 | 
					@ -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 */
 | 
					/* Unpack to "CMYK" image */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
| 
						 | 
					@ -1584,6 +1606,7 @@ static struct {
 | 
				
			||||||
    {"RGB", "RGBA;L", 32, unpackRGBAL},
 | 
					    {"RGB", "RGBA;L", 32, unpackRGBAL},
 | 
				
			||||||
    {"RGB", "RGBA;15", 16, ImagingUnpackRGBA15},
 | 
					    {"RGB", "RGBA;15", 16, ImagingUnpackRGBA15},
 | 
				
			||||||
    {"RGB", "BGRX", 32, ImagingUnpackBGRX},
 | 
					    {"RGB", "BGRX", 32, ImagingUnpackBGRX},
 | 
				
			||||||
 | 
					    {"RGB", "BGXR", 32, ImagingUnpackBGXR},
 | 
				
			||||||
    {"RGB", "XRGB", 32, ImagingUnpackXRGB},
 | 
					    {"RGB", "XRGB", 32, ImagingUnpackXRGB},
 | 
				
			||||||
    {"RGB", "XBGR", 32, ImagingUnpackXBGR},
 | 
					    {"RGB", "XBGR", 32, ImagingUnpackXBGR},
 | 
				
			||||||
    {"RGB", "YCC;P", 24, ImagingUnpackYCC},
 | 
					    {"RGB", "YCC;P", 24, ImagingUnpackYCC},
 | 
				
			||||||
| 
						 | 
					@ -1624,6 +1647,7 @@ static struct {
 | 
				
			||||||
    {"RGBA", "BGRA", 32, unpackBGRA},
 | 
					    {"RGBA", "BGRA", 32, unpackBGRA},
 | 
				
			||||||
    {"RGBA", "BGRA;16L", 64, unpackBGRA16L},
 | 
					    {"RGBA", "BGRA;16L", 64, unpackBGRA16L},
 | 
				
			||||||
    {"RGBA", "BGRA;16B", 64, unpackBGRA16B},
 | 
					    {"RGBA", "BGRA;16B", 64, unpackBGRA16B},
 | 
				
			||||||
 | 
					    {"RGBA", "BGAR", 32, unpackBGAR},
 | 
				
			||||||
    {"RGBA", "ARGB", 32, unpackARGB},
 | 
					    {"RGBA", "ARGB", 32, unpackARGB},
 | 
				
			||||||
    {"RGBA", "ABGR", 32, unpackABGR},
 | 
					    {"RGBA", "ABGR", 32, unpackABGR},
 | 
				
			||||||
    {"RGBA", "YCCA;P", 32, ImagingUnpackYCCA},
 | 
					    {"RGBA", "YCCA;P", 32, ImagingUnpackYCCA},
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user