Skip to content

Commit 119885a

Browse files
authored
Merge pull request #7589 from radarhere/dds_rgb
2 parents 5df7235 + 232094e commit 119885a

File tree

6 files changed

+48
-24
lines changed

6 files changed

+48
-24
lines changed

Tests/images/bgr15.dds

32.1 KB
Binary file not shown.

Tests/images/bgr15.png

17.5 KB
Loading
File renamed without changes.
-48.1 KB
Binary file not shown.

Tests/test_file_dds.py

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
TEST_FILE_UNCOMPRESSED_L = "Tests/images/uncompressed_l.dds"
3333
TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA = "Tests/images/uncompressed_la.dds"
3434
TEST_FILE_UNCOMPRESSED_RGB = "Tests/images/hopper.dds"
35+
TEST_FILE_UNCOMPRESSED_BGR15 = "Tests/images/bgr15.dds"
3536
TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA = "Tests/images/uncompressed_rgb.dds"
3637

3738

@@ -249,6 +250,7 @@ def test_dx10_r8g8b8a8_unorm_srgb():
249250
("L", (128, 128), TEST_FILE_UNCOMPRESSED_L),
250251
("LA", (128, 128), TEST_FILE_UNCOMPRESSED_L_WITH_ALPHA),
251252
("RGB", (128, 128), TEST_FILE_UNCOMPRESSED_RGB),
253+
("RGB", (128, 128), TEST_FILE_UNCOMPRESSED_BGR15),
252254
("RGBA", (800, 600), TEST_FILE_UNCOMPRESSED_RGB_WITH_ALPHA),
253255
],
254256
)
@@ -341,16 +343,9 @@ def test_palette():
341343
assert_image_equal_tofile(im, "Tests/images/transparent.gif")
342344

343345

344-
@pytest.mark.parametrize(
345-
"test_file",
346-
(
347-
"Tests/images/unsupported_bitcount_rgb.dds",
348-
"Tests/images/unsupported_bitcount_luminance.dds",
349-
),
350-
)
351-
def test_unsupported_bitcount(test_file):
346+
def test_unsupported_bitcount():
352347
with pytest.raises(OSError):
353-
with Image.open(test_file):
348+
with Image.open("Tests/images/unsupported_bitcount.dds"):
354349
pass
355350

356351

src/PIL/DdsImagePlugin.py

Lines changed: 44 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
from . import Image, ImageFile, ImagePalette
2020
from ._binary import i32le as i32
21+
from ._binary import o8
2122
from ._binary import o32le as o32
2223

2324
# Magic ("DDS ")
@@ -341,6 +342,7 @@ def _open(self):
341342

342343
flags, height, width = struct.unpack("<3I", header.read(12))
343344
self._size = (width, height)
345+
extents = (0, 0) + self.size
344346

345347
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12))
346348
struct.unpack("<11I", header.read(44)) # reserved
@@ -351,22 +353,16 @@ def _open(self):
351353
rawmode = None
352354
if pfflags & DDPF.RGB:
353355
# Texture contains uncompressed RGB data
354-
masks = struct.unpack("<4I", header.read(16))
355-
masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)}
356-
if bitcount == 24:
357-
self._mode = "RGB"
358-
rawmode = masks[0x000000FF] + masks[0x0000FF00] + masks[0x00FF0000]
359-
elif bitcount == 32 and pfflags & DDPF.ALPHAPIXELS:
356+
if pfflags & DDPF.ALPHAPIXELS:
360357
self._mode = "RGBA"
361-
rawmode = (
362-
masks[0x000000FF]
363-
+ masks[0x0000FF00]
364-
+ masks[0x00FF0000]
365-
+ masks[0xFF000000]
366-
)
358+
mask_count = 4
367359
else:
368-
msg = f"Unsupported bitcount {bitcount} for {pfflags}"
369-
raise OSError(msg)
360+
self._mode = "RGB"
361+
mask_count = 3
362+
363+
masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4))
364+
self.tile = [("dds_rgb", extents, 0, (bitcount, masks))]
365+
return
370366
elif pfflags & DDPF.LUMINANCE:
371367
if bitcount == 8:
372368
self._mode = "L"
@@ -464,7 +460,6 @@ def _open(self):
464460
msg = f"Unknown pixel format flags {pfflags}"
465461
raise NotImplementedError(msg)
466462

467-
extents = (0, 0) + self.size
468463
if n:
469464
self.tile = [
470465
ImageFile._Tile("bcn", extents, offset, (n, self.pixel_format))
@@ -476,6 +471,39 @@ def load_seek(self, pos):
476471
pass
477472

478473

474+
class DdsRgbDecoder(ImageFile.PyDecoder):
475+
_pulls_fd = True
476+
477+
def decode(self, buffer):
478+
bitcount, masks = self.args
479+
480+
# Some masks will be padded with zeros, e.g. R 0b11 G 0b1100
481+
# Calculate how many zeros each mask is padded with
482+
mask_offsets = []
483+
# And the maximum value of each channel without the padding
484+
mask_totals = []
485+
for mask in masks:
486+
offset = 0
487+
if mask != 0:
488+
while mask >> (offset + 1) << (offset + 1) == mask:
489+
offset += 1
490+
mask_offsets.append(offset)
491+
mask_totals.append(mask >> offset)
492+
493+
data = bytearray()
494+
bytecount = bitcount // 8
495+
while len(data) < self.state.xsize * self.state.ysize * len(masks):
496+
value = int.from_bytes(self.fd.read(bytecount), "little")
497+
for i, mask in enumerate(masks):
498+
masked_value = value & mask
499+
# Remove the zero padding, and scale it to 8 bits
500+
data += o8(
501+
int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255)
502+
)
503+
self.set_as_raw(bytes(data))
504+
return -1, 0
505+
506+
479507
def _save(im, fp, filename):
480508
if im.mode not in ("RGB", "RGBA", "L", "LA"):
481509
msg = f"cannot write mode {im.mode} as DDS"
@@ -533,5 +561,6 @@ def _accept(prefix):
533561

534562

535563
Image.register_open(DdsImageFile.format, DdsImageFile, _accept)
564+
Image.register_decoder("dds_rgb", DdsRgbDecoder)
536565
Image.register_save(DdsImageFile.format, _save)
537566
Image.register_extension(DdsImageFile.format, ".dds")

0 commit comments

Comments
 (0)