Skip to content

Commit afc3e2d

Browse files
committed
Added RGB saving
1 parent 8e293ca commit afc3e2d

File tree

2 files changed

+52
-1
lines changed

2 files changed

+52
-1
lines changed

Tests/test_file_dds.py

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
from PIL import DdsImagePlugin, Image
77

8-
from .helper import assert_image_equal, assert_image_equal_tofile
8+
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
99

1010
TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds"
1111
TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
@@ -199,3 +199,19 @@ def test_unimplemented_pixel_format():
199199
with pytest.raises(NotImplementedError):
200200
with Image.open("Tests/images/unimplemented_pixel_format.dds"):
201201
pass
202+
203+
204+
def test_save_unsupported_mode(tmp_path):
205+
out = str(tmp_path / "temp.dds")
206+
im = hopper("HSV")
207+
with pytest.raises(OSError):
208+
im.save(out)
209+
210+
211+
def test_save(tmp_path):
212+
out = str(tmp_path / "temp.dds")
213+
im = hopper()
214+
im.save(out)
215+
216+
with Image.open(out) as reloaded:
217+
assert_image_equal(im, reloaded)

src/PIL/DdsImagePlugin.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from io import BytesIO
1515

1616
from . import Image, ImageFile
17+
from ._binary import o32le as o32
1718

1819
# Magic ("DDS ")
1920
DDS_MAGIC = 0x20534444
@@ -184,9 +185,43 @@ def load_seek(self, pos):
184185
pass
185186

186187

188+
def _save(im, fp, filename):
189+
if im.mode != "RGB":
190+
raise OSError(f"cannot write mode {im.mode} as DDS")
191+
192+
fp.write(
193+
o32(DDS_MAGIC)
194+
+ o32(124) # header size
195+
+ o32(
196+
DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PITCH | DDSD_PIXELFORMAT
197+
) # flags
198+
+ o32(im.height)
199+
+ o32(im.width)
200+
+ o32((im.width * 24 + 7) // 8) # pitch
201+
+ o32(0) # depth
202+
+ o32(0) # mipmaps
203+
+ o32(0) * 11 # reserved
204+
+ o32(32) # pfsize
205+
+ o32(DDPF_RGB) # pfflags
206+
+ o32(0) # fourcc
207+
+ o32(24) # bitcount
208+
+ o32(0xFF0000) # rbitmask
209+
+ o32(0xFF00) # gbitmask
210+
+ o32(0xFF) # bbitmask
211+
+ o32(0) # abitmask
212+
+ o32(0) # dwCaps
213+
+ o32(0) # dwCaps2
214+
+ o32(0) # dwCaps3
215+
+ o32(0) # dwCaps4
216+
+ o32(0) # dwReserved2
217+
)
218+
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (im.mode[::-1], 0, 1))])
219+
220+
187221
def _accept(prefix):
188222
return prefix[:4] == b"DDS "
189223

190224

191225
Image.register_open(DdsImageFile.format, DdsImageFile, _accept)
226+
Image.register_save(DdsImageFile.format, _save)
192227
Image.register_extension(DdsImageFile.format, ".dds")

0 commit comments

Comments
 (0)