Skip to content

Commit f1a61a1

Browse files
committed
Added DXT3 saving
1 parent 9f619b8 commit f1a61a1

4 files changed

Lines changed: 83 additions & 19 deletions

File tree

Tests/test_file_dds.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,29 @@ def test_save_dxt1(tmp_path: Path) -> None:
436436
assert reloaded.getpixel((0, 0)) == (0, 0, 0, 0)
437437

438438

439+
def test_save_dxt3(tmp_path: Path) -> None:
440+
# RGB
441+
out = str(tmp_path / "temp.dds")
442+
with Image.open(TEST_FILE_DXT3) as im:
443+
im_rgb = im.convert("RGB")
444+
im_rgb.save(out, pixel_format="DXT3")
445+
assert_image_similar_tofile(im_rgb.convert("RGBA"), out, 1.26)
446+
447+
# RGBA
448+
im.save(out, pixel_format="DXT3")
449+
assert_image_similar_tofile(im, out, 3.81)
450+
451+
# L
452+
im_l = im.convert("L")
453+
im_l.save(out, pixel_format="DXT3")
454+
assert_image_similar_tofile(im_l.convert("RGBA"), out, 5.89)
455+
456+
# LA
457+
im_la = im.convert("LA")
458+
im_la.save(out, pixel_format="DXT3")
459+
assert_image_similar_tofile(im_la.convert("RGBA"), out, 8.44)
460+
461+
439462
def test_save_dxt5(tmp_path: Path) -> None:
440463
# RGB
441464
out = str(tmp_path / "temp.dds")

src/PIL/DdsImagePlugin.py

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -525,18 +525,24 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
525525
flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT
526526
bitcount = len(im.getbands()) * 8
527527
pixel_format = im.encoderinfo.get("pixel_format")
528-
if pixel_format in ("DXT1", "BC3", "DXT5"):
528+
args: tuple[int] | str
529+
if pixel_format in ("DXT1", "DXT3", "BC3", "DXT5"):
529530
codec_name = "bcn"
530531
flags |= DDSD.LINEARSIZE
531532
pitch = (im.width + 3) * 4
532-
args = pixel_format
533533
rgba_mask = [0, 0, 0, 0]
534534
pixel_flags = DDPF.FOURCC
535-
fourcc = {"DXT1": D3DFMT.DXT1, "BC3": D3DFMT.DX10, "DXT5": D3DFMT.DXT5}[
536-
pixel_format
537-
]
538-
if fourcc == D3DFMT.DX10:
539-
dxgi_format = DXGI_FORMAT.BC3_TYPELESS
535+
if pixel_format == "DXT1":
536+
fourcc = D3DFMT.DXT1
537+
args = (1,)
538+
elif pixel_format == "DXT3":
539+
fourcc = D3DFMT.DXT3
540+
args = (2,)
541+
else:
542+
fourcc = D3DFMT.DXT5 if pixel_format == "DXT5" else D3DFMT.DX10
543+
args = (3,)
544+
if fourcc == D3DFMT.DX10:
545+
dxgi_format = DXGI_FORMAT.BC3_TYPELESS
540546
else:
541547
codec_name = "raw"
542548
flags |= DDSD.PITCH

src/encode.c

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -360,19 +360,18 @@ PyImaging_BcnEncoderNew(PyObject *self, PyObject *args) {
360360
ImagingEncoderObject *encoder;
361361

362362
char *mode;
363-
char *pixel_format;
364-
if (!PyArg_ParseTuple(args, "ss", &mode, &pixel_format)) {
363+
int n;
364+
if (!PyArg_ParseTuple(args, "si", &mode, &n)) {
365365
return NULL;
366366
}
367367

368-
encoder = PyImaging_EncoderNew(sizeof(BCNSTATE));
368+
encoder = PyImaging_EncoderNew(0);
369369
if (encoder == NULL) {
370370
return NULL;
371371
}
372372

373373
encoder->encode = ImagingBcnEncode;
374-
375-
((BCNSTATE *)encoder->state.context)->pixel_format = pixel_format;
374+
encoder->state.state = n;
376375

377376
return (PyObject *)encoder;
378377
}

src/libImaging/BcnEncode.c

Lines changed: 43 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010

1111
#include "Imaging.h"
1212

13-
#include "Bcn.h"
14-
1513
typedef struct {
1614
UINT8 color[3];
1715
} rgb;
@@ -57,14 +55,18 @@ encode_bc1_color(Imaging im, ImagingCodecState state, UINT8 *dst, int separate_a
5755
int transparency = 0;
5856
for (i = 0; i < 4; i++) {
5957
for (j = 0; j < 4; j++) {
58+
current_rgba = &block[i + j * 4];
59+
6060
int x = state->x + i * im->pixelsize;
6161
int y = state->y + j;
6262
if (x >= state->xsize * im->pixelsize || y >= state->ysize) {
6363
// The 4x4 block extends past the edge of the image
64+
for (k = 0; k < 3; k++) {
65+
current_rgba->color[k] = 0;
66+
}
6467
continue;
6568
}
6669

67-
current_rgba = &block[i + j * 4];
6870
for (k = 0; k < 3; k++) {
6971
current_rgba->color[k] =
7072
(UINT8)im->image[y][x + (im->pixelsize == 1 ? 0 : k)];
@@ -152,6 +154,36 @@ encode_bc1_color(Imaging im, ImagingCodecState state, UINT8 *dst, int separate_a
152154
}
153155
}
154156

157+
static void
158+
encode_bc2_block(Imaging im, ImagingCodecState state, UINT8 *dst) {
159+
int i, j;
160+
UINT8 block[16], current_alpha;
161+
for (i = 0; i < 4; i++) {
162+
for (j = 0; j < 4; j++) {
163+
int x = state->x + i * im->pixelsize;
164+
int y = state->y + j;
165+
if (x >= state->xsize * im->pixelsize || y >= state->ysize) {
166+
// The 4x4 block extends past the edge of the image
167+
block[i + j * 4] = 0;
168+
continue;
169+
}
170+
171+
current_alpha = (UINT8)im->image[y][x + 3];
172+
block[i + j * 4] = current_alpha;
173+
}
174+
}
175+
176+
for (i = 0; i < 4; i++) {
177+
UINT16 l = 0;
178+
for (j = 3; j > -1; j--) {
179+
current_alpha = block[i * 4 + j];
180+
l |= current_alpha << (j * 4);
181+
}
182+
*dst++ = l;
183+
*dst++ = l >> 8;
184+
}
185+
}
186+
155187
static void
156188
encode_bc3_alpha(Imaging im, ImagingCodecState state, UINT8 *dst) {
157189
int i, j;
@@ -166,6 +198,7 @@ encode_bc3_alpha(Imaging im, ImagingCodecState state, UINT8 *dst) {
166198
int y = state->y + j;
167199
if (x >= state->xsize * im->pixelsize || y >= state->ysize) {
168200
// The 4x4 block extends past the edge of the image
201+
block[i + j * 4] = 0;
169202
continue;
170203
}
171204

@@ -217,17 +250,20 @@ encode_bc3_alpha(Imaging im, ImagingCodecState state, UINT8 *dst) {
217250

218251
int
219252
ImagingBcnEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) {
220-
char *pixel_format = ((BCNSTATE *)state->context)->pixel_format;
221-
int n = strcmp(pixel_format, "DXT1") == 0 ? 1 : 3;
253+
int n = state->state;
222254
int has_alpha_channel =
223255
strcmp(im->mode, "RGBA") == 0 || strcmp(im->mode, "LA") == 0;
224256

225257
UINT8 *dst = buf;
226258

227259
for (;;) {
228-
if (n == 3) {
260+
if (n == 2 || n == 3) {
229261
if (has_alpha_channel) {
230-
encode_bc3_alpha(im, state, dst);
262+
if (n == 2) {
263+
encode_bc2_block(im, state, dst);
264+
} else {
265+
encode_bc3_alpha(im, state, dst);
266+
}
231267
dst += 8;
232268
} else {
233269
for (int i = 0; i < 8; i++) {

0 commit comments

Comments
 (0)