From 9f619b814f65a99c2107852027ecf7db4ddec7ec Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 15 Mar 2025 00:21:34 +1100 Subject: [PATCH] Added BC3 loading and saving --- Tests/test_file_dds.py | 14 ++++++++++++++ src/PIL/DdsImagePlugin.py | 17 +++++++++++++++-- src/libImaging/BcnEncode.c | 2 +- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index 17d88451f..3c7c8e604 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -12,6 +12,7 @@ from PIL import DdsImagePlugin, Image from .helper import ( assert_image_equal, assert_image_equal_tofile, + assert_image_similar, assert_image_similar_tofile, hopper, ) @@ -114,6 +115,19 @@ def test_sanity_ati1_bc4u(image_path: str) -> None: assert_image_equal_tofile(im, TEST_FILE_ATI1.replace(".dds", ".png")) +def test_dx10_bc3(tmp_path: Path) -> None: + out = str(tmp_path / "temp.dds") + with Image.open(TEST_FILE_DXT5) as im: + im.save(out, pixel_format="BC3") + + with Image.open(out) as reloaded: + assert reloaded.format == "DDS" + assert reloaded.mode == "RGBA" + assert reloaded.size == (256, 256) + + assert_image_similar(im, reloaded, 3.69) + + @pytest.mark.parametrize( "image_path", ( diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 2d097fd16..a5e0a712b 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -419,6 +419,10 @@ class DdsImageFile(ImageFile.ImageFile): self._mode = "RGBA" self.pixel_format = "BC1" n = 1 + elif dxgi_format in (DXGI_FORMAT.BC3_TYPELESS, DXGI_FORMAT.BC3_UNORM): + self._mode = "RGBA" + self.pixel_format = "BC3" + n = 3 elif dxgi_format in (DXGI_FORMAT.BC4_TYPELESS, DXGI_FORMAT.BC4_UNORM): self._mode = "L" self.pixel_format = "BC4" @@ -521,14 +525,18 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT bitcount = len(im.getbands()) * 8 pixel_format = im.encoderinfo.get("pixel_format") - if pixel_format in ("DXT1", "DXT5"): + if pixel_format in ("DXT1", "BC3", "DXT5"): codec_name = "bcn" flags |= DDSD.LINEARSIZE pitch = (im.width + 3) * 4 args = pixel_format rgba_mask = [0, 0, 0, 0] pixel_flags = DDPF.FOURCC - fourcc = D3DFMT.DXT1 if pixel_format == "DXT1" else D3DFMT.DXT5 + fourcc = {"DXT1": D3DFMT.DXT1, "BC3": D3DFMT.DX10, "DXT5": D3DFMT.DXT5}[ + pixel_format + ] + if fourcc == D3DFMT.DX10: + dxgi_format = DXGI_FORMAT.BC3_TYPELESS else: codec_name = "raw" flags |= DDSD.PITCH @@ -573,6 +581,11 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: + struct.pack("<4I", *rgba_mask) # dwRGBABitMask + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0) ) + if fourcc == D3DFMT.DX10: + fp.write( + # dxgi_format, 2D resource, misc, array size, straight alpha + struct.pack("<5I", dxgi_format, 3, 0, 0, 1) + ) ImageFile._save(im, fp, [ImageFile._Tile(codec_name, (0, 0) + im.size, 0, args)]) diff --git a/src/libImaging/BcnEncode.c b/src/libImaging/BcnEncode.c index 57353246a..66f9f39b1 100644 --- a/src/libImaging/BcnEncode.c +++ b/src/libImaging/BcnEncode.c @@ -218,7 +218,7 @@ encode_bc3_alpha(Imaging im, ImagingCodecState state, UINT8 *dst) { int ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { char *pixel_format = ((BCNSTATE *)state->context)->pixel_format; - int n = strcmp(pixel_format, "DXT5") == 0 ? 3 : 1; + int n = strcmp(pixel_format, "DXT1") == 0 ? 1 : 3; int has_alpha_channel = strcmp(im->mode, "RGBA") == 0 || strcmp(im->mode, "LA") == 0;