Skip to content

Commit

Permalink
Merge pull request #6820 from radarhere/dds_l
Browse files Browse the repository at this point in the history
Added DDS support for uncompressed L and LA images
  • Loading branch information
hugovk committed Dec 26, 2022
2 parents 88420f6 + 941a2d6 commit 0a19b34
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 31 deletions.
Binary file added Tests/images/uncompressed_l.dds
Binary file not shown.
Binary file added Tests/images/uncompressed_l.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/uncompressed_la.dds
Binary file not shown.
Binary file added Tests/images/uncompressed_la.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
36 changes: 19 additions & 17 deletions Tests/test_file_dds.py
Expand Up @@ -22,6 +22,8 @@
TEST_FILE_DX10_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds"
TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds"
TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB = "Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SRGB.dds"
TEST_FILE_UNCOMPRESSED_L = "Tests/images/uncompressed_l.dds"
TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA = "Tests/images/uncompressed_la.dds"
TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/hopper.dds"
TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"

Expand Down Expand Up @@ -194,26 +196,24 @@ def test_unimplemented_dxgi_format():
pass


def test_uncompressed_rgb():
"""Check uncompressed RGB images can be opened"""

# convert -format dds -define dds:compression=none hopper.jpg hopper.dds
with Image.open(TEST_FILE_UNCOMPRESSED_RGB) as im:
assert im.format == "DDS"
assert im.mode == "RGB"
assert im.size == (128, 128)

assert_image_equal_tofile(im, "Tests/images/hopper.png")
@pytest.mark.parametrize(
("mode", "size", "test_file"),
[
("L", (128, 128), TEST_FILE_UNCOMPRESSED_L),
("LA", (128, 128), TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA),
("RGB", (128, 128), TEST_FILE_UNCOMPRESSED_RGB),
("RGBA", (800, 600), TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA),
],
)
def test_uncompressed(mode, size, test_file):
"""Check uncompressed images can be opened"""

# Test image with alpha
with Image.open(TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA) as im:
with Image.open(test_file) as im:
assert im.format == "DDS"
assert im.mode == "RGBA"
assert im.size == (800, 600)
assert im.mode == mode
assert im.size == size

assert_image_equal_tofile(
im, TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA.replace(".dds", ".png")
)
assert_image_equal_tofile(im, test_file.replace(".dds", ".png"))


def test__accept_true():
Expand Down Expand Up @@ -305,6 +305,8 @@ def test_save_unsupported_mode(tmp_path):
@pytest.mark.parametrize(
("mode", "test_file"),
[
("L", "Tests/images/linear_gradient.png"),
("LA", "Tests/images/uncompressed_la.png"),
("RGB", "Tests/images/hopper.png"),
("RGBA", "Tests/images/pil123rgba.png"),
],
Expand Down
7 changes: 4 additions & 3 deletions docs/releasenotes/9.4.0.rst
Expand Up @@ -98,7 +98,8 @@ TODO
Other Changes
=============

TODO
^^^^
Added support for DDS L and LA images
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

TODO
Support has been added to read and write L and LA DDS images in the uncompressed
format, known as "luminance" textures.
42 changes: 31 additions & 11 deletions src/PIL/DdsImagePlugin.py
Expand Up @@ -135,11 +135,19 @@ def _open(self):
fourcc = header.read(4)
(bitcount,) = struct.unpack("<I", header.read(4))
masks = struct.unpack("<4I", header.read(16))
if pfflags & DDPF_RGB:
if pfflags & DDPF_LUMINANCE:
# Texture contains uncompressed L or LA data
if pfflags & DDPF_ALPHAPIXELS:
self.mode = "LA"
else:
self.mode = "L"

self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))]
elif pfflags & DDPF_RGB:
# Texture contains uncompressed RGB data
masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)}
rawmode = ""
if bitcount == 32:
if pfflags & DDPF_ALPHAPIXELS:
rawmode += masks[0xFF000000]
else:
self.mode = "RGB"
Expand Down Expand Up @@ -223,9 +231,24 @@ def load_seek(self, pos):


def _save(im, fp, filename):
if im.mode not in ("RGB", "RGBA"):
if im.mode not in ("RGB", "RGBA", "L", "LA"):
raise OSError(f"cannot write mode {im.mode} as DDS")

rawmode = im.mode
masks = [0xFF0000, 0xFF00, 0xFF]
if im.mode in ("L", "LA"):
pixel_flags = DDPF_LUMINANCE
else:
pixel_flags = DDPF_RGB
rawmode = rawmode[::-1]
if im.mode in ("LA", "RGBA"):
pixel_flags |= DDPF_ALPHAPIXELS
masks.append(0xFF000000)

bitcount = len(masks) * 8
while len(masks) < 4:
masks.append(0)

fp.write(
o32(DDS_MAGIC)
+ o32(124) # header size
Expand All @@ -234,18 +257,15 @@ def _save(im, fp, filename):
) # flags
+ o32(im.height)
+ o32(im.width)
+ o32((im.width * (32 if im.mode == "RGBA" else 24) + 7) // 8) # pitch
+ o32((im.width * bitcount + 7) // 8) # pitch
+ o32(0) # depth
+ o32(0) # mipmaps
+ o32(0) * 11 # reserved
+ o32(32) # pfsize
+ o32(DDS_RGBA if im.mode == "RGBA" else DDPF_RGB) # pfflags
+ o32(pixel_flags) # pfflags
+ o32(0) # fourcc
+ o32(32 if im.mode == "RGBA" else 24) # bitcount
+ o32(0xFF0000) # rbitmask
+ o32(0xFF00) # gbitmask
+ o32(0xFF) # bbitmask
+ o32(0xFF000000 if im.mode == "RGBA" else 0) # abitmask
+ o32(bitcount) # bitcount
+ b"".join(o32(mask) for mask in masks) # rgbabitmask
+ o32(DDSCAPS_TEXTURE) # dwCaps
+ o32(0) # dwCaps2
+ o32(0) # dwCaps3
Expand All @@ -255,7 +275,7 @@ def _save(im, fp, filename):
if im.mode == "RGBA":
r, g, b, a = im.split()
im = Image.merge("RGBA", (a, r, g, b))
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (im.mode[::-1], 0, 1))])
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])


def _accept(prefix):
Expand Down

0 comments on commit 0a19b34

Please sign in to comment.