Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/PIL/DdsImagePlugin.py: 75%

Shortcuts on this page

r m x   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

514 statements  

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 

15__lazy_modules__ = {"struct"} 

16 

17import struct 

18import sys 

19from enum import IntEnum, IntFlag 

20 

21from . import Image, ImageFile, ImagePalette 

22from ._binary import i32le as i32 

23from ._binary import o8 

24from ._binary import o32le as o32 

25 

26TYPE_CHECKING = False 

27if TYPE_CHECKING: 

28 from typing import IO 

29 

30# Magic ("DDS ") 

31DDS_MAGIC = 0x20534444 

32 

33 

34# DDS flags 

35class DDSD(IntFlag): 

36 CAPS = 0x1 

37 HEIGHT = 0x2 

38 WIDTH = 0x4 

39 PITCH = 0x8 

40 PIXELFORMAT = 0x1000 

41 MIPMAPCOUNT = 0x20000 

42 LINEARSIZE = 0x80000 

43 DEPTH = 0x800000 

44 

45 

46# DDS caps 

47class DDSCAPS(IntFlag): 

48 COMPLEX = 0x8 

49 TEXTURE = 0x1000 

50 MIPMAP = 0x400000 

51 

52 

53class DDSCAPS2(IntFlag): 

54 CUBEMAP = 0x200 

55 CUBEMAP_POSITIVEX = 0x400 

56 CUBEMAP_NEGATIVEX = 0x800 

57 CUBEMAP_POSITIVEY = 0x1000 

58 CUBEMAP_NEGATIVEY = 0x2000 

59 CUBEMAP_POSITIVEZ = 0x4000 

60 CUBEMAP_NEGATIVEZ = 0x8000 

61 VOLUME = 0x200000 

62 

63 

64# Pixel Format 

65class DDPF(IntFlag): 

66 ALPHAPIXELS = 0x1 

67 ALPHA = 0x2 

68 FOURCC = 0x4 

69 PALETTEINDEXED8 = 0x20 

70 RGB = 0x40 

71 LUMINANCE = 0x20000 

72 

73 

74# dxgiformat.h 

75class DXGI_FORMAT(IntEnum): 

76 UNKNOWN = 0 

77 R32G32B32A32_TYPELESS = 1 

78 R32G32B32A32_FLOAT = 2 

79 R32G32B32A32_UINT = 3 

80 R32G32B32A32_SINT = 4 

81 R32G32B32_TYPELESS = 5 

82 R32G32B32_FLOAT = 6 

83 R32G32B32_UINT = 7 

84 R32G32B32_SINT = 8 

85 R16G16B16A16_TYPELESS = 9 

86 R16G16B16A16_FLOAT = 10 

87 R16G16B16A16_UNORM = 11 

88 R16G16B16A16_UINT = 12 

89 R16G16B16A16_SNORM = 13 

90 R16G16B16A16_SINT = 14 

91 R32G32_TYPELESS = 15 

92 R32G32_FLOAT = 16 

93 R32G32_UINT = 17 

94 R32G32_SINT = 18 

95 R32G8X24_TYPELESS = 19 

96 D32_FLOAT_S8X24_UINT = 20 

97 R32_FLOAT_X8X24_TYPELESS = 21 

98 X32_TYPELESS_G8X24_UINT = 22 

99 R10G10B10A2_TYPELESS = 23 

100 R10G10B10A2_UNORM = 24 

101 R10G10B10A2_UINT = 25 

102 R11G11B10_FLOAT = 26 

103 R8G8B8A8_TYPELESS = 27 

104 R8G8B8A8_UNORM = 28 

105 R8G8B8A8_UNORM_SRGB = 29 

106 R8G8B8A8_UINT = 30 

107 R8G8B8A8_SNORM = 31 

108 R8G8B8A8_SINT = 32 

109 R16G16_TYPELESS = 33 

110 R16G16_FLOAT = 34 

111 R16G16_UNORM = 35 

112 R16G16_UINT = 36 

113 R16G16_SNORM = 37 

114 R16G16_SINT = 38 

115 R32_TYPELESS = 39 

116 D32_FLOAT = 40 

117 R32_FLOAT = 41 

118 R32_UINT = 42 

119 R32_SINT = 43 

120 R24G8_TYPELESS = 44 

121 D24_UNORM_S8_UINT = 45 

122 R24_UNORM_X8_TYPELESS = 46 

123 X24_TYPELESS_G8_UINT = 47 

124 R8G8_TYPELESS = 48 

125 R8G8_UNORM = 49 

126 R8G8_UINT = 50 

127 R8G8_SNORM = 51 

128 R8G8_SINT = 52 

129 R16_TYPELESS = 53 

130 R16_FLOAT = 54 

131 D16_UNORM = 55 

132 R16_UNORM = 56 

133 R16_UINT = 57 

134 R16_SNORM = 58 

135 R16_SINT = 59 

136 R8_TYPELESS = 60 

137 R8_UNORM = 61 

138 R8_UINT = 62 

139 R8_SNORM = 63 

140 R8_SINT = 64 

141 A8_UNORM = 65 

142 R1_UNORM = 66 

143 R9G9B9E5_SHAREDEXP = 67 

144 R8G8_B8G8_UNORM = 68 

145 G8R8_G8B8_UNORM = 69 

146 BC1_TYPELESS = 70 

147 BC1_UNORM = 71 

148 BC1_UNORM_SRGB = 72 

149 BC2_TYPELESS = 73 

150 BC2_UNORM = 74 

151 BC2_UNORM_SRGB = 75 

152 BC3_TYPELESS = 76 

153 BC3_UNORM = 77 

154 BC3_UNORM_SRGB = 78 

155 BC4_TYPELESS = 79 

156 BC4_UNORM = 80 

157 BC4_SNORM = 81 

158 BC5_TYPELESS = 82 

159 BC5_UNORM = 83 

160 BC5_SNORM = 84 

161 B5G6R5_UNORM = 85 

162 B5G5R5A1_UNORM = 86 

163 B8G8R8A8_UNORM = 87 

164 B8G8R8X8_UNORM = 88 

165 R10G10B10_XR_BIAS_A2_UNORM = 89 

166 B8G8R8A8_TYPELESS = 90 

167 B8G8R8A8_UNORM_SRGB = 91 

168 B8G8R8X8_TYPELESS = 92 

169 B8G8R8X8_UNORM_SRGB = 93 

170 BC6H_TYPELESS = 94 

171 BC6H_UF16 = 95 

172 BC6H_SF16 = 96 

173 BC7_TYPELESS = 97 

174 BC7_UNORM = 98 

175 BC7_UNORM_SRGB = 99 

176 AYUV = 100 

177 Y410 = 101 

178 Y416 = 102 

179 NV12 = 103 

180 P010 = 104 

181 P016 = 105 

182 OPAQUE_420 = 106 

183 YUY2 = 107 

184 Y210 = 108 

185 Y216 = 109 

186 NV11 = 110 

187 AI44 = 111 

188 IA44 = 112 

189 P8 = 113 

190 A8P8 = 114 

191 B4G4R4A4_UNORM = 115 

192 P208 = 130 

193 V208 = 131 

194 V408 = 132 

195 SAMPLER_FEEDBACK_MIN_MIP_OPAQUE = 189 

196 SAMPLER_FEEDBACK_MIP_REGION_USED_OPAQUE = 190 

197 

198 

199class D3DFMT(IntEnum): 

200 UNKNOWN = 0 

201 R8G8B8 = 20 

202 A8R8G8B8 = 21 

203 X8R8G8B8 = 22 

204 R5G6B5 = 23 

205 X1R5G5B5 = 24 

206 A1R5G5B5 = 25 

207 A4R4G4B4 = 26 

208 R3G3B2 = 27 

209 A8 = 28 

210 A8R3G3B2 = 29 

211 X4R4G4B4 = 30 

212 A2B10G10R10 = 31 

213 A8B8G8R8 = 32 

214 X8B8G8R8 = 33 

215 G16R16 = 34 

216 A2R10G10B10 = 35 

217 A16B16G16R16 = 36 

218 A8P8 = 40 

219 P8 = 41 

220 L8 = 50 

221 A8L8 = 51 

222 A4L4 = 52 

223 V8U8 = 60 

224 L6V5U5 = 61 

225 X8L8V8U8 = 62 

226 Q8W8V8U8 = 63 

227 V16U16 = 64 

228 A2W10V10U10 = 67 

229 D16_LOCKABLE = 70 

230 D32 = 71 

231 D15S1 = 73 

232 D24S8 = 75 

233 D24X8 = 77 

234 D24X4S4 = 79 

235 D16 = 80 

236 D32F_LOCKABLE = 82 

237 D24FS8 = 83 

238 D32_LOCKABLE = 84 

239 S8_LOCKABLE = 85 

240 L16 = 81 

241 VERTEXDATA = 100 

242 INDEX16 = 101 

243 INDEX32 = 102 

244 Q16W16V16U16 = 110 

245 R16F = 111 

246 G16R16F = 112 

247 A16B16G16R16F = 113 

248 R32F = 114 

249 G32R32F = 115 

250 A32B32G32R32F = 116 

251 CxV8U8 = 117 

252 A1 = 118 

253 A2B10G10R10_XR_BIAS = 119 

254 BINARYBUFFER = 199 

255 

256 UYVY = i32(b"UYVY") 

257 R8G8_B8G8 = i32(b"RGBG") 

258 YUY2 = i32(b"YUY2") 

259 G8R8_G8B8 = i32(b"GRGB") 

260 DXT1 = i32(b"DXT1") 

261 DXT2 = i32(b"DXT2") 

262 DXT3 = i32(b"DXT3") 

263 DXT4 = i32(b"DXT4") 

264 DXT5 = i32(b"DXT5") 

265 DX10 = i32(b"DX10") 

266 BC4S = i32(b"BC4S") 

267 BC4U = i32(b"BC4U") 

268 BC5S = i32(b"BC5S") 

269 BC5U = i32(b"BC5U") 

270 ATI1 = i32(b"ATI1") 

271 ATI2 = i32(b"ATI2") 

272 MULTI2_ARGB8 = i32(b"MET1") 

273 

274 

275# Backward compatibility layer 

276module = sys.modules[__name__] 

277for item in DDSD: 

278 assert item.name is not None 

279 setattr(module, f"DDSD_{item.name}", item.value) 

280for item1 in DDSCAPS: 

281 assert item1.name is not None 

282 setattr(module, f"DDSCAPS_{item1.name}", item1.value) 

283for item2 in DDSCAPS2: 

284 assert item2.name is not None 

285 setattr(module, f"DDSCAPS2_{item2.name}", item2.value) 

286for item3 in DDPF: 

287 assert item3.name is not None 

288 setattr(module, f"DDPF_{item3.name}", item3.value) 

289 

290DDS_FOURCC = DDPF.FOURCC 

291DDS_RGB = DDPF.RGB 

292DDS_RGBA = DDPF.RGB | DDPF.ALPHAPIXELS 

293DDS_LUMINANCE = DDPF.LUMINANCE 

294DDS_LUMINANCEA = DDPF.LUMINANCE | DDPF.ALPHAPIXELS 

295DDS_ALPHA = DDPF.ALPHA 

296DDS_PAL8 = DDPF.PALETTEINDEXED8 

297 

298DDS_HEADER_FLAGS_TEXTURE = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT 

299DDS_HEADER_FLAGS_MIPMAP = DDSD.MIPMAPCOUNT 

300DDS_HEADER_FLAGS_VOLUME = DDSD.DEPTH 

301DDS_HEADER_FLAGS_PITCH = DDSD.PITCH 

302DDS_HEADER_FLAGS_LINEARSIZE = DDSD.LINEARSIZE 

303 

304DDS_HEIGHT = DDSD.HEIGHT 

305DDS_WIDTH = DDSD.WIDTH 

306 

307DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS.TEXTURE 

308DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS.COMPLEX | DDSCAPS.MIPMAP 

309DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS.COMPLEX 

310 

311DDS_CUBEMAP_POSITIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEX 

312DDS_CUBEMAP_NEGATIVEX = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEX 

313DDS_CUBEMAP_POSITIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEY 

314DDS_CUBEMAP_NEGATIVEY = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEY 

315DDS_CUBEMAP_POSITIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_POSITIVEZ 

316DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2.CUBEMAP | DDSCAPS2.CUBEMAP_NEGATIVEZ 

317 

318DXT1_FOURCC = D3DFMT.DXT1 

319DXT3_FOURCC = D3DFMT.DXT3 

320DXT5_FOURCC = D3DFMT.DXT5 

321 

322DXGI_FORMAT_R8G8B8A8_TYPELESS = DXGI_FORMAT.R8G8B8A8_TYPELESS 

323DXGI_FORMAT_R8G8B8A8_UNORM = DXGI_FORMAT.R8G8B8A8_UNORM 

324DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = DXGI_FORMAT.R8G8B8A8_UNORM_SRGB 

325DXGI_FORMAT_BC5_TYPELESS = DXGI_FORMAT.BC5_TYPELESS 

326DXGI_FORMAT_BC5_UNORM = DXGI_FORMAT.BC5_UNORM 

327DXGI_FORMAT_BC5_SNORM = DXGI_FORMAT.BC5_SNORM 

328DXGI_FORMAT_BC6H_UF16 = DXGI_FORMAT.BC6H_UF16 

329DXGI_FORMAT_BC6H_SF16 = DXGI_FORMAT.BC6H_SF16 

330DXGI_FORMAT_BC7_TYPELESS = DXGI_FORMAT.BC7_TYPELESS 

331DXGI_FORMAT_BC7_UNORM = DXGI_FORMAT.BC7_UNORM 

332DXGI_FORMAT_BC7_UNORM_SRGB = DXGI_FORMAT.BC7_UNORM_SRGB 

333 

334 

335class DdsImageFile(ImageFile.ImageFile): 

336 format = "DDS" 

337 format_description = "DirectDraw Surface" 

338 

339 def _open(self) -> None: 

340 assert self.fp is not None 

341 if not _accept(self.fp.read(4)): 

342 msg = "not a DDS file" 

343 raise SyntaxError(msg) 

344 (header_size,) = struct.unpack("<I", self.fp.read(4)) 

345 if header_size != 124: 

346 msg = f"Unsupported header size {repr(header_size)}" 

347 raise OSError(msg) 

348 header = self.fp.read(header_size - 4) 

349 if len(header) != 120: 

350 msg = f"Incomplete header: {len(header)} bytes" 

351 raise OSError(msg) 

352 

353 flags, height, width = struct.unpack("<3I", header[:12]) 

354 self._size = (width, height) 

355 

356 pitch, depth, mipmaps = struct.unpack("<3I", header[12:24]) 

357 # 24 - 68 is reserved 

358 

359 # pixel format 

360 pfsize, pfflags, fourcc, bitcount = struct.unpack("<4I", header[68:84]) 

361 n = 0 

362 rawmode = None 

363 if pfflags & DDPF.RGB: 

364 # Texture contains uncompressed RGB data 

365 if pfflags & DDPF.ALPHAPIXELS: 

366 self._mode = "RGBA" 

367 mask_count = 4 

368 else: 

369 self._mode = "RGB" 

370 mask_count = 3 

371 

372 masks = struct.unpack(f"<{mask_count}I", header[84 : 84 + mask_count * 4]) 

373 if masks == (0xFF0000, 0x00FF00, 0x0000FF): 

374 rawmode = "BGR" 

375 elif masks == (0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000): 

376 rawmode = "BGRA" 

377 else: 

378 self.tile = [ImageFile._Tile("dds_rgb", args=(bitcount, masks))] 

379 return 

380 elif pfflags & DDPF.LUMINANCE: 

381 if bitcount == 8: 

382 self._mode = "L" 

383 elif bitcount == 16 and pfflags & DDPF.ALPHAPIXELS: 

384 self._mode = "LA" 

385 else: 

386 msg = f"Unsupported bitcount {bitcount} for {pfflags}" 

387 raise OSError(msg) 

388 elif pfflags & DDPF.PALETTEINDEXED8: 

389 self._mode = "P" 

390 self.palette = ImagePalette.ImagePalette("RGBA", self.fp.read(1024)) 

391 self.palette.dirty = 1 

392 elif pfflags & DDPF.FOURCC: 

393 offset = header_size + 4 

394 if fourcc == D3DFMT.DXT1: 

395 self._mode = "RGBA" 

396 self.pixel_format = "DXT1" 

397 n = 1 

398 elif fourcc == D3DFMT.DXT3: 

399 self._mode = "RGBA" 

400 self.pixel_format = "DXT3" 

401 n = 2 

402 elif fourcc == D3DFMT.DXT5: 

403 self._mode = "RGBA" 

404 self.pixel_format = "DXT5" 

405 n = 3 

406 elif fourcc in (D3DFMT.BC4U, D3DFMT.ATI1): 

407 self._mode = "L" 

408 self.pixel_format = "BC4" 

409 n = 4 

410 elif fourcc == D3DFMT.BC5S: 

411 self._mode = "RGB" 

412 self.pixel_format = "BC5S" 

413 n = 5 

414 elif fourcc in (D3DFMT.BC5U, D3DFMT.ATI2): 

415 self._mode = "RGB" 

416 self.pixel_format = "BC5" 

417 n = 5 

418 elif fourcc == D3DFMT.DX10: 

419 offset += 20 

420 # ignoring flags which pertain to volume textures and cubemaps 

421 (dxgi_format,) = struct.unpack("<I", self.fp.read(4)) 

422 self.fp.read(16) 

423 if dxgi_format in ( 

424 DXGI_FORMAT.BC1_UNORM, 

425 DXGI_FORMAT.BC1_TYPELESS, 

426 ): 

427 self._mode = "RGBA" 

428 self.pixel_format = "BC1" 

429 n = 1 

430 elif dxgi_format in (DXGI_FORMAT.BC2_TYPELESS, DXGI_FORMAT.BC2_UNORM): 

431 self._mode = "RGBA" 

432 self.pixel_format = "BC2" 

433 n = 2 

434 elif dxgi_format in (DXGI_FORMAT.BC3_TYPELESS, DXGI_FORMAT.BC3_UNORM): 

435 self._mode = "RGBA" 

436 self.pixel_format = "BC3" 

437 n = 3 

438 elif dxgi_format in (DXGI_FORMAT.BC4_TYPELESS, DXGI_FORMAT.BC4_UNORM): 

439 self._mode = "L" 

440 self.pixel_format = "BC4" 

441 n = 4 

442 elif dxgi_format in (DXGI_FORMAT.BC5_TYPELESS, DXGI_FORMAT.BC5_UNORM): 

443 self._mode = "RGB" 

444 self.pixel_format = "BC5" 

445 n = 5 

446 elif dxgi_format == DXGI_FORMAT.BC5_SNORM: 

447 self._mode = "RGB" 

448 self.pixel_format = "BC5S" 

449 n = 5 

450 elif dxgi_format == DXGI_FORMAT.BC6H_UF16: 

451 self._mode = "RGB" 

452 self.pixel_format = "BC6H" 

453 n = 6 

454 elif dxgi_format == DXGI_FORMAT.BC6H_SF16: 

455 self._mode = "RGB" 

456 self.pixel_format = "BC6HS" 

457 n = 6 

458 elif dxgi_format in ( 

459 DXGI_FORMAT.BC7_TYPELESS, 

460 DXGI_FORMAT.BC7_UNORM, 

461 DXGI_FORMAT.BC7_UNORM_SRGB, 

462 ): 

463 self._mode = "RGBA" 

464 self.pixel_format = "BC7" 

465 n = 7 

466 if dxgi_format == DXGI_FORMAT.BC7_UNORM_SRGB: 

467 self.info["gamma"] = 1 / 2.2 

468 elif dxgi_format in ( 

469 DXGI_FORMAT.R8G8B8A8_TYPELESS, 

470 DXGI_FORMAT.R8G8B8A8_UNORM, 

471 DXGI_FORMAT.R8G8B8A8_UNORM_SRGB, 

472 ): 

473 self._mode = "RGBA" 

474 if dxgi_format == DXGI_FORMAT.R8G8B8A8_UNORM_SRGB: 

475 self.info["gamma"] = 1 / 2.2 

476 else: 

477 msg = f"Unimplemented DXGI format {dxgi_format}" 

478 raise NotImplementedError(msg) 

479 else: 

480 msg = f"Unimplemented pixel format {repr(fourcc)}" 

481 raise NotImplementedError(msg) 

482 else: 

483 msg = f"Unknown pixel format flags {pfflags}" 

484 raise NotImplementedError(msg) 

485 

486 if n: 

487 self.tile = [ImageFile._Tile("bcn", None, offset, (n, self.pixel_format))] 

488 else: 

489 self.tile = [ImageFile._Tile("raw", args=rawmode or self.mode)] 

490 

491 def load_seek(self, pos: int) -> None: 

492 pass 

493 

494 

495class DdsRgbDecoder(ImageFile.PyDecoder): 

496 _pulls_fd = True 

497 

498 def decode(self, buffer: Image.DecoderInput) -> tuple[int, int]: 

499 bitcount, masks = self.args 

500 

501 data = bytearray() 

502 bytecount = bitcount // 8 

503 if not bytecount: 

504 self.set_as_raw(data) 

505 return -1, 0 

506 

507 # Some masks will be padded with zeros, e.g. R 0b11 G 0b1100 

508 # Calculate how many zeros each mask is padded with 

509 mask_offsets = [] 

510 # And the maximum value of each channel without the padding 

511 mask_totals = [] 

512 for mask in masks: 

513 offset = 0 

514 if mask != 0: 

515 while mask >> (offset + 1) << (offset + 1) == mask: 

516 offset += 1 

517 mask_offsets.append(offset) 

518 mask_totals.append(mask >> offset) 

519 

520 assert self.fd is not None 

521 dest_length = self.state.xsize * self.state.ysize * len(masks) 

522 while len(data) < dest_length: 

523 bytes_read = self.fd.read(bytecount) 

524 if len(bytes_read) < bytecount: 

525 break 

526 value = int.from_bytes(bytes_read, "little") 

527 for i, mask in enumerate(masks): 

528 masked_value = value & mask 

529 # Remove the zero padding, and scale it to 8 bits 

530 data += o8( 

531 int(((masked_value >> mask_offsets[i]) / mask_totals[i]) * 255) 

532 if mask_totals[i] 

533 else 0 

534 ) 

535 self.set_as_raw(data) 

536 return -1, 0 

537 

538 

539def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: 

540 if im.mode not in ("RGB", "RGBA", "L", "LA"): 

541 msg = f"cannot write mode {im.mode} as DDS" 

542 raise OSError(msg) 

543 

544 flags = DDSD.CAPS | DDSD.HEIGHT | DDSD.WIDTH | DDSD.PIXELFORMAT 

545 bitcount = len(im.getbands()) * 8 

546 pixel_format = im.encoderinfo.get("pixel_format") 

547 args: tuple[int] | str 

548 if pixel_format: 

549 codec_name = "bcn" 

550 flags |= DDSD.LINEARSIZE 

551 pitch = (im.width + 3) * 4 

552 rgba_mask = [0, 0, 0, 0] 

553 pixel_flags = DDPF.FOURCC 

554 if pixel_format == "DXT1": 

555 fourcc = D3DFMT.DXT1 

556 args = (1,) 

557 elif pixel_format == "DXT3": 

558 fourcc = D3DFMT.DXT3 

559 args = (2,) 

560 elif pixel_format == "DXT5": 

561 fourcc = D3DFMT.DXT5 

562 args = (3,) 

563 else: 

564 fourcc = D3DFMT.DX10 

565 if pixel_format == "BC2": 

566 args = (2,) 

567 dxgi_format = DXGI_FORMAT.BC2_TYPELESS 

568 elif pixel_format == "BC3": 

569 args = (3,) 

570 dxgi_format = DXGI_FORMAT.BC3_TYPELESS 

571 elif pixel_format == "BC5": 

572 args = (5,) 

573 dxgi_format = DXGI_FORMAT.BC5_TYPELESS 

574 if im.mode != "RGB": 

575 msg = "only RGB mode can be written as BC5" 

576 raise OSError(msg) 

577 else: 

578 msg = f"cannot write pixel format {pixel_format}" 

579 raise OSError(msg) 

580 else: 

581 codec_name = "raw" 

582 flags |= DDSD.PITCH 

583 pitch = (im.width * bitcount + 7) // 8 

584 

585 alpha = im.mode[-1] == "A" 

586 if im.mode[0] == "L": 

587 pixel_flags = DDPF.LUMINANCE 

588 args = im.mode 

589 if alpha: 

590 rgba_mask = [0x000000FF, 0x000000FF, 0x000000FF] 

591 else: 

592 rgba_mask = [0xFF000000, 0xFF000000, 0xFF000000] 

593 else: 

594 pixel_flags = DDPF.RGB 

595 args = im.mode[::-1] 

596 rgba_mask = [0x00FF0000, 0x0000FF00, 0x000000FF] 

597 

598 if alpha: 

599 r, g, b, a = im.split() 

600 im = Image.merge("RGBA", (a, r, g, b)) 

601 if alpha: 

602 pixel_flags |= DDPF.ALPHAPIXELS 

603 rgba_mask.append(0xFF000000 if alpha else 0) 

604 

605 fourcc = D3DFMT.UNKNOWN 

606 fp.write( 

607 o32(DDS_MAGIC) 

608 + struct.pack( 

609 "<7I", 

610 124, # header size 

611 flags, # flags 

612 im.height, 

613 im.width, 

614 pitch, 

615 0, # depth 

616 0, # mipmaps 

617 ) 

618 + struct.pack("11I", *((0,) * 11)) # reserved 

619 # pfsize, pfflags, fourcc, bitcount 

620 + struct.pack("<4I", 32, pixel_flags, fourcc, bitcount) 

621 + struct.pack("<4I", *rgba_mask) # dwRGBABitMask 

622 + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0) 

623 ) 

624 if fourcc == D3DFMT.DX10: 

625 fp.write( 

626 # dxgi_format, 2D resource, misc, array size, straight alpha 

627 struct.pack("<5I", dxgi_format, 3, 0, 0, 1) 

628 ) 

629 ImageFile._save(im, fp, [ImageFile._Tile(codec_name, args=args)]) 

630 

631 

632def _accept(prefix: bytes) -> bool: 

633 return prefix.startswith(b"DDS ") 

634 

635 

636Image.register_open(DdsImageFile.format, DdsImageFile, _accept) 

637Image.register_decoder("dds_rgb", DdsRgbDecoder) 

638Image.register_save(DdsImageFile.format, _save) 

639Image.register_extension(DdsImageFile.format, ".dds")