diff --git a/Tests/images/bc5_snorm.dds b/Tests/images/bc5_snorm.dds new file mode 100644 index 000000000..0b999eed3 Binary files /dev/null and b/Tests/images/bc5_snorm.dds differ diff --git a/Tests/images/bc5_snorm.png b/Tests/images/bc5_snorm.png new file mode 100644 index 000000000..39d7811bf Binary files /dev/null and b/Tests/images/bc5_snorm.png differ diff --git a/Tests/test_file_dds.py b/Tests/test_file_dds.py index ba9220ca7..c9414fb9a 100644 --- a/Tests/test_file_dds.py +++ b/Tests/test_file_dds.py @@ -10,7 +10,8 @@ from .helper import assert_image_equal, assert_image_equal_tofile TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds" TEST_FILE_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds" TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_MipMaps-1.dds" -TEST_FILE_DX10_BC5 = "Tests/images/bc5_unorm.dds" +TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds" +TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds" TEST_FILE_DX10_BC7 = "Tests/images/bc7-argb-8bpp_MipMaps-1.dds" TEST_FILE_DX10_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds" TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds" @@ -59,17 +60,30 @@ def test_sanity_dxt3(): assert_image_equal_tofile(im, TEST_FILE_DXT3.replace(".dds", ".png")) -def test_dx10_bc5(): +def test_dx10_bc5_unorm(): """Check DX10 images can be opened""" - with Image.open(TEST_FILE_DX10_BC5) as im: + with Image.open(TEST_FILE_DX10_BC5_UNORM) as im: im.load() assert im.format == "DDS" assert im.mode == "RGB" assert im.size == (256, 256) - assert_image_equal_tofile(im, TEST_FILE_DX10_BC5.replace(".dds", ".png")) + assert_image_equal_tofile(im, TEST_FILE_DX10_BC5_UNORM.replace(".dds", ".png")) + + +def test_dx10_bc5_snorm(): + """Check DX10 images can be opened""" + + with Image.open(TEST_FILE_DX10_BC5_SNORM) as im: + im.load() + + assert im.format == "DDS" + assert im.mode == "RGB" + assert im.size == (256, 256) + + assert_image_equal_tofile(im, TEST_FILE_DX10_BC5_SNORM.replace(".dds", ".png")) def test_dx10_bc7(): diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 6be43917a..dba4a5d72 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -151,6 +151,10 @@ class DdsImageFile(ImageFile.ImageFile): elif fourcc == b"DXT5": self.pixel_format = "DXT5" n = 3 + elif fourcc == b"BC5S": + self.pixel_format = "BC5S" + n = 5 + self.mode = "RGB" elif fourcc == b"DX10": data_start += 20 # ignoring flags which pertain to volume textures and cubemaps @@ -183,7 +187,9 @@ class DdsImageFile(ImageFile.ImageFile): else: raise NotImplementedError(f"Unimplemented pixel format {repr(fourcc)}") - self.tile = [("bcn", (0, 0) + self.size, data_start, (n))] + self.tile = [ + ("bcn", (0, 0) + self.size, data_start, (n, self.pixel_format)) + ] def load_seek(self, pos): pass diff --git a/src/decode.c b/src/decode.c index c416f8a2c..2557bb36f 100644 --- a/src/decode.c +++ b/src/decode.c @@ -34,9 +34,10 @@ #include "libImaging/Imaging.h" +#include "libImaging/Bit.h" +#include "libImaging/Bcn.h" #include "libImaging/Gif.h" #include "libImaging/Raw.h" -#include "libImaging/Bit.h" #include "libImaging/Sgi.h" /* -------------------------------------------------------------------- */ @@ -359,8 +360,8 @@ PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) { char *mode; char *actual; int n = 0; - int ystep = 1; - if (!PyArg_ParseTuple(args, "s|ii", &mode, &n, &ystep)) { + char *pixel_format = ""; + if (!PyArg_ParseTuple(args, "si|s", &mode, &n, &pixel_format)) { return NULL; } @@ -391,14 +392,14 @@ PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) { return NULL; } - decoder = PyImaging_DecoderNew(0); + decoder = PyImaging_DecoderNew(sizeof(char *)); if (decoder == NULL) { return NULL; } decoder->decode = ImagingBcnDecode; decoder->state.state = n; - decoder->state.ystep = ystep; + ((BCNSTATE *)decoder->state.context)->pixel_format = pixel_format; return (PyObject *)decoder; } diff --git a/src/libImaging/Bcn.h b/src/libImaging/Bcn.h new file mode 100644 index 000000000..1a6fbee45 --- /dev/null +++ b/src/libImaging/Bcn.h @@ -0,0 +1,3 @@ +typedef struct { + char *pixel_format; +} BCNSTATE; diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 5e8b456b8..22b36eb7a 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -13,6 +13,8 @@ #include "Imaging.h" +#include "Bcn.h" + typedef struct { UINT8 r, g, b, a; } rgba; @@ -35,6 +37,11 @@ typedef struct { UINT8 lut[6]; } bc3_alpha; +typedef struct { + INT8 a0, a1; + UINT8 lut[6]; +} bc5s_alpha; + #define LOAD16(p) (p)[0] | ((p)[1] << 8) #define LOAD32(p) (p)[0] | ((p)[1] << 8) | ((p)[2] << 16) | ((p)[3] << 24) @@ -46,11 +53,6 @@ bc1_color_load(bc1_color *dst, const UINT8 *src) { dst->lut = LOAD32(src + 4); } -static void -bc3_alpha_load(bc3_alpha *dst, const UINT8 *src) { - memcpy(dst, src, sizeof(bc3_alpha)); -} - static rgba decode_565(UINT16 x) { rgba c; @@ -113,15 +115,26 @@ decode_bc1_color(rgba *dst, const UINT8 *src, int separate_alpha) { } static void -decode_bc3_alpha(char *dst, const UINT8 *src, int stride, int o) { - bc3_alpha b; +decode_bc3_alpha(char *dst, const UINT8 *src, int stride, int o, int sign) { UINT16 a0, a1; UINT8 a[8]; - int n, lut, aw; - bc3_alpha_load(&b, src); + int n, lut1, lut2, aw; + if (sign == 1) { + bc5s_alpha b; + memcpy(&b, src, sizeof(bc5s_alpha)); + a0 = (b.a0 + 255) / 2; + a1 = (b.a1 + 255) / 2; + lut1 = b.lut[0] | (b.lut[1] << 8) | (b.lut[2] << 16); + lut2 = b.lut[3] | (b.lut[4] << 8) | (b.lut[5] << 16); + } else { + bc3_alpha b; + memcpy(&b, src, sizeof(bc3_alpha)); + a0 = b.a0; + a1 = b.a1; + lut1 = b.lut[0] | (b.lut[1] << 8) | (b.lut[2] << 16); + lut2 = b.lut[3] | (b.lut[4] << 8) | (b.lut[5] << 16); + } - a0 = b.a0; - a1 = b.a1; a[0] = (UINT8)a0; a[1] = (UINT8)a1; if (a0 > a1) { @@ -139,14 +152,12 @@ decode_bc3_alpha(char *dst, const UINT8 *src, int stride, int o) { a[6] = 0; a[7] = 0xff; } - lut = b.lut[0] | (b.lut[1] << 8) | (b.lut[2] << 16); for (n = 0; n < 8; n++) { - aw = 7 & (lut >> (3 * n)); + aw = 7 & (lut1 >> (3 * n)); dst[stride * n + o] = a[aw]; } - lut = b.lut[3] | (b.lut[4] << 8) | (b.lut[5] << 16); for (n = 0; n < 8; n++) { - aw = 7 & (lut >> (3 * n)); + aw = 7 & (lut2 >> (3 * n)); dst[stride * (8 + n) + o] = a[aw]; } } @@ -172,18 +183,18 @@ decode_bc2_block(rgba *col, const UINT8 *src) { static void decode_bc3_block(rgba *col, const UINT8 *src) { decode_bc1_color(col, src + 8, 1); - decode_bc3_alpha((char *)col, src, sizeof(col[0]), 3); + decode_bc3_alpha((char *)col, src, sizeof(col[0]), 3, 0); } static void decode_bc4_block(lum *col, const UINT8 *src) { - decode_bc3_alpha((char *)col, src, sizeof(col[0]), 0); + decode_bc3_alpha((char *)col, src, sizeof(col[0]), 0, 0); } static void -decode_bc5_block(rgba *col, const UINT8 *src) { - decode_bc3_alpha((char *)col, src, sizeof(col[0]), 0); - decode_bc3_alpha((char *)col, src + 8, sizeof(col[0]), 1); +decode_bc5_block(rgba *col, const UINT8 *src, int sign) { + decode_bc3_alpha((char *)col, src, sizeof(col[0]), 0, sign); + decode_bc3_alpha((char *)col, src + 8, sizeof(col[0]), 1, sign); } /* BC6 and BC7 are described here: @@ -813,7 +824,7 @@ put_block(Imaging im, ImagingCodecState state, const char *col, int sz, int C) { static int decode_bcn( - Imaging im, ImagingCodecState state, const UINT8 *src, int bytes, int N, int C) { + Imaging im, ImagingCodecState state, const UINT8 *src, int bytes, int N, int C, char *pixel_format) { int ymax = state->ysize + state->yoff; const UINT8 *ptr = src; switch (N) { @@ -836,7 +847,19 @@ decode_bcn( DECODE_LOOP(2, 16, rgba); DECODE_LOOP(3, 16, rgba); DECODE_LOOP(4, 8, lum); - DECODE_LOOP(5, 16, rgba); + case 5: + while (bytes >= 16) { + rgba col[16]; + memset(col, 0, 16 * sizeof(col[0])); + decode_bc5_block(col, ptr, strcmp(pixel_format, "BC5S") == 0 ? 1 : 0); + put_block(im, state, (const char *)col, sizeof(col[0]), C); + ptr += 16; + bytes -= 16; + if (state->y >= ymax) { + return -1; + } + } + break; case 6: while (bytes >= 16) { rgb32f col[16]; @@ -849,7 +872,7 @@ decode_bcn( } } break; - DECODE_LOOP(7, 16, rgba); + DECODE_LOOP(7, 16, rgba); #undef DECODE_LOOP } return (int)(ptr - src); @@ -860,9 +883,7 @@ ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt int N = state->state & 0xf; int width = state->xsize; int height = state->ysize; - if ((width & 3) | (height & 3)) { - return decode_bcn(im, state, buf, bytes, N, 1); - } else { - return decode_bcn(im, state, buf, bytes, N, 0); - } + int C = (width & 3) | (height & 3) ? 1 : 0; + char *pixel_format = ((BCNSTATE *)state->context)->pixel_format; + return decode_bcn(im, state, buf, bytes, N, C, pixel_format); }