DdsImagePlugin.py 16 KB

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