Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/PIL/DdsImagePlugin.py: 40%

152 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-17 14:22 -0600

1""" 

2A Pillow loader for .dds files (S3TC-compressed aka DXTC) 

3Jerome Leclanche <jerome@leclan.ch> 

4 

5Documentation: 

6 https://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: 

10 https://creativecommons.org/publicdomain/zero/1.0/ 

11""" 

12 

13import struct 

14from io import BytesIO 

15 

16from . import Image, ImageFile 

17from ._binary import o32le as o32 

18 

19# Magic ("DDS ") 

20DDS_MAGIC = 0x20534444 

21 

22# DDS flags 

23DDSD_CAPS = 0x1 

24DDSD_HEIGHT = 0x2 

25DDSD_WIDTH = 0x4 

26DDSD_PITCH = 0x8 

27DDSD_PIXELFORMAT = 0x1000 

28DDSD_MIPMAPCOUNT = 0x20000 

29DDSD_LINEARSIZE = 0x80000 

30DDSD_DEPTH = 0x800000 

31 

32# DDS caps 

33DDSCAPS_COMPLEX = 0x8 

34DDSCAPS_TEXTURE = 0x1000 

35DDSCAPS_MIPMAP = 0x400000 

36 

37DDSCAPS2_CUBEMAP = 0x200 

38DDSCAPS2_CUBEMAP_POSITIVEX = 0x400 

39DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800 

40DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000 

41DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000 

42DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000 

43DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000 

44DDSCAPS2_VOLUME = 0x200000 

45 

46# Pixel Format 

47DDPF_ALPHAPIXELS = 0x1 

48DDPF_ALPHA = 0x2 

49DDPF_FOURCC = 0x4 

50DDPF_PALETTEINDEXED8 = 0x20 

51DDPF_RGB = 0x40 

52DDPF_LUMINANCE = 0x20000 

53 

54 

55# dds.h 

56 

57DDS_FOURCC = DDPF_FOURCC 

58DDS_RGB = DDPF_RGB 

59DDS_RGBA = DDPF_RGB | DDPF_ALPHAPIXELS 

60DDS_LUMINANCE = DDPF_LUMINANCE 

61DDS_LUMINANCEA = DDPF_LUMINANCE | DDPF_ALPHAPIXELS 

62DDS_ALPHA = DDPF_ALPHA 

63DDS_PAL8 = DDPF_PALETTEINDEXED8 

64 

65DDS_HEADER_FLAGS_TEXTURE = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT 

66DDS_HEADER_FLAGS_MIPMAP = DDSD_MIPMAPCOUNT 

67DDS_HEADER_FLAGS_VOLUME = DDSD_DEPTH 

68DDS_HEADER_FLAGS_PITCH = DDSD_PITCH 

69DDS_HEADER_FLAGS_LINEARSIZE = DDSD_LINEARSIZE 

70 

71DDS_HEIGHT = DDSD_HEIGHT 

72DDS_WIDTH = DDSD_WIDTH 

73 

74DDS_SURFACE_FLAGS_TEXTURE = DDSCAPS_TEXTURE 

75DDS_SURFACE_FLAGS_MIPMAP = DDSCAPS_COMPLEX | DDSCAPS_MIPMAP 

76DDS_SURFACE_FLAGS_CUBEMAP = DDSCAPS_COMPLEX 

77 

78DDS_CUBEMAP_POSITIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEX 

79DDS_CUBEMAP_NEGATIVEX = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEX 

80DDS_CUBEMAP_POSITIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEY 

81DDS_CUBEMAP_NEGATIVEY = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEY 

82DDS_CUBEMAP_POSITIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_POSITIVEZ 

83DDS_CUBEMAP_NEGATIVEZ = DDSCAPS2_CUBEMAP | DDSCAPS2_CUBEMAP_NEGATIVEZ 

84 

85 

86# DXT1 

87DXT1_FOURCC = 0x31545844 

88 

89# DXT3 

90DXT3_FOURCC = 0x33545844 

91 

92# DXT5 

93DXT5_FOURCC = 0x35545844 

94 

95 

96# dxgiformat.h 

97 

98DXGI_FORMAT_R8G8B8A8_TYPELESS = 27 

99DXGI_FORMAT_R8G8B8A8_UNORM = 28 

100DXGI_FORMAT_R8G8B8A8_UNORM_SRGB = 29 

101DXGI_FORMAT_BC5_TYPELESS = 82 

102DXGI_FORMAT_BC5_UNORM = 83 

103DXGI_FORMAT_BC5_SNORM = 84 

104DXGI_FORMAT_BC7_TYPELESS = 97 

105DXGI_FORMAT_BC7_UNORM = 98 

106DXGI_FORMAT_BC7_UNORM_SRGB = 99 

107 

108 

109class DdsImageFile(ImageFile.ImageFile): 

110 format = "DDS" 

111 format_description = "DirectDraw Surface" 

112 

113 def _open(self): 

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

115 raise SyntaxError("not a DDS file") 

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

117 if header_size != 124: 

118 raise OSError(f"Unsupported header size {repr(header_size)}") 

119 header_bytes = self.fp.read(header_size - 4) 

120 if len(header_bytes) != 120: 

121 raise OSError(f"Incomplete header: {len(header_bytes)} bytes") 

122 header = BytesIO(header_bytes) 

123 

124 flags, height, width = struct.unpack("<3I", header.read(12)) 

125 self._size = (width, height) 

126 self.mode = "RGBA" 

127 

128 pitch, depth, mipmaps = struct.unpack("<3I", header.read(12)) 

129 struct.unpack("<11I", header.read(44)) # reserved 

130 

131 # pixel format 

132 pfsize, pfflags = struct.unpack("<2I", header.read(8)) 

133 fourcc = header.read(4) 

134 (bitcount,) = struct.unpack("<I", header.read(4)) 

135 masks = struct.unpack("<4I", header.read(16)) 

136 if pfflags & DDPF_RGB: 

137 # Texture contains uncompressed RGB data 

138 masks = {mask: ["R", "G", "B", "A"][i] for i, mask in enumerate(masks)} 

139 rawmode = "" 

140 if bitcount == 32: 

141 rawmode += masks[0xFF000000] 

142 else: 

143 self.mode = "RGB" 

144 rawmode += masks[0xFF0000] + masks[0xFF00] + masks[0xFF] 

145 

146 self.tile = [("raw", (0, 0) + self.size, 0, (rawmode[::-1], 0, 1))] 

147 else: 

148 data_start = header_size + 4 

149 n = 0 

150 if fourcc == b"DXT1": 

151 self.pixel_format = "DXT1" 

152 n = 1 

153 elif fourcc == b"DXT3": 

154 self.pixel_format = "DXT3" 

155 n = 2 

156 elif fourcc == b"DXT5": 

157 self.pixel_format = "DXT5" 

158 n = 3 

159 elif fourcc == b"BC5S": 

160 self.pixel_format = "BC5S" 

161 n = 5 

162 self.mode = "RGB" 

163 elif fourcc == b"DX10": 

164 data_start += 20 

165 # ignoring flags which pertain to volume textures and cubemaps 

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

167 self.fp.read(16) 

168 if dxgi_format in (DXGI_FORMAT_BC5_TYPELESS, DXGI_FORMAT_BC5_UNORM): 

169 self.pixel_format = "BC5" 

170 n = 5 

171 self.mode = "RGB" 

172 elif dxgi_format == DXGI_FORMAT_BC5_SNORM: 

173 self.pixel_format = "BC5S" 

174 n = 5 

175 self.mode = "RGB" 

176 elif dxgi_format in (DXGI_FORMAT_BC7_TYPELESS, DXGI_FORMAT_BC7_UNORM): 

177 self.pixel_format = "BC7" 

178 n = 7 

179 elif dxgi_format == DXGI_FORMAT_BC7_UNORM_SRGB: 

180 self.pixel_format = "BC7" 

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

182 n = 7 

183 elif dxgi_format in ( 

184 DXGI_FORMAT_R8G8B8A8_TYPELESS, 

185 DXGI_FORMAT_R8G8B8A8_UNORM, 

186 DXGI_FORMAT_R8G8B8A8_UNORM_SRGB, 

187 ): 

188 self.tile = [("raw", (0, 0) + self.size, 0, ("RGBA", 0, 1))] 

189 if dxgi_format == DXGI_FORMAT_R8G8B8A8_UNORM_SRGB: 

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

191 return 

192 else: 

193 raise NotImplementedError( 

194 f"Unimplemented DXGI format {dxgi_format}" 

195 ) 

196 else: 

197 raise NotImplementedError(f"Unimplemented pixel format {repr(fourcc)}") 

198 

199 self.tile = [ 

200 ("bcn", (0, 0) + self.size, data_start, (n, self.pixel_format)) 

201 ] 

202 

203 def load_seek(self, pos): 

204 pass 

205 

206 

207def _save(im, fp, filename): 

208 if im.mode not in ("RGB", "RGBA"): 

209 raise OSError(f"cannot write mode {im.mode} as DDS") 

210 

211 fp.write( 

212 o32(DDS_MAGIC) 

213 + o32(124) # header size 

214 + o32( 

215 DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PITCH | DDSD_PIXELFORMAT 

216 ) # flags 

217 + o32(im.height) 

218 + o32(im.width) 

219 + o32((im.width * (32 if im.mode == "RGBA" else 24) + 7) // 8) # pitch 

220 + o32(0) # depth 

221 + o32(0) # mipmaps 

222 + o32(0) * 11 # reserved 

223 + o32(32) # pfsize 

224 + o32(DDS_RGBA if im.mode == "RGBA" else DDPF_RGB) # pfflags 

225 + o32(0) # fourcc 

226 + o32(32 if im.mode == "RGBA" else 24) # bitcount 

227 + o32(0xFF0000) # rbitmask 

228 + o32(0xFF00) # gbitmask 

229 + o32(0xFF) # bbitmask 

230 + o32(0xFF000000 if im.mode == "RGBA" else 0) # abitmask 

231 + o32(DDSCAPS_TEXTURE) # dwCaps 

232 + o32(0) # dwCaps2 

233 + o32(0) # dwCaps3 

234 + o32(0) # dwCaps4 

235 + o32(0) # dwReserved2 

236 ) 

237 if im.mode == "RGBA": 

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

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

240 ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (im.mode[::-1], 0, 1))]) 

241 

242 

243def _accept(prefix): 

244 return prefix[:4] == b"DDS " 

245 

246 

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

248Image.register_save(DdsImageFile.format, _save) 

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