mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-01-26 17:24:31 +03:00
Merge pull request #8651 from radarhere/blp
Corrected BLP1 alpha depth handling
This commit is contained in:
commit
dfb368a7e9
|
@ -4,7 +4,7 @@ from pathlib import Path
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from PIL import Image
|
from PIL import BlpImagePlugin, Image
|
||||||
|
|
||||||
from .helper import (
|
from .helper import (
|
||||||
assert_image_equal,
|
assert_image_equal,
|
||||||
|
@ -19,6 +19,7 @@ def test_load_blp1() -> None:
|
||||||
assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")
|
assert_image_equal_tofile(im, "Tests/images/blp/blp1_jpeg.png")
|
||||||
|
|
||||||
with Image.open("Tests/images/blp/blp1_jpeg2.blp") as im:
|
with Image.open("Tests/images/blp/blp1_jpeg2.blp") as im:
|
||||||
|
assert im.mode == "RGBA"
|
||||||
im.load()
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,6 +38,13 @@ def test_load_blp2_dxt1a() -> None:
|
||||||
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
|
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_file() -> None:
|
||||||
|
invalid_file = "Tests/images/flower.jpg"
|
||||||
|
|
||||||
|
with pytest.raises(BlpImagePlugin.BLPFormatError):
|
||||||
|
BlpImagePlugin.BlpImageFile(invalid_file)
|
||||||
|
|
||||||
|
|
||||||
def test_save(tmp_path: Path) -> None:
|
def test_save(tmp_path: Path) -> None:
|
||||||
f = str(tmp_path / "temp.blp")
|
f = str(tmp_path / "temp.blp")
|
||||||
|
|
||||||
|
|
|
@ -259,21 +259,36 @@ class BlpImageFile(ImageFile.ImageFile):
|
||||||
|
|
||||||
def _open(self) -> None:
|
def _open(self) -> None:
|
||||||
self.magic = self.fp.read(4)
|
self.magic = self.fp.read(4)
|
||||||
|
if not _accept(self.magic):
|
||||||
self.fp.seek(5, os.SEEK_CUR)
|
|
||||||
(self._blp_alpha_depth,) = struct.unpack("<b", self.fp.read(1))
|
|
||||||
|
|
||||||
self.fp.seek(2, os.SEEK_CUR)
|
|
||||||
self._size = struct.unpack("<II", self.fp.read(8))
|
|
||||||
|
|
||||||
if self.magic in (b"BLP1", b"BLP2"):
|
|
||||||
decoder = self.magic.decode()
|
|
||||||
else:
|
|
||||||
msg = f"Bad BLP magic {repr(self.magic)}"
|
msg = f"Bad BLP magic {repr(self.magic)}"
|
||||||
raise BLPFormatError(msg)
|
raise BLPFormatError(msg)
|
||||||
|
|
||||||
self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
|
compression = struct.unpack("<i", self.fp.read(4))[0]
|
||||||
self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, 0, self.mode)]
|
if self.magic == b"BLP1":
|
||||||
|
alpha = struct.unpack("<I", self.fp.read(4))[0] != 0
|
||||||
|
else:
|
||||||
|
encoding = struct.unpack("<b", self.fp.read(1))[0]
|
||||||
|
alpha = struct.unpack("<b", self.fp.read(1))[0] != 0
|
||||||
|
alpha_encoding = struct.unpack("<b", self.fp.read(1))[0]
|
||||||
|
self.fp.seek(1, os.SEEK_CUR) # mips
|
||||||
|
|
||||||
|
self._size = struct.unpack("<II", self.fp.read(8))
|
||||||
|
|
||||||
|
args: tuple[int, int, bool] | tuple[int, int, bool, int]
|
||||||
|
if self.magic == b"BLP1":
|
||||||
|
encoding = struct.unpack("<i", self.fp.read(4))[0]
|
||||||
|
self.fp.seek(4, os.SEEK_CUR) # subtype
|
||||||
|
|
||||||
|
args = (compression, encoding, alpha)
|
||||||
|
offset = 28
|
||||||
|
else:
|
||||||
|
args = (compression, encoding, alpha, alpha_encoding)
|
||||||
|
offset = 20
|
||||||
|
|
||||||
|
decoder = self.magic.decode()
|
||||||
|
|
||||||
|
self._mode = "RGBA" if alpha else "RGB"
|
||||||
|
self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, offset, args)]
|
||||||
|
|
||||||
|
|
||||||
class _BLPBaseDecoder(ImageFile.PyDecoder):
|
class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
|
@ -281,7 +296,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
|
|
||||||
def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
|
def decode(self, buffer: bytes | Image.SupportsArrayInterface) -> tuple[int, int]:
|
||||||
try:
|
try:
|
||||||
self._read_blp_header()
|
self._read_header()
|
||||||
self._load()
|
self._load()
|
||||||
except struct.error as e:
|
except struct.error as e:
|
||||||
msg = "Truncated BLP file"
|
msg = "Truncated BLP file"
|
||||||
|
@ -292,25 +307,9 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
def _load(self) -> None:
|
def _load(self) -> None:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def _read_blp_header(self) -> None:
|
def _read_header(self) -> None:
|
||||||
assert self.fd is not None
|
self._offsets = struct.unpack("<16I", self._safe_read(16 * 4))
|
||||||
self.fd.seek(4)
|
self._lengths = struct.unpack("<16I", self._safe_read(16 * 4))
|
||||||
(self._blp_compression,) = struct.unpack("<i", self._safe_read(4))
|
|
||||||
|
|
||||||
(self._blp_encoding,) = struct.unpack("<b", self._safe_read(1))
|
|
||||||
(self._blp_alpha_depth,) = struct.unpack("<b", self._safe_read(1))
|
|
||||||
(self._blp_alpha_encoding,) = struct.unpack("<b", self._safe_read(1))
|
|
||||||
self.fd.seek(1, os.SEEK_CUR) # mips
|
|
||||||
|
|
||||||
self.size = struct.unpack("<II", self._safe_read(8))
|
|
||||||
|
|
||||||
if isinstance(self, BLP1Decoder):
|
|
||||||
# Only present for BLP1
|
|
||||||
(self._blp_encoding,) = struct.unpack("<i", self._safe_read(4))
|
|
||||||
self.fd.seek(4, os.SEEK_CUR) # subtype
|
|
||||||
|
|
||||||
self._blp_offsets = struct.unpack("<16I", self._safe_read(16 * 4))
|
|
||||||
self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4))
|
|
||||||
|
|
||||||
def _safe_read(self, length: int) -> bytes:
|
def _safe_read(self, length: int) -> bytes:
|
||||||
assert self.fd is not None
|
assert self.fd is not None
|
||||||
|
@ -326,9 +325,11 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
ret.append((b, g, r, a))
|
ret.append((b, g, r, a))
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _read_bgra(self, palette: list[tuple[int, int, int, int]]) -> bytearray:
|
def _read_bgra(
|
||||||
|
self, palette: list[tuple[int, int, int, int]], alpha: bool
|
||||||
|
) -> bytearray:
|
||||||
data = bytearray()
|
data = bytearray()
|
||||||
_data = BytesIO(self._safe_read(self._blp_lengths[0]))
|
_data = BytesIO(self._safe_read(self._lengths[0]))
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
(offset,) = struct.unpack("<B", _data.read(1))
|
(offset,) = struct.unpack("<B", _data.read(1))
|
||||||
|
@ -336,7 +337,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
break
|
break
|
||||||
b, g, r, a = palette[offset]
|
b, g, r, a = palette[offset]
|
||||||
d: tuple[int, ...] = (r, g, b)
|
d: tuple[int, ...] = (r, g, b)
|
||||||
if self._blp_alpha_depth:
|
if alpha:
|
||||||
d += (a,)
|
d += (a,)
|
||||||
data.extend(d)
|
data.extend(d)
|
||||||
return data
|
return data
|
||||||
|
@ -344,19 +345,21 @@ class _BLPBaseDecoder(ImageFile.PyDecoder):
|
||||||
|
|
||||||
class BLP1Decoder(_BLPBaseDecoder):
|
class BLP1Decoder(_BLPBaseDecoder):
|
||||||
def _load(self) -> None:
|
def _load(self) -> None:
|
||||||
if self._blp_compression == Format.JPEG:
|
self._compression, self._encoding, alpha = self.args
|
||||||
|
|
||||||
|
if self._compression == Format.JPEG:
|
||||||
self._decode_jpeg_stream()
|
self._decode_jpeg_stream()
|
||||||
|
|
||||||
elif self._blp_compression == 1:
|
elif self._compression == 1:
|
||||||
if self._blp_encoding in (4, 5):
|
if self._encoding in (4, 5):
|
||||||
palette = self._read_palette()
|
palette = self._read_palette()
|
||||||
data = self._read_bgra(palette)
|
data = self._read_bgra(palette, alpha)
|
||||||
self.set_as_raw(data)
|
self.set_as_raw(data)
|
||||||
else:
|
else:
|
||||||
msg = f"Unsupported BLP encoding {repr(self._blp_encoding)}"
|
msg = f"Unsupported BLP encoding {repr(self._encoding)}"
|
||||||
raise BLPFormatError(msg)
|
raise BLPFormatError(msg)
|
||||||
else:
|
else:
|
||||||
msg = f"Unsupported BLP compression {repr(self._blp_encoding)}"
|
msg = f"Unsupported BLP compression {repr(self._encoding)}"
|
||||||
raise BLPFormatError(msg)
|
raise BLPFormatError(msg)
|
||||||
|
|
||||||
def _decode_jpeg_stream(self) -> None:
|
def _decode_jpeg_stream(self) -> None:
|
||||||
|
@ -365,8 +368,8 @@ class BLP1Decoder(_BLPBaseDecoder):
|
||||||
(jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
|
(jpeg_header_size,) = struct.unpack("<I", self._safe_read(4))
|
||||||
jpeg_header = self._safe_read(jpeg_header_size)
|
jpeg_header = self._safe_read(jpeg_header_size)
|
||||||
assert self.fd is not None
|
assert self.fd is not None
|
||||||
self._safe_read(self._blp_offsets[0] - self.fd.tell()) # What IS this?
|
self._safe_read(self._offsets[0] - self.fd.tell()) # What IS this?
|
||||||
data = self._safe_read(self._blp_lengths[0])
|
data = self._safe_read(self._lengths[0])
|
||||||
data = jpeg_header + data
|
data = jpeg_header + data
|
||||||
image = JpegImageFile(BytesIO(data))
|
image = JpegImageFile(BytesIO(data))
|
||||||
Image._decompression_bomb_check(image.size)
|
Image._decompression_bomb_check(image.size)
|
||||||
|
@ -383,47 +386,47 @@ class BLP1Decoder(_BLPBaseDecoder):
|
||||||
|
|
||||||
class BLP2Decoder(_BLPBaseDecoder):
|
class BLP2Decoder(_BLPBaseDecoder):
|
||||||
def _load(self) -> None:
|
def _load(self) -> None:
|
||||||
|
self._compression, self._encoding, alpha, self._alpha_encoding = self.args
|
||||||
|
|
||||||
palette = self._read_palette()
|
palette = self._read_palette()
|
||||||
|
|
||||||
assert self.fd is not None
|
assert self.fd is not None
|
||||||
self.fd.seek(self._blp_offsets[0])
|
self.fd.seek(self._offsets[0])
|
||||||
|
|
||||||
if self._blp_compression == 1:
|
if self._compression == 1:
|
||||||
# Uncompressed or DirectX compression
|
# Uncompressed or DirectX compression
|
||||||
|
|
||||||
if self._blp_encoding == Encoding.UNCOMPRESSED:
|
if self._encoding == Encoding.UNCOMPRESSED:
|
||||||
data = self._read_bgra(palette)
|
data = self._read_bgra(palette, alpha)
|
||||||
|
|
||||||
elif self._blp_encoding == Encoding.DXT:
|
elif self._encoding == Encoding.DXT:
|
||||||
data = bytearray()
|
data = bytearray()
|
||||||
if self._blp_alpha_encoding == AlphaEncoding.DXT1:
|
if self._alpha_encoding == AlphaEncoding.DXT1:
|
||||||
linesize = (self.size[0] + 3) // 4 * 8
|
linesize = (self.state.xsize + 3) // 4 * 8
|
||||||
for yb in range((self.size[1] + 3) // 4):
|
for yb in range((self.state.ysize + 3) // 4):
|
||||||
for d in decode_dxt1(
|
for d in decode_dxt1(self._safe_read(linesize), alpha):
|
||||||
self._safe_read(linesize), alpha=bool(self._blp_alpha_depth)
|
|
||||||
):
|
|
||||||
data += d
|
data += d
|
||||||
|
|
||||||
elif self._blp_alpha_encoding == AlphaEncoding.DXT3:
|
elif self._alpha_encoding == AlphaEncoding.DXT3:
|
||||||
linesize = (self.size[0] + 3) // 4 * 16
|
linesize = (self.state.xsize + 3) // 4 * 16
|
||||||
for yb in range((self.size[1] + 3) // 4):
|
for yb in range((self.state.ysize + 3) // 4):
|
||||||
for d in decode_dxt3(self._safe_read(linesize)):
|
for d in decode_dxt3(self._safe_read(linesize)):
|
||||||
data += d
|
data += d
|
||||||
|
|
||||||
elif self._blp_alpha_encoding == AlphaEncoding.DXT5:
|
elif self._alpha_encoding == AlphaEncoding.DXT5:
|
||||||
linesize = (self.size[0] + 3) // 4 * 16
|
linesize = (self.state.xsize + 3) // 4 * 16
|
||||||
for yb in range((self.size[1] + 3) // 4):
|
for yb in range((self.state.ysize + 3) // 4):
|
||||||
for d in decode_dxt5(self._safe_read(linesize)):
|
for d in decode_dxt5(self._safe_read(linesize)):
|
||||||
data += d
|
data += d
|
||||||
else:
|
else:
|
||||||
msg = f"Unsupported alpha encoding {repr(self._blp_alpha_encoding)}"
|
msg = f"Unsupported alpha encoding {repr(self._alpha_encoding)}"
|
||||||
raise BLPFormatError(msg)
|
raise BLPFormatError(msg)
|
||||||
else:
|
else:
|
||||||
msg = f"Unknown BLP encoding {repr(self._blp_encoding)}"
|
msg = f"Unknown BLP encoding {repr(self._encoding)}"
|
||||||
raise BLPFormatError(msg)
|
raise BLPFormatError(msg)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
msg = f"Unknown BLP compression {repr(self._blp_compression)}"
|
msg = f"Unknown BLP compression {repr(self._compression)}"
|
||||||
raise BLPFormatError(msg)
|
raise BLPFormatError(msg)
|
||||||
|
|
||||||
self.set_as_raw(data)
|
self.set_as_raw(data)
|
||||||
|
@ -472,8 +475,13 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
|
||||||
|
|
||||||
assert im.palette is not None
|
assert im.palette is not None
|
||||||
fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
|
fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
|
||||||
|
|
||||||
|
alpha_depth = 1 if im.palette.mode == "RGBA" else 0
|
||||||
|
if magic == b"BLP1":
|
||||||
|
fp.write(struct.pack("<L", alpha_depth))
|
||||||
|
else:
|
||||||
fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
|
fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
|
||||||
fp.write(struct.pack("<b", 1 if im.palette.mode == "RGBA" else 0))
|
fp.write(struct.pack("<b", alpha_depth))
|
||||||
fp.write(struct.pack("<b", 0)) # alpha encoding
|
fp.write(struct.pack("<b", 0)) # alpha encoding
|
||||||
fp.write(struct.pack("<b", 0)) # mips
|
fp.write(struct.pack("<b", 0)) # mips
|
||||||
fp.write(struct.pack("<II", *im.size))
|
fp.write(struct.pack("<II", *im.size))
|
||||||
|
|
Loading…
Reference in New Issue
Block a user