Skip to content

Commit dfb368a

Browse files
authored
Merge pull request #8651 from radarhere/blp
Corrected BLP1 alpha depth handling
2 parents aa0f412 + 5d998d3 commit dfb368a

File tree

2 files changed

+81
-65
lines changed

2 files changed

+81
-65
lines changed

Tests/test_file_blp.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import pytest
66

7-
from PIL import Image
7+
from PIL import BlpImagePlugin, Image
88

99
from .helper import (
1010
assert_image_equal,
@@ -19,6 +19,7 @@ def test_load_blp1() -> None:
1919
assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")
2020

2121
with Image.open("Tests/images/blp/blp1_jpeg2.blp") as im:
22+
assert im.mode == "RGBA"
2223
im.load()
2324

2425

@@ -37,6 +38,13 @@ def test_load_blp2_dxt1a() -> None:
3738
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
3839

3940

41+
def test_invalid_file() -> None:
42+
invalid_file = "Tests/images/flower.jpg"
43+
44+
with pytest.raises(BlpImagePlugin.BLPFormatError):
45+
BlpImagePlugin.BlpImageFile(invalid_file)
46+
47+
4048
def test_save(tmp_path: Path) -> None:
4149
f = str(tmp_path / "temp.blp")
4250

src/PIL/BlpImagePlugin.py

Lines changed: 72 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -259,29 +259,44 @@ class BlpImageFile(ImageFile.ImageFile):
259259

260260
def _open(self) -> None:
261261
self.magic = self.fp.read(4)
262+
if not _accept(self.magic):
263+
msg = f"Bad BLP magic {repr(self.magic)}"
264+
raise BLPFormatError(msg)
262265

263-
self.fp.seek(5, os.SEEK_CUR)
264-
(self._blp_alpha_depth,) = struct.unpack("<b", self.fp.read(1))
266+
compression = struct.unpack("<i", self.fp.read(4))[0]
267+
if self.magic == b"BLP1":
268+
alpha = struct.unpack("<I", self.fp.read(4))[0] != 0
269+
else:
270+
encoding = struct.unpack("<b", self.fp.read(1))[0]
271+
alpha = struct.unpack("<b", self.fp.read(1))[0] != 0
272+
alpha_encoding = struct.unpack("<b", self.fp.read(1))[0]
273+
self.fp.seek(1, os.SEEK_CUR) # mips
265274

266-
self.fp.seek(2, os.SEEK_CUR)
267275
self._size = struct.unpack("<II", self.fp.read(8))
268276

269-
if self.magic in (b"BLP1", b"BLP2"):
270-
decoder = self.magic.decode()
277+
args: tuple[int, int, bool] | tuple[int, int, bool, int]
278+
if self.magic == b"BLP1":
279+
encoding = struct.unpack("<i", self.fp.read(4))[0]
280+
self.fp.seek(4, os.SEEK_CUR) # subtype
281+
282+
args = (compression, encoding, alpha)
283+
offset = 28
271284
else:
272-
msg = f"Bad BLP magic {repr(self.magic)}"
273-
raise BLPFormatError(msg)
285+
args = (compression, encoding, alpha, alpha_encoding)
286+
offset = 20
274287

275-
self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
276-
self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, 0, self.mode)]
288+
decoder = self.magic.decode()
289+
290+
self._mode = "RGBA" if alpha else "RGB"
291+
self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, offset, args)]
277292

278293

279294
class _BLPBaseDecoder(ImageFile.PyDecoder):
280295
_pulls_fd = True
281296

282297
def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
283298
try:
284-
self._read_blp_header()
299+
self._read_header()
285300
self._load()
286301
except struct.error as e:
287302
msg = "Truncated BLP file"
@@ -292,25 +307,9 @@ def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int
292307
def _load(self) -> None:
293308
pass
294309

295-
def _read_blp_header(self) -> None:
296-
assert self.fd is not None
297-
self.fd.seek(4)
298-
(self._blp_compression,) = struct.unpack("<i", self._safe_read(4))
299-
300-
(self._blp_encoding,) = struct.unpack("<b", self._safe_read(1))
301-
(self._blp_alpha_depth,) = struct.unpack("<b", self._safe_read(1))
302-
(self._blp_alpha_encoding,) = struct.unpack("<b", self._safe_read(1))
303-
self.fd.seek(1, os.SEEK_CUR) # mips
304-
305-
self.size = struct.unpack("<II", self._safe_read(8))
306-
307-
if isinstance(self, BLP1Decoder):
308-
# Only present for BLP1
309-
(self._blp_encoding,) = struct.unpack("<i", self._safe_read(4))
310-
self.fd.seek(4, os.SEEK_CUR) # subtype
311-
312-
self._blp_offsets = struct.unpack("<16I", self._safe_read(16 * 4))
313-
self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
310+
def _read_header(self) -> None:
311+
self._offsets = struct.unpack("<16I", self._safe_read(16 * 4))
312+
self._lengths = struct.unpack("<16I", self._safe_read(16 * 4))
314313

315314
def _safe_read(self, length: int) -> bytes:
316315
assert self.fd is not None
@@ -326,37 +325,41 @@ def _read_palette(self) -> list[tuple[int, int, int, int]]:
326325
ret.append((b, g, r, a))
327326
return ret
328327

329-
def _read_bgra(self, palette: list[tuple[int, int, int, int]]) -> bytearray:
328+
def _read_bgra(
329+
self, palette: list[tuple[int, int, int, int]], alpha: bool
330+
) -> bytearray:
330331
data = bytearray()
331-
_data = BytesIO(self._safe_read(self._blp_lengths[0]))
332+
_data = BytesIO(self._safe_read(self._lengths[0]))
332333
while True:
333334
try:
334335
(offset,) = struct.unpack("<B", _data.read(1))
335336
except struct.error:
336337
break
337338
b, g, r, a = palette[offset]
338339
d: tuple[int, ...] = (r, g, b)
339-
if self._blp_alpha_depth:
340+
if alpha:
340341
d += (a,)
341342
data.extend(d)
342343
return data
343344

344345

345346
class BLP1Decoder(_BLPBaseDecoder):
346347
def _load(self) -> None:
347-
if self._blp_compression == Format.JPEG:
348+
self._compression, self._encoding, alpha = self.args
349+
350+
if self._compression == Format.JPEG:
348351
self._decode_jpeg_stream()
349352

350-
elif self._blp_compression == 1:
351-
if self._blp_encoding in (4, 5):
353+
elif self._compression == 1:
354+
if self._encoding in (4, 5):
352355
palette = self._read_palette()
353-
data = self._read_bgra(palette)
356+
data = self._read_bgra(palette, alpha)
354357
self.set_as_raw(data)
355358
else:
356-
msg = f"Unsupported BLP encoding {repr(self._blp_encoding)}"
359+
msg = f"Unsupported BLP encoding {repr(self._encoding)}"
357360
raise BLPFormatError(msg)
358361
else:
359-
msg = f"Unsupported BLP compression {repr(self._blp_encoding)}"
362+
msg = f"Unsupported BLP compression {repr(self._encoding)}"
360363
raise BLPFormatError(msg)
361364

362365
def _decode_jpeg_stream(self) -> None:
@@ -365,8 +368,8 @@ def _decode_jpeg_stream(self) -> None:
365368
(jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
366369
jpeg_header = self._safe_read(jpeg_header_size)
367370
assert self.fd is not None
368-
self._safe_read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
369-
data = self._safe_read(self._blp_lengths[0])
371+
self._safe_read(self._offsets[0] - self.fd.tell()) # What IS this?
372+
data = self._safe_read(self._lengths[0])
370373
data = jpeg_header + data
371374
image = JpegImageFile(BytesIO(data))
372375
Image._decompression_bomb_check(image.size)
@@ -383,47 +386,47 @@ def _decode_jpeg_stream(self) -> None:
383386

384387
class BLP2Decoder(_BLPBaseDecoder):
385388
def _load(self) -> None:
389+
self._compression, self._encoding, alpha, self._alpha_encoding = self.args
390+
386391
palette = self._read_palette()
387392

388393
assert self.fd is not None
389-
self.fd.seek(self._blp_offsets[0])
394+
self.fd.seek(self._offsets[0])
390395

391-
if self._blp_compression == 1:
396+
if self._compression == 1:
392397
# Uncompressed or DirectX compression
393398

394-
if self._blp_encoding == Encoding.UNCOMPRESSED:
395-
data = self._read_bgra(palette)
399+
if self._encoding == Encoding.UNCOMPRESSED:
400+
data = self._read_bgra(palette, alpha)
396401

397-
elif self._blp_encoding == Encoding.DXT:
402+
elif self._encoding == Encoding.DXT:
398403
data = bytearray()
399-
if self._blp_alpha_encoding == AlphaEncoding.DXT1:
400-
linesize = (self.size[0] + 3) // 4 * 8
401-
for yb in range((self.size[1] + 3) // 4):
402-
for d in decode_dxt1(
403-
self._safe_read(linesize), alpha=bool(self._blp_alpha_depth)
404-
):
404+
if self._alpha_encoding == AlphaEncoding.DXT1:
405+
linesize = (self.state.xsize + 3) // 4 * 8
406+
for yb in range((self.state.ysize + 3) // 4):
407+
for d in decode_dxt1(self._safe_read(linesize), alpha):
405408
data += d
406409

407-
elif self._blp_alpha_encoding == AlphaEncoding.DXT3:
408-
linesize = (self.size[0] + 3) // 4 * 16
409-
for yb in range((self.size[1] + 3) // 4):
410+
elif self._alpha_encoding == AlphaEncoding.DXT3:
411+
linesize = (self.state.xsize + 3) // 4 * 16
412+
for yb in range((self.state.ysize + 3) // 4):
410413
for d in decode_dxt3(self._safe_read(linesize)):
411414
data += d
412415

413-
elif self._blp_alpha_encoding == AlphaEncoding.DXT5:
414-
linesize = (self.size[0] + 3) // 4 * 16
415-
for yb in range((self.size[1] + 3) // 4):
416+
elif self._alpha_encoding == AlphaEncoding.DXT5:
417+
linesize = (self.state.xsize + 3) // 4 * 16
418+
for yb in range((self.state.ysize + 3) // 4):
416419
for d in decode_dxt5(self._safe_read(linesize)):
417420
data += d
418421
else:
419-
msg = f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
422+
msg = f"Unsupported alpha encoding {repr(self._alpha_encoding)}"
420423
raise BLPFormatError(msg)
421424
else:
422-
msg = f"Unknown BLP encoding {repr(self._blp_encoding)}"
425+
msg = f"Unknown BLP encoding {repr(self._encoding)}"
423426
raise BLPFormatError(msg)
424427

425428
else:
426-
msg = f"Unknown BLP compression {repr(self._blp_compression)}"
429+
msg = f"Unknown BLP compression {repr(self._compression)}"
427430
raise BLPFormatError(msg)
428431

429432
self.set_as_raw(data)
@@ -472,10 +475,15 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
472475

473476
assert im.palette is not None
474477
fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
475-
fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
476-
fp.write(struct.pack("<b", 1 if im.palette.mode == "RGBA" else 0))
477-
fp.write(struct.pack("<b", 0)) # alpha encoding
478-
fp.write(struct.pack("<b", 0)) # mips
478+
479+
alpha_depth = 1 if im.palette.mode == "RGBA" else 0
480+
if magic == b"BLP1":
481+
fp.write(struct.pack("<L", alpha_depth))
482+
else:
483+
fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
484+
fp.write(struct.pack("<b", alpha_depth))
485+
fp.write(struct.pack("<b", 0)) # alpha encoding
486+
fp.write(struct.pack("<b", 0)) # mips
479487
fp.write(struct.pack("<II", *im.size))
480488
if magic == b"BLP1":
481489
fp.write(struct.pack("<i", 5))

0 commit comments

Comments
 (0)