Merge pull request #5501 from radarhere/dds_bc5

This commit is contained in:
Hugo van Kemenade 2021-06-11 10:34:19 +03:00 committed by GitHub
commit 101887360c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 119 additions and 46 deletions

BIN
Tests/images/bc5_snorm.dds Normal file

Binary file not shown.

Binary file not shown.

BIN
Tests/images/bc5_unorm.dds Normal file

Binary file not shown.

BIN
Tests/images/bc5_unorm.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

BIN
Tests/images/bc5s.dds Normal file

Binary file not shown.

BIN
Tests/images/bc5s.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

View File

@ -10,6 +10,10 @@ from .helper import assert_image_equal, assert_image_equal_tofile
TEST_FILE_DXT1 = "Tests/images/dxt1-rgb-4bbp-noalpha_MipMaps-1.dds" 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_DXT3 = "Tests/images/dxt3-argb-8bbp-explicitalpha_MipMaps-1.dds"
TEST_FILE_DXT5 = "Tests/images/dxt5-argb-8bbp-interpolatedalpha_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 = "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_BC7_UNORM_SRGB = "Tests/images/DXGI_FORMAT_BC7_UNORM_SRGB.dds"
TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds" TEST_FILE_DX10_R8G8B8A8 = "Tests/images/argb-32bpp_MipMaps-1.dds"
@ -32,6 +36,19 @@ def test_sanity_dxt1():
assert_image_equal(im, target) 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(): def test_sanity_dxt5():
"""Check DXT5 images can be opened""" """Check DXT5 images can be opened"""
@ -45,17 +62,28 @@ def test_sanity_dxt5():
assert_image_equal_tofile(im, TEST_FILE_DXT5.replace(".dds", ".png")) assert_image_equal_tofile(im, TEST_FILE_DXT5.replace(".dds", ".png"))
def test_sanity_dxt3(): @pytest.mark.parametrize(
"""Check DXT3 images can be opened""" ("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(TEST_FILE_DXT3) as im: with Image.open(image_path) as im:
im.load() im.load()
assert im.format == "DDS" assert im.format == "DDS"
assert im.mode == "RGBA" assert im.mode == "RGB"
assert im.size == (256, 256) 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(): def test_dx10_bc7():

View File

@ -66,7 +66,8 @@ TODO
Other Changes 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.

View File

@ -97,6 +97,9 @@ DXT5_FOURCC = 0x35545844
DXGI_FORMAT_R8G8B8A8_TYPELESS = 27 DXGI_FORMAT_R8G8B8A8_TYPELESS = 27
DXGI_FORMAT_R8G8B8A8_UNORM = 28 DXGI_FORMAT_R8G8B8A8_UNORM = 28
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29 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_TYPELESS = 97
DXGI_FORMAT_BC7_UNORM = 98 DXGI_FORMAT_BC7_UNORM = 98
DXGI_FORMAT_BC7_UNORM_SRGB = 99 DXGI_FORMAT_BC7_UNORM_SRGB = 99
@ -150,12 +153,24 @@ class DdsImageFile(ImageFile.ImageFile):
elif fourcc == b"DXT5": elif fourcc == b"DXT5":
self.pixel_format = "DXT5" self.pixel_format = "DXT5"
n = 3 n = 3
elif fourcc == b"BC5S":
self.pixel_format = "BC5S"
n = 5
self.mode = "RGB"
elif fourcc == b"DX10": elif fourcc == b"DX10":
data_start += 20 data_start += 20
# ignoring flags which pertain to volume textures and cubemaps # ignoring flags which pertain to volume textures and cubemaps
dxt10 = BytesIO(self.fp.read(20)) (dxgi_format,) = struct.unpack("<I", self.fp.read(4))
dxgi_format, dimension = struct.unpack("<II", dxt10.read(8)) self.fp.read(16)
if dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM): 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" self.pixel_format = "BC7"
n = 7 n = 7
elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB: elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB:
@ -178,7 +193,9 @@ class DdsImageFile(ImageFile.ImageFile):
else: else:
raise NotImplementedError(f"Unimplemented pixel format {repr(fourcc)}") 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): def load_seek(self, pos):
pass pass

View File

@ -34,9 +34,10 @@
#include "libImaging/Imaging.h" #include "libImaging/Imaging.h"
#include "libImaging/Bit.h"
#include "libImaging/Bcn.h"
#include "libImaging/Gif.h" #include "libImaging/Gif.h"
#include "libImaging/Raw.h" #include "libImaging/Raw.h"
#include "libImaging/Bit.h"
#include "libImaging/Sgi.h" #include "libImaging/Sgi.h"
/* -------------------------------------------------------------------- */ /* -------------------------------------------------------------------- */
@ -359,8 +360,8 @@ PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) {
char *mode; char *mode;
char *actual; char *actual;
int n = 0; int n = 0;
int ystep = 1; char *pixel_format = "";
if (!PyArg_ParseTuple(args, "s|ii", &mode, &n, &ystep)) { if (!PyArg_ParseTuple(args, "si|s", &mode, &n, &pixel_format)) {
return NULL; return NULL;
} }
@ -368,13 +369,15 @@ PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) {
case 1: /* BC1: 565 color, 1-bit alpha */ case 1: /* BC1: 565 color, 1-bit alpha */
case 2: /* BC2: 565 color, 4-bit alpha */ case 2: /* BC2: 565 color, 4-bit alpha */
case 3: /* BC3: 565 color, 2-endpoint 8-bit interpolated 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 */ case 7: /* BC7: 4-channel 8-bit via everything */
actual = "RGBA"; actual = "RGBA";
break; break;
case 4: /* BC4: 1-channel 8-bit via 1 BC3 alpha block */ case 4: /* BC4: 1-channel 8-bit via 1 BC3 alpha block */
actual = "L"; actual = "L";
break; break;
case 5: /* BC5: 2-channel 8-bit via 2 BC3 alpha blocks */
actual = "RGB";
break;
case 6: /* BC6: 3-channel 16-bit float */ case 6: /* BC6: 3-channel 16-bit float */
/* TODO: support 4-channel floating point images */ /* TODO: support 4-channel floating point images */
actual = "RGBAF"; actual = "RGBAF";
@ -389,14 +392,14 @@ PyImaging_BcnDecoderNew(PyObject *self, PyObject *args) {
return NULL; return NULL;
} }
decoder = PyImaging_DecoderNew(0); decoder = PyImaging_DecoderNew(sizeof(char *));
if (decoder == NULL) { if (decoder == NULL) {
return NULL; return NULL;
} }
decoder->decode = ImagingBcnDecode; decoder->decode = ImagingBcnDecode;
decoder->state.state = n; decoder->state.state = n;
decoder->state.ystep = ystep; ((BCNSTATE *)decoder->state.context)->pixel_format = pixel_format;
return (PyObject *)decoder; return (PyObject *)decoder;
} }

3
src/libImaging/Bcn.h Normal file
View File

@ -0,0 +1,3 @@
typedef struct {
char *pixel_format;
} BCNSTATE;

View File

@ -13,6 +13,8 @@
#include "Imaging.h" #include "Imaging.h"
#include "Bcn.h"
typedef struct { typedef struct {
UINT8 r, g, b, a; UINT8 r, g, b, a;
} rgba; } rgba;
@ -35,6 +37,11 @@ typedef struct {
UINT8 lut[6]; UINT8 lut[6];
} bc3_alpha; } bc3_alpha;
typedef struct {
INT8 a0, a1;
UINT8 lut[6];
} bc5s_alpha;
#define LOAD16(p) (p)[0] | ((p)[1] << 8) #define LOAD16(p) (p)[0] | ((p)[1] << 8)
#define LOAD32(p) (p)[0] | ((p)[1] << 8) | ((p)[2] << 16) | ((p)[3] << 24) #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); dst->lut = LOAD32(src + 4);
} }
static void
bc3_alpha_load(bc3_alpha *dst, const UINT8 *src) {
memcpy(dst, src, sizeof(bc3_alpha));
}
static rgba static rgba
decode_565(UINT16 x) { decode_565(UINT16 x) {
rgba c; rgba c;
@ -113,15 +115,26 @@ decode_bc1_color(rgba *dst, const UINT8 *src, int separate_alpha) {
} }
static void static void
decode_bc3_alpha(char *dst, const UINT8 *src, int stride, int o) { decode_bc3_alpha(char *dst, const UINT8 *src, int stride, int o, int sign) {
bc3_alpha b;
UINT16 a0, a1; UINT16 a0, a1;
UINT8 a[8]; UINT8 a[8];
int n, lut, aw; int n, lut1, lut2, aw;
bc3_alpha_load(&b, src); 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[0] = (UINT8)a0;
a[1] = (UINT8)a1; a[1] = (UINT8)a1;
if (a0 > a1) { if (a0 > a1) {
@ -139,14 +152,12 @@ decode_bc3_alpha(char *dst, const UINT8 *src, int stride, int o) {
a[6] = 0; a[6] = 0;
a[7] = 0xff; a[7] = 0xff;
} }
lut = b.lut[0] | (b.lut[1] << 8) | (b.lut[2] << 16);
for (n = 0; n < 8; n++) { for (n = 0; n < 8; n++) {
aw = 7 & (lut >> (3 * n)); aw = 7 & (lut1 >> (3 * n));
dst[stride * n + o] = a[aw]; dst[stride * n + o] = a[aw];
} }
lut = b.lut[3] | (b.lut[4] << 8) | (b.lut[5] << 16);
for (n = 0; n < 8; n++) { for (n = 0; n < 8; n++) {
aw = 7 & (lut >> (3 * n)); aw = 7 & (lut2 >> (3 * n));
dst[stride * (8 + n) + o] = a[aw]; dst[stride * (8 + n) + o] = a[aw];
} }
} }
@ -172,18 +183,18 @@ decode_bc2_block(rgba *col, const UINT8 *src) {
static void static void
decode_bc3_block(rgba *col, const UINT8 *src) { decode_bc3_block(rgba *col, const UINT8 *src) {
decode_bc1_color(col, src + 8, 1); 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 static void
decode_bc4_block(lum *col, const UINT8 *src) { 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 static void
decode_bc5_block(rgba *col, const UINT8 *src) { decode_bc5_block(rgba *col, const UINT8 *src, int sign) {
decode_bc3_alpha((char *)col, src, sizeof(col[0]), 0); decode_bc3_alpha((char *)col, src, sizeof(col[0]), 0, sign);
decode_bc3_alpha((char *)col, src + 8, sizeof(col[0]), 1); decode_bc3_alpha((char *)col, src + 8, sizeof(col[0]), 1, sign);
} }
/* BC6 and BC7 are described here: /* 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 static int
decode_bcn( 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; int ymax = state->ysize + state->yoff;
const UINT8 *ptr = src; const UINT8 *ptr = src;
switch (N) { switch (N) {
@ -836,7 +847,19 @@ decode_bcn(
DECODE_LOOP(2, 16, rgba); DECODE_LOOP(2, 16, rgba);
DECODE_LOOP(3, 16, rgba); DECODE_LOOP(3, 16, rgba);
DECODE_LOOP(4, 8, lum); 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: case 6:
while (bytes >= 16) { while (bytes >= 16) {
rgb32f col[16]; rgb32f col[16];
@ -849,7 +872,7 @@ decode_bcn(
} }
} }
break; break;
DECODE_LOOP(7, 16, rgba); DECODE_LOOP(7, 16, rgba);
#undef DECODE_LOOP #undef DECODE_LOOP
} }
return (int)(ptr - src); 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 N = state->state & 0xf;
int width = state->xsize; int width = state->xsize;
int height = state->ysize; int height = state->ysize;
if ((width & 3) | (height & 3)) { int C = (width & 3) | (height & 3) ? 1 : 0;
return decode_bcn(im, state, buf, bytes, N, 1); char *pixel_format = ((BCNSTATE *)state->context)->pixel_format;
} else { return decode_bcn(im, state, buf, bytes, N, C, pixel_format);
return decode_bcn(im, state, buf, bytes, N, 0);
}
} }