Skip to content

Commit fefce6a

Browse files
committed
Scale maxval > 255 for P6 to 255
1 parent 9af736e commit fefce6a

File tree

2 files changed

+34
-24
lines changed

2 files changed

+34
-24
lines changed

Tests/test_file_ppm.py

Lines changed: 22 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,34 @@ def test_arbitrary_maxval():
2929
px = im.load()
3030
assert tuple(px[x, 0] for x in range(3)) == (0, 128, 255)
3131

32-
# P6
32+
# P6 with maxval < 255
3333
fp = BytesIO(b"P6 3 1 17 \x00\x01\x02\x08\x09\x0A\x0F\x10\x11")
3434
with Image.open(fp) as im:
3535
assert im.size == (3, 1)
3636
assert im.mode == "RGB"
3737

3838
px = im.load()
39-
assert tuple(px[x, 0] for x in range(3)) == ((0, 15, 30), (120, 135, 150), (225, 240, 255))
39+
assert tuple(px[x, 0] for x in range(3)) == (
40+
(0, 15, 30),
41+
(120, 135, 150),
42+
(225, 240, 255),
43+
)
44+
45+
# P6 with maxval > 255
46+
fp = BytesIO(
47+
b"P6 3 1 257 \x00\x00\x00\x01\x00\x02"
48+
b"\x00\x80\x00\x81\x00\x82\x01\x00\x01\x01\xFF\xFF"
49+
)
50+
with Image.open(fp) as im:
51+
assert im.size == (3, 1)
52+
assert im.mode == "RGB"
53+
54+
px = im.load()
55+
assert tuple(px[x, 0] for x in range(3)) == (
56+
(0, 1, 2),
57+
(127, 128, 129),
58+
(254, 255, 255),
59+
)
4060

4161

4262
def test_16bit_pgm():
@@ -107,18 +127,6 @@ def test_token_too_long(tmp_path):
107127
assert str(e.value) == "Token too long in file header: b'01234567890'"
108128

109129

110-
def test_too_many_colors(tmp_path):
111-
path = str(tmp_path / "temp.ppm")
112-
with open(path, "wb") as f:
113-
f.write(b"P6\n1 1\n1000\n")
114-
115-
with pytest.raises(ValueError) as e:
116-
with Image.open(path):
117-
pass
118-
119-
assert str(e.value) == "Too many colors for band: 1000"
120-
121-
122130
def test_truncated_file(tmp_path):
123131
# Test EOF in header
124132
path = str(tmp_path / "temp.pgm")

src/PIL/PpmImagePlugin.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717

1818
from . import Image, ImageFile
19-
19+
from ._binary import i16be as i16
2020
from ._binary import o8
2121

2222
#
@@ -115,12 +115,10 @@ def _open(self):
115115
break
116116
elif ix == 2: # token is maxval
117117
maxval = token
118-
if maxval > 255:
119-
if mode != "L":
120-
raise ValueError(f"Too many colors for band: {token}")
118+
if maxval > 255 and mode == "L":
121119
self.mode = "I"
122120
rawmode = "I;16B" if maxval < 2**16 else "I;32B"
123-
elif maxval < 255:
121+
elif maxval != 255:
124122
decoder_name = "ppm"
125123
args = (rawmode, 0, 1) if decoder_name == "raw" else (rawmode, maxval)
126124

@@ -133,15 +131,19 @@ class PpmDecoder(ImageFile.PyDecoder):
133131

134132
def decode(self, buffer):
135133
data = b""
136-
maxval = self.args[-1]
134+
maxval = min(self.args[-1], 65535)
135+
in_byte_count = 1 if maxval < 256 else 2
137136
bands = Image.getmodebands(self.mode)
138137
while len(data) < self.state.xsize * self.state.ysize * bands:
139-
pixels = self.fd.read(bands)
140-
if len(pixels) < bands:
138+
pixels = self.fd.read(in_byte_count * bands)
139+
if len(pixels) < in_byte_count * bands:
141140
# eof
142141
break
143-
for pixel in pixels:
144-
value = min(255, round(pixel / maxval * 255))
142+
for b in range(bands):
143+
value = (
144+
pixels[b] if in_byte_count == 1 else i16(pixels, b * in_byte_count)
145+
)
146+
value = min(255, round(value / maxval * 255))
145147
data += o8(value)
146148
self.set_as_raw(data, (self.mode, 0, 1))
147149
return -1, 0

0 commit comments

Comments
 (0)