From 4a4a1ee6adb1982e318ba3032ee3fa747d32f758 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Oct 2023 16:03:21 +1100 Subject: [PATCH 1/4] Simplified tile creation --- src/PIL/DdsImagePlugin.py | 47 ++++++++++++++------------------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index ad6561d91..40276cde2 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -394,31 +394,31 @@ class DdsImageFile(ImageFile.ImageFile): if fourcc == D3DFMT.DXT1: self._mode = "RGBA" self.pixel_format = "DXT1" - tile = Image._Tile("bcn", extents, data_offs, (1, self.pixel_format)) + n = 1 elif fourcc == D3DFMT.DXT3: self._mode = "RGBA" self.pixel_format = "DXT3" - tile = Image._Tile("bcn", extents, data_offs, (2, self.pixel_format)) + n = 2 elif fourcc == D3DFMT.DXT5: self._mode = "RGBA" self.pixel_format = "DXT5" - tile = Image._Tile("bcn", extents, data_offs, (3, self.pixel_format)) + n = 3 elif fourcc == D3DFMT.ATI1 or fourcc == D3DFMT.BC4U: self._mode = "L" self.pixel_format = "BC4" - tile = Image._Tile("bcn", extents, data_offs, (4, self.pixel_format)) + n = 4 elif fourcc == D3DFMT.BC5S: self._mode = "RGB" self.pixel_format = "BC5S" - tile = Image._Tile("bcn", extents, data_offs, (5, self.pixel_format)) + n = 5 elif fourcc == D3DFMT.BC5U: self._mode = "RGB" self.pixel_format = "BC5U" - tile = Image._Tile("bcn", extents, data_offs, (5, self.pixel_format)) + n = 5 elif fourcc == D3DFMT.ATI2: self._mode = "RGB" self.pixel_format = "BC5" - tile = Image._Tile("bcn", extents, data_offs, (5, self.pixel_format)) + n = 5 elif fourcc == D3DFMT.DX10: data_offs += 20 # ignoring flags which pertain to volume textures and cubemaps @@ -431,55 +431,42 @@ class DdsImageFile(ImageFile.ImageFile): ): self._mode = "RGBA" self.pixel_format = "BC1" - tile = Image._Tile( - "bcn", extents, data_offs, (1, self.pixel_format) - ) + n = 1 elif dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM): self._mode = "RGB" self.pixel_format = "BC5" - tile = Image._Tile( - "bcn", extents, data_offs, (5, self.pixel_format) - ) + n = 5 elif dxgi_format == DXGI_FORMAT.BC5_SNORM: self._mode = "RGB" self.pixel_format = "BC5S" - tile = Image._Tile( - "bcn", extents, data_offs, (5, self.pixel_format) - ) + n = 5 elif dxgi_format == DXGI_FORMAT.BC6H_UF16: self._mode = "RGB" self.pixel_format = "BC6H" - tile = Image._Tile( - "bcn", extents, data_offs, (6, self.pixel_format) - ) + n = 6 elif dxgi_format == DXGI_FORMAT.BC6H_SF16: self._mode = "RGB" self.pixel_format = "BC6HS" - tile = Image._Tile( - "bcn", extents, data_offs, (6, self.pixel_format) - ) + n = 6 elif dxgi_format in (DXGI_FORMAT.BC7_TYPELESS, DXGI_FORMAT.BC7_UNORM): self._mode = "RGBA" self.pixel_format = "BC7" - tile = Image._Tile( - "bcn", extents, data_offs, (7, self.pixel_format) - ) + n = 7 elif dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB: self._mode = "RGBA" self.pixel_format = "BC7" self.info["gamma"] = 1 / 2.2 - tile = Image._Tile( - "bcn", extents, data_offs, (7, self.pixel_format) - ) + n = 7 elif dxgi_format in ( DXGI_FORMAT.R8G8B8A8_TYPELESS, DXGI_FORMAT.R8G8B8A8_UNORM, DXGI_FORMAT.R8G8B8A8_UNORM_SRGB, ): self._mode = "RGBA" - tile = Image._Tile("raw", extents, 0, ("RGBA", 0, 1)) + self.tile = [Image._Tile("raw", extents, 0, ("RGBA", 0, 1))] if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB: self.info["gamma"] = 1 / 2.2 + return else: msg = f"Unimplemented DXGI format {dxgi_format}" raise NotImplementedError(msg) @@ -487,7 +474,7 @@ class DdsImageFile(ImageFile.ImageFile): msg = f"Unimplemented pixel format {repr(fourcc)}" raise NotImplementedError(msg) - self.tile = [tile] + self.tile = [Image._Tile("bcn", extents, data_offs, (n, self.pixel_format))] else: msg = f"Unknown pixel format flags {repr(pfflags)}" raise NotImplementedError(msg) From 8fbb6103786e47a376059970690583adf5108aed Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Oct 2023 17:33:04 +1100 Subject: [PATCH 2/4] Derive bit count from number of modes --- src/PIL/DdsImagePlugin.py | 44 +++++++++++++++++---------------------- 1 file changed, 19 insertions(+), 25 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 40276cde2..5965477aa 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -403,7 +403,7 @@ class DdsImageFile(ImageFile.ImageFile): self._mode = "RGBA" self.pixel_format = "DXT5" n = 3 - elif fourcc == D3DFMT.ATI1 or fourcc == D3DFMT.BC4U: + elif fourcc in (D3DFMT.BC4U, D3DFMT.ATI1): self._mode = "L" self.pixel_format = "BC4" n = 4 @@ -411,11 +411,7 @@ class DdsImageFile(ImageFile.ImageFile): self._mode = "RGB" self.pixel_format = "BC5S" n = 5 - elif fourcc == D3DFMT.BC5U: - self._mode = "RGB" - self.pixel_format = "BC5U" - n = 5 - elif fourcc == D3DFMT.ATI2: + elif fourcc in (D3DFMT.BC5U, D3DFMT.ATI2): self._mode = "RGB" self.pixel_format = "BC5" n = 5 @@ -488,32 +484,30 @@ def _save(im, fp, filename): msg = f"cannot write mode {im.mode} as DDS" raise OSError(msg) - pixel_flags = DDPF.RGB - if im.mode == "RGB": - rgba_mask = struct.pack("<4I", 0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000) - bit_count = 24 - elif im.mode == "RGBA": - pixel_flags |= DDPF.ALPHAPIXELS - rgba_mask = struct.pack("<4I", 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000) - bit_count = 32 + if im.mode == "RGBA": + pixel_flags = DDPF.RGB | DDPF.ALPHAPIXELS + rgba_mask = (0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000) + r, g, b, a = im.split() im = Image.merge("RGBA", (a, r, g, b)) + elif im.mode == "RGB": + pixel_flags = DDPF.RGB + rgba_mask = (0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000) elif im.mode == "LA": pixel_flags = DDPF.LUMINANCE | DDPF.ALPHAPIXELS - rgba_mask = struct.pack("<4I", 0x000000FF, 0x000000FF, 0x000000FF, 0x0000FF00) - bit_count = 16 + rgba_mask = (0x000000FF, 0x000000FF, 0x000000FF, 0x0000FF00) else: # im.mode == "L" pixel_flags = DDPF.LUMINANCE - rgba_mask = struct.pack("<4I", 0xFF000000, 0xFF000000, 0xFF000000, 0x00000000) - bit_count = 8 + rgba_mask = (0xFF000000, 0xFF000000, 0xFF000000, 0x00000000) flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT - + bit_count = len(im.getbands()) * 8 stride = (im.width * bit_count + 7) // 8 + fp.write( o32(DDS_MAGIC) + struct.pack( - " Date: Sat, 21 Oct 2023 19:12:52 +1100 Subject: [PATCH 3/4] Simplified creating raw tiles --- src/PIL/DdsImagePlugin.py | 68 ++++++++++++++++++++------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 5965477aa..bf3807c84 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -348,14 +348,14 @@ class DdsImageFile(ImageFile.ImageFile): masks = struct.unpack("<4I", header.read(16)) if flags & DDSD.CAPS: header.seek(20, io.SEEK_CUR) - extents = (0, 0) + self.size + n = 0 + rawmode = None if pfflags & DDPF.RGB: # Texture contains uncompressed RGB data masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} if bitcount == 24: - rawmode = masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF] self._mode = "RGB" - self.tile = [("raw", extents, 0, (rawmode[::-1], 0, 1))] + rawmode = masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF] elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS: self._mode = "RGBA" rawmode = ( @@ -364,31 +364,27 @@ class DdsImageFile(ImageFile.ImageFile): + masks[0x0000FF00] + masks[0x000000FF] ) - self.tile = [("raw", extents, 0, (rawmode[::-1], 0, 1))] else: msg = f"Unsupported bitcount {bitcount} for {pfflags}" raise OSError(msg) + rawmode = rawmode[::-1] elif pfflags & DDPF.ALPHA: if bitcount == 8: self._mode = "L" - self.tile = [("raw", extents, 0, ("L", 0, 1))] else: msg = f"Unsupported bitcount {bitcount} for {pfflags}" raise OSError(msg) elif pfflags & DDPF.LUMINANCE: if bitcount == 8: self._mode = "L" - self.tile = [("raw", extents, 0, ("L", 0, 1))] elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS: self._mode = "LA" - self.tile = [("raw", extents, 0, ("LA", 0, 1))] else: msg = f"Unsupported bitcount {bitcount} for {pfflags}" raise OSError(msg) elif pfflags & DDPF.PALETTEINDEXED8: self._mode = "P" self.palette = ImagePalette.raw("RGBA", self.fp.read(1024)) - self.tile = [("raw", (0, 0) + self.size, 0, "L")] elif pfflags & DDPF.FOURCC: data_offs = header_size + 4 if fourcc == D3DFMT.DXT1: @@ -444,36 +440,39 @@ class DdsImageFile(ImageFile.ImageFile): self._mode = "RGB" self.pixel_format = "BC6HS" n = 6 - elif dxgi_format in (DXGI_FORMAT.BC7_TYPELESS, DXGI_FORMAT.BC7_UNORM): + elif dxgi_format in ( + DXGI_FORMAT.BC7_TYPELESS, + DXGI_FORMAT.BC7_UNORM, + DXGI_FORMAT.BC7_UNORM_SRGB, + ): self._mode = "RGBA" self.pixel_format = "BC7" n = 7 - elif dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB: - self._mode = "RGBA" - self.pixel_format = "BC7" - self.info["gamma"] = 1 / 2.2 - n = 7 + if dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB: + self.info["gamma"] = 1 / 2.2 elif dxgi_format in ( DXGI_FORMAT.R8G8B8A8_TYPELESS, DXGI_FORMAT.R8G8B8A8_UNORM, DXGI_FORMAT.R8G8B8A8_UNORM_SRGB, ): self._mode = "RGBA" - self.tile = [Image._Tile("raw", extents, 0, ("RGBA", 0, 1))] if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB: self.info["gamma"] = 1 / 2.2 - return else: msg = f"Unimplemented DXGI format {dxgi_format}" raise NotImplementedError(msg) else: msg = f"Unimplemented pixel format {repr(fourcc)}" raise NotImplementedError(msg) + else: + msg = f"Unknown pixel format flags {pfflags}" + raise NotImplementedError(msg) + extents = (0, 0) + self.size + if n: self.tile = [Image._Tile("bcn", extents, data_offs, (n, self.pixel_format))] else: - msg = f"Unknown pixel format flags {repr(pfflags)}" - raise NotImplementedError(msg) + self.tile = [Image._Tile("raw", extents, 0, rawmode or self.mode)] def load_seek(self, pos): pass @@ -484,21 +483,25 @@ def _save(im, fp, filename): msg = f"cannot write mode {im.mode} as DDS" raise OSError(msg) - if im.mode == "RGBA": - pixel_flags = DDPF.RGB | DDPF.ALPHAPIXELS - rgba_mask = (0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000) - - r, g, b, a = im.split() - im = Image.merge("RGBA", (a, r, g, b)) - elif im.mode == "RGB": - pixel_flags = DDPF.RGB - rgba_mask = (0x00FF0000, 0x0000FF00, 0x000000FF, 0x00000000) - elif im.mode == "LA": - pixel_flags = DDPF.LUMINANCE | DDPF.ALPHAPIXELS - rgba_mask = (0x000000FF, 0x000000FF, 0x000000FF, 0x0000FF00) - else: # im.mode == "L" + alpha = im.mode[-1] == "A" + if im.mode[0] == "L": pixel_flags = DDPF.LUMINANCE - rgba_mask = (0xFF000000, 0xFF000000, 0xFF000000, 0x00000000) + rawmode = im.mode + if alpha: + rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF] + else: + rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000] + else: + pixel_flags = DDPF.RGB + rawmode = im.mode[::-1] + rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF] + + if alpha: + r, g, b, a = im.split() + im = Image.merge("RGBA", (a, r, g, b)) + if alpha: + pixel_flags |= DDPF.ALPHAPIXELS + rgba_mask.append(0xFF000000 if alpha else 0) flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT bit_count = len(im.getbands()) * 8 @@ -522,7 +525,6 @@ def _save(im, fp, filename): + struct.pack("<4I", *rgba_mask) # dwRGBABitMask + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0) ) - rawmode = "LA" if im.mode == "LA" else im.mode[::-1] ImageFile._save(im, fp, [Image._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) From 93e0f39ff3614e865bf4e177e12b916d46d5a990 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Oct 2023 19:14:46 +1100 Subject: [PATCH 4/4] Removed "mode-" prefix from image names that are not modes --- Tests/images/{mode-ati1.dds => ati1.dds} | Bin Tests/images/{mode-ati1.png => ati1.png} | Bin Tests/images/{mode-ati2.dds => ati2.dds} | Bin Tests/test_file_dds.py | 6 +++--- 4 files changed, 3 insertions(+), 3 deletions(-) rename Tests/images/{mode-ati1.dds => ati1.dds} (100%) rename Tests/images/{mode-ati1.png => ati1.png} (100%) rename Tests/images/{mode-ati2.dds => ati2.dds} (100%) diff --git a/Tests/images/mode-ati1.dds b/Tests/images/ati1.dds similarity index 100% rename from Tests/images/mode-ati1.dds rename to Tests/images/ati1.dds diff --git a/Tests/images/mode-ati1.png b/Tests/images/ati1.png similarity index 100% rename from Tests/images/mode-ati1.png rename to Tests/images/ati1.png diff --git a/Tests/images/mode-ati2.dds b/Tests/images/ati2.dds similarity index 100% rename from Tests/images/mode-ati2.dds rename to Tests/images/ati2.dds diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index fbc523ed1..72bb2df7b 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -10,8 +10,8 @@ from .helper import assert_image_equal, assert_image_equal_tofile, hopper TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds" TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds" TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds" -TEST_FILE_ATI1 = "Tests/images/mode-ati1.dds" -TEST_FILE_ATI2 = "Tests/images/mode-ati2.dds" +TEST_FILE_ATI1 = "Tests/images/ati1.dds" +TEST_FILE_ATI2 = "Tests/images/ati2.dds" TEST_FILE_DX10_BC5_TYPELESS = "Tests/images/bc5_typeless.dds" TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds" TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds" @@ -347,7 +347,7 @@ def test_open(mode, test_file): ("LA", "Tests/images/uncompressed_la.png"), ("RGB", "Tests/images/hopper.png"), ("RGBA", "Tests/images/pil123rgba.png"), - ("L", "Tests/images/mode-ati1.dds"), + ("L", TEST_FILE_ATI1), ], ) def test_save(mode, test_file, tmp_path):