diff --git a/Tests/images/l.dds b/Tests/images/l.dds new file mode 100644 index 000000000..b82282587 Binary files /dev/null and b/Tests/images/l.dds differ diff --git a/Tests/images/l.png b/Tests/images/l.png new file mode 100644 index 000000000..9d22a26a4 Binary files /dev/null and b/Tests/images/l.png differ diff --git a/Tests/images/rgb.dds b/Tests/images/rgb.dds new file mode 100644 index 000000000..ad0e630b4 Binary files /dev/null and b/Tests/images/rgb.dds differ diff --git a/Tests/images/rgb.png b/Tests/images/rgb.png new file mode 100644 index 000000000..d0de36d82 Binary files /dev/null and b/Tests/images/rgb.png differ diff --git a/Tests/images/rgba.dds b/Tests/images/rgba.dds new file mode 100644 index 000000000..d4808ca68 Binary files /dev/null and b/Tests/images/rgba.dds differ diff --git a/Tests/images/rgba.png b/Tests/images/rgba.png new file mode 100644 index 000000000..33b41a547 Binary files /dev/null and b/Tests/images/rgba.png differ diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index ec8434d75..88032e4ae 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -320,3 +320,19 @@ def test_save(mode, test_file, tmp_path): with Image.open(out) as reloaded: assert_image_equal(im, reloaded) + + +@pytest.mark.parametrize( + ("mode", "expected_file", "input_file"), + [ + ("L", "Tests/images/l.png", "Tests/images/l.dds"), + ("LA", "Tests/images/la.png", "Tests/images/la.dds"), + ("RGB", "Tests/images/rgb.png", "Tests/images/rgb.DDS"), + ("RGBA", "Tests/images/rgba.png", "Tests/images/rgba.DDS"), + ], +) +def test_open(mode, expected_file, input_file): + with Image.open(input_file) as im: + assert im.mode == mode + with Image.open(expected_file) as im2: + assert_image_equal(im, im2) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 8175f182e..b1a296f59 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -277,6 +277,7 @@ class DdsImageFile(ImageFile.ImageFile): format = "DDS" format_description = "DirectDraw Surface" + # fmt: off def _open(self): if not _accept(self.fp.read(4)): msg = "not a DDS file" @@ -294,7 +295,6 @@ class DdsImageFile(ImageFile.ImageFile): flags_, height, width = struct.unpack("<3I", header.read(12)) flags = DDSD(flags_) self._size = (width, height) - self.mode = "RGBA" pitch, depth, mipmaps = struct.unpack("<3I", header.read(12)) struct.unpack("<11I", header.read(44)) # reserved @@ -305,21 +305,9 @@ class DdsImageFile(ImageFile.ImageFile): fourcc = D3DFMT(fourcc_) masks = struct.unpack("<4I", header.read(16)) if flags & DDSD.CAPS: - ( - caps1_, - caps2_, - caps3, - caps4, - _, - ) = struct.unpack("<5I", header.read(20)) + (caps1_, caps2_, caps3, caps4, _,) = struct.unpack("<5I", header.read(20)) else: - (caps1_, caps2_, caps3, caps4, _,) = ( - 0, - 0, - 0, - 0, - 0, - ) + (caps1_, caps2_, caps3, caps4, _,) = (0, 0, 0, 0, 0,) caps1 = DDSCAPS(caps1_) caps2 = DDSCAPS2(caps2_) if pfflags & DDPF.LUMINANCE: @@ -333,101 +321,87 @@ class DdsImageFile(ImageFile.ImageFile): elif 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", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] + elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS: + self.mode = "RGBA" + rawmode = (masks[0xFF000000] + masks[0x00FF0000] + masks[0x0000FF00] + masks[0x000000FF]) + self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] + else: + raise OSError(f"Unsupported bitcount {bitcount} for {pfflags} DDS texture") + elif pfflags & DDPF.LUMINANCE: if bitcount == 8: self.mode = "L" - rawmode = "L" - elif bitcount == 24: - self.mode = "RGB" - rawmode = masks[0xFF0000] + masks[0xFF00] + masks[0xFF] - elif bitcount == 32: - self.mode = "RGBA" - rawmode = ( - masks[0xFF000000] + masks[0xFF0000] + masks[0xFF00] + masks[0xFF] - ) + self.tile = [("raw", (0, 0) + self.size, 0, ("L", 0, 1))] + elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS: + self.mode = "LA" + self.tile = [("raw", (0, 0) + self.size, 0, ("LA", 0, 1))] else: - raise OSError(f"Unsupported bitcount {bitcount} for DDS texture") - - self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] + raise OSError(f"Unsupported bitcount {bitcount} for {pfflags} DDS texture") elif pfflags & DDPF.FOURCC: data_start = header_size + 4 if fourcc == D3DFMT.DXT1: + self.mode = "RGBA" self.pixel_format = "DXT1" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (1, self.pixel_format) - ) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (1, self.pixel_format)) elif fourcc == D3DFMT.DXT3: + self.mode = "RGBA" self.pixel_format = "DXT3" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (2, self.pixel_format) - ) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (2, self.pixel_format)) elif fourcc == D3DFMT.DXT5: + self.mode = "RGBA" self.pixel_format = "DXT5" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (3, self.pixel_format) - ) + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (3, self.pixel_format)) elif fourcc == D3DFMT.ATI1: - self.pixel_format = "BC4" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (4, self.pixel_format) - ) self.mode = "L" + self.pixel_format = "BC4" + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (4, self.pixel_format)) elif fourcc == D3DFMT.BC5S: + self.mode = "RGB" self.pixel_format = "BC5S" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (5, self.pixel_format) - ) - self.mode = "RGB" + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) elif fourcc == D3DFMT.ATI2: - self.pixel_format = "BC5" - tile = Image.Tile( - "bcn", (0, 0) + self.size, data_start, (5, self.pixel_format) - ) self.mode = "RGB" + self.pixel_format = "BC5" + tile = Image.Tile("bcn", (0, 0) + self.size, data_start, (5, self.pixel_format)) elif fourcc == D3DFMT.DX10: data_start += 20 # ignoring flags which pertain to volume textures and cubemaps (dxgi_format,) = struct.unpack("