Skip to content

Commit

Permalink
Merge pull request #5501 from radarhere/dds_bc5
Browse files Browse the repository at this point in the history
  • Loading branch information
hugovk committed Jun 11, 2021
2 parents b1b0dcb + cc28ad2 commit 1018873
Show file tree
Hide file tree
Showing 12 changed files with 120 additions and 47 deletions.
Binary file added Tests/images/bc5_snorm.dds
Binary file not shown.
Binary file added Tests/images/bc5_typeless.dds
Binary file not shown.
Binary file added Tests/images/bc5_unorm.dds
Binary file not shown.
Binary file added Tests/images/bc5_unorm.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added Tests/images/bc5s.dds
Binary file not shown.
Binary file added Tests/images/bc5s.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
40 changes: 34 additions & 6 deletions Tests/test_file_dds.py
Expand Up @@ -10,6 +10,10 @@
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_TYPELESS = "Tests/images/bc5_typeless.dds"
TEST_FILE_DX10_BC5_UNORM = "Tests/images/bc5_unorm.dds"
TEST_FILE_DX10_BC5_SNORM = "Tests/images/bc5_snorm.dds"
TEST_FILE_BC5S = "Tests/images/bc5s.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"
Expand All @@ -32,6 +36,19 @@ def test_sanity_dxt1():
assert_image_equal(im, target)


def test_sanity_dxt3():
"""Check DXT3 images can be opened"""

with Image.open(TEST_FILE_DXT3) as im:
im.load()

assert im.format == "DDS"
assert im.mode == "RGBA"
assert im.size == (256, 256)

assert_image_equal_tofile(im, TEST_FILE_DXT3.replace(".dds", ".png"))


def test_sanity_dxt5():
"""Check DXT5 images can be opened"""

Expand All @@ -45,17 +62,28 @@ def test_sanity_dxt5():
assert_image_equal_tofile(im, TEST_FILE_DXT5.replace(".dds", ".png"))


def test_sanity_dxt3():
"""Check DXT3 images can be opened"""

with Image.open(TEST_FILE_DXT3) as im:
@pytest.mark.parametrize(
("image_path", "expected_path"),
(
# hexeditted to be typeless
(TEST_FILE_DX10_BC5_TYPELESS, TEST_FILE_DX10_BC5_UNORM),
(TEST_FILE_DX10_BC5_UNORM, TEST_FILE_DX10_BC5_UNORM),
# hexeditted to use DX10 FourCC
(TEST_FILE_DX10_BC5_SNORM, TEST_FILE_BC5S),
(TEST_FILE_BC5S, TEST_FILE_BC5S),
),
)
def test_dx10_bc5(image_path, expected_path):
"""Check DX10 BC5 images can be opened"""

with Image.open(image_path) as im:
im.load()

assert im.format == "DDS"
assert im.mode == "RGBA"
assert im.mode == "RGB"
assert im.size == (256, 256)

assert_image_equal_tofile(im, TEST_FILE_DXT3.replace(".dds", ".png"))
assert_image_equal_tofile(im, expected_path.replace(".dds", ".png"))


def test_dx10_bc7():
Expand Down
7 changes: 4 additions & 3 deletions docs/releasenotes/8.3.0.rst
Expand Up @@ -66,7 +66,8 @@ TODO
Other Changes
=============

TODO
^^^^
Added DDS BC5 reading
^^^^^^^^^^^^^^^^^^^^^

TODO
Support has been added to read the BC5 format of DDS images, whether UNORM, SNORM or
TYPELESS.
25 changes: 21 additions & 4 deletions src/PIL/DdsImagePlugin.py
Expand Up @@ -97,6 +97,9 @@
DXGI_FORMAT_R8G8B8A8_TYPELESS = 27
DXGI_FORMAT_R8G8B8A8_UNORM = 28
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29
DXGI_FORMAT_BC5_TYPELESS = 82
DXGI_FORMAT_BC5_UNORM = 83
DXGI_FORMAT_BC5_SNORM = 84
DXGI_FORMAT_BC7_TYPELESS = 97
DXGI_FORMAT_BC7_UNORM = 98
DXGI_FORMAT_BC7_UNORM_SRGB = 99
Expand Down Expand Up @@ -150,12 +153,24 @@ def _open(self):
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
dxt10 = BytesIO(self.fp.read(20))
dxgi_format, dimension = struct.unpack("<II", dxt10.read(8))
if dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
(dxgi_format,) = struct.unpack("<I", self.fp.read(4))
self.fp.read(16)
if dxgi_format in (DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM):
self.pixel_format = "BC5"
n = 5
self.mode = "RGB"
elif dxgi_format == DXGI_FORMAT_BC5_SNORM:
self.pixel_format = "BC5S"
n = 5
self.mode = "RGB"
elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM):
self.pixel_format = "BC7"
n = 7
elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:
Expand All @@ -178,7 +193,9 @@ def _open(self):
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
Expand Down
15 changes: 9 additions & 6 deletions src/decode.c
Expand Up @@ -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"

/* -------------------------------------------------------------------- */
Expand Down Expand Up @@ -359,22 +360,24 @@ 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;
}

switch (n) {
case 1: /* BC1: 565 color, 1-bit alpha */
case 2: /* BC2: 565 color, 4-bit alpha */
case 3: /* BC3: 565 color, 2-endpoint 8-bit interpolated alpha */
case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */
case 7: /* BC7: 4-channel 8-bit via everything */
actual = "RGBA";
break;
case 4: /* BC4: 1-channel 8-bit via 1 BC3 alpha block */
actual = "L";
break;
case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */
actual = "RGB";
break;
case 6: /* BC6: 3-channel 16-bit float */
/* TODO: support 4-channel floating point images */
actual = "RGBAF";
Expand All @@ -389,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;
}
Expand Down
3 changes: 3 additions & 0 deletions src/libImaging/Bcn.h
@@ -0,0 +1,3 @@
typedef struct {
char *pixel_format;
} BCNSTATE;
77 changes: 49 additions & 28 deletions src/libImaging/BcnDecode.c
Expand Up @@ -13,6 +13,8 @@

#include "Imaging.h"

#include "Bcn.h"

typedef struct {
UINT8 r, g, b, a;
} rgba;
Expand All @@ -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)
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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];
}
}
Expand All @@ -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:
Expand Down Expand Up @@ -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) {
Expand All @@ -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];
Expand All @@ -849,7 +872,7 @@ decode_bcn(
}
}
break;
DECODE_LOOP(7, 16, rgba);
DECODE_LOOP(7, 16, rgba);
#undef DECODE_LOOP
}
return (int)(ptr - src);
Expand All @@ -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);
}

0 comments on commit 1018873

Please sign in to comment.