Skip to content

Commit 4e1b4e3

Browse files
committed
Added DXT5 saving
1 parent 97ba5b6 commit 4e1b4e3

File tree

4 files changed

+228
-111
lines changed

4 files changed

+228
-111
lines changed

Tests/test_file_dds.py

+28-1
Original file line numberDiff line numberDiff line change
@@ -398,21 +398,48 @@ def test_save(mode: str, test_file: str, tmp_path: Path) -> None:
398398

399399

400400
def test_save_dxt1(tmp_path: Path) -> None:
401+
# RGB
401402
out = str(tmp_path / "temp.dds")
402403
with Image.open(TEST_FILE_DXT1) as im:
403404
im.convert("RGB").save(out, pixel_format="DXT1")
404405
assert_image_similar_tofile(im, out, 1.84)
405406

407+
# RGBA
406408
im_alpha = im.copy()
407409
im_alpha.putpixel((0, 0), (0, 0, 0, 0))
408410
im_alpha.save(out, pixel_format="DXT1")
409411
with Image.open(out) as reloaded:
410412
assert reloaded.getpixel((0, 0)) == (0, 0, 0, 0)
411413

414+
# L
412415
im_l = im.convert("L")
413416
im_l.save(out, pixel_format="DXT1")
414-
assert_image_similar_tofile(im_l.convert("RGBA"), out, 9.25)
417+
assert_image_similar_tofile(im_l.convert("RGBA"), out, 6.07)
415418

419+
# LA
416420
im_alpha.convert("LA").save(out, pixel_format="DXT1")
417421
with Image.open(out) as reloaded:
418422
assert reloaded.getpixel((0, 0)) == (0, 0, 0, 0)
423+
424+
425+
def test_save_dxt5(tmp_path: Path) -> None:
426+
# RGB
427+
out = str(tmp_path / "temp.dds")
428+
with Image.open(TEST_FILE_DXT1) as im:
429+
im.convert("RGB").save(out, pixel_format="DXT5")
430+
assert_image_similar_tofile(im, out, 1.84)
431+
432+
# RGBA
433+
with Image.open(TEST_FILE_DXT5) as im_rgba:
434+
im_rgba.save(out, pixel_format="DXT5")
435+
assert_image_similar_tofile(im_rgba, out, 3.69)
436+
437+
# L
438+
im_l = im.convert("L")
439+
im_l.save(out, pixel_format="DXT5")
440+
assert_image_similar_tofile(im_l.convert("RGBA"), out, 6.07)
441+
442+
# LA
443+
im_la = im_rgba.convert("LA")
444+
im_la.save(out, pixel_format="DXT5")
445+
assert_image_similar_tofile(im_la.convert("RGBA"), out, 8.32)

src/PIL/DdsImagePlugin.py

+14-14
Original file line numberDiff line numberDiff line change
@@ -520,23 +520,31 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
520520

521521
flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT
522522
bitcount = len(im.getbands()) * 8
523-
raw = im.encoderinfo.get("pixel_format") != "DXT1"
524-
if raw:
523+
pixel_format = im.encoderinfo.get("pixel_format")
524+
if pixel_format in ("DXT1", "DXT5"):
525+
codec_name = "bcn"
526+
flags |= DDSD.LINEARSIZE
527+
pitch = (im.width + 3) * 4
528+
args = pixel_format
529+
rgba_mask = [0, 0, 0, 0]
530+
pixel_flags = DDPF.FOURCC
531+
fourcc = D3DFMT.DXT1 if pixel_format == "DXT1" else D3DFMT.DXT5
532+
else:
525533
codec_name = "raw"
526534
flags |= DDSD.PITCH
527535
pitch = (im.width * bitcount + 7) // 8
528536

529537
alpha = im.mode[-1] == "A"
530538
if im.mode[0] == "L":
531539
pixel_flags = DDPF.LUMINANCE
532-
rawmode = im.mode
540+
args = im.mode
533541
if alpha:
534542
rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF]
535543
else:
536544
rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000]
537545
else:
538546
pixel_flags = DDPF.RGB
539-
rawmode = im.mode[::-1]
547+
args = im.mode[::-1]
540548
rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF]
541549

542550
if alpha:
@@ -546,15 +554,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
546554
pixel_flags |= DDPF.ALPHAPIXELS
547555
rgba_mask.append(0xFF000000 if alpha else 0)
548556

549-
fourcc = 0
550-
else:
551-
codec_name = "bcn"
552-
flags |= DDSD.LINEARSIZE
553-
pitch = (im.width + 3) * 4
554-
rawmode = None
555-
rgba_mask = [0, 0, 0, 0]
556-
pixel_flags = DDPF.FOURCC
557-
fourcc = D3DFMT.DXT1
557+
fourcc = D3DFMT.UNKNOWN
558558
fp.write(
559559
o32(DDS_MAGIC)
560560
+ struct.pack(
@@ -573,7 +573,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
573573
+ struct.pack("<4I", *rgba_mask) # dwRGBABitMask
574574
+ struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
575575
)
576-
ImageFile._save(im, fp, [ImageFile._Tile(codec_name, (0, 0) + im.size, 0, rawmode)])
576+
ImageFile._save(im, fp, [ImageFile._Tile(codec_name, (0, 0) + im.size, 0, args)])
577577

578578

579579
def _accept(prefix: bytes) -> bool:

src/encode.c

+10-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
#include "thirdparty/pythoncapi_compat.h"
2929
#include "libImaging/Imaging.h"
30+
#include "libImaging/Bcn.h"
3031
#include "libImaging/Gif.h"
3132

3233
#ifdef HAVE_UNISTD_H
@@ -358,13 +359,21 @@ PyObject *
358359
PyImaging_BcnEncoderNew(PyObject *self, PyObject *args) {
359360
ImagingEncoderObject *encoder;
360361

361-
encoder = PyImaging_EncoderNew(0);
362+
char *mode;
363+
char *pixel_format;
364+
if (!PyArg_ParseTuple(args, "ss", &mode, &pixel_format)) {
365+
return NULL;
366+
}
367+
368+
encoder = PyImaging_EncoderNew(sizeof(BCNSTATE));
362369
if (encoder == NULL) {
363370
return NULL;
364371
}
365372

366373
encoder->encode = ImagingBcnEncode;
367374

375+
((BCNSTATE *)encoder->state.context)->pixel_format = pixel_format;
376+
368377
return (PyObject *)encoder;
369378
}
370379

0 commit comments

Comments
 (0)