Skip to content

Commit

Permalink
Added BLP saving
Browse files Browse the repository at this point in the history
  • Loading branch information
radarhere committed Feb 25, 2022
1 parent 747029b commit 2863296
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 5 deletions.
11 changes: 10 additions & 1 deletion Tests/test_file_blp.py
Expand Up @@ -2,7 +2,7 @@

from PIL import BlpImagePlugin, Image

from .helper import assert_image_equal_tofile
from .helper import assert_image_equal, assert_image_equal_tofile, hopper


def test_load_blp1():
Expand All @@ -25,6 +25,15 @@ def test_load_blp2_dxt1a():
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")


def test_save(tmp_path):
im = hopper("P")
f = str(tmp_path / "temp.blp")
im.save(f)

with Image.open(f) as reloaded:
assert_image_equal(im, reloaded.convert("RGB"))


@pytest.mark.parametrize(
"test_file",
[
Expand Down
54 changes: 50 additions & 4 deletions src/PIL/BlpImagePlugin.py
Expand Up @@ -267,6 +267,10 @@ class BLPFormatError(NotImplementedError):
pass


def _accept(prefix):
return prefix[:4] in (b"BLP1", b"BLP2")


class BlpImageFile(ImageFile.ImageFile):
"""
Blizzard Mipmap Format
Expand Down Expand Up @@ -304,7 +308,7 @@ def decode(self, buffer):
self._read_blp_header()
self._load()
except struct.error as e:
raise OSError("Truncated Blp file") from e
raise OSError("Truncated BLP file") from e
return 0, 0

def _safe_read(self, length):
Expand Down Expand Up @@ -439,12 +443,54 @@ def _load(self):
self.set_as_raw(bytes(data))


def _accept(prefix):
return prefix[:4] in (b"BLP1", b"BLP2")
class BLP2Encoder(ImageFile.PyEncoder):
_pushes_fd = True

def _write_palette(self):
data = b""
palette = self.im.getpalette("RGBA", "RGBA")
for i in range(256):
r, g, b, a = palette[i * 4 : (i + 1) * 4]
data += struct.pack("<4B", b, g, r, a)
return data

def encode(self, bufsize):
palette_data = self._write_palette()

offset = 20 + 16 * 4 * 2 + len(palette_data)
data = struct.pack("<16I", offset, *((0,) * 15))

w, h = self.im.size
data += struct.pack("<16I", w * h, *((0,) * 15))

data += palette_data

for y in range(h):
for x in range(w):
data += struct.pack("<B", self.im.getpixel((x, y)))

return len(data), 0, data


def _save(im, fp, filename, save_all=False):
if im.mode != "P":
raise ValueError("Unsupported BLP image mode")

fp.write(b"BLP2")
fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
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", 0)) # alpha encoding
fp.write(struct.pack("<b", 0)) # mips
fp.write(struct.pack("<II", *im.size))

ImageFile._save(im, fp, [("BLP2", (0, 0) + im.size, 0, im.mode)])


Image.register_open(BlpImageFile.format, BlpImageFile, _accept)
Image.register_extension(BlpImageFile.format, ".blp")

Image.register_decoder("BLP1", BLP1Decoder)
Image.register_decoder("BLP2", BLP2Decoder)

Image.register_save(BlpImageFile.format, _save)
Image.register_encoder("BLP2", BLP2Encoder)

0 comments on commit 2863296

Please sign in to comment.