Skip to content

Commit 0a19b34

Browse files
authored
Merge pull request #6820 from radarhere/dds_l
Added DDS support for uncompressed L and LA images
2 parents 88420f6 + 941a2d6 commit 0a19b34

File tree

7 files changed

+54
-31
lines changed

7 files changed

+54
-31
lines changed

Tests/images/uncompressed_l.dds

16.1 KB
Binary file not shown.

Tests/images/uncompressed_l.png

861 Bytes
Loading

Tests/images/uncompressed_la.dds

32.1 KB
Binary file not shown.

Tests/images/uncompressed_la.png

1.04 KB
Loading

Tests/test_file_dds.py

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@
2222
TEST_FILE_DX10_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds"
2323
TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds"
2424
TEST_FILE_DX10_R8G8B8A8_UNORM_SRGB = "Tests/images/DXGI_FORMAT_R8G8B8A8_UNORM_SRGB.dds"
25+
TEST_FILE_UNCOMPRESSED_L = "Tests/images/uncompressed_l.dds"
26+
TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA = "Tests/images/uncompressed_la.dds"
2527
TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/hopper.dds"
2628
TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
2729

@@ -194,26 +196,24 @@ def test_unimplemented_dxgi_format():
194196
pass
195197

196198

197-
def test_uncompressed_rgb():
198-
"""Check uncompressed RGB images can be opened"""
199-
200-
# convert -format dds -define dds:compression=none hopper.jpg hopper.dds
201-
with Image.open(TEST_FILE_UNCOMPRESSED_RGB) as im:
202-
assert im.format == "DDS"
203-
assert im.mode == "RGB"
204-
assert im.size == (128, 128)
205-
206-
assert_image_equal_tofile(im, "Tests/images/hopper.png")
199+
@pytest.mark.parametrize(
200+
("mode", "size", "test_file"),
201+
[
202+
("L", (128, 128), TEST_FILE_UNCOMPRESSED_L),
203+
("LA", (128, 128), TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA),
204+
("RGB", (128, 128), TEST_FILE_UNCOMPRESSED_RGB),
205+
("RGBA", (800, 600), TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA),
206+
],
207+
)
208+
def test_uncompressed(mode, size, test_file):
209+
"""Check uncompressed images can be opened"""
207210

208-
# Test image with alpha
209-
with Image.open(TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA) as im:
211+
with Image.open(test_file) as im:
210212
assert im.format == "DDS"
211-
assert im.mode == "RGBA"
212-
assert im.size == (800, 600)
213+
assert im.mode == mode
214+
assert im.size == size
213215

214-
assert_image_equal_tofile(
215-
im, TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA.replace(".dds", ".png")
216-
)
216+
assert_image_equal_tofile(im, test_file.replace(".dds", ".png"))
217217

218218

219219
def test__accept_true():
@@ -305,6 +305,8 @@ def test_save_unsupported_mode(tmp_path):
305305
@pytest.mark.parametrize(
306306
("mode", "test_file"),
307307
[
308+
("L", "Tests/images/linear_gradient.png"),
309+
("LA", "Tests/images/uncompressed_la.png"),
308310
("RGB", "Tests/images/hopper.png"),
309311
("RGBA", "Tests/images/pil123rgba.png"),
310312
],

docs/releasenotes/9.4.0.rst

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,8 @@ TODO
9898
Other Changes
9999
=============
100100

101-
TODO
102-
^^^^
101+
Added support for DDS L and LA images
102+
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
103103

104-
TODO
104+
Support has been added to read and write L and LA DDS images in the uncompressed
105+
format, known as "luminance" textures.

src/PIL/DdsImagePlugin.py

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -135,11 +135,19 @@ def _open(self):
135135
fourcc = header.read(4)
136136
(bitcount,) = struct.unpack("<I", header.read(4))
137137
masks = struct.unpack("<4I", header.read(16))
138-
if pfflags & DDPF_RGB:
138+
if pfflags & DDPF_LUMINANCE:
139+
# Texture contains uncompressed L or LA data
140+
if pfflags & DDPF_ALPHAPIXELS:
141+
self.mode = "LA"
142+
else:
143+
self.mode = "L"
144+
145+
self.tile = [("raw", (0, 0) + self.size, 0, (self.mode, 0, 1))]
146+
elif pfflags & DDPF_RGB:
139147
# Texture contains uncompressed RGB data
140148
masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)}
141149
rawmode = ""
142-
if bitcount == 32:
150+
if pfflags & DDPF_ALPHAPIXELS:
143151
rawmode += masks[0xFF000000]
144152
else:
145153
self.mode = "RGB"
@@ -223,9 +231,24 @@ def load_seek(self, pos):
223231

224232

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

237+
rawmode = im.mode
238+
masks = [0xFF0000, 0xFF00, 0xFF]
239+
if im.mode in ("L", "LA"):
240+
pixel_flags = DDPF_LUMINANCE
241+
else:
242+
pixel_flags = DDPF_RGB
243+
rawmode = rawmode[::-1]
244+
if im.mode in ("LA", "RGBA"):
245+
pixel_flags |= DDPF_ALPHAPIXELS
246+
masks.append(0xFF000000)
247+
248+
bitcount = len(masks) * 8
249+
while len(masks) < 4:
250+
masks.append(0)
251+
229252
fp.write(
230253
o32(DDS_MAGIC)
231254
+ o32(124) # header size
@@ -234,18 +257,15 @@ def _save(im, fp, filename):
234257
) # flags
235258
+ o32(im.height)
236259
+ o32(im.width)
237-
+ o32((im.width * (32 if im.mode == "RGBA" else 24) + 7) // 8) # pitch
260+
+ o32((im.width * bitcount + 7) // 8) # pitch
238261
+ o32(0) # depth
239262
+ o32(0) # mipmaps
240263
+ o32(0) * 11 # reserved
241264
+ o32(32) # pfsize
242-
+ o32(DDS_RGBA if im.mode == "RGBA" else DDPF_RGB) # pfflags
265+
+ o32(pixel_flags) # pfflags
243266
+ o32(0) # fourcc
244-
+ o32(32 if im.mode == "RGBA" else 24) # bitcount
245-
+ o32(0xFF0000) # rbitmask
246-
+ o32(0xFF00) # gbitmask
247-
+ o32(0xFF) # bbitmask
248-
+ o32(0xFF000000 if im.mode == "RGBA" else 0) # abitmask
267+
+ o32(bitcount) # bitcount
268+
+ b"".join(o32(mask) for mask in masks) # rgbabitmask
249269
+ o32(DDSCAPS_TEXTURE) # dwCaps
250270
+ o32(0) # dwCaps2
251271
+ o32(0) # dwCaps3
@@ -255,7 +275,7 @@ def _save(im, fp, filename):
255275
if im.mode == "RGBA":
256276
r, g, b, a = im.split()
257277
im = Image.merge("RGBA", (a, r, g, b))
258-
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (im.mode[::-1], 0, 1))])
278+
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
259279

260280

261281
def _accept(prefix):

0 commit comments

Comments
 (0)