|
""" |
|
A Pillow loader for .dds files (S3TC-compressed aka DXTC) |
|
Jerome Leclanche <jerome@leclan.ch> |
|
|
|
Documentation: |
|
https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt |
|
|
|
The contents of this file are hereby released in the public domain (CC0) |
|
Full text of the CC0 license: |
|
https://creativecommons.org/publicdomain/zero/1.0/ |
|
""" |
|
from __future__ import annotations |
|
|
|
import io |
|
import struct |
|
import sys |
|
from enum import IntEnum, IntFlag |
|
|
|
from . import Image, ImageFile, ImagePalette |
|
from ._binary import i32le as i32 |
|
from ._binary import o8 |
|
from ._binary import o32le as o32 |
|
|
|
|
|
DDS_MAGIC = 0x20534444 |
|
|
|
|
|
|
|
class DDSD(IntFlag): |
|
CAPS = 0x1 |
|
HEIGHT = 0x2 |
|
WIDTH = 0x4 |
|
PITCH = 0x8 |
|
PIXELFORMAT = 0x1000 |
|
MIPMAPCOUNT = 0x20000 |
|
LINEARSIZE = 0x80000 |
|
DEPTH = 0x800000 |
|
|
|
|
|
|
|
class DDSCAPS(IntFlag): |
|
COMPLEX = 0x8 |
|
TEXTURE = 0x1000 |
|
MIPMAP = 0x400000 |
|
|
|
|
|
class DDSCAPS2(IntFlag): |
|
CUBEMAP = 0x200 |
|
CUBEMAP_POSITIVEX = 0x400 |
|
CUBEMAP_NEGATIVEX = 0x800 |
|
CUBEMAP_POSITIVEY = 0x1000 |
|
CUBEMAP_NEGATIVEY = 0x2000 |
|
CUBEMAP_POSITIVEZ = 0x4000 |
|
CUBEMAP_NEGATIVEZ = 0x8000 |
|
VOLUME = 0x200000 |
|
|
|
|
|
|
|
class DDPF(IntFlag): |
|
ALPHAPIXELS = 0x1 |
|
ALPHA = 0x2 |
|
FOURCC = 0x4 |
|
PALETTEINDEXED8 = 0x20 |
|
RGB = 0x40 |
|
LUMINANCE = 0x20000 |
|
|
|
|
|
|
|
class DXGI_FORMAT(IntEnum): |
|
UNKNOWN = 0 |
|
R32G32B32A32_TYPELESS = 1 |
|
R32G32B32A32_FLOAT = 2 |
|
R32G32B32A32_UINT = 3 |
|
R32G32B32A32_SINT = 4 |
|
R32G32B32_TYPELESS = 5 |
|
R32G32B32_FLOAT = 6 |
|
R32G32B32_UINT = 7 |
|
R32G32B32_SINT = 8 |
|
R16G16B16A16_TYPELESS = 9 |
|
R16G16B16A16_FLOAT = 10 |
|
R16G16B16A16_UNORM = 11 |
|
R16G16B16A16_UINT = 12 |
|
R16G16B16A16_SNORM = 13 |
|
R16G16B16A16_SINT = 14 |
|
R32G32_TYPELESS = 15 |
|
R32G32_FLOAT = 16 |
|
R32G32_UINT = 17 |
|
R32G32_SINT = 18 |
|
R32G8X24_TYPELESS = 19 |
|
D32_FLOAT_S8X24_UINT = 20 |
|
R32_FLOAT_X8X24_TYPELESS = 21 |
|
X32_TYPELESS_G8X24_UINT = 22 |
|
R10G10B10A2_TYPELESS = 23 |
|
R10G10B10A2_UNORM = 24 |
|
R10G10B10A2_UINT = 25 |
|
R11G11B10_FLOAT = 26 |
|
R8G8B8A8_TYPELESS = 27 |
|
R8G8B8A8_UNORM = 28 |
|
R8G8B8A8_UNORM_SRGB = 29 |
|
R8G8B8A8_UINT = 30 |
|
R8G8B8A8_SNORM = 31 |
|
R8G8B8A8_SINT = 32 |
|
R16G16_TYPELESS = 33 |
|
R16G16_FLOAT = 34 |
|
R16G16_UNORM = 35 |
|
R16G16_UINT = 36 |
|
R16G16_SNORM = 37 |
|
R16G16_SINT = 38 |
|
R32_TYPELESS = 39 |
|
D32_FLOAT = 40 |
|
R32_FLOAT = 41 |
|
R32_UINT = 42 |
|
R32_SINT = 43 |
|
R24G8_TYPELESS = 44 |
|
D24_UNORM_S8_UINT = 45 |
|
R24_UNORM_X8_TYPELESS = 46 |
|
X24_TYPELESS_G8_UINT = 47 |
|
R8G8_TYPELESS = 48 |
|
R8G8_UNORM = 49 |
|
R8G8_UINT = 50 |
|
R8G8_SNORM = 51 |
|
R8G8_SINT = 52 |
|
R16_TYPELESS = 53 |
|
R16_FLOAT = 54 |
|
D16_UNORM = 55 |
|
R16_UNORM = 56 |
|
R16_UINT = 57 |
|
R16_SNORM = 58 |
|
R16_SINT = 59 |
|
R8_TYPELESS = 60 |
|
R8_UNORM = 61 |
|
R8_UINT = 62 |
|
R8_SNORM = 63 |
|
R8_SINT = 64 |
|
A8_UNORM = 65 |
|
R1_UNORM = 66 |
|
R9G9B9E5_SHAREDEXP = 67 |
|
R8G8_B8G8_UNORM = 68 |
|
G8R8_G8B8_UNORM = 69 |
|
BC1_TYPELESS = 70 |
|
BC1_UNORM = 71 |
|
BC1_UNORM_SRGB = 72 |
|
BC2_TYPELESS = 73 |
|
BC2_UNORM = 74 |
|
BC2_UNORM_SRGB = 75 |
|
BC3_TYPELESS = 76 |
|
BC3_UNORM = 77 |
|
BC3_UNORM_SRGB = 78 |
|
BC4_TYPELESS = 79 |
|
BC4_UNORM = 80 |
|
BC4_SNORM = 81 |
|
BC5_TYPELESS = 82 |
|
BC5_UNORM = 83 |
|
BC5_SNORM = 84 |
|
B5G6R5_UNORM = 85 |
|
B5G5R5A1_UNORM = 86 |
|
B8G8R8A8_UNORM = 87 |
|
B8G8R8X8_UNORM = 88 |
|
R10G10B10_XR_BIAS_A2_UNORM = 89 |
|
B8G8R8A8_TYPELESS = 90 |
|
B8G8R8A8_UNORM_SRGB = 91 |
|
B8G8R8X8_TYPELESS = 92 |
|
B8G8R8X8_UNORM_SRGB = 93 |
|
BC6H_TYPELESS = 94 |
|
BC6H_UF16 = 95 |
|
BC6H_SF16 = 96 |
|
BC7_TYPELESS = 97 |
|
BC7_UNORM = 98 |
|
BC7_UNORM_SRGB = 99 |
|
AYUV = 100 |
|
Y410 = 101 |
|
Y416 = 102 |
|
NV12 = 103 |
|
P010 = 104 |
|
P016 = 105 |
|
OPAQUE_420 = 106 |
|
YUY2 = 107 |
|
Y210 = 108 |
|
Y216 = 109 |
|
NV11 = 110 |
|
AI44 = 111 |
|
IA44 = 112 |
|
P8 = 113 |
|
A8P8 = 114 |
|
B4G4R4A4_UNORM = 115 |
|
P208 = 130 |
|
V208 = 131 |
|
V408 = 132 |
|
SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189 |
|
SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190 |
|
|
|
|
|
class D3DFMT(IntEnum): |
|
UNKNOWN = 0 |
|
R8G8B8 = 20 |
|
A8R8G8B8 = 21 |
|
X8R8G8B8 = 22 |
|
R5G6B5 = 23 |
|
X1R5G5B5 = 24 |
|
A1R5G5B5 = 25 |
|
A4R4G4B4 = 26 |
|
R3G3B2 = 27 |
|
A8 = 28 |
|
A8R3G3B2 = 29 |
|
X4R4G4B4 = 30 |
|
A2B10G10R10 = 31 |
|
A8B8G8R8 = 32 |
|
X8B8G8R8 = 33 |
|
G16R16 = 34 |
|
A2R10G10B10 = 35 |
|
A16B16G16R16 = 36 |
|
A8P8 = 40 |
|
P8 = 41 |
|
L8 = 50 |
|
A8L8 = 51 |
|
A4L4 = 52 |
|
V8U8 = 60 |
|
L6V5U5 = 61 |
|
X8L8V8U8 = 62 |
|
Q8W8V8U8 = 63 |
|
V16U16 = 64 |
|
A2W10V10U10 = 67 |
|
D16_LOCKABLE = 70 |
|
D32 = 71 |
|
D15S1 = 73 |
|
D24S8 = 75 |
|
D24X8 = 77 |
|
D24X4S4 = 79 |
|
D16 = 80 |
|
D32F_LOCKABLE = 82 |
|
D24FS8 = 83 |
|
D32_LOCKABLE = 84 |
|
S8_LOCKABLE = 85 |
|
L16 = 81 |
|
VERTEXDATA = 100 |
|
INDEX16 = 101 |
|
INDEX32 = 102 |
|
Q16W16V16U16 = 110 |
|
R16F = 111 |
|
G16R16F = 112 |
|
A16B16G16R16F = 113 |
|
R32F = 114 |
|
G32R32F = 115 |
|
A32B32G32R32F = 116 |
|
CxV8U8 = 117 |
|
A1 = 118 |
|
A2B10G10R10_XR_BIAS = 119 |
|
BINARYBUFFER = 199 |
|
|
|
UYVY = i32(b"UYVY") |
|
R8G8_B8G8 = i32(b"RGBG") |
|
YUY2 = i32(b"YUY2") |
|
G8R8_G8B8 = i32(b"GRGB") |
|
DXT1 = i32(b"DXT1") |
|
DXT2 = i32(b"DXT2") |
|
DXT3 = i32(b"DXT3") |
|
DXT4 = i32(b"DXT4") |
|
DXT5 = i32(b"DXT5") |
|
DX10 = i32(b"DX10") |
|
BC4S = i32(b"BC4S") |
|
BC4U = i32(b"BC4U") |
|
BC5S = i32(b"BC5S") |
|
BC5U = i32(b"BC5U") |
|
ATI1 = i32(b"ATI1") |
|
ATI2 = i32(b"ATI2") |
|
MULTI2_ARGB8 = i32(b"MET1") |
|
|
|
|
|
|
|
module = sys.modules[__name__] |
|
for item in DDSD: |
|
setattr(module, "DDSD_" + item.name, item.value) |
|
for item in DDSCAPS: |
|
setattr(module, "DDSCAPS_" + item.name, item.value) |
|
for item in DDSCAPS2: |
|
setattr(module, "DDSCAPS2_" + item.name, item.value) |
|
for item in DDPF: |
|
setattr(module, "DDPF_" + item.name, item.value) |
|
|
|
DDS_FOURCC = DDPF.FOURCC |
|
DDS_RGB = DDPF.RGB |
|
DDS_RGBA = DDPF.RGB | DDPF.ALPHAPIXELS |
|
DDS_LUMINANCE = DDPF.LUMINANCE |
|
DDS_LUMINANCEA = DDPF.LUMINANCE | DDPF.ALPHAPIXELS |
|
DDS_ALPHA = DDPF.ALPHA |
|
DDS_PAL8 = DDPF.PALETTEINDEXED8 |
|
|
|
DDS_HEADER_FLAGS_TEXTURE = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT |
|
DDS_HEADER_FLAGS_MIPMAP = DDSD.MIPMAPCOUNT |
|
DDS_HEADER_FLAGS_VOLUME = DDSD.DEPTH |
|
DDS_HEADER_FLAGS_PITCH = DDSD.PITCH |
|
DDS_HEADER_FLAGS_LINEARSIZE = DDSD.LINEARSIZE |
|
|
|
DDS_HEIGHT = DDSD.HEIGHT |
|
DDS_WIDTH = DDSD.WIDTH |
|
|
|
DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS.TEXTURE |
|
DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS.COMPLEX | DDSCAPS.MIPMAP |
|
DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS.COMPLEX |
|
|
|
DDS_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEX |
|
DDS_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEX |
|
DDS_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEY |
|
DDS_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEY |
|
DDS_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEZ |
|
DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEZ |
|
|
|
DXT1_FOURCC = D3DFMT.DXT1 |
|
DXT3_FOURCC = D3DFMT.DXT3 |
|
DXT5_FOURCC = D3DFMT.DXT5 |
|
|
|
DXGI_FORMAT_R8G8B8A8_TYPELESS = DXGI_FORMAT.R8G8B8A8_TYPELESS |
|
DXGI_FORMAT_R8G8B8A8_UNORM = DXGI_FORMAT.R8G8B8A8_UNORM |
|
DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = DXGI_FORMAT.R8G8B8A8_UNORM_SRGB |
|
DXGI_FORMAT_BC5_TYPELESS = DXGI_FORMAT.BC5_TYPELESS |
|
DXGI_FORMAT_BC5_UNORM = DXGI_FORMAT.BC5_UNORM |
|
DXGI_FORMAT_BC5_SNORM = DXGI_FORMAT.BC5_SNORM |
|
DXGI_FORMAT_BC6H_UF16 = DXGI_FORMAT.BC6H_UF16 |
|
DXGI_FORMAT_BC6H_SF16 = DXGI_FORMAT.BC6H_SF16 |
|
DXGI_FORMAT_BC7_TYPELESS = DXGI_FORMAT.BC7_TYPELESS |
|
DXGI_FORMAT_BC7_UNORM = DXGI_FORMAT.BC7_UNORM |
|
DXGI_FORMAT_BC7_UNORM_SRGB = DXGI_FORMAT.BC7_UNORM_SRGB |
|
|
|
|
|
class DdsImageFile(ImageFile.ImageFile): |
|
format = "DDS" |
|
format_description = "DirectDraw Surface" |
|
|
|
def _open(self): |
|
if not _accept(self.fp.read(4)): |
|
msg = "not a DDS file" |
|
raise SyntaxError(msg) |
|
(header_size,) = struct.unpack("<I", self.fp.read(4)) |
|
if header_size != 124: |
|
msg = f"Unsupported header size {repr(header_size)}" |
|
raise OSError(msg) |
|
header_bytes = self.fp.read(header_size - 4) |
|
if len(header_bytes) != 120: |
|
msg = f"Incomplete header: {len(header_bytes)} bytes" |
|
raise OSError(msg) |
|
header = io.BytesIO(header_bytes) |
|
|
|
flags, height, width = struct.unpack("<3I", header.read(12)) |
|
self._size = (width, height) |
|
extents = (0, 0) + self.size |
|
|
|
pitch, depth, mipmaps = struct.unpack("<3I", header.read(12)) |
|
struct.unpack("<11I", header.read(44)) |
|
|
|
|
|
pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header.read(16)) |
|
n = 0 |
|
rawmode = None |
|
if pfflags & DDPF.RGB: |
|
|
|
if pfflags & DDPF.ALPHAPIXELS: |
|
self._mode = "RGBA" |
|
mask_count = 4 |
|
else: |
|
self._mode = "RGB" |
|
mask_count = 3 |
|
|
|
masks = struct.unpack(f"<{mask_count}I", header.read(mask_count * 4)) |
|
self.tile = [("dds_rgb", extents, 0, (bitcount, masks))] |
|
return |
|
elif pfflags & DDPF.LUMINANCE: |
|
if bitcount == 8: |
|
self._mode = "L" |
|
elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS: |
|
self._mode = "LA" |
|
else: |
|
msg = f"Unsupported bitcount {bitcount} for {pfflags}" |
|
raise OSError(msg) |
|
elif pfflags & DDPF.PALETTEINDEXED8: |
|
self._mode = "P" |
|
self.palette = ImagePalette.raw("RGBA", self.fp.read(1024)) |
|
elif pfflags & DDPF.FOURCC: |
|
offset = header_size + 4 |
|
if fourcc == D3DFMT.DXT1: |
|
self._mode = "RGBA" |
|
self.pixel_format = "DXT1" |
|
n = 1 |
|
elif fourcc == D3DFMT.DXT3: |
|
self._mode = "RGBA" |
|
self.pixel_format = "DXT3" |
|
n = 2 |
|
elif fourcc == D3DFMT.DXT5: |
|
self._mode = "RGBA" |
|
self.pixel_format = "DXT5" |
|
n = 3 |
|
elif fourcc in (D3DFMT.BC4U, D3DFMT.ATI1): |
|
self._mode = "L" |
|
self.pixel_format = "BC4" |
|
n = 4 |
|
elif fourcc == D3DFMT.BC5S: |
|
self._mode = "RGB" |
|
self.pixel_format = "BC5S" |
|
n = 5 |
|
elif fourcc in (D3DFMT.BC5U, D3DFMT.ATI2): |
|
self._mode = "RGB" |
|
self.pixel_format = "BC5" |
|
n = 5 |
|
elif fourcc == D3DFMT.DX10: |
|
offset += 20 |
|
|
|
(dxgi_format,) = struct.unpack("<I", self.fp.read(4)) |
|
self.fp.read(16) |
|
if dxgi_format in ( |
|
DXGI_FORMAT.BC1_UNORM, |
|
DXGI_FORMAT.BC1_TYPELESS, |
|
): |
|
self._mode = "RGBA" |
|
self.pixel_format = "BC1" |
|
n = 1 |
|
elif dxgi_format in (DXGI_FORMAT.BC4_TYPELESS, DXGI_FORMAT.BC4_UNORM): |
|
self._mode = "L" |
|
self.pixel_format = "BC4" |
|
n = 4 |
|
elif dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM): |
|
self._mode = "RGB" |
|
self.pixel_format = "BC5" |
|
n = 5 |
|
elif dxgi_format == DXGI_FORMAT.BC5_SNORM: |
|
self._mode = "RGB" |
|
self.pixel_format = "BC5S" |
|
n = 5 |
|
elif dxgi_format == DXGI_FORMAT.BC6H_UF16: |
|
self._mode = "RGB" |
|
self.pixel_format = "BC6H" |
|
n = 6 |
|
elif dxgi_format == DXGI_FORMAT.BC6H_SF16: |
|
self._mode = "RGB" |
|
self.pixel_format = "BC6HS" |
|
n = 6 |
|
elif dxgi_format in ( |
|
DXGI_FORMAT.BC7_TYPELESS, |
|
DXGI_FORMAT.BC7_UNORM, |
|
DXGI_FORMAT.BC7_UNORM_SRGB, |
|
): |
|
self._mode = "RGBA" |
|
self.pixel_format = "BC7" |
|
n = 7 |
|
if dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB: |
|
self.info["gamma"] = 1 / 2.2 |
|
elif dxgi_format in ( |
|
DXGI_FORMAT.R8G8B8A8_TYPELESS, |
|
DXGI_FORMAT.R8G8B8A8_UNORM, |
|
DXGI_FORMAT.R8G8B8A8_UNORM_SRGB, |
|
): |
|
self._mode = "RGBA" |
|
if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB: |
|
self.info["gamma"] = 1 / 2.2 |
|
else: |
|
msg = f"Unimplemented DXGI format {dxgi_format}" |
|
raise NotImplementedError(msg) |
|
else: |
|
msg = f"Unimplemented pixel format {repr(fourcc)}" |
|
raise NotImplementedError(msg) |
|
else: |
|
msg = f"Unknown pixel format flags {pfflags}" |
|
raise NotImplementedError(msg) |
|
|
|
if n: |
|
self.tile = [ |
|
ImageFile._Tile("bcn", extents, offset, (n, self.pixel_format)) |
|
] |
|
else: |
|
self.tile = [ImageFile._Tile("raw", extents, 0, rawmode or self.mode)] |
|
|
|
def load_seek(self, pos): |
|
pass |
|
|
|
|
|
class DdsRgbDecoder(ImageFile.PyDecoder): |
|
_pulls_fd = True |
|
|
|
def decode(self, buffer): |
|
bitcount, masks = self.args |
|
|
|
|
|
|
|
mask_offsets = [] |
|
|
|
mask_totals = [] |
|
for mask in masks: |
|
offset = 0 |
|
if mask != 0: |
|
while mask >> (offset + 1) << (offset + 1) == mask: |
|
offset += 1 |
|
mask_offsets.append(offset) |
|
mask_totals.append(mask >> offset) |
|
|
|
data = bytearray() |
|
bytecount = bitcount // 8 |
|
while len(data) < self.state.xsize * self.state.ysize * len(masks): |
|
value = int.from_bytes(self.fd.read(bytecount), "little") |
|
for i, mask in enumerate(masks): |
|
masked_value = value & mask |
|
|
|
data += o8( |
|
int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255) |
|
) |
|
self.set_as_raw(bytes(data)) |
|
return -1, 0 |
|
|
|
|
|
def _save(im, fp, filename): |
|
if im.mode not in ("RGB", "RGBA", "L", "LA"): |
|
msg = f"cannot write mode {im.mode} as DDS" |
|
raise OSError(msg) |
|
|
|
alpha = im.mode[-1] == "A" |
|
if im.mode[0] == "L": |
|
pixel_flags = DDPF.LUMINANCE |
|
rawmode = im.mode |
|
if alpha: |
|
rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF] |
|
else: |
|
rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000] |
|
else: |
|
pixel_flags = DDPF.RGB |
|
rawmode = im.mode[::-1] |
|
rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF] |
|
|
|
if alpha: |
|
r, g, b, a = im.split() |
|
im = Image.merge("RGBA", (a, r, g, b)) |
|
if alpha: |
|
pixel_flags |= DDPF.ALPHAPIXELS |
|
rgba_mask.append(0xFF000000 if alpha else 0) |
|
|
|
flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PITCH | DDSD.PIXELFORMAT |
|
bitcount = len(im.getbands()) * 8 |
|
pitch = (im.width * bitcount + 7) // 8 |
|
|
|
fp.write( |
|
o32(DDS_MAGIC) |
|
+ struct.pack( |
|
"<7I", |
|
124, |
|
flags, |
|
im.height, |
|
im.width, |
|
pitch, |
|
0, |
|
0, |
|
) |
|
+ struct.pack("11I", *((0,) * 11)) |
|
|
|
+ struct.pack("<4I", 32, pixel_flags, 0, bitcount) |
|
+ struct.pack("<4I", *rgba_mask) |
|
+ struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0) |
|
) |
|
ImageFile._save( |
|
im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))] |
|
) |
|
|
|
|
|
def _accept(prefix): |
|
return prefix[:4] == b"DDS " |
|
|
|
|
|
Image.register_open(DdsImageFile.format, DdsImageFile, _accept) |
|
Image.register_decoder("dds_rgb", DdsRgbDecoder) |
|
Image.register_save(DdsImageFile.format, _save) |
|
Image.register_extension(DdsImageFile.format, ".dds") |
|
|