mirror of
https://github.com/python-pillow/Pillow.git
synced 2025-03-03 11:35:52 +03:00
Merge pull request #6119 from radarhere/ppm_maxval
This commit is contained in:
commit
7857a1dff0
|
@ -13,16 +13,53 @@ TEST_FILE = "Tests/images/hopper.ppm"
|
||||||
|
|
||||||
def test_sanity():
|
def test_sanity():
|
||||||
with Image.open(TEST_FILE) as im:
|
with Image.open(TEST_FILE) as im:
|
||||||
im.load()
|
|
||||||
assert im.mode == "RGB"
|
assert im.mode == "RGB"
|
||||||
assert im.size == (128, 128)
|
assert im.size == (128, 128)
|
||||||
assert im.format, "PPM"
|
assert im.format == "PPM"
|
||||||
assert im.get_format_mimetype() == "image/x-portable-pixmap"
|
assert im.get_format_mimetype() == "image/x-portable-pixmap"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"data, mode, pixels",
|
||||||
|
(
|
||||||
|
(b"P5 3 1 4 \x00\x02\x04", "L", (0, 128, 255)),
|
||||||
|
(b"P5 3 1 257 \x00\x00\x00\x80\x01\x01", "I", (0, 32640, 65535)),
|
||||||
|
# P6 with maxval < 255
|
||||||
|
(
|
||||||
|
b"P6 3 1 17 \x00\x01\x02\x08\x09\x0A\x0F\x10\x11",
|
||||||
|
"RGB",
|
||||||
|
(
|
||||||
|
(0, 15, 30),
|
||||||
|
(120, 135, 150),
|
||||||
|
(225, 240, 255),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
# P6 with maxval > 255
|
||||||
|
# Scale down to 255, since there is no RGB mode with more than 8-bit
|
||||||
|
(
|
||||||
|
b"P6 3 1 257 \x00\x00\x00\x01\x00\x02"
|
||||||
|
b"\x00\x80\x00\x81\x00\x82\x01\x00\x01\x01\xFF\xFF",
|
||||||
|
"RGB",
|
||||||
|
(
|
||||||
|
(0, 1, 2),
|
||||||
|
(127, 128, 129),
|
||||||
|
(254, 255, 255),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_arbitrary_maxval(data, mode, pixels):
|
||||||
|
fp = BytesIO(data)
|
||||||
|
with Image.open(fp) as im:
|
||||||
|
assert im.size == (3, 1)
|
||||||
|
assert im.mode == mode
|
||||||
|
|
||||||
|
px = im.load()
|
||||||
|
assert tuple(px[x, 0] for x in range(3)) == pixels
|
||||||
|
|
||||||
|
|
||||||
def test_16bit_pgm():
|
def test_16bit_pgm():
|
||||||
with Image.open("Tests/images/16_bit_binary.pgm") as im:
|
with Image.open("Tests/images/16_bit_binary.pgm") as im:
|
||||||
im.load()
|
|
||||||
assert im.mode == "I"
|
assert im.mode == "I"
|
||||||
assert im.size == (20, 100)
|
assert im.size == (20, 100)
|
||||||
assert im.get_format_mimetype() == "image/x-portable-graymap"
|
assert im.get_format_mimetype() == "image/x-portable-graymap"
|
||||||
|
@ -32,8 +69,6 @@ def test_16bit_pgm():
|
||||||
|
|
||||||
def test_16bit_pgm_write(tmp_path):
|
def test_16bit_pgm_write(tmp_path):
|
||||||
with Image.open("Tests/images/16_bit_binary.pgm") as im:
|
with Image.open("Tests/images/16_bit_binary.pgm") as im:
|
||||||
im.load()
|
|
||||||
|
|
||||||
f = str(tmp_path / "temp.pgm")
|
f = str(tmp_path / "temp.pgm")
|
||||||
im.save(f, "PPM")
|
im.save(f, "PPM")
|
||||||
|
|
||||||
|
@ -91,19 +126,8 @@ def test_token_too_long(tmp_path):
|
||||||
assert str(e.value) == "Token too long in file header: b'01234567890'"
|
assert str(e.value) == "Token too long in file header: b'01234567890'"
|
||||||
|
|
||||||
|
|
||||||
def test_too_many_colors(tmp_path):
|
|
||||||
path = str(tmp_path / "temp.ppm")
|
|
||||||
with open(path, "wb") as f:
|
|
||||||
f.write(b"P6\n1 1\n1000\n")
|
|
||||||
|
|
||||||
with pytest.raises(ValueError) as e:
|
|
||||||
with Image.open(path):
|
|
||||||
pass
|
|
||||||
|
|
||||||
assert str(e.value) == "Too many colors for band: 1000"
|
|
||||||
|
|
||||||
|
|
||||||
def test_truncated_file(tmp_path):
|
def test_truncated_file(tmp_path):
|
||||||
|
# Test EOF in header
|
||||||
path = str(tmp_path / "temp.pgm")
|
path = str(tmp_path / "temp.pgm")
|
||||||
with open(path, "w") as f:
|
with open(path, "w") as f:
|
||||||
f.write("P6")
|
f.write("P6")
|
||||||
|
@ -114,6 +138,12 @@ def test_truncated_file(tmp_path):
|
||||||
|
|
||||||
assert str(e.value) == "Reached EOF while reading header"
|
assert str(e.value) == "Reached EOF while reading header"
|
||||||
|
|
||||||
|
# Test EOF for PyDecoder
|
||||||
|
fp = BytesIO(b"P5 3 1 4")
|
||||||
|
with Image.open(fp) as im:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
im.load()
|
||||||
|
|
||||||
|
|
||||||
def test_neg_ppm():
|
def test_neg_ppm():
|
||||||
# Storage.c accepted negative values for xsize, ysize. the
|
# Storage.c accepted negative values for xsize, ysize. the
|
||||||
|
|
|
@ -743,7 +743,7 @@ parameter must be set to ``True``. The following parameters can also be set:
|
||||||
PPM
|
PPM
|
||||||
^^^
|
^^^
|
||||||
|
|
||||||
Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L`` or
|
Pillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L``, ``I`` or
|
||||||
``RGB`` data.
|
``RGB`` data.
|
||||||
|
|
||||||
SGI
|
SGI
|
||||||
|
|
|
@ -16,6 +16,9 @@
|
||||||
|
|
||||||
|
|
||||||
from . import Image, ImageFile
|
from . import Image, ImageFile
|
||||||
|
from ._binary import i16be as i16
|
||||||
|
from ._binary import o8
|
||||||
|
from ._binary import o32le as o32
|
||||||
|
|
||||||
#
|
#
|
||||||
# --------------------------------------------------------------------
|
# --------------------------------------------------------------------
|
||||||
|
@ -102,6 +105,7 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
else:
|
else:
|
||||||
self.mode = rawmode = mode
|
self.mode = rawmode = mode
|
||||||
|
|
||||||
|
decoder_name = "raw"
|
||||||
for ix in range(3):
|
for ix in range(3):
|
||||||
token = int(self._read_token())
|
token = int(self._read_token())
|
||||||
if ix == 0: # token is the x size
|
if ix == 0: # token is the x size
|
||||||
|
@ -112,18 +116,44 @@ class PpmImageFile(ImageFile.ImageFile):
|
||||||
break
|
break
|
||||||
elif ix == 2: # token is maxval
|
elif ix == 2: # token is maxval
|
||||||
maxval = token
|
maxval = token
|
||||||
if maxval > 255:
|
if maxval > 255 and mode == "L":
|
||||||
if not mode == "L":
|
self.mode = "I"
|
||||||
raise ValueError(f"Too many colors for band: {token}")
|
|
||||||
if maxval < 2**16:
|
# If maxval matches a bit depth, use the raw decoder directly
|
||||||
self.mode = "I"
|
if maxval == 65535 and mode == "L":
|
||||||
rawmode = "I;16B"
|
rawmode = "I;16B"
|
||||||
else:
|
elif maxval != 255:
|
||||||
self.mode = "I"
|
decoder_name = "ppm"
|
||||||
rawmode = "I;32B"
|
args = (rawmode, 0, 1) if decoder_name == "raw" else (rawmode, maxval)
|
||||||
|
|
||||||
self._size = xsize, ysize
|
self._size = xsize, ysize
|
||||||
self.tile = [("raw", (0, 0, xsize, ysize), self.fp.tell(), (rawmode, 0, 1))]
|
self.tile = [(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), args)]
|
||||||
|
|
||||||
|
|
||||||
|
class PpmDecoder(ImageFile.PyDecoder):
|
||||||
|
_pulls_fd = True
|
||||||
|
|
||||||
|
def decode(self, buffer):
|
||||||
|
data = bytearray()
|
||||||
|
maxval = min(self.args[-1], 65535)
|
||||||
|
in_byte_count = 1 if maxval < 256 else 2
|
||||||
|
out_byte_count = 4 if self.mode == "I" else 1
|
||||||
|
out_max = 65535 if self.mode == "I" else 255
|
||||||
|
bands = Image.getmodebands(self.mode)
|
||||||
|
while len(data) < self.state.xsize * self.state.ysize * bands * out_byte_count:
|
||||||
|
pixels = self.fd.read(in_byte_count * bands)
|
||||||
|
if len(pixels) < in_byte_count * bands:
|
||||||
|
# eof
|
||||||
|
break
|
||||||
|
for b in range(bands):
|
||||||
|
value = (
|
||||||
|
pixels[b] if in_byte_count == 1 else i16(pixels, b * in_byte_count)
|
||||||
|
)
|
||||||
|
value = min(out_max, round(value / maxval * out_max))
|
||||||
|
data += o32(value) if self.mode == "I" else o8(value)
|
||||||
|
rawmode = "I;32" if self.mode == "I" else self.mode
|
||||||
|
self.set_as_raw(bytes(data), (rawmode, 0, 1))
|
||||||
|
return -1, 0
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
|
@ -136,26 +166,19 @@ def _save(im, fp, filename):
|
||||||
elif im.mode == "L":
|
elif im.mode == "L":
|
||||||
rawmode, head = "L", b"P5"
|
rawmode, head = "L", b"P5"
|
||||||
elif im.mode == "I":
|
elif im.mode == "I":
|
||||||
if im.getextrema()[1] < 2**16:
|
rawmode, head = "I;16B", b"P5"
|
||||||
rawmode, head = "I;16B", b"P5"
|
elif im.mode in ("RGB", "RGBA"):
|
||||||
else:
|
|
||||||
rawmode, head = "I;32B", b"P5"
|
|
||||||
elif im.mode == "RGB":
|
|
||||||
rawmode, head = "RGB", b"P6"
|
|
||||||
elif im.mode == "RGBA":
|
|
||||||
rawmode, head = "RGB", b"P6"
|
rawmode, head = "RGB", b"P6"
|
||||||
else:
|
else:
|
||||||
raise OSError(f"cannot write mode {im.mode} as PPM")
|
raise OSError(f"cannot write mode {im.mode} as PPM")
|
||||||
fp.write(head + ("\n%d %d\n" % im.size).encode("ascii"))
|
fp.write(head + b"\n%d %d\n" % im.size)
|
||||||
if head == b"P6":
|
if head == b"P6":
|
||||||
fp.write(b"255\n")
|
fp.write(b"255\n")
|
||||||
if head == b"P5":
|
elif head == b"P5":
|
||||||
if rawmode == "L":
|
if rawmode == "L":
|
||||||
fp.write(b"255\n")
|
fp.write(b"255\n")
|
||||||
elif rawmode == "I;16B":
|
else:
|
||||||
fp.write(b"65535\n")
|
fp.write(b"65535\n")
|
||||||
elif rawmode == "I;32B":
|
|
||||||
fp.write(b"2147483648\n")
|
|
||||||
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
|
ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])
|
||||||
|
|
||||||
# ALTERNATIVE: save via builtin debug function
|
# ALTERNATIVE: save via builtin debug function
|
||||||
|
@ -169,6 +192,8 @@ def _save(im, fp, filename):
|
||||||
Image.register_open(PpmImageFile.format, PpmImageFile, _accept)
|
Image.register_open(PpmImageFile.format, PpmImageFile, _accept)
|
||||||
Image.register_save(PpmImageFile.format, _save)
|
Image.register_save(PpmImageFile.format, _save)
|
||||||
|
|
||||||
|
Image.register_decoder("ppm", PpmDecoder)
|
||||||
|
|
||||||
Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm", ".pnm"])
|
Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm", ".pnm"])
|
||||||
|
|
||||||
Image.register_mime(PpmImageFile.format, "image/x-portable-anymap")
|
Image.register_mime(PpmImageFile.format, "image/x-portable-anymap")
|
||||||
|
|
Loading…
Reference in New Issue
Block a user