Skip to content

Commit

Permalink
Merge pull request #5242 from Piolie/plainPPM
Browse files Browse the repository at this point in the history
 Add support for decoding plain PPM formats
  • Loading branch information
radarhere committed Jun 14, 2022
2 parents c083ead + 1bac1cf commit aad3af4
Show file tree
Hide file tree
Showing 10 changed files with 304 additions and 28 deletions.
Binary file added Tests/images/hopper_16bit.pgm
Binary file not shown.
4 changes: 4 additions & 0 deletions Tests/images/hopper_16bit_plain.pgm

Large diffs are not rendered by default.

Binary file added Tests/images/hopper_1bit.pbm
Binary file not shown.
14 changes: 14 additions & 0 deletions Tests/images/hopper_1bit_plain.pbm

Large diffs are not rendered by default.

Binary file added Tests/images/hopper_8bit.pgm
Binary file not shown.
Binary file added Tests/images/hopper_8bit.ppm
Binary file not shown.
4 changes: 4 additions & 0 deletions Tests/images/hopper_8bit_plain.pgm

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Tests/images/hopper_8bit_plain.ppm

Large diffs are not rendered by default.

127 changes: 119 additions & 8 deletions Tests/test_file_ppm.py
Expand Up @@ -3,7 +3,7 @@

import pytest

from PIL import Image, UnidentifiedImageError
from PIL import Image, PpmImagePlugin

from .helper import assert_image_equal_tofile, assert_image_similar, hopper

Expand All @@ -22,6 +22,21 @@ def test_sanity():
@pytest.mark.parametrize(
"data, mode, pixels",
(
(b"P2 3 1 4 0 2 4", "L", (0, 128, 255)),
(b"P2 3 1 257 0 128 257", "I", (0, 32640, 65535)),
# P3 with maxval < 255
(
b"P3 3 1 17 0 1 2 8 9 10 15 16 17",
"RGB",
((0, 15, 30), (120, 135, 150), (225, 240, 255)),
),
# P3 with maxval > 255
# Scale down to 255, since there is no RGB mode with more than 8-bit
(
b"P3 3 1 257 0 1 2 128 129 130 256 257 257",
"RGB",
((0, 1, 2), (127, 128, 129), (254, 255, 255)),
),
(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
Expand All @@ -35,7 +50,6 @@ def test_sanity():
),
),
# 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",
Expand Down Expand Up @@ -85,14 +99,111 @@ def test_pnm(tmp_path):
assert_image_equal_tofile(im, f)


def test_magic(tmp_path):
@pytest.mark.parametrize(
"plain_path, raw_path",
(
(
"Tests/images/hopper_1bit_plain.pbm", # P1
"Tests/images/hopper_1bit.pbm", # P4
),
(
"Tests/images/hopper_8bit_plain.pgm", # P2
"Tests/images/hopper_8bit.pgm", # P5
),
(
"Tests/images/hopper_8bit_plain.ppm", # P3
"Tests/images/hopper_8bit.ppm", # P6
),
),
)
def test_plain(plain_path, raw_path):
with Image.open(plain_path) as im:
assert_image_equal_tofile(im, raw_path)


def test_16bit_plain_pgm():
# P2 with maxval 2 ** 16 - 1
with Image.open("Tests/images/hopper_16bit_plain.pgm") as im:
assert im.mode == "I"
assert im.size == (128, 128)
assert im.get_format_mimetype() == "image/x-portable-graymap"

# P5 with maxval 2 ** 16 - 1
assert_image_equal_tofile(im, "Tests/images/hopper_16bit.pgm")


@pytest.mark.parametrize(
"header, data, comment_count",
(
(b"P1\n2 2", b"1010", 10**6),
(b"P2\n3 1\n4", b"0 2 4", 1),
(b"P3\n2 2\n255", b"0 0 0 001 1 1 2 2 2 255 255 255", 10**6),
),
)
def test_plain_data_with_comment(tmp_path, header, data, comment_count):
path1 = str(tmp_path / "temp1.ppm")
path2 = str(tmp_path / "temp2.ppm")
comment = b"# comment" * comment_count
with open(path1, "wb") as f1, open(path2, "wb") as f2:
f1.write(header + b"\n\n" + data)
f2.write(header + b"\n" + comment + b"\n" + data + comment)

with Image.open(path1) as im:
assert_image_equal_tofile(im, path2)


@pytest.mark.parametrize("data", (b"P1\n128 128\n", b"P3\n128 128\n255\n"))
def test_plain_truncated_data(tmp_path, data):
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(b"PyInvalid")
f.write(data)

with pytest.raises(UnidentifiedImageError):
with Image.open(path):
pass
with Image.open(path) as im:
with pytest.raises(ValueError):
im.load()


@pytest.mark.parametrize("data", (b"P1\n128 128\n1009", b"P3\n128 128\n255\n100A"))
def test_plain_invalid_data(tmp_path, data):
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(data)

with Image.open(path) as im:
with pytest.raises(ValueError):
im.load()


@pytest.mark.parametrize(
"data",
(
b"P3\n128 128\n255\n012345678910", # half token too long
b"P3\n128 128\n255\n012345678910 0", # token too long
),
)
def test_plain_ppm_token_too_long(tmp_path, data):
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(data)

with Image.open(path) as im:
with pytest.raises(ValueError):
im.load()


def test_plain_ppm_value_too_large(tmp_path):
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(b"P3\n128 128\n255\n256")

with Image.open(path) as im:
with pytest.raises(ValueError):
im.load()


def test_magic():
with pytest.raises(SyntaxError):
PpmImagePlugin.PpmImageFile(fp=BytesIO(b"PyInvalid"))


def test_header_with_comments(tmp_path):
Expand All @@ -114,7 +225,7 @@ def test_non_integer_token(tmp_path):
pass


def test_token_too_long(tmp_path):
def test_header_token_too_long(tmp_path):
path = str(tmp_path / "temp.ppm")
with open(path, "wb") as f:
f.write(b"P6\n 01234567890")
Expand Down

0 comments on commit aad3af4

Please sign in to comment.