BlpImagePlugin.py 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501
  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. from __future__ import annotations
  27. import abc
  28. import os
  29. import struct
  30. from enum import IntEnum
  31. from io import BytesIO
  32. from typing import IO
  33. from . import Image, ImageFile
  34. class Format(IntEnum):
  35. JPEG = 0
  36. class Encoding(IntEnum):
  37. UNCOMPRESSED = 1
  38. DXT = 2
  39. UNCOMPRESSED_RAW_BGRA = 3
  40. class AlphaEncoding(IntEnum):
  41. DXT1 = 0
  42. DXT3 = 1
  43. DXT5 = 7
  44. def unpack_565(i: int) -> tuple[int, int, int]:
  45. return ((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3
  46. def decode_dxt1(
  47. data: bytes, alpha: bool = False
  48. ) -> tuple[bytearray, bytearray, bytearray, bytearray]:
  49. """
  50. input: one "row" of data (i.e. will produce 4*width pixels)
  51. """
  52. blocks = len(data) // 8 # number of blocks in row
  53. ret = (bytearray(), bytearray(), bytearray(), bytearray())
  54. for block_index in range(blocks):
  55. # Decode next 8-byte block.
  56. idx = block_index * 8
  57. color0, color1, bits = struct.unpack_from("<HHI", data, idx)
  58. r0, g0, b0 = unpack_565(color0)
  59. r1, g1, b1 = unpack_565(color1)
  60. # Decode this block into 4x4 pixels
  61. # Accumulate the results onto our 4 row accumulators
  62. for j in range(4):
  63. for i in range(4):
  64. # get next control op and generate a pixel
  65. control = bits & 3
  66. bits = bits >> 2
  67. a = 0xFF
  68. if control == 0:
  69. r, g, b = r0, g0, b0
  70. elif control == 1:
  71. r, g, b = r1, g1, b1
  72. elif control == 2:
  73. if color0 > color1:
  74. r = (2 * r0 + r1) // 3
  75. g = (2 * g0 + g1) // 3
  76. b = (2 * b0 + b1) // 3
  77. else:
  78. r = (r0 + r1) // 2
  79. g = (g0 + g1) // 2
  80. b = (b0 + b1) // 2
  81. elif control == 3:
  82. if color0 > color1:
  83. r = (2 * r1 + r0) // 3
  84. g = (2 * g1 + g0) // 3
  85. b = (2 * b1 + b0) // 3
  86. else:
  87. r, g, b, a = 0, 0, 0, 0
  88. if alpha:
  89. ret[j].extend([r, g, b, a])
  90. else:
  91. ret[j].extend([r, g, b])
  92. return ret
  93. def decode_dxt3(data: bytes) -> tuple[bytearray, bytearray, bytearray, bytearray]:
  94. """
  95. input: one "row" of data (i.e. will produce 4*width pixels)
  96. """
  97. blocks = len(data) // 16 # number of blocks in row
  98. ret = (bytearray(), bytearray(), bytearray(), bytearray())
  99. for block_index in range(blocks):
  100. idx = block_index * 16
  101. block = data[idx : idx + 16]
  102. # Decode next 16-byte block.
  103. bits = struct.unpack_from("<8B", block)
  104. color0, color1 = struct.unpack_from("<HH", block, 8)
  105. (code,) = struct.unpack_from("<I", block, 12)
  106. r0, g0, b0 = unpack_565(color0)
  107. r1, g1, b1 = unpack_565(color1)
  108. for j in range(4):
  109. high = False # Do we want the higher bits?
  110. for i in range(4):
  111. alphacode_index = (4 * j + i) // 2
  112. a = bits[alphacode_index]
  113. if high:
  114. high = False
  115. a >>= 4
  116. else:
  117. high = True
  118. a &= 0xF
  119. a *= 17 # We get a value between 0 and 15
  120. color_code = (code >> 2 * (4 * j + i)) & 0x03
  121. if color_code == 0:
  122. r, g, b = r0, g0, b0
  123. elif color_code == 1:
  124. r, g, b = r1, g1, b1
  125. elif color_code == 2:
  126. r = (2 * r0 + r1) // 3
  127. g = (2 * g0 + g1) // 3
  128. b = (2 * b0 + b1) // 3
  129. elif color_code == 3:
  130. r = (2 * r1 + r0) // 3
  131. g = (2 * g1 + g0) // 3
  132. b = (2 * b1 + b0) // 3
  133. ret[j].extend([r, g, b, a])
  134. return ret
  135. def decode_dxt5(data: bytes) -> tuple[bytearray, bytearray, bytearray, bytearray]:
  136. """
  137. input: one "row" of data (i.e. will produce 4 * width pixels)
  138. """
  139. blocks = len(data) // 16 # number of blocks in row
  140. ret = (bytearray(), bytearray(), bytearray(), bytearray())
  141. for block_index in range(blocks):
  142. idx = block_index * 16
  143. block = data[idx : idx + 16]
  144. # Decode next 16-byte block.
  145. a0, a1 = struct.unpack_from("<BB", block)
  146. bits = struct.unpack_from("<6B", block, 2)
  147. alphacode1 = bits[2] | (bits[3] << 8) | (bits[4] << 16) | (bits[5] << 24)
  148. alphacode2 = bits[0] | (bits[1] << 8)
  149. color0, color1 = struct.unpack_from("<HH", block, 8)
  150. (code,) = struct.unpack_from("<I", block, 12)
  151. r0, g0, b0 = unpack_565(color0)
  152. r1, g1, b1 = unpack_565(color1)
  153. for j in range(4):
  154. for i in range(4):
  155. # get next control op and generate a pixel
  156. alphacode_index = 3 * (4 * j + i)
  157. if alphacode_index <= 12:
  158. alphacode = (alphacode2 >> alphacode_index) & 0x07
  159. elif alphacode_index == 15:
  160. alphacode = (alphacode2 >> 15) | ((alphacode1 << 1) & 0x06)
  161. else: # alphacode_index >= 18 and alphacode_index <= 45
  162. alphacode = (alphacode1 >> (alphacode_index - 16)) & 0x07
  163. if alphacode == 0:
  164. a = a0
  165. elif alphacode == 1:
  166. a = a1
  167. elif a0 > a1:
  168. a = ((8 - alphacode) * a0 + (alphacode - 1) * a1) // 7
  169. elif alphacode == 6:
  170. a = 0
  171. elif alphacode == 7:
  172. a = 255
  173. else:
  174. a = ((6 - alphacode) * a0 + (alphacode - 1) * a1) // 5
  175. color_code = (code >> 2 * (4 * j + i)) & 0x03
  176. if color_code == 0:
  177. r, g, b = r0, g0, b0
  178. elif color_code == 1:
  179. r, g, b = r1, g1, b1
  180. elif color_code == 2:
  181. r = (2 * r0 + r1) // 3
  182. g = (2 * g0 + g1) // 3
  183. b = (2 * b0 + b1) // 3
  184. elif color_code == 3:
  185. r = (2 * r1 + r0) // 3
  186. g = (2 * g1 + g0) // 3
  187. b = (2 * b1 + b0) // 3
  188. ret[j].extend([r, g, b, a])
  189. return ret
  190. class BLPFormatError(NotImplementedError):
  191. pass
  192. def _accept(prefix: bytes) -> bool:
  193. return prefix[:4] in (b"BLP1", b"BLP2")
  194. class BlpImageFile(ImageFile.ImageFile):
  195. """
  196. Blizzard Mipmap Format
  197. """
  198. format = "BLP"
  199. format_description = "Blizzard Mipmap Format"
  200. def _open(self) -> None:
  201. self.magic = self.fp.read(4)
  202. if not _accept(self.magic):
  203. msg = f"Bad BLP magic {repr(self.magic)}"
  204. raise BLPFormatError(msg)
  205. compression = struct.unpack("<i", self.fp.read(4))[0]
  206. if self.magic == b"BLP1":
  207. alpha = struct.unpack("<I", self.fp.read(4))[0] != 0
  208. else:
  209. encoding = struct.unpack("<b", self.fp.read(1))[0]
  210. alpha = struct.unpack("<b", self.fp.read(1))[0] != 0
  211. alpha_encoding = struct.unpack("<b", self.fp.read(1))[0]
  212. self.fp.seek(1, os.SEEK_CUR) # mips
  213. self._size = struct.unpack("<II", self.fp.read(8))
  214. args: tuple[int, int, bool] | tuple[int, int, bool, int]
  215. if self.magic == b"BLP1":
  216. encoding = struct.unpack("<i", self.fp.read(4))[0]
  217. self.fp.seek(4, os.SEEK_CUR) # subtype
  218. args = (compression, encoding, alpha)
  219. offset = 28
  220. else:
  221. args = (compression, encoding, alpha, alpha_encoding)
  222. offset = 20
  223. decoder = self.magic.decode()
  224. self._mode = "RGBA" if alpha else "RGB"
  225. self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, offset, args)]
  226. class _BLPBaseDecoder(ImageFile.PyDecoder):
  227. _pulls_fd = True
  228. def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
  229. try:
  230. self._read_header()
  231. self._load()
  232. except struct.error as e:
  233. msg = "Truncated BLP file"
  234. raise OSError(msg) from e
  235. return -1, 0
  236. @abc.abstractmethod
  237. def _load(self) -> None:
  238. pass
  239. def _read_header(self) -> None:
  240. self._offsets = struct.unpack("<16I", self._safe_read(16 * 4))
  241. self._lengths = struct.unpack("<16I", self._safe_read(16 * 4))
  242. def _safe_read(self, length: int) -> bytes:
  243. assert self.fd is not None
  244. return ImageFile._safe_read(self.fd, length)
  245. def _read_palette(self) -> list[tuple[int, int, int, int]]:
  246. ret = []
  247. for i in range(256):
  248. try:
  249. b, g, r, a = struct.unpack("<4B", self._safe_read(4))
  250. except struct.error:
  251. break
  252. ret.append((b, g, r, a))
  253. return ret
  254. def _read_bgra(
  255. self, palette: list[tuple[int, int, int, int]], alpha: bool
  256. ) -> bytearray:
  257. data = bytearray()
  258. _data = BytesIO(self._safe_read(self._lengths[0]))
  259. while True:
  260. try:
  261. (offset,) = struct.unpack("<B", _data.read(1))
  262. except struct.error:
  263. break
  264. b, g, r, a = palette[offset]
  265. d: tuple[int, ...] = (r, g, b)
  266. if alpha:
  267. d += (a,)
  268. data.extend(d)
  269. return data
  270. class BLP1Decoder(_BLPBaseDecoder):
  271. def _load(self) -> None:
  272. self._compression, self._encoding, alpha = self.args
  273. if self._compression == Format.JPEG:
  274. self._decode_jpeg_stream()
  275. elif self._compression == 1:
  276. if self._encoding in (4, 5):
  277. palette = self._read_palette()
  278. data = self._read_bgra(palette, alpha)
  279. self.set_as_raw(data)
  280. else:
  281. msg = f"Unsupported BLP encoding {repr(self._encoding)}"
  282. raise BLPFormatError(msg)
  283. else:
  284. msg = f"Unsupported BLP compression {repr(self._encoding)}"
  285. raise BLPFormatError(msg)
  286. def _decode_jpeg_stream(self) -> None:
  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. assert self.fd is not None
  291. self._safe_read(self._offsets[0] - self.fd.tell()) # What IS this?
  292. data = self._safe_read(self._lengths[0])
  293. data = jpeg_header + data
  294. image = JpegImageFile(BytesIO(data))
  295. Image._decompression_bomb_check(image.size)
  296. if image.mode == "CMYK":
  297. decoder_name, extents, offset, args = image.tile[0]
  298. assert isinstance(args, tuple)
  299. image.tile = [
  300. ImageFile._Tile(decoder_name, extents, offset, (args[0], "CMYK"))
  301. ]
  302. r, g, b = image.convert("RGB").split()
  303. reversed_image = Image.merge("RGB", (b, g, r))
  304. self.set_as_raw(reversed_image.tobytes())
  305. class BLP2Decoder(_BLPBaseDecoder):
  306. def _load(self) -> None:
  307. self._compression, self._encoding, alpha, self._alpha_encoding = self.args
  308. palette = self._read_palette()
  309. assert self.fd is not None
  310. self.fd.seek(self._offsets[0])
  311. if self._compression == 1:
  312. # Uncompressed or DirectX compression
  313. if self._encoding == Encoding.UNCOMPRESSED:
  314. data = self._read_bgra(palette, alpha)
  315. elif self._encoding == Encoding.DXT:
  316. data = bytearray()
  317. if self._alpha_encoding == AlphaEncoding.DXT1:
  318. linesize = (self.state.xsize + 3) // 4 * 8
  319. for yb in range((self.state.ysize + 3) // 4):
  320. for d in decode_dxt1(self._safe_read(linesize), alpha):
  321. data += d
  322. elif self._alpha_encoding == AlphaEncoding.DXT3:
  323. linesize = (self.state.xsize + 3) // 4 * 16
  324. for yb in range((self.state.ysize + 3) // 4):
  325. for d in decode_dxt3(self._safe_read(linesize)):
  326. data += d
  327. elif self._alpha_encoding == AlphaEncoding.DXT5:
  328. linesize = (self.state.xsize + 3) // 4 * 16
  329. for yb in range((self.state.ysize + 3) // 4):
  330. for d in decode_dxt5(self._safe_read(linesize)):
  331. data += d
  332. else:
  333. msg = f"Unsupported alpha encoding {repr(self._alpha_encoding)}"
  334. raise BLPFormatError(msg)
  335. else:
  336. msg = f"Unknown BLP encoding {repr(self._encoding)}"
  337. raise BLPFormatError(msg)
  338. else:
  339. msg = f"Unknown BLP compression {repr(self._compression)}"
  340. raise BLPFormatError(msg)
  341. self.set_as_raw(data)
  342. class BLPEncoder(ImageFile.PyEncoder):
  343. _pushes_fd = True
  344. def _write_palette(self) -> bytes:
  345. data = b""
  346. assert self.im is not None
  347. palette = self.im.getpalette("RGBA", "RGBA")
  348. for i in range(len(palette) // 4):
  349. r, g, b, a = palette[i * 4 : (i + 1) * 4]
  350. data += struct.pack("<4B", b, g, r, a)
  351. while len(data) < 256 * 4:
  352. data += b"\x00" * 4
  353. return data
  354. def encode(self, bufsize: int) -> tuple[int, int, bytes]:
  355. palette_data = self._write_palette()
  356. offset = 20 + 16 * 4 * 2 + len(palette_data)
  357. data = struct.pack("<16I", offset, *((0,) * 15))
  358. assert self.im is not None
  359. w, h = self.im.size
  360. data += struct.pack("<16I", w * h, *((0,) * 15))
  361. data += palette_data
  362. for y in range(h):
  363. for x in range(w):
  364. data += struct.pack("<B", self.im.getpixel((x, y)))
  365. return len(data), 0, data
  366. def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
  367. if im.mode != "P":
  368. msg = "Unsupported BLP image mode"
  369. raise ValueError(msg)
  370. magic = b"BLP1" if im.encoderinfo.get("blp_version") == "BLP1" else b"BLP2"
  371. fp.write(magic)
  372. assert im.palette is not None
  373. fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
  374. alpha_depth = 1 if im.palette.mode == "RGBA" else 0
  375. if magic == b"BLP1":
  376. fp.write(struct.pack("<L", alpha_depth))
  377. else:
  378. fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
  379. fp.write(struct.pack("<b", alpha_depth))
  380. fp.write(struct.pack("<b", 0)) # alpha encoding
  381. fp.write(struct.pack("<b", 0)) # mips
  382. fp.write(struct.pack("<II", *im.size))
  383. if magic == b"BLP1":
  384. fp.write(struct.pack("<i", 5))
  385. fp.write(struct.pack("<i", 0))
  386. ImageFile._save(im, fp, [ImageFile._Tile("BLP", (0, 0) + im.size, 0, im.mode)])
  387. Image.register_open(BlpImageFile.format, BlpImageFile, _accept)
  388. Image.register_extension(BlpImageFile.format, ".blp")
  389. Image.register_decoder("BLP1", BLP1Decoder)
  390. Image.register_decoder("BLP2", BLP2Decoder)
  391. Image.register_save(BlpImageFile.format, _save)
  392. Image.register_encoder("BLP", BLPEncoder)