Skip to content

Commit 169025d

Browse files
committed
Added BLP saving
1 parent 747029b commit 169025d

File tree

2 files changed

+64
-5
lines changed

2 files changed

+64
-5
lines changed

Tests/test_file_blp.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from PIL import BlpImagePlugin, Image
44

5-
from .helper import assert_image_equal_tofile
5+
from .helper import assert_image_equal, assert_image_equal_tofile, hopper
66

77

88
def test_load_blp1():
@@ -25,6 +25,19 @@ def test_load_blp2_dxt1a():
2525
assert_image_equal_tofile(im, "Tests/images/blp/blp2_dxt1a.png")
2626

2727

28+
def test_save(tmp_path):
29+
im = hopper("P")
30+
f = str(tmp_path / "temp.blp")
31+
im.save(f)
32+
33+
with Image.open(f) as reloaded:
34+
assert_image_equal(im.convert("RGB"), reloaded)
35+
36+
im = hopper()
37+
with pytest.raises(ValueError):
38+
im.save(f)
39+
40+
2841
@pytest.mark.parametrize(
2942
"test_file",
3043
[

src/PIL/BlpImagePlugin.py

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,10 @@ class BLPFormatError(NotImplementedError):
267267
pass
268268

269269

270+
def _accept(prefix):
271+
return prefix[:4] in (b"BLP1", b"BLP2")
272+
273+
270274
class BlpImageFile(ImageFile.ImageFile):
271275
"""
272276
Blizzard Mipmap Format
@@ -304,7 +308,7 @@ def decode(self, buffer):
304308
self._read_blp_header()
305309
self._load()
306310
except struct.error as e:
307-
raise OSError("Truncated Blp file") from e
311+
raise OSError("Truncated BLP file") from e
308312
return 0, 0
309313

310314
def _safe_read(self, length):
@@ -439,12 +443,54 @@ def _load(self):
439443
self.set_as_raw(bytes(data))
440444

441445

442-
def _accept(prefix):
443-
return prefix[:4] in (b"BLP1", b"BLP2")
446+
class BLP2Encoder(ImageFile.PyEncoder):
447+
_pushes_fd = True
448+
449+
def _write_palette(self):
450+
data = b""
451+
palette = self.im.getpalette("RGBA", "RGBA")
452+
for i in range(256):
453+
r, g, b, a = palette[i * 4 : (i + 1) * 4]
454+
data += struct.pack("<4B", b, g, r, a)
455+
return data
456+
457+
def encode(self, bufsize):
458+
palette_data = self._write_palette()
459+
460+
offset = 20 + 16 * 4 * 2 + len(palette_data)
461+
data = struct.pack("<16I", offset, *((0,) * 15))
462+
463+
w, h = self.im.size
464+
data += struct.pack("<16I", w * h, *((0,) * 15))
465+
466+
data += palette_data
467+
468+
for y in range(h):
469+
for x in range(w):
470+
data += struct.pack("<B", self.im.getpixel((x, y)))
471+
472+
return len(data), 0, data
473+
474+
475+
def _save(im, fp, filename, save_all=False):
476+
if im.mode != "P":
477+
raise ValueError("Unsupported BLP image mode")
478+
479+
fp.write(b"BLP2")
480+
fp.write(struct.pack("<i", 1)) # Uncompressed or DirectX compression
481+
fp.write(struct.pack("<b", Encoding.UNCOMPRESSED))
482+
fp.write(struct.pack("<b", 1 if im.palette.mode == "RGBA" else 0))
483+
fp.write(struct.pack("<b", 0)) # alpha encoding
484+
fp.write(struct.pack("<b", 0)) # mips
485+
fp.write(struct.pack("<II", *im.size))
486+
487+
ImageFile._save(im, fp, [("BLP2", (0, 0) + im.size, 0, im.mode)])
444488

445489

446490
Image.register_open(BlpImageFile.format, BlpImageFile, _accept)
447491
Image.register_extension(BlpImageFile.format, ".blp")
448-
449492
Image.register_decoder("BLP1", BLP1Decoder)
450493
Image.register_decoder("BLP2", BLP2Decoder)
494+
495+
Image.register_save(BlpImageFile.format, _save)
496+
Image.register_encoder("BLP2", BLP2Encoder)

0 commit comments

Comments
 (0)