diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index d5a2c0cb78a..201b7704502 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -29,14 +29,34 @@ def test_arbitrary_maxval(): px = im.load() assert tuple(px[x, 0] for x in range(3)) == (0, 128, 255) - # P6 + # P6 with maxval < 255 fp = BytesIO(b"P6 3 1 17 \x00\x01\x02\x08\x09\x0A\x0F\x10\x11") with Image.open(fp) as im: assert im.size == (3, 1) assert im.mode == "RGB" px = im.load() - assert tuple(px[x, 0] for x in range(3)) == ((0, 15, 30), (120, 135, 150), (225, 240, 255)) + assert tuple(px[x, 0] for x in range(3)) == ( + (0, 15, 30), + (120, 135, 150), + (225, 240, 255), + ) + + # P6 with maxval > 255 + fp = BytesIO( + b"P6 3 1 257 \x00\x00\x00\x01\x00\x02" + b"\x00\x80\x00\x81\x00\x82\x01\x00\x01\x01\xFF\xFF" + ) + with Image.open(fp) as im: + assert im.size == (3, 1) + assert im.mode == "RGB" + + px = im.load() + assert tuple(px[x, 0] for x in range(3)) == ( + (0, 1, 2), + (127, 128, 129), + (254, 255, 255), + ) def test_16bit_pgm(): @@ -107,18 +127,6 @@ def test_token_too_long(tmp_path): 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): # Test EOF in header path = str(tmp_path / "temp.pgm") diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index 82283222b51..915e72f23a9 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -16,7 +16,7 @@ from . import Image, ImageFile - +from ._binary import i16be as i16 from ._binary import o8 # @@ -115,12 +115,10 @@ def _open(self): break elif ix == 2: # token is maxval maxval = token - if maxval > 255: - if mode != "L": - raise ValueError(f"Too many colors for band: {token}") + if maxval > 255 and mode == "L": self.mode = "I" rawmode = "I;16B" if maxval < 2**16 else "I;32B" - elif maxval < 255: + elif maxval != 255: decoder_name = "ppm" args = (rawmode, 0, 1) if decoder_name == "raw" else (rawmode, maxval) @@ -133,15 +131,19 @@ class PpmDecoder(ImageFile.PyDecoder): def decode(self, buffer): data = b"" - maxval = self.args[-1] + maxval = min(self.args[-1], 65535) + in_byte_count = 1 if maxval < 256 else 2 bands = Image.getmodebands(self.mode) while len(data) < self.state.xsize * self.state.ysize * bands: - pixels = self.fd.read(bands) - if len(pixels) < bands: + pixels = self.fd.read(in_byte_count * bands) + if len(pixels) < in_byte_count * bands: # eof break - for pixel in pixels: - value = min(255, round(pixel / maxval * 255)) + for b in range(bands): + value = ( + pixels[b] if in_byte_count == 1 else i16(pixels, b * in_byte_count) + ) + value = min(255, round(value / maxval * 255)) data += o8(value) self.set_as_raw(data, (self.mode, 0, 1)) return -1, 0