CoolFace
Apppublic

Aluode/PerceptionLabPortable

sourceHugging Faceupdated 9mo agoView on Hugging Face
0likes
DdsImagePlugin.py626 linesDownload Raw Back to PIL
1"""
2A Pillow plugin for .dds files (S3TC-compressed aka DXTC)
3Jerome Leclanche <jerome@leclan.ch>
4
5Documentation:
6https://web.archive.org/web/20170802060935/http://oss.sgi.com/projects/ogl-sample/registry/EXT/texture_compression_s3tc.txt
7
8The contents of this file are hereby released in the public domain (CC0)
9Full text of the CC0 license:
10https://creativecommons.org/publicdomain/zero/1.0/
11"""
12
13from __future__ import annotations
14
15import struct
16import sys
17from enum import IntEnum, IntFlag
18from typing import IO
19
20from . import Image, ImageFile, ImagePalette
21from ._binary import i32le as i32
22from ._binary import o8
23from ._binary import o32le as o32
24
25# Magic ("DDS ")
26DDS_MAGIC = 0x20534444
27
28
29# DDS flags
30class DDSD(IntFlag):
31    CAPS = 0x1
32    HEIGHT = 0x2
33    WIDTH = 0x4
34    PITCH = 0x8
35    PIXELFORMAT = 0x1000
36    MIPMAPCOUNT = 0x20000
37    LINEARSIZE = 0x80000
38    DEPTH = 0x800000
39
40
41# DDS caps
42class DDSCAPS(IntFlag):
43    COMPLEX = 0x8
44    TEXTURE = 0x1000
45    MIPMAP = 0x400000
46
47
48class DDSCAPS2(IntFlag):
49    CUBEMAP = 0x200
50    CUBEMAP_POSITIVEX = 0x400
51    CUBEMAP_NEGATIVEX = 0x800
52    CUBEMAP_POSITIVEY = 0x1000
53    CUBEMAP_NEGATIVEY = 0x2000
54    CUBEMAP_POSITIVEZ = 0x4000
55    CUBEMAP_NEGATIVEZ = 0x8000
56    VOLUME = 0x200000
57
58
59# Pixel Format
60class DDPF(IntFlag):
61    ALPHAPIXELS = 0x1
62    ALPHA = 0x2
63    FOURCC = 0x4
64    PALETTEINDEXED8 = 0x20
65    RGB = 0x40
66    LUMINANCE = 0x20000
67
68
69# dxgiformat.h
70class DXGI_FORMAT(IntEnum):
71    UNKNOWN = 0
72    R32G32B32A32_TYPELESS = 1
73    R32G32B32A32_FLOAT = 2
74    R32G32B32A32_UINT = 3
75    R32G32B32A32_SINT = 4
76    R32G32B32_TYPELESS = 5
77    R32G32B32_FLOAT = 6
78    R32G32B32_UINT = 7
79    R32G32B32_SINT = 8
80    R16G16B16A16_TYPELESS = 9
81    R16G16B16A16_FLOAT = 10
82    R16G16B16A16_UNORM = 11
83    R16G16B16A16_UINT = 12
84    R16G16B16A16_SNORM = 13
85    R16G16B16A16_SINT = 14
86    R32G32_TYPELESS = 15
87    R32G32_FLOAT = 16
88    R32G32_UINT = 17
89    R32G32_SINT = 18
90    R32G8X24_TYPELESS = 19
91    D32_FLOAT_S8X24_UINT = 20
92    R32_FLOAT_X8X24_TYPELESS = 21
93    X32_TYPELESS_G8X24_UINT = 22
94    R10G10B10A2_TYPELESS = 23
95    R10G10B10A2_UNORM = 24
96    R10G10B10A2_UINT = 25
97    R11G11B10_FLOAT = 26
98    R8G8B8A8_TYPELESS = 27
99    R8G8B8A8_UNORM = 28
100    R8G8B8A8_UNORM_SRGB = 29
101    R8G8B8A8_UINT = 30
102    R8G8B8A8_SNORM = 31
103    R8G8B8A8_SINT = 32
104    R16G16_TYPELESS = 33
105    R16G16_FLOAT = 34
106    R16G16_UNORM = 35
107    R16G16_UINT = 36
108    R16G16_SNORM = 37
109    R16G16_SINT = 38
110    R32_TYPELESS = 39
111    D32_FLOAT = 40
112    R32_FLOAT = 41
113    R32_UINT = 42
114    R32_SINT = 43
115    R24G8_TYPELESS = 44
116    D24_UNORM_S8_UINT = 45
117    R24_UNORM_X8_TYPELESS = 46
118    X24_TYPELESS_G8_UINT = 47
119    R8G8_TYPELESS = 48
120    R8G8_UNORM = 49
121    R8G8_UINT = 50
122    R8G8_SNORM = 51
123    R8G8_SINT = 52
124    R16_TYPELESS = 53
125    R16_FLOAT = 54
126    D16_UNORM = 55
127    R16_UNORM = 56
128    R16_UINT = 57
129    R16_SNORM = 58
130    R16_SINT = 59
131    R8_TYPELESS = 60
132    R8_UNORM = 61
133    R8_UINT = 62
134    R8_SNORM = 63
135    R8_SINT = 64
136    A8_UNORM = 65
137    R1_UNORM = 66
138    R9G9B9E5_SHAREDEXP = 67
139    R8G8_B8G8_UNORM = 68
140    G8R8_G8B8_UNORM = 69
141    BC1_TYPELESS = 70
142    BC1_UNORM = 71
143    BC1_UNORM_SRGB = 72
144    BC2_TYPELESS = 73
145    BC2_UNORM = 74
146    BC2_UNORM_SRGB = 75
147    BC3_TYPELESS = 76
148    BC3_UNORM = 77
149    BC3_UNORM_SRGB = 78
150    BC4_TYPELESS = 79
151    BC4_UNORM = 80
152    BC4_SNORM = 81
153    BC5_TYPELESS = 82
154    BC5_UNORM = 83
155    BC5_SNORM = 84
156    B5G6R5_UNORM = 85
157    B5G5R5A1_UNORM = 86
158    B8G8R8A8_UNORM = 87
159    B8G8R8X8_UNORM = 88
160    R10G10B10_XR_BIAS_A2_UNORM = 89
161    B8G8R8A8_TYPELESS = 90
162    B8G8R8A8_UNORM_SRGB = 91
163    B8G8R8X8_TYPELESS = 92
164    B8G8R8X8_UNORM_SRGB = 93
165    BC6H_TYPELESS = 94
166    BC6H_UF16 = 95
167    BC6H_SF16 = 96
168    BC7_TYPELESS = 97
169    BC7_UNORM = 98
170    BC7_UNORM_SRGB = 99
171    AYUV = 100
172    Y410 = 101
173    Y416 = 102
174    NV12 = 103
175    P010 = 104
176    P016 = 105
177    OPAQUE_420 = 106
178    YUY2 = 107
179    Y210 = 108
180    Y216 = 109
181    NV11 = 110
182    AI44 = 111
183    IA44 = 112
184    P8 = 113
185    A8P8 = 114
186    B4G4R4A4_UNORM = 115
187    P208 = 130
188    V208 = 131
189    V408 = 132
190    SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189
191    SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190
192
193
194class D3DFMT(IntEnum):
195    UNKNOWN = 0
196    R8G8B8 = 20
197    A8R8G8B8 = 21
198    X8R8G8B8 = 22
199    R5G6B5 = 23
200    X1R5G5B5 = 24
201    A1R5G5B5 = 25
202    A4R4G4B4 = 26
203    R3G3B2 = 27
204    A8 = 28
205    A8R3G3B2 = 29
206    X4R4G4B4 = 30
207    A2B10G10R10 = 31
208    A8B8G8R8 = 32
209    X8B8G8R8 = 33
210    G16R16 = 34
211    A2R10G10B10 = 35
212    A16B16G16R16 = 36
213    A8P8 = 40
214    P8 = 41
215    L8 = 50
216    A8L8 = 51
217    A4L4 = 52
218    V8U8 = 60
219    L6V5U5 = 61
220    X8L8V8U8 = 62
221    Q8W8V8U8 = 63
222    V16U16 = 64
223    A2W10V10U10 = 67
224    D16_LOCKABLE = 70
225    D32 = 71
226    D15S1 = 73
227    D24S8 = 75
228    D24X8 = 77
229    D24X4S4 = 79
230    D16 = 80
231    D32F_LOCKABLE = 82
232    D24FS8 = 83
233    D32_LOCKABLE = 84
234    S8_LOCKABLE = 85
235    L16 = 81
236    VERTEXDATA = 100
237    INDEX16 = 101
238    INDEX32 = 102
239    Q16W16V16U16 = 110
240    R16F = 111
241    G16R16F = 112
242    A16B16G16R16F = 113
243    R32F = 114
244    G32R32F = 115
245    A32B32G32R32F = 116
246    CxV8U8 = 117
247    A1 = 118
248    A2B10G10R10_XR_BIAS = 119
249    BINARYBUFFER = 199
250
251    UYVY = i32(b"UYVY")
252    R8G8_B8G8 = i32(b"RGBG")
253    YUY2 = i32(b"YUY2")
254    G8R8_G8B8 = i32(b"GRGB")
255    DXT1 = i32(b"DXT1")
256    DXT2 = i32(b"DXT2")
257    DXT3 = i32(b"DXT3")
258    DXT4 = i32(b"DXT4")
259    DXT5 = i32(b"DXT5")
260    DX10 = i32(b"DX10")
261    BC4S = i32(b"BC4S")
262    BC4U = i32(b"BC4U")
263    BC5S = i32(b"BC5S")
264    BC5U = i32(b"BC5U")
265    ATI1 = i32(b"ATI1")
266    ATI2 = i32(b"ATI2")
267    MULTI2_ARGB8 = i32(b"MET1")
268
269
270# Backward compatibility layer
271module = sys.modules[__name__]
272for item in DDSD:
273    assert item.name is not None
274    setattr(module, f"DDSD_{item.name}", item.value)
275for item1 in DDSCAPS:
276    assert item1.name is not None
277    setattr(module, f"DDSCAPS_{item1.name}", item1.value)
278for item2 in DDSCAPS2:
279    assert item2.name is not None
280    setattr(module, f"DDSCAPS2_{item2.name}", item2.value)
281for item3 in DDPF:
282    assert item3.name is not None
283    setattr(module, f"DDPF_{item3.name}", item3.value)
284
285DDS_FOURCC = DDPF.FOURCC
286DDS_RGB = DDPF.RGB
287DDS_RGBA = DDPF.RGB | DDPF.ALPHAPIXELS
288DDS_LUMINANCE = DDPF.LUMINANCE
289DDS_LUMINANCEA = DDPF.LUMINANCE | DDPF.ALPHAPIXELS
290DDS_ALPHA = DDPF.ALPHA
291DDS_PAL8 = DDPF.PALETTEINDEXED8
292
293DDS_HEADER_FLAGS_TEXTURE = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT
294DDS_HEADER_FLAGS_MIPMAP = DDSD.MIPMAPCOUNT
295DDS_HEADER_FLAGS_VOLUME = DDSD.DEPTH
296DDS_HEADER_FLAGS_PITCH = DDSD.PITCH
297DDS_HEADER_FLAGS_LINEARSIZE = DDSD.LINEARSIZE
298
299DDS_HEIGHT = DDSD.HEIGHT
300DDS_WIDTH = DDSD.WIDTH
301
302DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS.TEXTURE
303DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS.COMPLEX | DDSCAPS.MIPMAP
304DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS.COMPLEX
305
306DDS_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEX
307DDS_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEX
308DDS_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEY
309DDS_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEY
310DDS_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEZ
311DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEZ
312
313DXT1_FOURCC = D3DFMT.DXT1
314DXT3_FOURCC = D3DFMT.DXT3
315DXT5_FOURCC = D3DFMT.DXT5
316
317DXGI_FORMAT_R8G8B8A8_TYPELESS = DXGI_FORMAT.R8G8B8A8_TYPELESS
318DXGI_FORMAT_R8G8B8A8_UNORM = DXGI_FORMAT.R8G8B8A8_UNORM
319DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = DXGI_FORMAT.R8G8B8A8_UNORM_SRGB
320DXGI_FORMAT_BC5_TYPELESS = DXGI_FORMAT.BC5_TYPELESS
321DXGI_FORMAT_BC5_UNORM = DXGI_FORMAT.BC5_UNORM
322DXGI_FORMAT_BC5_SNORM = DXGI_FORMAT.BC5_SNORM
323DXGI_FORMAT_BC6H_UF16 = DXGI_FORMAT.BC6H_UF16
324DXGI_FORMAT_BC6H_SF16 = DXGI_FORMAT.BC6H_SF16
325DXGI_FORMAT_BC7_TYPELESS = DXGI_FORMAT.BC7_TYPELESS
326DXGI_FORMAT_BC7_UNORM = DXGI_FORMAT.BC7_UNORM
327DXGI_FORMAT_BC7_UNORM_SRGB = DXGI_FORMAT.BC7_UNORM_SRGB
328
329
330class DdsImageFile(ImageFile.ImageFile):
331    format = "DDS"
332    format_description = "DirectDraw Surface"
333
334    def _open(self) -> None:
335        assert self.fp is not None
336        if not _accept(self.fp.read(4)):
337            msg = "not a DDS file"
338            raise SyntaxError(msg)
339        (header_size,) = struct.unpack("<I", self.fp.read(4))
340        if header_size != 124:
341            msg = f"Unsupported header size {repr(header_size)}"
342            raise OSError(msg)
343        header = self.fp.read(header_size - 4)
344        if len(header) != 120:
345            msg = f"Incomplete header: {len(header)} bytes"
346            raise OSError(msg)
347
348        flags, height, width = struct.unpack("<3I", header[:12])
349        self._size = (width, height)
350        extents = (0, 0) + self.size
351
352        pitch, depth, mipmaps = struct.unpack("<3I", header[12:24])
353        struct.unpack("<11I", header[24:68])  # reserved
354
355        # pixel format
356        pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header[68:84])
357        n = 0
358        rawmode = None
359        if pfflags & DDPF.RGB:
360            # Texture contains uncompressed RGB data
361            if pfflags & DDPF.ALPHAPIXELS:
362                self._mode = "RGBA"
363                mask_count = 4
364            else:
365                self._mode = "RGB"
366                mask_count = 3
367
368            masks = struct.unpack(f"<{mask_count}I", header[84 : 84 + mask_count * 4])
369            self.tile = [ImageFile._Tile("dds_rgb", extents, 0, (bitcount, masks))]
370            return
371        elif pfflags & DDPF.LUMINANCE:
372            if bitcount == 8:
373                self._mode = "L"
374            elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS:
375                self._mode = "LA"
376            else:
377                msg = f"Unsupported bitcount {bitcount} for {pfflags}"
378                raise OSError(msg)
379        elif pfflags & DDPF.PALETTEINDEXED8:
380            self._mode = "P"
381            self.palette = ImagePalette.raw("RGBA", self.fp.read(1024))
382            self.palette.mode = "RGBA"
383        elif pfflags & DDPF.FOURCC:
384            offset = header_size + 4
385            if fourcc == D3DFMT.DXT1:
386                self._mode = "RGBA"
387                self.pixel_format = "DXT1"
388                n = 1
389            elif fourcc == D3DFMT.DXT3:
390                self._mode = "RGBA"
391                self.pixel_format = "DXT3"
392                n = 2
393            elif fourcc == D3DFMT.DXT5:
394                self._mode = "RGBA"
395                self.pixel_format = "DXT5"
396                n = 3
397            elif fourcc in (D3DFMT.BC4U, D3DFMT.ATI1):
398                self._mode = "L"
399                self.pixel_format = "BC4"
400                n = 4
401            elif fourcc == D3DFMT.BC5S:
402                self._mode = "RGB"
403                self.pixel_format = "BC5S"
404                n = 5
405            elif fourcc in (D3DFMT.BC5U, D3DFMT.ATI2):
406                self._mode = "RGB"
407                self.pixel_format = "BC5"
408                n = 5
409            elif fourcc == D3DFMT.DX10:
410                offset += 20
411                # ignoring flags which pertain to volume textures and cubemaps
412                (dxgi_format,) = struct.unpack("<I", self.fp.read(4))
413                self.fp.read(16)
414                if dxgi_format in (
415                    DXGI_FORMAT.BC1_UNORM,
416                    DXGI_FORMAT.BC1_TYPELESS,
417                ):
418                    self._mode = "RGBA"
419                    self.pixel_format = "BC1"
420                    n = 1
421                elif dxgi_format in (DXGI_FORMAT.BC2_TYPELESS, DXGI_FORMAT.BC2_UNORM):
422                    self._mode = "RGBA"
423                    self.pixel_format = "BC2"
424                    n = 2
425                elif dxgi_format in (DXGI_FORMAT.BC3_TYPELESS, DXGI_FORMAT.BC3_UNORM):
426                    self._mode = "RGBA"
427                    self.pixel_format = "BC3"
428                    n = 3
429                elif dxgi_format in (DXGI_FORMAT.BC4_TYPELESS, DXGI_FORMAT.BC4_UNORM):
430                    self._mode = "L"
431                    self.pixel_format = "BC4"
432                    n = 4
433                elif dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM):
434                    self._mode = "RGB"
435                    self.pixel_format = "BC5"
436                    n = 5
437                elif dxgi_format == DXGI_FORMAT.BC5_SNORM:
438                    self._mode = "RGB"
439                    self.pixel_format = "BC5S"
440                    n = 5
441                elif dxgi_format == DXGI_FORMAT.BC6H_UF16:
442                    self._mode = "RGB"
443                    self.pixel_format = "BC6H"
444                    n = 6
445                elif dxgi_format == DXGI_FORMAT.BC6H_SF16:
446                    self._mode = "RGB"
447                    self.pixel_format = "BC6HS"
448                    n = 6
449                elif dxgi_format in (
450                    DXGI_FORMAT.BC7_TYPELESS,
451                    DXGI_FORMAT.BC7_UNORM,
452                    DXGI_FORMAT.BC7_UNORM_SRGB,
453                ):
454                    self._mode = "RGBA"
455                    self.pixel_format = "BC7"
456                    n = 7
457                    if dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB:
458                        self.info["gamma"] = 1 / 2.2
459                elif dxgi_format in (
460                    DXGI_FORMAT.R8G8B8A8_TYPELESS,
461                    DXGI_FORMAT.R8G8B8A8_UNORM,
462                    DXGI_FORMAT.R8G8B8A8_UNORM_SRGB,
463                ):
464                    self._mode = "RGBA"
465                    if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB:
466                        self.info["gamma"] = 1 / 2.2
467                else:
468                    msg = f"Unimplemented DXGI format {dxgi_format}"
469                    raise NotImplementedError(msg)
470            else:
471                msg = f"Unimplemented pixel format {repr(fourcc)}"
472                raise NotImplementedError(msg)
473        else:
474            msg = f"Unknown pixel format flags {pfflags}"
475            raise NotImplementedError(msg)
476
477        if n:
478            self.tile = [
479                ImageFile._Tile("bcn", extents, offset, (n, self.pixel_format))
480            ]
481        else:
482            self.tile = [ImageFile._Tile("raw", extents, 0, rawmode or self.mode)]
483
484    def load_seek(self, pos: int) -> None:
485        pass
486
487
488class DdsRgbDecoder(ImageFile.PyDecoder):
489    _pulls_fd = True
490
491    def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
492        assert self.fd is not None
493        bitcount, masks = self.args
494
495        # Some masks will be padded with zeros, e.g. R 0b11 G 0b1100
496        # Calculate how many zeros each mask is padded with
497        mask_offsets = []
498        # And the maximum value of each channel without the padding
499        mask_totals = []
500        for mask in masks:
501            offset = 0
502            if mask != 0:
503                while mask >> (offset + 1) << (offset + 1) == mask:
504                    offset += 1
505            mask_offsets.append(offset)
506            mask_totals.append(mask >> offset)
507
508        data = bytearray()
509        bytecount = bitcount // 8
510        dest_length = self.state.xsize * self.state.ysize * len(masks)
511        while len(data) < dest_length:
512            value = int.from_bytes(self.fd.read(bytecount), "little")
513            for i, mask in enumerate(masks):
514                masked_value = value & mask
515                # Remove the zero padding, and scale it to 8 bits
516                data += o8(
517                    int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255)
518                    if mask_totals[i]
519                    else 0
520                )
521        self.set_as_raw(data)
522        return -1, 0
523
524
525def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
526    if im.mode not in ("RGB", "RGBA", "L", "LA"):
527        msg = f"cannot write mode {im.mode} as DDS"
528        raise OSError(msg)
529
530    flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT
531    bitcount = len(im.getbands()) * 8
532    pixel_format = im.encoderinfo.get("pixel_format")
533    args: tuple[int] | str
534    if pixel_format:
535        codec_name = "bcn"
536        flags |= DDSD.LINEARSIZE
537        pitch = (im.width + 3) * 4
538        rgba_mask = [0, 0, 0, 0]
539        pixel_flags = DDPF.FOURCC
540        if pixel_format == "DXT1":
541            fourcc = D3DFMT.DXT1
542            args = (1,)
543        elif pixel_format == "DXT3":
544            fourcc = D3DFMT.DXT3
545            args = (2,)
546        elif pixel_format == "DXT5":
547            fourcc = D3DFMT.DXT5
548            args = (3,)
549        else:
550            fourcc = D3DFMT.DX10
551            if pixel_format == "BC2":
552                args = (2,)
553                dxgi_format = DXGI_FORMAT.BC2_TYPELESS
554            elif pixel_format == "BC3":
555                args = (3,)
556                dxgi_format = DXGI_FORMAT.BC3_TYPELESS
557            elif pixel_format == "BC5":
558                args = (5,)
559                dxgi_format = DXGI_FORMAT.BC5_TYPELESS
560                if im.mode != "RGB":
561                    msg = "only RGB mode can be written as BC5"
562                    raise OSError(msg)
563            else:
564                msg = f"cannot write pixel format {pixel_format}"
565                raise OSError(msg)
566    else:
567        codec_name = "raw"
568        flags |= DDSD.PITCH
569        pitch = (im.width * bitcount + 7) // 8
570
571        alpha = im.mode[-1] == "A"
572        if im.mode[0] == "L":
573            pixel_flags = DDPF.LUMINANCE
574            args = im.mode
575            if alpha:
576                rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF]
577            else:
578                rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000]
579        else:
580            pixel_flags = DDPF.RGB
581            args = im.mode[::-1]
582            rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF]
583
584            if alpha:
585                r, g, b, a = im.split()
586                im = Image.merge("RGBA", (a, r, g, b))
587        if alpha:
588            pixel_flags |= DDPF.ALPHAPIXELS
589        rgba_mask.append(0xFF000000 if alpha else 0)
590
591        fourcc = D3DFMT.UNKNOWN
592    fp.write(
593        o32(DDS_MAGIC)
594        + struct.pack(
595            "<7I",
596            124,  # header size
597            flags,  # flags
598            im.height,
599            im.width,
600            pitch,
601            0,  # depth
602            0,  # mipmaps
603        )
604        + struct.pack("11I", *((0,) * 11))  # reserved
605        # pfsize, pfflags, fourcc, bitcount
606        + struct.pack("<4I", 32, pixel_flags, fourcc, bitcount)
607        + struct.pack("<4I", *rgba_mask)  # dwRGBABitMask
608        + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
609    )
610    if fourcc == D3DFMT.DX10:
611        fp.write(
612            # dxgi_format, 2D resource, misc, array size, straight alpha
613            struct.pack("<5I", dxgi_format, 3, 0, 0, 1)
614        )
615    ImageFile._save(im, fp, [ImageFile._Tile(codec_name, (0, 0) + im.size, 0, args)])
616
617
618def _accept(prefix: bytes) -> bool:
619    return prefix.startswith(b"DDS ")
620
621
622Image.register_open(DdsImageFile.format, DdsImageFile, _accept)
623Image.register_decoder("dds_rgb", DdsRgbDecoder)
624Image.register_save(DdsImageFile.format, _save)
625Image.register_extension(DdsImageFile.format, ".dds")
626 
Aluode/PerceptionLabPortable · CoolFace