Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

BlpImagePlugin.py 16KB

1 year ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488
  1. """
  2. Blizzard Mipmap Format (.blp)
  3. Jerome Leclanche <jerome@leclan.ch>
  4. The contents of this file are hereby released in the public domain (CC0)
  5. Full text of the CC0 license:
  6. https://creativecommons.org/publicdomain/zero/1.0/
  7. BLP1 files, used mostly in Warcraft III, are not fully supported.
  8. All types of BLP2 files used in World of Warcraft are supported.
  9. The BLP file structure consists of a header, up to 16 mipmaps of the
  10. texture
  11. Texture sizes must be powers of two, though the two dimensions do
  12. not have to be equal; 512x256 is valid, but 512x200 is not.
  13. The first mipmap (mipmap #0) is the full size image; each subsequent
  14. mipmap halves both dimensions. The final mipmap should be 1x1.
  15. BLP files come in many different flavours:
  16. * JPEG-compressed (type == 0) - only supported for BLP1.
  17. * RAW images (type == 1, encoding == 1). Each mipmap is stored as an
  18. array of 8-bit values, one per pixel, left to right, top to bottom.
  19. Each value is an index to the palette.
  20. * DXT-compressed (type == 1, encoding == 2):
  21. - DXT1 compression is used if alpha_encoding == 0.
  22. - An additional alpha bit is used if alpha_depth == 1.
  23. - DXT3 compression is used if alpha_encoding == 1.
  24. - DXT5 compression is used if alpha_encoding == 7.
  25. """
  26. import os
  27. import struct
  28. from enum import IntEnum
  29. from io import BytesIO
  30. from . import Image, ImageFile
  31. from ._deprecate import deprecate
  32. class Format(IntEnum):
  33. JPEG = 0
  34. class Encoding(IntEnum):
  35. UNCOMPRESSED = 1
  36. DXT = 2
  37. UNCOMPRESSED_RAW_BGRA = 3
  38. class AlphaEncoding(IntEnum):
  39. DXT1 = 0
  40. DXT3 = 1
  41. DXT5 = 7
  42. def __getattr__(name):
  43. for enum, prefix in {
  44. Format: "BLP_FORMAT_",
  45. Encoding: "BLP_ENCODING_",
  46. AlphaEncoding: "BLP_ALPHA_ENCODING_",
  47. }.items():
  48. if name.startswith(prefix):
  49. name = name[len(prefix) :]
  50. if name in enum.__members__:
  51. deprecate(f"{prefix}{name}", 10, f"{enum.__name__}.{name}")
  52. return enum[name]
  53. msg = f"module '{__name__}' has no attribute '{name}'"
  54. raise AttributeError(msg)
  55. def unpack_565(i):
  56. return ((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3
  57. def decode_dxt1(data, alpha=False):
  58. """
  59. input: one "row" of data (i.e. will produce 4*width pixels)
  60. """
  61. blocks = len(data) // 8 # number of blocks in row
  62. ret = (bytearray(), bytearray(), bytearray(), bytearray())
  63. for block in range(blocks):
  64. # Decode next 8-byte block.
  65. idx = block * 8
  66. color0, color1, bits = struct.unpack_from("<HHI", data, idx)
  67. r0, g0, b0 = unpack_565(color0)
  68. r1, g1, b1 = unpack_565(color1)
  69. # Decode this block into 4x4 pixels
  70. # Accumulate the results onto our 4 row accumulators
  71. for j in range(4):
  72. for i in range(4):
  73. # get next control op and generate a pixel
  74. control = bits & 3
  75. bits = bits >> 2
  76. a = 0xFF
  77. if control == 0:
  78. r, g, b = r0, g0, b0
  79. elif control == 1:
  80. r, g, b = r1, g1, b1
  81. elif control == 2:
  82. if color0 > color1:
  83. r = (2 * r0 + r1) // 3
  84. g = (2 * g0 + g1) // 3
  85. b = (2 * b0 + b1) // 3
  86. else:
  87. r = (r0 + r1) // 2
  88. g = (g0 + g1) // 2
  89. b = (b0 + b1) // 2
  90. elif control == 3:
  91. if color0 > color1:
  92. r = (2 * r1 + r0) // 3
  93. g = (2 * g1 + g0) // 3
  94. b = (2 * b1 + b0) // 3
  95. else:
  96. r, g, b, a = 0, 0, 0, 0
  97. if alpha:
  98. ret[j].extend([r, g, b, a])
  99. else:
  100. ret[j].extend([r, g, b])
  101. return ret
  102. def decode_dxt3(data):
  103. """
  104. input: one "row" of data (i.e. will produce 4*width pixels)
  105. """
  106. blocks = len(data) // 16 # number of blocks in row
  107. ret = (bytearray(), bytearray(), bytearray(), bytearray())
  108. for block in range(blocks):
  109. idx = block * 16
  110. block = data[idx : idx + 16]
  111. # Decode next 16-byte block.
  112. bits = struct.unpack_from("<8B", block)
  113. color0, color1 = struct.unpack_from("<HH", block, 8)
  114. (code,) = struct.unpack_from("<I", block, 12)
  115. r0, g0, b0 = unpack_565(color0)
  116. r1, g1, b1 = unpack_565(color1)
  117. for j in range(4):
  118. high = False # Do we want the higher bits?
  119. for i in range(4):
  120. alphacode_index = (4 * j + i) // 2
  121. a = bits[alphacode_index]
  122. if high:
  123. high = False
  124. a >>= 4
  125. else:
  126. high = True
  127. a &= 0xF
  128. a *= 17 # We get a value between 0 and 15
  129. color_code = (code >> 2 * (4 * j + i)) & 0x03
  130. if color_code == 0:
  131. r, g, b = r0, g0, b0
  132. elif color_code == 1:
  133. r, g, b = r1, g1, b1
  134. elif color_code == 2:
  135. r = (2 * r0 + r1) // 3
  136. g = (2 * g0 + g1) // 3
  137. b = (2 * b0 + b1) // 3
  138. elif color_code == 3:
  139. r = (2 * r1 + r0) // 3
  140. g = (2 * g1 + g0) // 3
  141. b = (2 * b1 + b0) // 3
  142. ret[j].extend([r, g, b, a])
  143. return ret
  144. def decode_dxt5(data):
  145. """
  146. input: one "row" of data (i.e. will produce 4 * width pixels)
  147. """
  148. blocks = len(data) // 16 # number of blocks in row
  149. ret = (bytearray(), bytearray(), bytearray(), bytearray())
  150. for block in range(blocks):
  151. idx = block * 16
  152. block = data[idx : idx + 16]
  153. # Decode next 16-byte block.
  154. a0, a1 = struct.unpack_from("<BB", block)
  155. bits = struct.unpack_from("<6B", block, 2)
  156. alphacode1 = bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
  157. alphacode2 = bits[0] | (bits[1] << 8)
  158. color0, color1 = struct.unpack_from("<HH", block, 8)
  159. (code,) = struct.unpack_from("<I", block, 12)
  160. r0, g0, b0 = unpack_565(color0)
  161. r1, g1, b1 = unpack_565(color1)
  162. for j in range(4):
  163. for i in range(4):
  164. # get next control op and generate a pixel
  165. alphacode_index = 3 * (4 * j + i)
  166. if alphacode_index <= 12:
  167. alphacode = (alphacode2 >> alphacode_index) & 0x07
  168. elif alphacode_index == 15:
  169. alphacode = (alphacode2 >> 15) | ((alphacode1 << 1) & 0x06)
  170. else: # alphacode_index >= 18 and alphacode_index <= 45
  171. alphacode = (alphacode1 >> (alphacode_index - 16)) & 0x07
  172. if alphacode == 0:
  173. a = a0
  174. elif alphacode == 1:
  175. a = a1
  176. elif a0 > a1:
  177. a = ((8 - alphacode) * a0 + (alphacode - 1) * a1) // 7
  178. elif alphacode == 6:
  179. a = 0
  180. elif alphacode == 7:
  181. a = 255
  182. else:
  183. a = ((6 - alphacode) * a0 + (alphacode - 1) * a1) // 5
  184. color_code = (code >> 2 * (4 * j + i)) & 0x03
  185. if color_code == 0:
  186. r, g, b = r0, g0, b0
  187. elif color_code == 1:
  188. r, g, b = r1, g1, b1
  189. elif color_code == 2:
  190. r = (2 * r0 + r1) // 3
  191. g = (2 * g0 + g1) // 3
  192. b = (2 * b0 + b1) // 3
  193. elif color_code == 3:
  194. r = (2 * r1 + r0) // 3
  195. g = (2 * g1 + g0) // 3
  196. b = (2 * b1 + b0) // 3
  197. ret[j].extend([r, g, b, a])
  198. return ret
  199. class BLPFormatError(NotImplementedError):
  200. pass
  201. def _accept(prefix):
  202. return prefix[:4] in (b"BLP1", b"BLP2")
  203. class BlpImageFile(ImageFile.ImageFile):
  204. """
  205. Blizzard Mipmap Format
  206. """
  207. format = "BLP"
  208. format_description = "Blizzard Mipmap Format"
  209. def _open(self):
  210. self.magic = self.fp.read(4)
  211. self.fp.seek(5, os.SEEK_CUR)
  212. (self._blp_alpha_depth,) = struct.unpack("<b", self.fp.read(1))
  213. self.fp.seek(2, os.SEEK_CUR)
  214. self._size = struct.unpack("<II", self.fp.read(8))
  215. if self.magic in (b"BLP1", b"BLP2"):
  216. decoder = self.magic.decode()
  217. else:
  218. msg = f"Bad BLP magic {repr(self.magic)}"
  219. raise BLPFormatError(msg)
  220. self.mode = "RGBA" if self._blp_alpha_depth else "RGB"
  221. self.tile = [(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
  222. class _BLPBaseDecoder(ImageFile.PyDecoder):
  223. _pulls_fd = True
  224. def decode(self, buffer):
  225. try:
  226. self._read_blp_header()
  227. self._load()
  228. except struct.error as e:
  229. msg = "Truncated BLP file"
  230. raise OSError(msg) from e
  231. return -1, 0
  232. def _read_blp_header(self):
  233. self.fd.seek(4)
  234. (self._blp_compression,) = struct.unpack("<i", self._safe_read(4))
  235. (self._blp_encoding,) = struct.unpack("<b", self._safe_read(1))
  236. (self._blp_alpha_depth,) = struct.unpack("<b", self._safe_read(1))
  237. (self._blp_alpha_encoding,) = struct.unpack("<b", self._safe_read(1))
  238. self.fd.seek(1, os.SEEK_CUR) # mips
  239. self.size = struct.unpack("<II", self._safe_read(8))
  240. if isinstance(self, BLP1Decoder):
  241. # Only present for BLP1
  242. (self._blp_encoding,) = struct.unpack("<i", self._safe_read(4))
  243. self.fd.seek(4, os.SEEK_CUR) # subtype
  244. self._blp_offsets = struct.unpack("<16I", self._safe_read(16 * 4))
  245. self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
  246. def _safe_read(self, length):
  247. return ImageFile._safe_read(self.fd, length)
  248. def _read_palette(self):
  249. ret = []
  250. for i in range(256):
  251. try:
  252. b, g, r, a = struct.unpack("<4B", self._safe_read(4))
  253. except struct.error:
  254. break
  255. ret.append((b, g, r, a))
  256. return ret
  257. def _read_bgra(self, palette):
  258. data = bytearray()
  259. _data = BytesIO(self._safe_read(self._blp_lengths[0]))
  260. while True:
  261. try:
  262. (offset,) = struct.unpack("<B", _data.read(1))
  263. except struct.error:
  264. break
  265. b, g, r, a = palette[offset]
  266. d = (r, g, b)
  267. if self._blp_alpha_depth:
  268. d += (a,)
  269. data.extend(d)
  270. return data
  271. class BLP1Decoder(_BLPBaseDecoder):
  272. def _load(self):
  273. if self._blp_compression == Format.JPEG:
  274. self._decode_jpeg_stream()
  275. elif self._blp_compression == 1:
  276. if self._blp_encoding in (4, 5):
  277. palette = self._read_palette()
  278. data = self._read_bgra(palette)
  279. self.set_as_raw(bytes(data))
  280. else:
  281. msg = f"Unsupported BLP encoding {repr(self._blp_encoding)}"
  282. raise BLPFormatError(msg)
  283. else:
  284. msg = f"Unsupported BLP compression {repr(self._blp_encoding)}"
  285. raise BLPFormatError(msg)
  286. def _decode_jpeg_stream(self):
  287. from .JpegImagePlugin import JpegImageFile
  288. (jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
  289. jpeg_header = self._safe_read(jpeg_header_size)
  290. self._safe_read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
  291. data = self._safe_read(self._blp_lengths[0])
  292. data = jpeg_header + data
  293. data = BytesIO(data)
  294. image = JpegImageFile(data)
  295. Image._decompression_bomb_check(image.size)
  296. if image.mode == "CMYK":
  297. decoder_name, extents, offset, args = image.tile[0]
  298. image.tile = [(decoder_name, extents, offset, (args[0], "CMYK"))]
  299. r, g, b = image.convert("RGB").split()
  300. image = Image.merge("RGB", (b, g, r))
  301. self.set_as_raw(image.tobytes())
  302. class BLP2Decoder(_BLPBaseDecoder):
  303. def _load(self):
  304. palette = self._read_palette()
  305. self.fd.seek(self._blp_offsets[0])
  306. if self._blp_compression == 1:
  307. # Uncompressed or DirectX compression
  308. if self._blp_encoding == Encoding.UNCOMPRESSED:
  309. data = self._read_bgra(palette)
  310. elif self._blp_encoding == Encoding.DXT:
  311. data = bytearray()
  312. if self._blp_alpha_encoding == AlphaEncoding.DXT1:
  313. linesize = (self.size[0] + 3) // 4 * 8
  314. for yb in range((self.size[1] + 3) // 4):
  315. for d in decode_dxt1(
  316. self._safe_read(linesize), alpha=bool(self._blp_alpha_depth)
  317. ):
  318. data += d
  319. elif self._blp_alpha_encoding == AlphaEncoding.DXT3:
  320. linesize = (self.size[0] + 3) // 4 * 16
  321. for yb in range((self.size[1] + 3) // 4):
  322. for d in decode_dxt3(self._safe_read(linesize)):
  323. data += d
  324. elif self._blp_alpha_encoding == AlphaEncoding.DXT5:
  325. linesize = (self.size[0] + 3) // 4 * 16
  326. for yb in range((self.size[1] + 3) // 4):
  327. for d in decode_dxt5(self._safe_read(linesize)):
  328. data += d
  329. else:
  330. msg = f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
  331. raise BLPFormatError(msg)
  332. else:
  333. msg = f"Unknown BLP encoding {repr(self._blp_encoding)}"
  334. raise BLPFormatError(msg)
  335. else:
  336. msg = f"Unknown BLP compression {repr(self._blp_compression)}"
  337. raise BLPFormatError(msg)
  338. self.set_as_raw(bytes(data))
  339. class BLPEncoder(ImageFile.PyEncoder):
  340. _pushes_fd = True
  341. def _write_palette(self):
  342. data = b""
  343. palette = self.im.getpalette("RGBA", "RGBA")
  344. for i in range(256):
  345. r, g, b, a = palette[i * 4 : (i + 1) * 4]
  346. data += struct.pack("<4B", b, g, r, a)
  347. return data
  348. def encode(self, bufsize):
  349. palette_data = self._write_palette()
  350. offset = 20 + 16 * 4 * 2 + len(palette_data)
  351. data = struct.pack("<16I", offset, *((0,) * 15))
  352. w, h = self.im.size
  353. data += struct.pack("<16I", w * h, *((0,) * 15))
  354. data += palette_data
  355. for y in range(h):
  356. for x in range(w):
  357. data += struct.pack("<B", self.im.getpixel((x, y)))
  358. return len(data), 0, data
  359. def _save(im, fp, filename, save_all=False):
  360. if im.mode != "P":
  361. msg = "Unsupported BLP image mode"
  362. raise ValueError(msg)
  363. magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
  364. fp.write(magic)
  365. fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
  366. fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
  367. fp.write(struct.pack("<b", 1 if im.palette.mode == "RGBA" else 0))
  368. fp.write(struct.pack("<b", 0)) # alpha encoding
  369. fp.write(struct.pack("<b", 0)) # mips
  370. fp.write(struct.pack("<II", *im.size))
  371. if magic == b"BLP1":
  372. fp.write(struct.pack("<i", 5))
  373. fp.write(struct.pack("<i", 0))
  374. ImageFile._save(im, fp, [("BLP", (0, 0) + im.size, 0, im.mode)])
  375. Image.register_open(BlpImageFile.format, BlpImageFile, _accept)
  376. Image.register_extension(BlpImageFile.format, ".blp")
  377. Image.register_decoder("BLP1", BLP1Decoder)
  378. Image.register_decoder("BLP2", BLP2Decoder)
  379. Image.register_save(BlpImageFile.format, _save)
  380. Image.register_encoder("BLP", BLPEncoder)