{
    "file_diffs": [
        {
            "old_file_content": "import sys\nfrom io import BytesIO\n\nimport pytest\n\nfrom PIL import Image, PpmImagePlugin\n\nfrom .helper import assert_image_equal_tofile, assert_image_similar, hopper\n\n# sample ppm stream\nTEST_FILE = \"Tests/images/hopper.ppm\"\n\n\ndef test_sanity():\n    with Image.open(TEST_FILE) as im:\n        assert im.mode == \"RGB\"\n        assert im.size == (128, 128)\n        assert im.format == \"PPM\"\n        assert im.get_format_mimetype() == \"image/x-portable-pixmap\"\n\n\n@pytest.mark.parametrize(\n    \"data, mode, pixels\",\n    (\n        (b\"P2 3 1 4 0 2 4\", \"L\", (0, 128, 255)),\n        (b\"P2 3 1 257 0 128 257\", \"I\", (0, 32640, 65535)),\n        # P3 with maxval < 255\n        (\n            b\"P3 3 1 17 0 1 2 8 9 10 15 16 17\",\n            \"RGB\",\n            ((0, 15, 30), (120, 135, 150), (225, 240, 255)),\n        ),\n        # P3 with maxval > 255\n        # Scale down to 255, since there is no RGB mode with more than 8-bit\n        (\n            b\"P3 3 1 257 0 1 2 128 129 130 256 257 257\",\n            \"RGB\",\n            ((0, 1, 2), (127, 128, 129), (254, 255, 255)),\n        ),\n        (b\"P5 3 1 4 \\x00\\x02\\x04\", \"L\", (0, 128, 255)),\n        (b\"P5 3 1 257 \\x00\\x00\\x00\\x80\\x01\\x01\", \"I\", (0, 32640, 65535)),\n        # P6 with maxval < 255\n        (\n            b\"P6 3 1 17 \\x00\\x01\\x02\\x08\\x09\\x0A\\x0F\\x10\\x11\",\n            \"RGB\",\n            (\n                (0, 15, 30),\n                (120, 135, 150),\n                (225, 240, 255),\n            ),\n        ),\n        # P6 with maxval > 255\n        (\n            b\"P6 3 1 257 \\x00\\x00\\x00\\x01\\x00\\x02\"\n            b\"\\x00\\x80\\x00\\x81\\x00\\x82\\x01\\x00\\x01\\x01\\xFF\\xFF\",\n            \"RGB\",\n            (\n                (0, 1, 2),\n                (127, 128, 129),\n                (254, 255, 255),\n            ),\n        ),\n    ),\n)\ndef test_arbitrary_maxval(data, mode, pixels):\n    fp = BytesIO(data)\n    with Image.open(fp) as im:\n        assert im.size == (3, 1)\n        assert im.mode == mode\n\n        px = im.load()\n        assert tuple(px[x, 0] for x in range(3)) == pixels\n\n\ndef test_16bit_pgm():\n    with Image.open(\"Tests/images/16_bit_binary.pgm\") as im:\n        assert im.mode == \"I\"\n        assert im.size == (20, 100)\n        assert im.get_format_mimetype() == \"image/x-portable-graymap\"\n\n        assert_image_equal_tofile(im, \"Tests/images/16_bit_binary_pgm.png\")\n\n\ndef test_16bit_pgm_write(tmp_path):\n    with Image.open(\"Tests/images/16_bit_binary.pgm\") as im:\n        f = str(tmp_path / \"temp.pgm\")\n        im.save(f, \"PPM\")\n\n        assert_image_equal_tofile(im, f)\n\n\ndef test_pnm(tmp_path):\n    with Image.open(\"Tests/images/hopper.pnm\") as im:\n        assert_image_similar(im, hopper(), 0.0001)\n\n        f = str(tmp_path / \"temp.pnm\")\n        im.save(f)\n\n        assert_image_equal_tofile(im, f)\n\n\n@pytest.mark.parametrize(\n    \"plain_path, raw_path\",\n    (\n        (\n            \"Tests/images/hopper_1bit_plain.pbm\",  # P1\n            \"Tests/images/hopper_1bit.pbm\",  # P4\n        ),\n        (\n            \"Tests/images/hopper_8bit_plain.pgm\",  # P2\n            \"Tests/images/hopper_8bit.pgm\",  # P5\n        ),\n        (\n            \"Tests/images/hopper_8bit_plain.ppm\",  # P3\n            \"Tests/images/hopper_8bit.ppm\",  # P6\n        ),\n    ),\n)\ndef test_plain(plain_path, raw_path):\n    with Image.open(plain_path) as im:\n        assert_image_equal_tofile(im, raw_path)\n\n\ndef test_16bit_plain_pgm():\n    # P2 with maxval 2 ** 16 - 1\n    with Image.open(\"Tests/images/hopper_16bit_plain.pgm\") as im:\n        assert im.mode == \"I\"\n        assert im.size == (128, 128)\n        assert im.get_format_mimetype() == \"image/x-portable-graymap\"\n\n        # P5 with maxval 2 ** 16 - 1\n        assert_image_equal_tofile(im, \"Tests/images/hopper_16bit.pgm\")\n\n\n@pytest.mark.parametrize(\n    \"header, data, comment_count\",\n    (\n        (b\"P1\\n2 2\", b\"1010\", 10**6),\n        (b\"P2\\n3 1\\n4\", b\"0 2 4\", 1),\n        (b\"P3\\n2 2\\n255\", b\"0 0 0 001 1 1 2 2 2 255 255 255\", 10**6),\n    ),\n)\ndef test_plain_data_with_comment(tmp_path, header, data, comment_count):\n    path1 = str(tmp_path / \"temp1.ppm\")\n    path2 = str(tmp_path / \"temp2.ppm\")\n    comment = b\"# comment\" * comment_count\n    with open(path1, \"wb\") as f1, open(path2, \"wb\") as f2:\n        f1.write(header + b\"\\n\\n\" + data)\n        f2.write(header + b\"\\n\" + comment + b\"\\n\" + data + comment)\n\n    with Image.open(path1) as im:\n        assert_image_equal_tofile(im, path2)\n\n\n@pytest.mark.parametrize(\"data\", (b\"P1\\n128 128\\n\", b\"P3\\n128 128\\n255\\n\"))\ndef test_plain_truncated_data(tmp_path, data):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(data)\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\n@pytest.mark.parametrize(\"data\", (b\"P1\\n128 128\\n1009\", b\"P3\\n128 128\\n255\\n100A\"))\ndef test_plain_invalid_data(tmp_path, data):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(data)\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\n@pytest.mark.parametrize(\n    \"data\",\n    (\n        b\"P3\\n128 128\\n255\\n012345678910\",  # half token too long\n        b\"P3\\n128 128\\n255\\n012345678910 0\",  # token too long\n    ),\n)\ndef test_plain_ppm_token_too_long(tmp_path, data):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(data)\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\ndef test_plain_ppm_value_too_large(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P3\\n128 128\\n255\\n256\")\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\ndef test_magic():\n    with pytest.raises(SyntaxError):\n        PpmImagePlugin.PpmImageFile(fp=BytesIO(b\"PyInvalid\"))\n\n\ndef test_header_with_comments(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6 #comment\\n#comment\\r12#comment\\r8\\n128 #comment\\n255\\n\")\n\n    with Image.open(path) as im:\n        assert im.size == (128, 128)\n\n\ndef test_non_integer_token(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6\\nTEST\")\n\n    with pytest.raises(ValueError):\n        with Image.open(path):\n            pass\n\n\ndef test_header_token_too_long(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6\\n 01234567890\")\n\n    with pytest.raises(ValueError) as e:\n        with Image.open(path):\n            pass\n\n    assert str(e.value) == \"Token too long in file header: 01234567890\"\n\n\ndef test_truncated_file(tmp_path):\n    # Test EOF in header\n    path = str(tmp_path / \"temp.pgm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6\")\n\n    with pytest.raises(ValueError) as e:\n        with Image.open(path):\n            pass\n\n    assert str(e.value) == \"Reached EOF while reading header\"\n\n    # Test EOF for PyDecoder\n    fp = BytesIO(b\"P5 3 1 4\")\n    with Image.open(fp) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\n@pytest.mark.parametrize(\"maxval\", (b\"0\", b\"65536\"))\ndef test_invalid_maxval(maxval, tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6\\n3 1 \" + maxval)\n\n    with pytest.raises(ValueError) as e:\n        with Image.open(path):\n            pass\n\n    assert str(e.value) == \"maxval must be greater than 0 and less than 65536\"\n\n\ndef test_neg_ppm():\n    # Storage.c accepted negative values for xsize, ysize.  the\n    # internal open_ppm function didn't check for sanity but it\n    # has been removed. The default opener doesn't accept negative\n    # sizes.\n\n    with pytest.raises(OSError):\n        with Image.open(\"Tests/images/negative_size.ppm\"):\n            pass\n\n\ndef test_mimetypes(tmp_path):\n    path = str(tmp_path / \"temp.pgm\")\n\n    with open(path, \"wb\") as f:\n        f.write(b\"P4\\n128 128\\n255\")\n    with Image.open(path) as im:\n        assert im.get_format_mimetype() == \"image/x-portable-bitmap\"\n\n    with open(path, \"wb\") as f:\n        f.write(b\"PyCMYK\\n128 128\\n255\")\n    with Image.open(path) as im:\n        assert im.get_format_mimetype() == \"image/x-portable-anymap\"\n\n\n@pytest.mark.parametrize(\"buffer\", (True, False))\ndef test_save_stdout(buffer):\n    old_stdout = sys.stdout\n\n    if buffer:\n\n        class MyStdOut:\n            buffer = BytesIO()\n\n        mystdout = MyStdOut()\n    else:\n        mystdout = BytesIO()\n\n    sys.stdout = mystdout\n\n    with Image.open(TEST_FILE) as im:\n        im.save(sys.stdout, \"PPM\")\n\n    # Reset stdout\n    sys.stdout = old_stdout\n\n    if buffer:\n        mystdout = mystdout.buffer\n    with Image.open(mystdout) as reloaded:\n        assert_image_equal_tofile(reloaded, TEST_FILE)\n",
            "new_file_content": "import sys\nfrom io import BytesIO\n\nimport pytest\n\nfrom PIL import Image, PpmImagePlugin\n\nfrom .helper import assert_image_equal_tofile, assert_image_similar, hopper\n\n# sample ppm stream\nTEST_FILE = \"Tests/images/hopper.ppm\"\n\n\ndef test_sanity():\n    with Image.open(TEST_FILE) as im:\n        assert im.mode == \"RGB\"\n        assert im.size == (128, 128)\n        assert im.format == \"PPM\"\n        assert im.get_format_mimetype() == \"image/x-portable-pixmap\"\n\n\n@pytest.mark.parametrize(\n    \"data, mode, pixels\",\n    (\n        (b\"P2 3 1 4 0 2 4\", \"L\", (0, 128, 255)),\n        (b\"P2 3 1 257 0 128 257\", \"I\", (0, 32640, 65535)),\n        # P3 with maxval < 255\n        (\n            b\"P3 3 1 17 0 1 2 8 9 10 15 16 17\",\n            \"RGB\",\n            ((0, 15, 30), (120, 135, 150), (225, 240, 255)),\n        ),\n        # P3 with maxval > 255\n        # Scale down to 255, since there is no RGB mode with more than 8-bit\n        (\n            b\"P3 3 1 257 0 1 2 128 129 130 256 257 257\",\n            \"RGB\",\n            ((0, 1, 2), (127, 128, 129), (254, 255, 255)),\n        ),\n        (b\"P5 3 1 4 \\x00\\x02\\x04\", \"L\", (0, 128, 255)),\n        (b\"P5 3 1 257 \\x00\\x00\\x00\\x80\\x01\\x01\", \"I\", (0, 32640, 65535)),\n        # P6 with maxval < 255\n        (\n            b\"P6 3 1 17 \\x00\\x01\\x02\\x08\\x09\\x0A\\x0F\\x10\\x11\",\n            \"RGB\",\n            (\n                (0, 15, 30),\n                (120, 135, 150),\n                (225, 240, 255),\n            ),\n        ),\n        # P6 with maxval > 255\n        (\n            b\"P6 3 1 257 \\x00\\x00\\x00\\x01\\x00\\x02\"\n            b\"\\x00\\x80\\x00\\x81\\x00\\x82\\x01\\x00\\x01\\x01\\xFF\\xFF\",\n            \"RGB\",\n            (\n                (0, 1, 2),\n                (127, 128, 129),\n                (254, 255, 255),\n            ),\n        ),\n    ),\n)\ndef test_arbitrary_maxval(data, mode, pixels):\n    fp = BytesIO(data)\n    with Image.open(fp) as im:\n        assert im.size == (3, 1)\n        assert im.mode == mode\n\n        px = im.load()\n        assert tuple(px[x, 0] for x in range(3)) == pixels\n\n\ndef test_16bit_pgm():\n    with Image.open(\"Tests/images/16_bit_binary.pgm\") as im:\n        assert im.mode == \"I\"\n        assert im.size == (20, 100)\n        assert im.get_format_mimetype() == \"image/x-portable-graymap\"\n\n        assert_image_equal_tofile(im, \"Tests/images/16_bit_binary_pgm.png\")\n\n\ndef test_16bit_pgm_write(tmp_path):\n    with Image.open(\"Tests/images/16_bit_binary.pgm\") as im:\n        f = str(tmp_path / \"temp.pgm\")\n        im.save(f, \"PPM\")\n\n        assert_image_equal_tofile(im, f)\n\n\ndef test_pnm(tmp_path):\n    with Image.open(\"Tests/images/hopper.pnm\") as im:\n        assert_image_similar(im, hopper(), 0.0001)\n\n        f = str(tmp_path / \"temp.pnm\")\n        im.save(f)\n\n        assert_image_equal_tofile(im, f)\n\n\n@pytest.mark.parametrize(\n    \"plain_path, raw_path\",\n    (\n        (\n            \"Tests/images/hopper_1bit_plain.pbm\",  # P1\n            \"Tests/images/hopper_1bit.pbm\",  # P4\n        ),\n        (\n            \"Tests/images/hopper_8bit_plain.pgm\",  # P2\n            \"Tests/images/hopper_8bit.pgm\",  # P5\n        ),\n        (\n            \"Tests/images/hopper_8bit_plain.ppm\",  # P3\n            \"Tests/images/hopper_8bit.ppm\",  # P6\n        ),\n    ),\n)\ndef test_plain(plain_path, raw_path):\n    with Image.open(plain_path) as im:\n        assert_image_equal_tofile(im, raw_path)\n\n\ndef test_16bit_plain_pgm():\n    # P2 with maxval 2 ** 16 - 1\n    with Image.open(\"Tests/images/hopper_16bit_plain.pgm\") as im:\n        assert im.mode == \"I\"\n        assert im.size == (128, 128)\n        assert im.get_format_mimetype() == \"image/x-portable-graymap\"\n\n        # P5 with maxval 2 ** 16 - 1\n        assert_image_equal_tofile(im, \"Tests/images/hopper_16bit.pgm\")\n\n\n@pytest.mark.parametrize(\n    \"header, data, comment_count\",\n    (\n        (b\"P1\\n2 2\", b\"1010\", 10**6),\n        (b\"P2\\n3 1\\n4\", b\"0 2 4\", 1),\n        (b\"P3\\n2 2\\n255\", b\"0 0 0 001 1 1 2 2 2 255 255 255\", 10**6),\n    ),\n)\ndef test_plain_data_with_comment(tmp_path, header, data, comment_count):\n    path1 = str(tmp_path / \"temp1.ppm\")\n    path2 = str(tmp_path / \"temp2.ppm\")\n    comment = b\"# comment\" * comment_count\n    with open(path1, \"wb\") as f1, open(path2, \"wb\") as f2:\n        f1.write(header + b\"\\n\\n\" + data)\n        f2.write(header + b\"\\n\" + comment + b\"\\n\" + data + comment)\n\n    with Image.open(path1) as im:\n        assert_image_equal_tofile(im, path2)\n\n\n@pytest.mark.parametrize(\"data\", (b\"P1\\n128 128\\n\", b\"P3\\n128 128\\n255\\n\"))\ndef test_plain_truncated_data(tmp_path, data):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(data)\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\n@pytest.mark.parametrize(\"data\", (b\"P1\\n128 128\\n1009\", b\"P3\\n128 128\\n255\\n100A\"))\ndef test_plain_invalid_data(tmp_path, data):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(data)\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\n@pytest.mark.parametrize(\n    \"data\",\n    (\n        b\"P3\\n128 128\\n255\\n012345678910\",  # half token too long\n        b\"P3\\n128 128\\n255\\n012345678910 0\",  # token too long\n    ),\n)\ndef test_plain_ppm_token_too_long(tmp_path, data):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(data)\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\ndef test_plain_ppm_value_too_large(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P3\\n128 128\\n255\\n256\")\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\ndef test_magic():\n    with pytest.raises(SyntaxError):\n        PpmImagePlugin.PpmImageFile(fp=BytesIO(b\"PyInvalid\"))\n\n\ndef test_header_with_comments(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6 #comment\\n#comment\\r12#comment\\r8\\n128 #comment\\n255\\n\")\n\n    with Image.open(path) as im:\n        assert im.size == (128, 128)\n\n\ndef test_non_integer_token(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6\\nTEST\")\n\n    with pytest.raises(ValueError):\n        with Image.open(path):\n            pass\n\n\ndef test_header_token_too_long(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6\\n 01234567890\")\n\n    with pytest.raises(ValueError) as e:\n        with Image.open(path):\n            pass\n\n    assert str(e.value) == \"Token too long in file header: 01234567890\"\n\n\ndef test_truncated_file(tmp_path):\n    # Test EOF in header\n    path = str(tmp_path / \"temp.pgm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6\")\n\n    with pytest.raises(ValueError) as e:\n        with Image.open(path):\n            pass\n\n    assert str(e.value) == \"Reached EOF while reading header\"\n\n    # Test EOF for PyDecoder\n    fp = BytesIO(b\"P5 3 1 4\")\n    with Image.open(fp) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\ndef test_not_enough_image_data(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P2 1 2 255 255\")\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\n@pytest.mark.parametrize(\"maxval\", (b\"0\", b\"65536\"))\ndef test_invalid_maxval(maxval, tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6\\n3 1 \" + maxval)\n\n    with pytest.raises(ValueError) as e:\n        with Image.open(path):\n            pass\n\n    assert str(e.value) == \"maxval must be greater than 0 and less than 65536\"\n\n\ndef test_neg_ppm():\n    # Storage.c accepted negative values for xsize, ysize.  the\n    # internal open_ppm function didn't check for sanity but it\n    # has been removed. The default opener doesn't accept negative\n    # sizes.\n\n    with pytest.raises(OSError):\n        with Image.open(\"Tests/images/negative_size.ppm\"):\n            pass\n\n\ndef test_mimetypes(tmp_path):\n    path = str(tmp_path / \"temp.pgm\")\n\n    with open(path, \"wb\") as f:\n        f.write(b\"P4\\n128 128\\n255\")\n    with Image.open(path) as im:\n        assert im.get_format_mimetype() == \"image/x-portable-bitmap\"\n\n    with open(path, \"wb\") as f:\n        f.write(b\"PyCMYK\\n128 128\\n255\")\n    with Image.open(path) as im:\n        assert im.get_format_mimetype() == \"image/x-portable-anymap\"\n\n\n@pytest.mark.parametrize(\"buffer\", (True, False))\ndef test_save_stdout(buffer):\n    old_stdout = sys.stdout\n\n    if buffer:\n\n        class MyStdOut:\n            buffer = BytesIO()\n\n        mystdout = MyStdOut()\n    else:\n        mystdout = BytesIO()\n\n    sys.stdout = mystdout\n\n    with Image.open(TEST_FILE) as im:\n        im.save(sys.stdout, \"PPM\")\n\n    # Reset stdout\n    sys.stdout = old_stdout\n\n    if buffer:\n        mystdout = mystdout.buffer\n    with Image.open(mystdout) as reloaded:\n        assert_image_equal_tofile(reloaded, TEST_FILE)\n",
            "header": {
                "file": {
                    "path": "Tests/test_file_ppm.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "fbcbea6c6",
                "new_commit_hash": "292642ca9",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Tests/test_file_ppm.py"
            },
            "plus_file": {
                "path": "b/Tests/test_file_ppm.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 256,
                            "length": 6
                        },
                        "new_range": {
                            "start": 256,
                            "length": 16
                        },
                        "section": "def test_truncated_file(tmp_path):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            im.load()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "def test_not_enough_image_data(tmp_path):",
                                "type": "added"
                            },
                            {
                                "content": "    path = str(tmp_path / \"temp.ppm\")",
                                "type": "added"
                            },
                            {
                                "content": "    with open(path, \"wb\") as f:",
                                "type": "added"
                            },
                            {
                                "content": "        f.write(b\"P2 1 2 255 255\")",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    with Image.open(path) as im:",
                                "type": "added"
                            },
                            {
                                "content": "        with pytest.raises(ValueError):",
                                "type": "added"
                            },
                            {
                                "content": "            im.load()",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "@pytest.mark.parametrize(\"maxval\", (b\"0\", b\"65536\"))",
                                "type": "context"
                            },
                            {
                                "content": "def test_invalid_maxval(maxval, tmp_path):",
                                "type": "context"
                            },
                            {
                                "content": "    path = str(tmp_path / \"temp.ppm\")",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [
                        {
                            "file_name": "Tests/test_file_ppm.py",
                            "type": "function",
                            "name": "test_not_enough_image_data",
                            "content": "def test_not_enough_image_data(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P2 1 2 255 255\")\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 259,
                            "end_lineno": 266,
                            "parent": null
                        }
                    ],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "9.5.0\n-----\n\nBackwards Incompatible Changes\n==============================\n\nTODO\n^^^^\n\nTODO\n\nDeprecations\n============\n\nTODO\n^^^^\n\nTODO\n\nAPI Changes\n===========\n\nTODO\n^^^^\n\nTODO\n\nAPI Additions\n=============\n\nQOI file format\n^^^^^^^^^^^^^^^\n\nPillow can now read images in Quite OK Image format.\n\nAdded ``dpi`` argument when saving PDFs\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen saving a PDF, resolution could already be specified using the\n``resolution`` argument. Now, a tuple of ``(x_resolution, y_resolution)`` can\nbe provided as ``dpi``. If both are provided, ``dpi`` will override\n``resolution``.\n\nAdded ``corners`` argument to ``ImageDraw.rounded_rectangle()``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:py:meth:`.ImageDraw.rounded_rectangle` now accepts a keyword argument of\n``corners``. This a tuple of Booleans, specifying whether to round each corner,\n``(top_left, top_right, bottom_right, bottom_left)``.\n\nJPEG2000 comments and PLT marker\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen opening a JPEG2000 image, the comment may now be read into\n:py:attr:`~PIL.Image.Image.info`. The ``comment`` keyword argument can be used\nto save it back again.\n\nIf OpenJPEG 2.4.0 or later is available and the ``plt`` keyword argument\nis present and true when saving JPEG2000 images, tell the encoder to generate\nPLT markers.\n\nSecurity\n========\n\nTODO\n^^^^\n\nTODO\n\nOther Changes\n=============\n\nAdded support for saving PDFs in RGBA mode\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nUsing the JPXDecode filter, PDFs can now be saved in RGBA mode.\n\nImproved I;16N support\n^^^^^^^^^^^^^^^^^^^^^^\n\nSupport has been added for I;16N access, packing and unpacking. Conversion to\nand from L mode has also been added.\n\nBGR;* modes\n^^^^^^^^^^^\n\nIt is now possible to create new BGR;15, BGR;16 and BGR;24 images. Conversely, BGR;32\nhas been removed from ImageMode and its associated methods, dropping the little support\nPillow had for the mode.\n\nWith that, all modes listed under :ref:`concept-modes` can now be used to create a new\nimage.\n",
            "new_file_content": "9.5.0\n-----\n\nBackwards Incompatible Changes\n==============================\n\nTODO\n^^^^\n\nTODO\n\nDeprecations\n============\n\nTODO\n^^^^\n\nTODO\n\nAPI Changes\n===========\n\nTODO\n^^^^\n\nTODO\n\nAPI Additions\n=============\n\nQOI file format\n^^^^^^^^^^^^^^^\n\nPillow can now read images in Quite OK Image format.\n\nAdded ``dpi`` argument when saving PDFs\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen saving a PDF, resolution could already be specified using the\n``resolution`` argument. Now, a tuple of ``(x_resolution, y_resolution)`` can\nbe provided as ``dpi``. If both are provided, ``dpi`` will override\n``resolution``.\n\nAdded ``corners`` argument to ``ImageDraw.rounded_rectangle()``\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\n:py:meth:`.ImageDraw.rounded_rectangle` now accepts a keyword argument of\n``corners``. This a tuple of Booleans, specifying whether to round each corner,\n``(top_left, top_right, bottom_right, bottom_left)``.\n\nJPEG2000 comments and PLT marker\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nWhen opening a JPEG2000 image, the comment may now be read into\n:py:attr:`~PIL.Image.Image.info`. The ``comment`` keyword argument can be used\nto save it back again.\n\nIf OpenJPEG 2.4.0 or later is available and the ``plt`` keyword argument\nis present and true when saving JPEG2000 images, tell the encoder to generate\nPLT markers.\n\nSecurity\n========\n\nClear PPM half token after use\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nImage files that are small on disk are often prevented from expanding to be\nbig images consuming a large amount of resources simply because they lack the\ndata to populate those resources.\n\nPpmImagePlugin might hold onto the last data read for a pixel value in case the\npixel value has not been finished yet. However, that data was not being cleared\nafterwards, meaning that infinite data could be available to fill any image\nsize.\n\nThat data is now cleared after use.\n\nOther Changes\n=============\n\nAdded support for saving PDFs in RGBA mode\n^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n\nUsing the JPXDecode filter, PDFs can now be saved in RGBA mode.\n\nImproved I;16N support\n^^^^^^^^^^^^^^^^^^^^^^\n\nSupport has been added for I;16N access, packing and unpacking. Conversion to\nand from L mode has also been added.\n\nBGR;* modes\n^^^^^^^^^^^\n\nIt is now possible to create new BGR;15, BGR;16 and BGR;24 images. Conversely, BGR;32\nhas been removed from ImageMode and its associated methods, dropping the little support\nPillow had for the mode.\n\nWith that, all modes listed under :ref:`concept-modes` can now be used to create a new\nimage.\n",
            "header": {
                "file": {
                    "path": "docs/releasenotes/9.5.0.rst"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "0b0e0dd2f",
                "new_commit_hash": "1ba9b9890",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/docs/releasenotes/9.5.0.rst"
            },
            "plus_file": {
                "path": "b/docs/releasenotes/9.5.0.rst"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 62,
                            "length": 10
                        },
                        "new_range": {
                            "start": 62,
                            "length": 19
                        },
                        "section": "PLT markers."
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "Security",
                                "type": "context"
                            },
                            {
                                "content": "========",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "TODO",
                                "type": "deleted"
                            },
                            {
                                "content": "^^^^",
                                "type": "deleted"
                            },
                            {
                                "content": "Clear PPM half token after use",
                                "type": "added"
                            },
                            {
                                "content": "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "TODO",
                                "type": "deleted"
                            },
                            {
                                "content": "Image files that are small on disk are often prevented from expanding to be",
                                "type": "added"
                            },
                            {
                                "content": "big images consuming a large amount of resources simply because they lack the",
                                "type": "added"
                            },
                            {
                                "content": "data to populate those resources.",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "PpmImagePlugin might hold onto the last data read for a pixel value in case the",
                                "type": "added"
                            },
                            {
                                "content": "pixel value has not been finished yet. However, that data was not being cleared",
                                "type": "added"
                            },
                            {
                                "content": "afterwards, meaning that infinite data could be available to fill any image",
                                "type": "added"
                            },
                            {
                                "content": "size.",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "That data is now cleared after use.",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "Other Changes",
                                "type": "context"
                            },
                            {
                                "content": "=============",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "#\n# The Python Imaging Library.\n# $Id$\n#\n# PPM support for PIL\n#\n# History:\n#       96-03-24 fl     Created\n#       98-03-06 fl     Write RGBA images (as RGB, that is)\n#\n# Copyright (c) Secret Labs AB 1997-98.\n# Copyright (c) Fredrik Lundh 1996.\n#\n# See the README file for information on usage and redistribution.\n#\n\n\nfrom . import Image, ImageFile\nfrom ._binary import i16be as i16\nfrom ._binary import o8\nfrom ._binary import o32le as o32\n\n#\n# --------------------------------------------------------------------\n\nb_whitespace = b\"\\x20\\x09\\x0a\\x0b\\x0c\\x0d\"\n\nMODES = {\n    # standard\n    b\"P1\": \"1\",\n    b\"P2\": \"L\",\n    b\"P3\": \"RGB\",\n    b\"P4\": \"1\",\n    b\"P5\": \"L\",\n    b\"P6\": \"RGB\",\n    # extensions\n    b\"P0CMYK\": \"CMYK\",\n    # PIL extensions (for test purposes only)\n    b\"PyP\": \"P\",\n    b\"PyRGBA\": \"RGBA\",\n    b\"PyCMYK\": \"CMYK\",\n}\n\n\ndef _accept(prefix):\n    return prefix[0:1] == b\"P\" and prefix[1] in b\"0123456y\"\n\n\n##\n# Image plugin for PBM, PGM, and PPM images.\n\n\nclass PpmImageFile(ImageFile.ImageFile):\n    format = \"PPM\"\n    format_description = \"Pbmplus image\"\n\n    def _read_magic(self):\n        magic = b\"\"\n        # read until whitespace or longest available magic number\n        for _ in range(6):\n            c = self.fp.read(1)\n            if not c or c in b_whitespace:\n                break\n            magic += c\n        return magic\n\n    def _read_token(self):\n        token = b\"\"\n        while len(token) <= 10:  # read until next whitespace or limit of 10 characters\n            c = self.fp.read(1)\n            if not c:\n                break\n            elif c in b_whitespace:  # token ended\n                if not token:\n                    # skip whitespace at start\n                    continue\n                break\n            elif c == b\"#\":\n                # ignores rest of the line; stops at CR, LF or EOF\n                while self.fp.read(1) not in b\"\\r\\n\":\n                    pass\n                continue\n            token += c\n        if not token:\n            # Token was not even 1 byte\n            msg = \"Reached EOF while reading header\"\n            raise ValueError(msg)\n        elif len(token) > 10:\n            msg = f\"Token too long in file header: {token.decode()}\"\n            raise ValueError(msg)\n        return token\n\n    def _open(self):\n        magic_number = self._read_magic()\n        try:\n            mode = MODES[magic_number]\n        except KeyError:\n            msg = \"not a PPM file\"\n            raise SyntaxError(msg)\n\n        if magic_number in (b\"P1\", b\"P4\"):\n            self.custom_mimetype = \"image/x-portable-bitmap\"\n        elif magic_number in (b\"P2\", b\"P5\"):\n            self.custom_mimetype = \"image/x-portable-graymap\"\n        elif magic_number in (b\"P3\", b\"P6\"):\n            self.custom_mimetype = \"image/x-portable-pixmap\"\n\n        maxval = None\n        decoder_name = \"raw\"\n        if magic_number in (b\"P1\", b\"P2\", b\"P3\"):\n            decoder_name = \"ppm_plain\"\n        for ix in range(3):\n            token = int(self._read_token())\n            if ix == 0:  # token is the x size\n                xsize = token\n            elif ix == 1:  # token is the y size\n                ysize = token\n                if mode == \"1\":\n                    self.mode = \"1\"\n                    rawmode = \"1;I\"\n                    break\n                else:\n                    self.mode = rawmode = mode\n            elif ix == 2:  # token is maxval\n                maxval = token\n                if not 0 < maxval < 65536:\n                    msg = \"maxval must be greater than 0 and less than 65536\"\n                    raise ValueError(msg)\n                if maxval > 255 and mode == \"L\":\n                    self.mode = \"I\"\n\n                if decoder_name != \"ppm_plain\":\n                    # If maxval matches a bit depth, use the raw decoder directly\n                    if maxval == 65535 and mode == \"L\":\n                        rawmode = \"I;16B\"\n                    elif maxval != 255:\n                        decoder_name = \"ppm\"\n\n        args = (rawmode, 0, 1) if decoder_name == \"raw\" else (rawmode, maxval)\n        self._size = xsize, ysize\n        self.tile = [(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), args)]\n\n\n#\n# --------------------------------------------------------------------\n\n\nclass PpmPlainDecoder(ImageFile.PyDecoder):\n    _pulls_fd = True\n\n    def _read_block(self):\n        return self.fd.read(ImageFile.SAFEBLOCK)\n\n    def _find_comment_end(self, block, start=0):\n        a = block.find(b\"\\n\", start)\n        b = block.find(b\"\\r\", start)\n        return min(a, b) if a * b > 0 else max(a, b)  # lowest nonnegative index (or -1)\n\n    def _ignore_comments(self, block):\n        if self._comment_spans:\n            # Finish current comment\n            while block:\n                comment_end = self._find_comment_end(block)\n                if comment_end != -1:\n                    # Comment ends in this block\n                    # Delete tail of comment\n                    block = block[comment_end + 1 :]\n                    break\n                else:\n                    # Comment spans whole block\n                    # So read the next block, looking for the end\n                    block = self._read_block()\n\n        # Search for any further comments\n        self._comment_spans = False\n        while True:\n            comment_start = block.find(b\"#\")\n            if comment_start == -1:\n                # No comment found\n                break\n            comment_end = self._find_comment_end(block, comment_start)\n            if comment_end != -1:\n                # Comment ends in this block\n                # Delete comment\n                block = block[:comment_start] + block[comment_end + 1 :]\n            else:\n                # Comment continues to next block(s)\n                block = block[:comment_start]\n                self._comment_spans = True\n                break\n        return block\n\n    def _decode_bitonal(self):\n        \"\"\"\n        This is a separate method because in the plain PBM format, all data tokens are\n        exactly one byte, so the inter-token whitespace is optional.\n        \"\"\"\n        data = bytearray()\n        total_bytes = self.state.xsize * self.state.ysize\n\n        while len(data) != total_bytes:\n            block = self._read_block()  # read next block\n            if not block:\n                # eof\n                break\n\n            block = self._ignore_comments(block)\n\n            tokens = b\"\".join(block.split())\n            for token in tokens:\n                if token not in (48, 49):\n                    msg = b\"Invalid token for this mode: %s\" % bytes([token])\n                    raise ValueError(msg)\n            data = (data + tokens)[:total_bytes]\n        invert = bytes.maketrans(b\"01\", b\"\\xFF\\x00\")\n        return data.translate(invert)\n\n    def _decode_blocks(self, maxval):\n        data = bytearray()\n        max_len = 10\n        out_byte_count = 4 if self.mode == \"I\" else 1\n        out_max = 65535 if self.mode == \"I\" else 255\n        bands = Image.getmodebands(self.mode)\n        total_bytes = self.state.xsize * self.state.ysize * bands * out_byte_count\n\n        half_token = False\n        while len(data) != total_bytes:\n            block = self._read_block()  # read next block\n            if not block:\n                if half_token:\n                    block = bytearray(b\" \")  # flush half_token\n                else:\n                    # eof\n                    break\n\n            block = self._ignore_comments(block)\n\n            if half_token:\n                block = half_token + block  # stitch half_token to new block\n\n            tokens = block.split()\n\n            if block and not block[-1:].isspace():  # block might split token\n                half_token = tokens.pop()  # save half token for later\n                if len(half_token) > max_len:  # prevent buildup of half_token\n                    msg = (\n                        b\"Token too long found in data: %s\" % half_token[: max_len + 1]\n                    )\n                    raise ValueError(msg)\n\n            for token in tokens:\n                if len(token) > max_len:\n                    msg = b\"Token too long found in data: %s\" % token[: max_len + 1]\n                    raise ValueError(msg)\n                value = int(token)\n                if value > maxval:\n                    msg = f\"Channel value too large for this mode: {value}\"\n                    raise ValueError(msg)\n                value = round(value / maxval * out_max)\n                data += o32(value) if self.mode == \"I\" else o8(value)\n                if len(data) == total_bytes:  # finished!\n                    break\n        return data\n\n    def decode(self, buffer):\n        self._comment_spans = False\n        if self.mode == \"1\":\n            data = self._decode_bitonal()\n            rawmode = \"1;8\"\n        else:\n            maxval = self.args[-1]\n            data = self._decode_blocks(maxval)\n            rawmode = \"I;32\" if self.mode == \"I\" else self.mode\n        self.set_as_raw(bytes(data), rawmode)\n        return -1, 0\n\n\nclass PpmDecoder(ImageFile.PyDecoder):\n    _pulls_fd = True\n\n    def decode(self, buffer):\n        data = bytearray()\n        maxval = self.args[-1]\n        in_byte_count = 1 if maxval < 256 else 2\n        out_byte_count = 4 if self.mode == \"I\" else 1\n        out_max = 65535 if self.mode == \"I\" else 255\n        bands = Image.getmodebands(self.mode)\n        while len(data) < self.state.xsize * self.state.ysize * bands * out_byte_count:\n            pixels = self.fd.read(in_byte_count * bands)\n            if len(pixels) < in_byte_count * bands:\n                # eof\n                break\n            for b in range(bands):\n                value = (\n                    pixels[b] if in_byte_count == 1 else i16(pixels, b * in_byte_count)\n                )\n                value = min(out_max, round(value / maxval * out_max))\n                data += o32(value) if self.mode == \"I\" else o8(value)\n        rawmode = \"I;32\" if self.mode == \"I\" else self.mode\n        self.set_as_raw(bytes(data), rawmode)\n        return -1, 0\n\n\n#\n# --------------------------------------------------------------------\n\n\ndef _save(im, fp, filename):\n    if im.mode == \"1\":\n        rawmode, head = \"1;I\", b\"P4\"\n    elif im.mode == \"L\":\n        rawmode, head = \"L\", b\"P5\"\n    elif im.mode == \"I\":\n        rawmode, head = \"I;16B\", b\"P5\"\n    elif im.mode in (\"RGB\", \"RGBA\"):\n        rawmode, head = \"RGB\", b\"P6\"\n    else:\n        msg = f\"cannot write mode {im.mode} as PPM\"\n        raise OSError(msg)\n    fp.write(head + b\"\\n%d %d\\n\" % im.size)\n    if head == b\"P6\":\n        fp.write(b\"255\\n\")\n    elif head == b\"P5\":\n        if rawmode == \"L\":\n            fp.write(b\"255\\n\")\n        else:\n            fp.write(b\"65535\\n\")\n    ImageFile._save(im, fp, [(\"raw\", (0, 0) + im.size, 0, (rawmode, 0, 1))])\n\n    # ALTERNATIVE: save via builtin debug function\n    # im._dump(filename)\n\n\n#\n# --------------------------------------------------------------------\n\n\nImage.register_open(PpmImageFile.format, PpmImageFile, _accept)\nImage.register_save(PpmImageFile.format, _save)\n\nImage.register_decoder(\"ppm\", PpmDecoder)\nImage.register_decoder(\"ppm_plain\", PpmPlainDecoder)\n\nImage.register_extensions(PpmImageFile.format, [\".pbm\", \".pgm\", \".ppm\", \".pnm\"])\n\nImage.register_mime(PpmImageFile.format, \"image/x-portable-anymap\")\n",
            "new_file_content": "#\n# The Python Imaging Library.\n# $Id$\n#\n# PPM support for PIL\n#\n# History:\n#       96-03-24 fl     Created\n#       98-03-06 fl     Write RGBA images (as RGB, that is)\n#\n# Copyright (c) Secret Labs AB 1997-98.\n# Copyright (c) Fredrik Lundh 1996.\n#\n# See the README file for information on usage and redistribution.\n#\n\n\nfrom . import Image, ImageFile\nfrom ._binary import i16be as i16\nfrom ._binary import o8\nfrom ._binary import o32le as o32\n\n#\n# --------------------------------------------------------------------\n\nb_whitespace = b\"\\x20\\x09\\x0a\\x0b\\x0c\\x0d\"\n\nMODES = {\n    # standard\n    b\"P1\": \"1\",\n    b\"P2\": \"L\",\n    b\"P3\": \"RGB\",\n    b\"P4\": \"1\",\n    b\"P5\": \"L\",\n    b\"P6\": \"RGB\",\n    # extensions\n    b\"P0CMYK\": \"CMYK\",\n    # PIL extensions (for test purposes only)\n    b\"PyP\": \"P\",\n    b\"PyRGBA\": \"RGBA\",\n    b\"PyCMYK\": \"CMYK\",\n}\n\n\ndef _accept(prefix):\n    return prefix[0:1] == b\"P\" and prefix[1] in b\"0123456y\"\n\n\n##\n# Image plugin for PBM, PGM, and PPM images.\n\n\nclass PpmImageFile(ImageFile.ImageFile):\n    format = \"PPM\"\n    format_description = \"Pbmplus image\"\n\n    def _read_magic(self):\n        magic = b\"\"\n        # read until whitespace or longest available magic number\n        for _ in range(6):\n            c = self.fp.read(1)\n            if not c or c in b_whitespace:\n                break\n            magic += c\n        return magic\n\n    def _read_token(self):\n        token = b\"\"\n        while len(token) <= 10:  # read until next whitespace or limit of 10 characters\n            c = self.fp.read(1)\n            if not c:\n                break\n            elif c in b_whitespace:  # token ended\n                if not token:\n                    # skip whitespace at start\n                    continue\n                break\n            elif c == b\"#\":\n                # ignores rest of the line; stops at CR, LF or EOF\n                while self.fp.read(1) not in b\"\\r\\n\":\n                    pass\n                continue\n            token += c\n        if not token:\n            # Token was not even 1 byte\n            msg = \"Reached EOF while reading header\"\n            raise ValueError(msg)\n        elif len(token) > 10:\n            msg = f\"Token too long in file header: {token.decode()}\"\n            raise ValueError(msg)\n        return token\n\n    def _open(self):\n        magic_number = self._read_magic()\n        try:\n            mode = MODES[magic_number]\n        except KeyError:\n            msg = \"not a PPM file\"\n            raise SyntaxError(msg)\n\n        if magic_number in (b\"P1\", b\"P4\"):\n            self.custom_mimetype = \"image/x-portable-bitmap\"\n        elif magic_number in (b\"P2\", b\"P5\"):\n            self.custom_mimetype = \"image/x-portable-graymap\"\n        elif magic_number in (b\"P3\", b\"P6\"):\n            self.custom_mimetype = \"image/x-portable-pixmap\"\n\n        maxval = None\n        decoder_name = \"raw\"\n        if magic_number in (b\"P1\", b\"P2\", b\"P3\"):\n            decoder_name = \"ppm_plain\"\n        for ix in range(3):\n            token = int(self._read_token())\n            if ix == 0:  # token is the x size\n                xsize = token\n            elif ix == 1:  # token is the y size\n                ysize = token\n                if mode == \"1\":\n                    self.mode = \"1\"\n                    rawmode = \"1;I\"\n                    break\n                else:\n                    self.mode = rawmode = mode\n            elif ix == 2:  # token is maxval\n                maxval = token\n                if not 0 < maxval < 65536:\n                    msg = \"maxval must be greater than 0 and less than 65536\"\n                    raise ValueError(msg)\n                if maxval > 255 and mode == \"L\":\n                    self.mode = \"I\"\n\n                if decoder_name != \"ppm_plain\":\n                    # If maxval matches a bit depth, use the raw decoder directly\n                    if maxval == 65535 and mode == \"L\":\n                        rawmode = \"I;16B\"\n                    elif maxval != 255:\n                        decoder_name = \"ppm\"\n\n        args = (rawmode, 0, 1) if decoder_name == \"raw\" else (rawmode, maxval)\n        self._size = xsize, ysize\n        self.tile = [(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), args)]\n\n\n#\n# --------------------------------------------------------------------\n\n\nclass PpmPlainDecoder(ImageFile.PyDecoder):\n    _pulls_fd = True\n\n    def _read_block(self):\n        return self.fd.read(ImageFile.SAFEBLOCK)\n\n    def _find_comment_end(self, block, start=0):\n        a = block.find(b\"\\n\", start)\n        b = block.find(b\"\\r\", start)\n        return min(a, b) if a * b > 0 else max(a, b)  # lowest nonnegative index (or -1)\n\n    def _ignore_comments(self, block):\n        if self._comment_spans:\n            # Finish current comment\n            while block:\n                comment_end = self._find_comment_end(block)\n                if comment_end != -1:\n                    # Comment ends in this block\n                    # Delete tail of comment\n                    block = block[comment_end + 1 :]\n                    break\n                else:\n                    # Comment spans whole block\n                    # So read the next block, looking for the end\n                    block = self._read_block()\n\n        # Search for any further comments\n        self._comment_spans = False\n        while True:\n            comment_start = block.find(b\"#\")\n            if comment_start == -1:\n                # No comment found\n                break\n            comment_end = self._find_comment_end(block, comment_start)\n            if comment_end != -1:\n                # Comment ends in this block\n                # Delete comment\n                block = block[:comment_start] + block[comment_end + 1 :]\n            else:\n                # Comment continues to next block(s)\n                block = block[:comment_start]\n                self._comment_spans = True\n                break\n        return block\n\n    def _decode_bitonal(self):\n        \"\"\"\n        This is a separate method because in the plain PBM format, all data tokens are\n        exactly one byte, so the inter-token whitespace is optional.\n        \"\"\"\n        data = bytearray()\n        total_bytes = self.state.xsize * self.state.ysize\n\n        while len(data) != total_bytes:\n            block = self._read_block()  # read next block\n            if not block:\n                # eof\n                break\n\n            block = self._ignore_comments(block)\n\n            tokens = b\"\".join(block.split())\n            for token in tokens:\n                if token not in (48, 49):\n                    msg = b\"Invalid token for this mode: %s\" % bytes([token])\n                    raise ValueError(msg)\n            data = (data + tokens)[:total_bytes]\n        invert = bytes.maketrans(b\"01\", b\"\\xFF\\x00\")\n        return data.translate(invert)\n\n    def _decode_blocks(self, maxval):\n        data = bytearray()\n        max_len = 10\n        out_byte_count = 4 if self.mode == \"I\" else 1\n        out_max = 65535 if self.mode == \"I\" else 255\n        bands = Image.getmodebands(self.mode)\n        total_bytes = self.state.xsize * self.state.ysize * bands * out_byte_count\n\n        half_token = False\n        while len(data) != total_bytes:\n            block = self._read_block()  # read next block\n            if not block:\n                if half_token:\n                    block = bytearray(b\" \")  # flush half_token\n                else:\n                    # eof\n                    break\n\n            block = self._ignore_comments(block)\n\n            if half_token:\n                block = half_token + block  # stitch half_token to new block\n                half_token = False\n\n            tokens = block.split()\n\n            if block and not block[-1:].isspace():  # block might split token\n                half_token = tokens.pop()  # save half token for later\n                if len(half_token) > max_len:  # prevent buildup of half_token\n                    msg = (\n                        b\"Token too long found in data: %s\" % half_token[: max_len + 1]\n                    )\n                    raise ValueError(msg)\n\n            for token in tokens:\n                if len(token) > max_len:\n                    msg = b\"Token too long found in data: %s\" % token[: max_len + 1]\n                    raise ValueError(msg)\n                value = int(token)\n                if value > maxval:\n                    msg = f\"Channel value too large for this mode: {value}\"\n                    raise ValueError(msg)\n                value = round(value / maxval * out_max)\n                data += o32(value) if self.mode == \"I\" else o8(value)\n                if len(data) == total_bytes:  # finished!\n                    break\n        return data\n\n    def decode(self, buffer):\n        self._comment_spans = False\n        if self.mode == \"1\":\n            data = self._decode_bitonal()\n            rawmode = \"1;8\"\n        else:\n            maxval = self.args[-1]\n            data = self._decode_blocks(maxval)\n            rawmode = \"I;32\" if self.mode == \"I\" else self.mode\n        self.set_as_raw(bytes(data), rawmode)\n        return -1, 0\n\n\nclass PpmDecoder(ImageFile.PyDecoder):\n    _pulls_fd = True\n\n    def decode(self, buffer):\n        data = bytearray()\n        maxval = self.args[-1]\n        in_byte_count = 1 if maxval < 256 else 2\n        out_byte_count = 4 if self.mode == \"I\" else 1\n        out_max = 65535 if self.mode == \"I\" else 255\n        bands = Image.getmodebands(self.mode)\n        while len(data) < self.state.xsize * self.state.ysize * bands * out_byte_count:\n            pixels = self.fd.read(in_byte_count * bands)\n            if len(pixels) < in_byte_count * bands:\n                # eof\n                break\n            for b in range(bands):\n                value = (\n                    pixels[b] if in_byte_count == 1 else i16(pixels, b * in_byte_count)\n                )\n                value = min(out_max, round(value / maxval * out_max))\n                data += o32(value) if self.mode == \"I\" else o8(value)\n        rawmode = \"I;32\" if self.mode == \"I\" else self.mode\n        self.set_as_raw(bytes(data), rawmode)\n        return -1, 0\n\n\n#\n# --------------------------------------------------------------------\n\n\ndef _save(im, fp, filename):\n    if im.mode == \"1\":\n        rawmode, head = \"1;I\", b\"P4\"\n    elif im.mode == \"L\":\n        rawmode, head = \"L\", b\"P5\"\n    elif im.mode == \"I\":\n        rawmode, head = \"I;16B\", b\"P5\"\n    elif im.mode in (\"RGB\", \"RGBA\"):\n        rawmode, head = \"RGB\", b\"P6\"\n    else:\n        msg = f\"cannot write mode {im.mode} as PPM\"\n        raise OSError(msg)\n    fp.write(head + b\"\\n%d %d\\n\" % im.size)\n    if head == b\"P6\":\n        fp.write(b\"255\\n\")\n    elif head == b\"P5\":\n        if rawmode == \"L\":\n            fp.write(b\"255\\n\")\n        else:\n            fp.write(b\"65535\\n\")\n    ImageFile._save(im, fp, [(\"raw\", (0, 0) + im.size, 0, (rawmode, 0, 1))])\n\n    # ALTERNATIVE: save via builtin debug function\n    # im._dump(filename)\n\n\n#\n# --------------------------------------------------------------------\n\n\nImage.register_open(PpmImageFile.format, PpmImageFile, _accept)\nImage.register_save(PpmImageFile.format, _save)\n\nImage.register_decoder(\"ppm\", PpmDecoder)\nImage.register_decoder(\"ppm_plain\", PpmPlainDecoder)\n\nImage.register_extensions(PpmImageFile.format, [\".pbm\", \".pgm\", \".ppm\", \".pnm\"])\n\nImage.register_mime(PpmImageFile.format, \"image/x-portable-anymap\")\n",
            "header": {
                "file": {
                    "path": "src/PIL/PpmImagePlugin.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "5aa418044",
                "new_commit_hash": "2cb1e5636",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/src/PIL/PpmImagePlugin.py"
            },
            "plus_file": {
                "path": "b/src/PIL/PpmImagePlugin.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 237,
                            "length": 6
                        },
                        "new_range": {
                            "start": 237,
                            "length": 7
                        },
                        "section": "class PpmPlainDecoder(ImageFile.PyDecoder):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "            if half_token:",
                                "type": "context"
                            },
                            {
                                "content": "                block = half_token + block  # stitch half_token to new block",
                                "type": "context"
                            },
                            {
                                "content": "                half_token = False",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "            tokens = block.split()",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/PpmImagePlugin.py",
                            "type": "class",
                            "name": "PpmPlainDecoder",
                            "content": "class PpmPlainDecoder(ImageFile.PyDecoder):\n    _pulls_fd = True\n\n    def _read_block(self):\n        return self.fd.read(ImageFile.SAFEBLOCK)\n\n    def _find_comment_end(self, block, start=0):\n        a = block.find(b\"\\n\", start)\n        b = block.find(b\"\\r\", start)\n        return min(a, b) if a * b > 0 else max(a, b)  # lowest nonnegative index (or -1)\n\n    def _ignore_comments(self, block):\n        if self._comment_spans:\n            # Finish current comment\n            while block:\n                comment_end = self._find_comment_end(block)\n                if comment_end != -1:\n                    # Comment ends in this block\n                    # Delete tail of comment\n                    block = block[comment_end + 1 :]\n                    break\n                else:\n                    # Comment spans whole block\n                    # So read the next block, looking for the end\n                    block = self._read_block()\n\n        # Search for any further comments\n        self._comment_spans = False\n        while True:\n            comment_start = block.find(b\"#\")\n            if comment_start == -1:\n                # No comment found\n                break\n            comment_end = self._find_comment_end(block, comment_start)\n            if comment_end != -1:\n                # Comment ends in this block\n                # Delete comment\n                block = block[:comment_start] + block[comment_end + 1 :]\n            else:\n                # Comment continues to next block(s)\n                block = block[:comment_start]\n                self._comment_spans = True\n                break\n        return block\n\n    def _decode_bitonal(self):\n        \"\"\"\n        This is a separate method because in the plain PBM format, all data tokens are\n        exactly one byte, so the inter-token whitespace is optional.\n        \"\"\"\n        data = bytearray()\n        total_bytes = self.state.xsize * self.state.ysize\n\n        while len(data) != total_bytes:\n            block = self._read_block()  # read next block\n            if not block:\n                # eof\n                break\n\n            block = self._ignore_comments(block)\n\n            tokens = b\"\".join(block.split())\n            for token in tokens:\n                if token not in (48, 49):\n                    msg = b\"Invalid token for this mode: %s\" % bytes([token])\n                    raise ValueError(msg)\n            data = (data + tokens)[:total_bytes]\n        invert = bytes.maketrans(b\"01\", b\"\\xFF\\x00\")\n        return data.translate(invert)\n\n    def _decode_blocks(self, maxval):\n        data = bytearray()\n        max_len = 10\n        out_byte_count = 4 if self.mode == \"I\" else 1\n        out_max = 65535 if self.mode == \"I\" else 255\n        bands = Image.getmodebands(self.mode)\n        total_bytes = self.state.xsize * self.state.ysize * bands * out_byte_count\n\n        half_token = False\n        while len(data) != total_bytes:\n            block = self._read_block()  # read next block\n            if not block:\n                if half_token:\n                    block = bytearray(b\" \")  # flush half_token\n                else:\n                    # eof\n                    break\n\n            block = self._ignore_comments(block)\n\n            if half_token:\n                block = half_token + block  # stitch half_token to new block\n                half_token = False\n\n            tokens = block.split()\n\n            if block and not block[-1:].isspace():  # block might split token\n                half_token = tokens.pop()  # save half token for later\n                if len(half_token) > max_len:  # prevent buildup of half_token\n                    msg = (\n                        b\"Token too long found in data: %s\" % half_token[: max_len + 1]\n                    )\n                    raise ValueError(msg)\n\n            for token in tokens:\n                if len(token) > max_len:\n                    msg = b\"Token too long found in data: %s\" % token[: max_len + 1]\n                    raise ValueError(msg)\n                value = int(token)\n                if value > maxval:\n                    msg = f\"Channel value too large for this mode: {value}\"\n                    raise ValueError(msg)\n                value = round(value / maxval * out_max)\n                data += o32(value) if self.mode == \"I\" else o8(value)\n                if len(data) == total_bytes:  # finished!\n                    break\n        return data\n\n    def decode(self, buffer):\n        self._comment_spans = False\n        if self.mode == \"1\":\n            data = self._decode_bitonal()\n            rawmode = \"1;8\"\n        else:\n            maxval = self.args[-1]\n            data = self._decode_blocks(maxval)\n            rawmode = \"I;32\" if self.mode == \"I\" else self.mode\n        self.set_as_raw(bytes(data), rawmode)\n        return -1, 0",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 148,
                            "end_lineno": 276,
                            "parent": null
                        },
                        {
                            "file_name": "src/PIL/PpmImagePlugin.py",
                            "type": "method",
                            "name": "PpmPlainDecoder._decode_blocks",
                            "content": "class PpmPlainDecoder(ImageFile.PyDecoder):\n    _pulls_fd = True\n\n    def _read_block(self):\n        return self.fd.read(ImageFile.SAFEBLOCK)\n\n    def _find_comment_end(self, block, start=0):\n        a = block.find(b\"\\n\", start)\n        b = block.find(b\"\\r\", start)\n        return min(a, b) if a * b > 0 else max(a, b)  # lowest nonnegative index (or -1)\n\n    def _ignore_comments(self, block):\n        if self._comment_spans:\n            # Finish current comment\n            while block:\n                comment_end = self._find_comment_end(block)\n                if comment_end != -1:\n                    # Comment ends in this block\n                    # Delete tail of comment\n                    block = block[comment_end + 1 :]\n                    break\n                else:\n                    # Comment spans whole block\n                    # So read the next block, looking for the end\n                    block = self._read_block()\n\n        # Search for any further comments\n        self._comment_spans = False\n        while True:\n            comment_start = block.find(b\"#\")\n            if comment_start == -1:\n                # No comment found\n                break\n            comment_end = self._find_comment_end(block, comment_start)\n            if comment_end != -1:\n                # Comment ends in this block\n                # Delete comment\n                block = block[:comment_start] + block[comment_end + 1 :]\n            else:\n                # Comment continues to next block(s)\n                block = block[:comment_start]\n                self._comment_spans = True\n                break\n        return block\n\n    def _decode_bitonal(self):\n        \"\"\"\n        This is a separate method because in the plain PBM format, all data tokens are\n        exactly one byte, so the inter-token whitespace is optional.\n        \"\"\"\n        data = bytearray()\n        total_bytes = self.state.xsize * self.state.ysize\n\n        while len(data) != total_bytes:\n            block = self._read_block()  # read next block\n            if not block:\n                # eof\n                break\n\n            block = self._ignore_comments(block)\n\n            tokens = b\"\".join(block.split())\n            for token in tokens:\n                if token not in (48, 49):\n                    msg = b\"Invalid token for this mode: %s\" % bytes([token])\n                    raise ValueError(msg)\n            data = (data + tokens)[:total_bytes]\n        invert = bytes.maketrans(b\"01\", b\"\\xFF\\x00\")\n        return data.translate(invert)\n\n    def _decode_blocks(self, maxval):\n        data = bytearray()\n        max_len = 10\n        out_byte_count = 4 if self.mode == \"I\" else 1\n        out_max = 65535 if self.mode == \"I\" else 255\n        bands = Image.getmodebands(self.mode)\n        total_bytes = self.state.xsize * self.state.ysize * bands * out_byte_count\n\n        half_token = False\n        while len(data) != total_bytes:\n            block = self._read_block()  # read next block\n            if not block:\n                if half_token:\n                    block = bytearray(b\" \")  # flush half_token\n                else:\n                    # eof\n                    break\n\n            block = self._ignore_comments(block)\n\n            if half_token:\n                block = half_token + block  # stitch half_token to new block\n                half_token = False\n\n            tokens = block.split()\n\n            if block and not block[-1:].isspace():  # block might split token\n                half_token = tokens.pop()  # save half token for later\n                if len(half_token) > max_len:  # prevent buildup of half_token\n                    msg = (\n                        b\"Token too long found in data: %s\" % half_token[: max_len + 1]\n                    )\n                    raise ValueError(msg)\n\n            for token in tokens:\n                if len(token) > max_len:\n                    msg = b\"Token too long found in data: %s\" % token[: max_len + 1]\n                    raise ValueError(msg)\n                value = int(token)\n                if value > maxval:\n                    msg = f\"Channel value too large for this mode: {value}\"\n                    raise ValueError(msg)\n                value = round(value / maxval * out_max)\n                data += o32(value) if self.mode == \"I\" else o8(value)\n                if len(data) == total_bytes:  # finished!\n                    break\n        return data\n\n    def decode(self, buffer):\n        self._comment_spans = False\n        if self.mode == \"1\":\n            data = self._decode_bitonal()\n            rawmode = \"1;8\"\n        else:\n            maxval = self.args[-1]\n            data = self._decode_blocks(maxval)\n            rawmode = \"I;32\" if self.mode == \"I\" else self.mode\n        self.set_as_raw(bytes(data), rawmode)\n        return -1, 0",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 218,
                            "end_lineno": 264,
                            "parent": {
                                "file_name": "src/PIL/PpmImagePlugin.py",
                                "type": "class",
                                "name": "PpmPlainDecoder",
                                "content": "class PpmPlainDecoder(ImageFile.PyDecoder):\n    _pulls_fd = True\n\n    def _read_block(self):\n        return self.fd.read(ImageFile.SAFEBLOCK)\n\n    def _find_comment_end(self, block, start=0):\n        a = block.find(b\"\\n\", start)\n        b = block.find(b\"\\r\", start)\n        return min(a, b) if a * b > 0 else max(a, b)  # lowest nonnegative index (or -1)\n\n    def _ignore_comments(self, block):\n        if self._comment_spans:\n            # Finish current comment\n            while block:\n                comment_end = self._find_comment_end(block)\n                if comment_end != -1:\n                    # Comment ends in this block\n                    # Delete tail of comment\n                    block = block[comment_end + 1 :]\n                    break\n                else:\n                    # Comment spans whole block\n                    # So read the next block, looking for the end\n                    block = self._read_block()\n\n        # Search for any further comments\n        self._comment_spans = False\n        while True:\n            comment_start = block.find(b\"#\")\n            if comment_start == -1:\n                # No comment found\n                break\n            comment_end = self._find_comment_end(block, comment_start)\n            if comment_end != -1:\n                # Comment ends in this block\n                # Delete comment\n                block = block[:comment_start] + block[comment_end + 1 :]\n            else:\n                # Comment continues to next block(s)\n                block = block[:comment_start]\n                self._comment_spans = True\n                break\n        return block\n\n    def _decode_bitonal(self):\n        \"\"\"\n        This is a separate method because in the plain PBM format, all data tokens are\n        exactly one byte, so the inter-token whitespace is optional.\n        \"\"\"\n        data = bytearray()\n        total_bytes = self.state.xsize * self.state.ysize\n\n        while len(data) != total_bytes:\n            block = self._read_block()  # read next block\n            if not block:\n                # eof\n                break\n\n            block = self._ignore_comments(block)\n\n            tokens = b\"\".join(block.split())\n            for token in tokens:\n                if token not in (48, 49):\n                    msg = b\"Invalid token for this mode: %s\" % bytes([token])\n                    raise ValueError(msg)\n            data = (data + tokens)[:total_bytes]\n        invert = bytes.maketrans(b\"01\", b\"\\xFF\\x00\")\n        return data.translate(invert)\n\n    def _decode_blocks(self, maxval):\n        data = bytearray()\n        max_len = 10\n        out_byte_count = 4 if self.mode == \"I\" else 1\n        out_max = 65535 if self.mode == \"I\" else 255\n        bands = Image.getmodebands(self.mode)\n        total_bytes = self.state.xsize * self.state.ysize * bands * out_byte_count\n\n        half_token = False\n        while len(data) != total_bytes:\n            block = self._read_block()  # read next block\n            if not block:\n                if half_token:\n                    block = bytearray(b\" \")  # flush half_token\n                else:\n                    # eof\n                    break\n\n            block = self._ignore_comments(block)\n\n            if half_token:\n                block = half_token + block  # stitch half_token to new block\n                half_token = False\n\n            tokens = block.split()\n\n            if block and not block[-1:].isspace():  # block might split token\n                half_token = tokens.pop()  # save half token for later\n                if len(half_token) > max_len:  # prevent buildup of half_token\n                    msg = (\n                        b\"Token too long found in data: %s\" % half_token[: max_len + 1]\n                    )\n                    raise ValueError(msg)\n\n            for token in tokens:\n                if len(token) > max_len:\n                    msg = b\"Token too long found in data: %s\" % token[: max_len + 1]\n                    raise ValueError(msg)\n                value = int(token)\n                if value > maxval:\n                    msg = f\"Channel value too large for this mode: {value}\"\n                    raise ValueError(msg)\n                value = round(value / maxval * out_max)\n                data += o32(value) if self.mode == \"I\" else o8(value)\n                if len(data) == total_bytes:  # finished!\n                    break\n        return data\n\n    def decode(self, buffer):\n        self._comment_spans = False\n        if self.mode == \"1\":\n            data = self._decode_bitonal()\n            rawmode = \"1;8\"\n        else:\n            maxval = self.args[-1]\n            data = self._decode_blocks(maxval)\n            rawmode = \"I;32\" if self.mode == \"I\" else self.mode\n        self.set_as_raw(bytes(data), rawmode)\n        return -1, 0",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 148,
                                "end_lineno": 276,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "5932a0bd19b513c3c86d9fc8dec5d41384247056^",
    "new_commit_hash": "5932a0bd19b513c3c86d9fc8dec5d41384247056",
    "commit_message": "Clear half token after use",
    "commit_date": "2023-04-01T09:23:57+11:00",
    "metadata": {}
}
{
    "repo_name": "pillow",
    "new_commit_hash": "5932a0bd19b513c3c86d9fc8dec5d41384247056",
    "test_file_codes": [
        "import sys\nfrom io import BytesIO\n\nimport pytest\n\nfrom PIL import Image, PpmImagePlugin\n\nfrom .helper import assert_image_equal_tofile, assert_image_similar, hopper\n\n# sample ppm stream\nTEST_FILE = \"Tests/images/hopper.ppm\"\n\n\ndef test_sanity():\n    with Image.open(TEST_FILE) as im:\n        assert im.mode == \"RGB\"\n        assert im.size == (128, 128)\n        assert im.format == \"PPM\"\n        assert im.get_format_mimetype() == \"image/x-portable-pixmap\"\n\n\n@pytest.mark.parametrize(\n    \"data, mode, pixels\",\n    (\n        (b\"P2 3 1 4 0 2 4\", \"L\", (0, 128, 255)),\n        (b\"P2 3 1 257 0 128 257\", \"I\", (0, 32640, 65535)),\n        # P3 with maxval < 255\n        (\n            b\"P3 3 1 17 0 1 2 8 9 10 15 16 17\",\n            \"RGB\",\n            ((0, 15, 30), (120, 135, 150), (225, 240, 255)),\n        ),\n        # P3 with maxval > 255\n        # Scale down to 255, since there is no RGB mode with more than 8-bit\n        (\n            b\"P3 3 1 257 0 1 2 128 129 130 256 257 257\",\n            \"RGB\",\n            ((0, 1, 2), (127, 128, 129), (254, 255, 255)),\n        ),\n        (b\"P5 3 1 4 \\x00\\x02\\x04\", \"L\", (0, 128, 255)),\n        (b\"P5 3 1 257 \\x00\\x00\\x00\\x80\\x01\\x01\", \"I\", (0, 32640, 65535)),\n        # P6 with maxval < 255\n        (\n            b\"P6 3 1 17 \\x00\\x01\\x02\\x08\\x09\\x0A\\x0F\\x10\\x11\",\n            \"RGB\",\n            (\n                (0, 15, 30),\n                (120, 135, 150),\n                (225, 240, 255),\n            ),\n        ),\n        # P6 with maxval > 255\n        (\n            b\"P6 3 1 257 \\x00\\x00\\x00\\x01\\x00\\x02\"\n            b\"\\x00\\x80\\x00\\x81\\x00\\x82\\x01\\x00\\x01\\x01\\xFF\\xFF\",\n            \"RGB\",\n            (\n                (0, 1, 2),\n                (127, 128, 129),\n                (254, 255, 255),\n            ),\n        ),\n    ),\n)\ndef test_arbitrary_maxval(data, mode, pixels):\n    fp = BytesIO(data)\n    with Image.open(fp) as im:\n        assert im.size == (3, 1)\n        assert im.mode == mode\n\n        px = im.load()\n        assert tuple(px[x, 0] for x in range(3)) == pixels\n\n\ndef test_16bit_pgm():\n    with Image.open(\"Tests/images/16_bit_binary.pgm\") as im:\n        assert im.mode == \"I\"\n        assert im.size == (20, 100)\n        assert im.get_format_mimetype() == \"image/x-portable-graymap\"\n\n        assert_image_equal_tofile(im, \"Tests/images/16_bit_binary_pgm.png\")\n\n\ndef test_16bit_pgm_write(tmp_path):\n    with Image.open(\"Tests/images/16_bit_binary.pgm\") as im:\n        f = str(tmp_path / \"temp.pgm\")\n        im.save(f, \"PPM\")\n\n        assert_image_equal_tofile(im, f)\n\n\ndef test_pnm(tmp_path):\n    with Image.open(\"Tests/images/hopper.pnm\") as im:\n        assert_image_similar(im, hopper(), 0.0001)\n\n        f = str(tmp_path / \"temp.pnm\")\n        im.save(f)\n\n        assert_image_equal_tofile(im, f)\n\n\n@pytest.mark.parametrize(\n    \"plain_path, raw_path\",\n    (\n        (\n            \"Tests/images/hopper_1bit_plain.pbm\",  # P1\n            \"Tests/images/hopper_1bit.pbm\",  # P4\n        ),\n        (\n            \"Tests/images/hopper_8bit_plain.pgm\",  # P2\n            \"Tests/images/hopper_8bit.pgm\",  # P5\n        ),\n        (\n            \"Tests/images/hopper_8bit_plain.ppm\",  # P3\n            \"Tests/images/hopper_8bit.ppm\",  # P6\n        ),\n    ),\n)\ndef test_plain(plain_path, raw_path):\n    with Image.open(plain_path) as im:\n        assert_image_equal_tofile(im, raw_path)\n\n\ndef test_16bit_plain_pgm():\n    # P2 with maxval 2 ** 16 - 1\n    with Image.open(\"Tests/images/hopper_16bit_plain.pgm\") as im:\n        assert im.mode == \"I\"\n        assert im.size == (128, 128)\n        assert im.get_format_mimetype() == \"image/x-portable-graymap\"\n\n        # P5 with maxval 2 ** 16 - 1\n        assert_image_equal_tofile(im, \"Tests/images/hopper_16bit.pgm\")\n\n\n@pytest.mark.parametrize(\n    \"header, data, comment_count\",\n    (\n        (b\"P1\\n2 2\", b\"1010\", 10**6),\n        (b\"P2\\n3 1\\n4\", b\"0 2 4\", 1),\n        (b\"P3\\n2 2\\n255\", b\"0 0 0 001 1 1 2 2 2 255 255 255\", 10**6),\n    ),\n)\ndef test_plain_data_with_comment(tmp_path, header, data, comment_count):\n    path1 = str(tmp_path / \"temp1.ppm\")\n    path2 = str(tmp_path / \"temp2.ppm\")\n    comment = b\"# comment\" * comment_count\n    with open(path1, \"wb\") as f1, open(path2, \"wb\") as f2:\n        f1.write(header + b\"\\n\\n\" + data)\n        f2.write(header + b\"\\n\" + comment + b\"\\n\" + data + comment)\n\n    with Image.open(path1) as im:\n        assert_image_equal_tofile(im, path2)\n\n\n@pytest.mark.parametrize(\"data\", (b\"P1\\n128 128\\n\", b\"P3\\n128 128\\n255\\n\"))\ndef test_plain_truncated_data(tmp_path, data):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(data)\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\n@pytest.mark.parametrize(\"data\", (b\"P1\\n128 128\\n1009\", b\"P3\\n128 128\\n255\\n100A\"))\ndef test_plain_invalid_data(tmp_path, data):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(data)\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\n@pytest.mark.parametrize(\n    \"data\",\n    (\n        b\"P3\\n128 128\\n255\\n012345678910\",  # half token too long\n        b\"P3\\n128 128\\n255\\n012345678910 0\",  # token too long\n    ),\n)\ndef test_plain_ppm_token_too_long(tmp_path, data):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(data)\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\ndef test_plain_ppm_value_too_large(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P3\\n128 128\\n255\\n256\")\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\ndef test_magic():\n    with pytest.raises(SyntaxError):\n        PpmImagePlugin.PpmImageFile(fp=BytesIO(b\"PyInvalid\"))\n\n\ndef test_header_with_comments(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6 #comment\\n#comment\\r12#comment\\r8\\n128 #comment\\n255\\n\")\n\n    with Image.open(path) as im:\n        assert im.size == (128, 128)\n\n\ndef test_non_integer_token(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6\\nTEST\")\n\n    with pytest.raises(ValueError):\n        with Image.open(path):\n            pass\n\n\ndef test_header_token_too_long(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6\\n 01234567890\")\n\n    with pytest.raises(ValueError) as e:\n        with Image.open(path):\n            pass\n\n    assert str(e.value) == \"Token too long in file header: 01234567890\"\n\n\ndef test_truncated_file(tmp_path):\n    # Test EOF in header\n    path = str(tmp_path / \"temp.pgm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6\")\n\n    with pytest.raises(ValueError) as e:\n        with Image.open(path):\n            pass\n\n    assert str(e.value) == \"Reached EOF while reading header\"\n\n    # Test EOF for PyDecoder\n    fp = BytesIO(b\"P5 3 1 4\")\n    with Image.open(fp) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\ndef test_not_enough_image_data(tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P2 1 2 255 255\")\n\n    with Image.open(path) as im:\n        with pytest.raises(ValueError):\n            im.load()\n\n\n@pytest.mark.parametrize(\"maxval\", (b\"0\", b\"65536\"))\ndef test_invalid_maxval(maxval, tmp_path):\n    path = str(tmp_path / \"temp.ppm\")\n    with open(path, \"wb\") as f:\n        f.write(b\"P6\\n3 1 \" + maxval)\n\n    with pytest.raises(ValueError) as e:\n        with Image.open(path):\n            pass\n\n    assert str(e.value) == \"maxval must be greater than 0 and less than 65536\"\n\n\ndef test_neg_ppm():\n    # Storage.c accepted negative values for xsize, ysize.  the\n    # internal open_ppm function didn't check for sanity but it\n    # has been removed. The default opener doesn't accept negative\n    # sizes.\n\n    with pytest.raises(OSError):\n        with Image.open(\"Tests/images/negative_size.ppm\"):\n            pass\n\n\ndef test_mimetypes(tmp_path):\n    path = str(tmp_path / \"temp.pgm\")\n\n    with open(path, \"wb\") as f:\n        f.write(b\"P4\\n128 128\\n255\")\n    with Image.open(path) as im:\n        assert im.get_format_mimetype() == \"image/x-portable-bitmap\"\n\n    with open(path, \"wb\") as f:\n        f.write(b\"PyCMYK\\n128 128\\n255\")\n    with Image.open(path) as im:\n        assert im.get_format_mimetype() == \"image/x-portable-anymap\"\n\n\n@pytest.mark.parametrize(\"buffer\", (True, False))\ndef test_save_stdout(buffer):\n    old_stdout = sys.stdout\n\n    if buffer:\n\n        class MyStdOut:\n            buffer = BytesIO()\n\n        mystdout = MyStdOut()\n    else:\n        mystdout = BytesIO()\n\n    sys.stdout = mystdout\n\n    with Image.open(TEST_FILE) as im:\n        im.save(sys.stdout, \"PPM\")\n\n    # Reset stdout\n    sys.stdout = old_stdout\n\n    if buffer:\n        mystdout = mystdout.buffer\n    with Image.open(mystdout) as reloaded:\n        assert_image_equal_tofile(reloaded, TEST_FILE)\n"
    ],
    "test_file_names": [
        "test_1.py"
    ],
    "setup_res_code": 0,
    "setup_res_stdout": "Starting Pillow installation attempts...\nVerifying Pillow installation...\n✅ Pillow installation successful!\nSuccessfully installed Pillow\n",
    "setup_res_stderr": "Using CPython 3.9.21\nCreating virtual environment at: .venv\nActivate with: source .venv/bin/activate\nResolved 12 packages in 16ms\nInstalled 12 packages in 82ms\n + coverage==7.6.10\n + exceptiongroup==1.2.2\n + iniconfig==2.0.0\n + packaging==24.2\n + pluggy==1.5.0\n + pyqt5==5.15.11\n + pyqt5-qt5==5.15.16\n + pyqt5-sip==12.16.1\n + pytest==8.3.4\n + pytest-cov==6.0.0\n + setuptools==75.8.0\n + tomli==2.2.1\nResolved 1 package in 660ms\nPrepared 1 package in 32.39s\nInstalled 1 package in 0.94ms\n + pillow==9.5.0.dev0 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_5932a0bd19b513c3c86d9fc8dec5d41384247056)\n",
    "new_commit_res_code": 0,
    "new_commit_res_stdout": "\u001b[1m============================= test session starts ==============================\u001b[0m\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_5932a0bd19b513c3c86d9fc8dec5d41384247056\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 38 items\n\nr2e_tests/test_1.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m               [100%]\u001b[0m\n\n==================================== PASSES ====================================\n\u001b[36m\u001b[1m=========================== short test summary info ============================\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_sanity\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P2 3 1 4 0 2 4-L-pixels0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P2 3 1 257 0 128 257-I-pixels1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P3 3 1 17 0 1 2 8 9 10 15 16 17-RGB-pixels2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P3 3 1 257 0 1 2 128 129 130 256 257 257-RGB-pixels3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P5 3 1 4 \\x00\\x02\\x04-L-pixels4]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P5 3 1 257 \\x00\\x00\\x00\\x80\\x01\\x01-I-pixels5]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P6 3 1 17 \\x00\\x01\\x02\\x08\\t\\n\\x0f\\x10\\x11-RGB-pixels6]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P6 3 1 257 \\x00\\x00\\x00\\x01\\x00\\x02\\x00\\x80\\x00\\x81\\x00\\x82\\x01\\x00\\x01\\x01\\xff\\xff-RGB-pixels7]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_16bit_pgm\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_16bit_pgm_write\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pnm\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain[Tests/images/hopper_1bit_plain.pbm-Tests/images/hopper_1bit.pbm]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain[Tests/images/hopper_8bit_plain.pgm-Tests/images/hopper_8bit.pgm]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain[Tests/images/hopper_8bit_plain.ppm-Tests/images/hopper_8bit.ppm]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_16bit_plain_pgm\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_data_with_comment[P1\\n2 2-1010-1000000]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_data_with_comment[P2\\n3 1\\n4-0 2 4-1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_data_with_comment[P3\\n2 2\\n255-0 0 0 001 1 1 2 2 2 255 255 255-1000000]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_truncated_data[P1\\n128 128\\n]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_truncated_data[P3\\n128 128\\n255\\n]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_invalid_data[P1\\n128 128\\n1009]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_invalid_data[P3\\n128 128\\n255\\n100A]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_ppm_token_too_long[P3\\n128 128\\n255\\n012345678910]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_ppm_token_too_long[P3\\n128 128\\n255\\n012345678910 0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_ppm_value_too_large\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_magic\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_header_with_comments\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_non_integer_token\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_header_token_too_long\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_truncated_file\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_not_enough_image_data\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_invalid_maxval[0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_invalid_maxval[65536]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_neg_ppm\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mimetypes\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_save_stdout[True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_save_stdout[False]\u001b[0m\n\u001b[32m============================== \u001b[32m\u001b[1m38 passed\u001b[0m\u001b[32m in 0.44s\u001b[0m\u001b[32m ==============================\u001b[0m\n",
    "new_commit_res_stderr": "",
    "old_commit_res_code": 1,
    "old_commit_res_stdout": "\u001b[1m============================= test session starts ==============================\u001b[0m\nplatform linux -- Python 3.9.21, pytest-8.3.4, pluggy-1.5.0\nrootdir: /home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_5932a0bd19b513c3c86d9fc8dec5d41384247056\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 38 items\n\nr2e_tests/test_1.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31mF\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31m               [100%]\u001b[0m\n\n=================================== FAILURES ===================================\n\u001b[31m\u001b[1m__________________________ test_not_enough_image_data __________________________\u001b[0m\n\ntmp_path = PosixPath('/tmp/pytest-of-gcpuser/pytest-284/test_not_enough_image_data0')\n\n    def test_not_enough_image_data(tmp_path):\n        path = str(tmp_path / \"temp.ppm\")\n        with open(path, \"wb\") as f:\n            f.write(b\"P2 1 2 255 255\")\n    \n        with Image.open(path) as im:\n            with pytest.raises(ValueError):\n>               im.load()\n\u001b[1m\u001b[31mE               Failed: DID NOT RAISE <class 'ValueError'>\u001b[0m\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:266: Failed\n==================================== PASSES ====================================\n\u001b[36m\u001b[1m=========================== short test summary info ============================\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_sanity\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P2 3 1 4 0 2 4-L-pixels0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P2 3 1 257 0 128 257-I-pixels1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P3 3 1 17 0 1 2 8 9 10 15 16 17-RGB-pixels2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P3 3 1 257 0 1 2 128 129 130 256 257 257-RGB-pixels3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P5 3 1 4 \\x00\\x02\\x04-L-pixels4]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P5 3 1 257 \\x00\\x00\\x00\\x80\\x01\\x01-I-pixels5]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P6 3 1 17 \\x00\\x01\\x02\\x08\\t\\n\\x0f\\x10\\x11-RGB-pixels6]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_arbitrary_maxval[P6 3 1 257 \\x00\\x00\\x00\\x01\\x00\\x02\\x00\\x80\\x00\\x81\\x00\\x82\\x01\\x00\\x01\\x01\\xff\\xff-RGB-pixels7]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_16bit_pgm\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_16bit_pgm_write\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pnm\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain[Tests/images/hopper_1bit_plain.pbm-Tests/images/hopper_1bit.pbm]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain[Tests/images/hopper_8bit_plain.pgm-Tests/images/hopper_8bit.pgm]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain[Tests/images/hopper_8bit_plain.ppm-Tests/images/hopper_8bit.ppm]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_16bit_plain_pgm\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_data_with_comment[P1\\n2 2-1010-1000000]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_data_with_comment[P2\\n3 1\\n4-0 2 4-1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_data_with_comment[P3\\n2 2\\n255-0 0 0 001 1 1 2 2 2 255 255 255-1000000]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_truncated_data[P1\\n128 128\\n]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_truncated_data[P3\\n128 128\\n255\\n]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_invalid_data[P1\\n128 128\\n1009]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_invalid_data[P3\\n128 128\\n255\\n100A]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_ppm_token_too_long[P3\\n128 128\\n255\\n012345678910]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_ppm_token_too_long[P3\\n128 128\\n255\\n012345678910 0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_plain_ppm_value_too_large\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_magic\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_header_with_comments\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_non_integer_token\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_header_token_too_long\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_truncated_file\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_invalid_maxval[0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_invalid_maxval[65536]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_neg_ppm\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_mimetypes\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_save_stdout[True]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_save_stdout[False]\u001b[0m\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_not_enough_image_data\u001b[0m - Failed: DID NOT RAISE <class 'ValueError'>\n\u001b[31m========================= \u001b[31m\u001b[1m1 failed\u001b[0m, \u001b[32m37 passed\u001b[0m\u001b[31m in 0.33s\u001b[0m\u001b[31m =========================\u001b[0m\n",
    "old_commit_res_stderr": ""
}
Tests/test_file_ppm.py docs/releasenotes/9.5.0.rst src/PIL/PpmImagePlugin.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]

**Title:** PPM Decoder Does Not Raise ValueError for Incomplete Image Data

**Description:**
When attempting to load a PPM image with insufficient pixel data, the decoder fails to raise a `ValueError`. This can lead to unexpected behavior or crashes downstream when the image data is used without proper validation.

**Example Code:**
```python
path = "temp.ppm"
with open(path, "wb") as f:
    f.write(b"P2 1 2 255 255")  # Incomplete pixel data

with Image.open(path) as im:
    im.load()  # Expected to raise ValueError due to insufficient data
```

**Expected Behavior:**
Loading the image should raise a `ValueError` indicating that there is not enough image data to complete the loading process.

**Actual Behavior:**
No error is raised, allowing the program to proceed despite the incomplete image data.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### src/PIL/PpmImagePlugin.py
#
# The Python Imaging Library.
# $Id$
#
# PPM support for PIL
#
# History:
#       96-03-24 fl     Created
#       98-03-06 fl     Write RGBA images (as RGB, that is)
#
# Copyright (c) Secret Labs AB 1997-98.
# Copyright (c) Fredrik Lundh 1996.
#
# See the README file for information on usage and redistribution.
#


from . import Image, ImageFile
from ._binary import i16be as i16
from ._binary import o8
from ._binary import o32le as o32

#
# --------------------------------------------------------------------

b_whitespace = b"\x20\x09\x0a\x0b\x0c\x0d"

MODES = {
    # standard
    b"P1": "1",
    b"P2": "L",
    b"P3": "RGB",
    b"P4": "1",
    b"P5": "L",
    b"P6": "RGB",
    # extensions
    b"P0CMYK": "CMYK",
    # PIL extensions (for test purposes only)
    b"PyP": "P",
    b"PyRGBA": "RGBA",
    b"PyCMYK": "CMYK",
}


def _accept(prefix):
    return prefix[0:1] == b"P" and prefix[1] in b"0123456y"


##
# Image plugin for PBM, PGM, and PPM images.


class PpmImageFile(ImageFile.ImageFile):
    format = "PPM"
    format_description = "Pbmplus image"

    def _read_magic(self):
        magic = b""
        # read until whitespace or longest available magic number
        for _ in range(6):
            c = self.fp.read(1)
            if not c or c in b_whitespace:
                break
            magic += c
        return magic

    def _read_token(self):
        token = b""
        while len(token) <= 10:  # read until next whitespace or limit of 10 characters
            c = self.fp.read(1)
            if not c:
                break
            elif c in b_whitespace:  # token ended
                if not token:
                    # skip whitespace at start
                    continue
                break
            elif c == b"#":
                # ignores rest of the line; stops at CR, LF or EOF
                while self.fp.read(1) not in b"\r\n":
                    pass
                continue
            token += c
        if not token:
            # Token was not even 1 byte
            msg = "Reached EOF while reading header"
            raise ValueError(msg)
        elif len(token) > 10:
            msg = f"Token too long in file header: {token.decode()}"
            raise ValueError(msg)
        return token

    def _open(self):
        magic_number = self._read_magic()
        try:
            mode = MODES[magic_number]
        except KeyError:
            msg = "not a PPM file"
            raise SyntaxError(msg)

        if magic_number in (b"P1", b"P4"):
            self.custom_mimetype = "image/x-portable-bitmap"
        elif magic_number in (b"P2", b"P5"):
            self.custom_mimetype = "image/x-portable-graymap"
        elif magic_number in (b"P3", b"P6"):
            self.custom_mimetype = "image/x-portable-pixmap"

        maxval = None
        decoder_name = "raw"
        if magic_number in (b"P1", b"P2", b"P3"):
            decoder_name = "ppm_plain"
        for ix in range(3):
            token = int(self._read_token())
            if ix == 0:  # token is the x size
                xsize = token
            elif ix == 1:  # token is the y size
                ysize = token
                if mode == "1":
                    self.mode = "1"
                    rawmode = "1;I"
                    break
                else:
                    self.mode = rawmode = mode
            elif ix == 2:  # token is maxval
                maxval = token
                if not 0 < maxval < 65536:
                    msg = "maxval must be greater than 0 and less than 65536"
                    raise ValueError(msg)
                if maxval > 255 and mode == "L":
                    self.mode = "I"

                if decoder_name != "ppm_plain":
                    # If maxval matches a bit depth, use the raw decoder directly
                    if maxval == 65535 and mode == "L":
                        rawmode = "I;16B"
                    elif maxval != 255:
                        decoder_name = "ppm"

        args = (rawmode, 0, 1) if decoder_name == "raw" else (rawmode, maxval)
        self._size = xsize, ysize
        self.tile = [(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), args)]


#
# --------------------------------------------------------------------


class PpmPlainDecoder(ImageFile.PyDecoder):
    _pulls_fd = True

    def _read_block(self):
        return self.fd.read(ImageFile.SAFEBLOCK)

    def _find_comment_end(self, block, start=0):
        a = block.find(b"\n", start)
        b = block.find(b"\r", start)
        return min(a, b) if a * b > 0 else max(a, b)  # lowest nonnegative index (or -1)

    def _ignore_comments(self, block):
        if self._comment_spans:
            # Finish current comment
            while block:
                comment_end = self._find_comment_end(block)
                if comment_end != -1:
                    # Comment ends in this block
                    # Delete tail of comment
                    block = block[comment_end + 1 :]
                    break
                else:
                    # Comment spans whole block
                    # So read the next block, looking for the end
                    block = self._read_block()

        # Search for any further comments
        self._comment_spans = False
        while True:
            comment_start = block.find(b"#")
            if comment_start == -1:
                # No comment found
                break
            comment_end = self._find_comment_end(block, comment_start)
            if comment_end != -1:
                # Comment ends in this block
                # Delete comment
                block = block[:comment_start] + block[comment_end + 1 :]
            else:
                # Comment continues to next block(s)
                block = block[:comment_start]
                self._comment_spans = True
                break
        return block

    def _decode_bitonal(self):
        """
        This is a separate method because in the plain PBM format, all data tokens are
        exactly one byte, so the inter-token whitespace is optional.
        """
        data = bytearray()
        total_bytes = self.state.xsize * self.state.ysize

        while len(data) != total_bytes:
            block = self._read_block()  # read next block
            if not block:
                # eof
                break

            block = self._ignore_comments(block)

            tokens = b"".join(block.split())
            for token in tokens:
                if token not in (48, 49):
                    msg = b"Invalid token for this mode: %s" % bytes([token])
                    raise ValueError(msg)
            data = (data + tokens)[:total_bytes]
        invert = bytes.maketrans(b"01", b"\xFF\x00")
        return data.translate(invert)

    def _decode_blocks(self, maxval):
        data = bytearray()
        max_len = 10
        out_byte_count = 4 if self.mode == "I" else 1
        out_max = 65535 if self.mode == "I" else 255
        bands = Image.getmodebands(self.mode)
        total_bytes = self.state.xsize * self.state.ysize * bands * out_byte_count

        half_token = False
        while len(data) != total_bytes:
            block = self._read_block()  # read next block
            if not block:
                if half_token:
                    block = bytearray(b" ")  # flush half_token
                else:
                    # eof
                    break

            block = self._ignore_comments(block)

            if half_token:
                block = half_token + block  # stitch half_token to new block

            tokens = block.split()

            if block and not block[-1:].isspace():  # block might split token
                half_token = tokens.pop()  # save half token for later
                if len(half_token) > max_len:  # prevent buildup of half_token
                    msg = (
                        b"Token too long found in data: %s" % half_token[: max_len + 1]
                    )
                    raise ValueError(msg)

            for token in tokens:
                if len(token) > max_len:
                    msg = b"Token too long found in data: %s" % token[: max_len + 1]
                    raise ValueError(msg)
                value = int(token)
                if value > maxval:
                    msg = f"Channel value too large for this mode: {value}"
                    raise ValueError(msg)
                value = round(value / maxval * out_max)
                data += o32(value) if self.mode == "I" else o8(value)
                if len(data) == total_bytes:  # finished!
                    break
        return data

    def decode(self, buffer):
        self._comment_spans = False
        if self.mode == "1":
            data = self._decode_bitonal()
            rawmode = "1;8"
        else:
            maxval = self.args[-1]
            data = self._decode_blocks(maxval)
            rawmode = "I;32" if self.mode == "I" else self.mode
        self.set_as_raw(bytes(data), rawmode)
        return -1, 0


class PpmDecoder(ImageFile.PyDecoder):
    _pulls_fd = True

    def decode(self, buffer):
        data = bytearray()
        maxval = self.args[-1]
        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)
        return -1, 0


#
# --------------------------------------------------------------------


def _save(im, fp, filename):
    if im.mode == "1":
        rawmode, head = "1;I", b"P4"
    elif im.mode == "L":
        rawmode, head = "L", b"P5"
    elif im.mode == "I":
        rawmode, head = "I;16B", b"P5"
    elif im.mode in ("RGB", "RGBA"):
        rawmode, head = "RGB", b"P6"
    else:
        msg = f"cannot write mode {im.mode} as PPM"
        raise OSError(msg)
    fp.write(head + b"\n%d %d\n" % im.size)
    if head == b"P6":
        fp.write(b"255\n")
    elif head == b"P5":
        if rawmode == "L":
            fp.write(b"255\n")
        else:
            fp.write(b"65535\n")
    ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])

    # ALTERNATIVE: save via builtin debug function
    # im._dump(filename)


#
# --------------------------------------------------------------------


Image.register_open(PpmImageFile.format, PpmImageFile, _accept)
Image.register_save(PpmImageFile.format, _save)

Image.register_decoder("ppm", PpmDecoder)
Image.register_decoder("ppm_plain", PpmPlainDecoder)

Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm", ".pnm"])

Image.register_mime(PpmImageFile.format, "image/x-portable-anymap")

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
[ISSUE]

**Title:** PPM Decoder Does Not Raise ValueError for Incomplete Image Data

**Description:**
When attempting to load a PPM image with insufficient pixel data, the decoder fails to raise a `ValueError`. This can lead to unexpected behavior or crashes downstream when the image data is used without proper validation.

**Example Code:**
```python
path = "temp.ppm"
with open(path, "wb") as f:
    f.write(b"P2 1 2 255 255")  # Incomplete pixel data

with Image.open(path) as im:
    im.load()  # Expected to raise ValueError due to insufficient data
```

**Expected Behavior:**
Loading the image should raise a `ValueError` indicating that there is not enough image data to complete the loading process.

**Actual Behavior:**
No error is raised, allowing the program to proceed despite the incomplete image data.

[/ISSUE]
{
    "\u001b[1mtest_sanity\u001b[0m": "PASSED",
    "\u001b[1mtest_arbitrary_maxval[P2 3 1 4 0 2 4-L-pixels0]\u001b[0m": "PASSED",
    "\u001b[1mtest_arbitrary_maxval[P2 3 1 257 0 128 257-I-pixels1]\u001b[0m": "PASSED",
    "\u001b[1mtest_arbitrary_maxval[P3 3 1 17 0 1 2 8 9 10 15 16 17-RGB-pixels2]\u001b[0m": "PASSED",
    "\u001b[1mtest_arbitrary_maxval[P3 3 1 257 0 1 2 128 129 130 256 257 257-RGB-pixels3]\u001b[0m": "PASSED",
    "\u001b[1mtest_arbitrary_maxval[P5 3 1 4 \\x00\\x02\\x04-L-pixels4]\u001b[0m": "PASSED",
    "\u001b[1mtest_arbitrary_maxval[P5 3 1 257 \\x00\\x00\\x00\\x80\\x01\\x01-I-pixels5]\u001b[0m": "PASSED",
    "\u001b[1mtest_arbitrary_maxval[P6 3 1 17 \\x00\\x01\\x02\\x08\\t\\n\\x0f\\x10\\x11-RGB-pixels6]\u001b[0m": "PASSED",
    "\u001b[1mtest_arbitrary_maxval[P6 3 1 257 \\x00\\x00\\x00\\x01\\x00\\x02\\x00\\x80\\x00\\x81\\x00\\x82\\x01\\x00\\x01\\x01\\xff\\xff-RGB-pixels7]\u001b[0m": "PASSED",
    "\u001b[1mtest_16bit_pgm\u001b[0m": "PASSED",
    "\u001b[1mtest_16bit_pgm_write\u001b[0m": "PASSED",
    "\u001b[1mtest_pnm\u001b[0m": "PASSED",
    "\u001b[1mtest_plain[Tests/images/hopper_1bit_plain.pbm-Tests/images/hopper_1bit.pbm]\u001b[0m": "PASSED",
    "\u001b[1mtest_plain[Tests/images/hopper_8bit_plain.pgm-Tests/images/hopper_8bit.pgm]\u001b[0m": "PASSED",
    "\u001b[1mtest_plain[Tests/images/hopper_8bit_plain.ppm-Tests/images/hopper_8bit.ppm]\u001b[0m": "PASSED",
    "\u001b[1mtest_16bit_plain_pgm\u001b[0m": "PASSED",
    "\u001b[1mtest_plain_data_with_comment[P1\\n2 2-1010-1000000]\u001b[0m": "PASSED",
    "\u001b[1mtest_plain_data_with_comment[P2\\n3 1\\n4-0 2 4-1]\u001b[0m": "PASSED",
    "\u001b[1mtest_plain_data_with_comment[P3\\n2 2\\n255-0 0 0 001 1 1 2 2 2 255 255 255-1000000]\u001b[0m": "PASSED",
    "\u001b[1mtest_plain_truncated_data[P1\\n128 128\\n]\u001b[0m": "PASSED",
    "\u001b[1mtest_plain_truncated_data[P3\\n128 128\\n255\\n]\u001b[0m": "PASSED",
    "\u001b[1mtest_plain_invalid_data[P1\\n128 128\\n1009]\u001b[0m": "PASSED",
    "\u001b[1mtest_plain_invalid_data[P3\\n128 128\\n255\\n100A]\u001b[0m": "PASSED",
    "\u001b[1mtest_plain_ppm_token_too_long[P3\\n128 128\\n255\\n012345678910]\u001b[0m": "PASSED",
    "\u001b[1mtest_plain_ppm_token_too_long[P3\\n128 128\\n255\\n012345678910 0]\u001b[0m": "PASSED",
    "\u001b[1mtest_plain_ppm_value_too_large\u001b[0m": "PASSED",
    "\u001b[1mtest_magic\u001b[0m": "PASSED",
    "\u001b[1mtest_header_with_comments\u001b[0m": "PASSED",
    "\u001b[1mtest_non_integer_token\u001b[0m": "PASSED",
    "\u001b[1mtest_header_token_too_long\u001b[0m": "PASSED",
    "\u001b[1mtest_truncated_file\u001b[0m": "PASSED",
    "\u001b[1mtest_not_enough_image_data\u001b[0m": "PASSED",
    "\u001b[1mtest_invalid_maxval[0]\u001b[0m": "PASSED",
    "\u001b[1mtest_invalid_maxval[65536]\u001b[0m": "PASSED",
    "\u001b[1mtest_neg_ppm\u001b[0m": "PASSED",
    "\u001b[1mtest_mimetypes\u001b[0m": "PASSED",
    "\u001b[1mtest_save_stdout[True]\u001b[0m": "PASSED",
    "\u001b[1mtest_save_stdout[False]\u001b[0m": "PASSED"
}
diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py
index 5aa418044..2cb1e5636 100644
--- a/src/PIL/PpmImagePlugin.py
+++ b/src/PIL/PpmImagePlugin.py
@@ -237,6 +237,7 @@ class PpmPlainDecoder(ImageFile.PyDecoder):
 
             if half_token:
                 block = half_token + block  # stitch half_token to new block
+                half_token = False
 
             tokens = block.split()
 
 

{"src/PIL/PpmImagePlugin.py": "#\n# The Python Imaging Library.\n# $Id$\n#\n# PPM support for PIL\n#\n# History:\n#       96-03-24 fl     Created\n#       98-03-06 fl     Write RGBA images (as RGB, that is)\n#\n# Copyright (c) Secret Labs AB 1997-98.\n# Copyright (c) Fredrik Lundh 1996.\n#\n# See the README file for information on usage and redistribution.\n#\n\n\nfrom . import Image, ImageFile\nfrom ._binary import i16be as i16\nfrom ._binary import o8\nfrom ._binary import o32le as o32\n\n#\n# --------------------------------------------------------------------\n\nb_whitespace = b\"\\x20\\x09\\x0a\\x0b\\x0c\\x0d\"\n\nMODES = {\n    # standard\n    b\"P1\": \"1\",\n    b\"P2\": \"L\",\n    b\"P3\": \"RGB\",\n    b\"P4\": \"1\",\n    b\"P5\": \"L\",\n    b\"P6\": \"RGB\",\n    # extensions\n    b\"P0CMYK\": \"CMYK\",\n    # PIL extensions (for test purposes only)\n    b\"PyP\": \"P\",\n    b\"PyRGBA\": \"RGBA\",\n    b\"PyCMYK\": \"CMYK\",\n}\n\n\ndef _accept(prefix):\n    return prefix[0:1] == b\"P\" and prefix[1] in b\"0123456y\"\n\n\n##\n# Image plugin for PBM, PGM, and PPM images.\n\n\nclass PpmImageFile(ImageFile.ImageFile):\n    format = \"PPM\"\n    format_description = \"Pbmplus image\"\n\n    def _read_magic(self):\n        magic = b\"\"\n        # read until whitespace or longest available magic number\n        for _ in range(6):\n            c = self.fp.read(1)\n            if not c or c in b_whitespace:\n                break\n            magic += c\n        return magic\n\n    def _read_token(self):\n        token = b\"\"\n        while len(token) <= 10:  # read until next whitespace or limit of 10 characters\n            c = self.fp.read(1)\n            if not c:\n                break\n            elif c in b_whitespace:  # token ended\n                if not token:\n                    # skip whitespace at start\n                    continue\n                break\n            elif c == b\"#\":\n                # ignores rest of the line; stops at CR, LF or EOF\n                while self.fp.read(1) not in b\"\\r\\n\":\n                    pass\n                continue\n            token += c\n        if not token:\n            # Token was not even 1 byte\n            msg = \"Reached EOF while reading header\"\n            raise ValueError(msg)\n        elif len(token) > 10:\n            msg = f\"Token too long in file header: {token.decode()}\"\n            raise ValueError(msg)\n        return token\n\n    def _open(self):\n        magic_number = self._read_magic()\n        try:\n            mode = MODES[magic_number]\n        except KeyError:\n            msg = \"not a PPM file\"\n            raise SyntaxError(msg)\n\n        if magic_number in (b\"P1\", b\"P4\"):\n            self.custom_mimetype = \"image/x-portable-bitmap\"\n        elif magic_number in (b\"P2\", b\"P5\"):\n            self.custom_mimetype = \"image/x-portable-graymap\"\n        elif magic_number in (b\"P3\", b\"P6\"):\n            self.custom_mimetype = \"image/x-portable-pixmap\"\n\n        maxval = None\n        decoder_name = \"raw\"\n        if magic_number in (b\"P1\", b\"P2\", b\"P3\"):\n            decoder_name = \"ppm_plain\"\n        for ix in range(3):\n            token = int(self._read_token())\n            if ix == 0:  # token is the x size\n                xsize = token\n            elif ix == 1:  # token is the y size\n                ysize = token\n                if mode == \"1\":\n                    self.mode = \"1\"\n                    rawmode = \"1;I\"\n                    break\n                else:\n                    self.mode = rawmode = mode\n            elif ix == 2:  # token is maxval\n                maxval = token\n                if not 0 < maxval < 65536:\n                    msg = \"maxval must be greater than 0 and less than 65536\"\n                    raise ValueError(msg)\n                if maxval > 255 and mode == \"L\":\n                    self.mode = \"I\"\n\n                if decoder_name != \"ppm_plain\":\n                    # If maxval matches a bit depth, use the raw decoder directly\n                    if maxval == 65535 and mode == \"L\":\n                        rawmode = \"I;16B\"\n                    elif maxval != 255:\n                        decoder_name = \"ppm\"\n\n        args = (rawmode, 0, 1) if decoder_name == \"raw\" else (rawmode, maxval)\n        self._size = xsize, ysize\n        self.tile = [(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), args)]\n\n\n#\n# --------------------------------------------------------------------\n\n\nclass PpmPlainDecoder(ImageFile.PyDecoder):\n    _pulls_fd = True\n\n    def _read_block(self):\n        return self.fd.read(ImageFile.SAFEBLOCK)\n\n    def _find_comment_end(self, block, start=0):\n        a = block.find(b\"\\n\", start)\n        b = block.find(b\"\\r\", start)\n        return min(a, b) if a * b > 0 else max(a, b)  # lowest nonnegative index (or -1)\n\n    def _ignore_comments(self, block):\n        if self._comment_spans:\n            # Finish current comment\n            while block:\n                comment_end = self._find_comment_end(block)\n                if comment_end != -1:\n                    # Comment ends in this block\n                    # Delete tail of comment\n                    block = block[comment_end + 1 :]\n                    break\n                else:\n                    # Comment spans whole block\n                    # So read the next block, looking for the end\n                    block = self._read_block()\n\n        # Search for any further comments\n        self._comment_spans = False\n        while True:\n            comment_start = block.find(b\"#\")\n            if comment_start == -1:\n                # No comment found\n                break\n            comment_end = self._find_comment_end(block, comment_start)\n            if comment_end != -1:\n                # Comment ends in this block\n                # Delete comment\n                block = block[:comment_start] + block[comment_end + 1 :]\n            else:\n                # Comment continues to next block(s)\n                block = block[:comment_start]\n                self._comment_spans = True\n                break\n        return block\n\n    def _decode_bitonal(self):\n        \"\"\"\n        This is a separate method because in the plain PBM format, all data tokens are\n        exactly one byte, so the inter-token whitespace is optional.\n        \"\"\"\n        data = bytearray()\n        total_bytes = self.state.xsize * self.state.ysize\n\n        while len(data) != total_bytes:\n            block = self._read_block()  # read next block\n            if not block:\n                # eof\n                break\n\n            block = self._ignore_comments(block)\n\n            tokens = b\"\".join(block.split())\n            for token in tokens:\n                if token not in (48, 49):\n                    msg = b\"Invalid token for this mode: %s\" % bytes([token])\n                    raise ValueError(msg)\n            data = (data + tokens)[:total_bytes]\n        invert = bytes.maketrans(b\"01\", b\"\\xFF\\x00\")\n        return data.translate(invert)\n\n    def _decode_blocks(self, maxval):\n        data = bytearray()\n        max_len = 10\n        out_byte_count = 4 if self.mode == \"I\" else 1\n        out_max = 65535 if self.mode == \"I\" else 255\n        bands = Image.getmodebands(self.mode)\n        total_bytes = self.state.xsize * self.state.ysize * bands * out_byte_count\n\n        half_token = False\n        while len(data) != total_bytes:\n            block = self._read_block()  # read next block\n            if not block:\n                if half_token:\n                    block = bytearray(b\" \")  # flush half_token\n                else:\n                    # eof\n                    break\n\n            block = self._ignore_comments(block)\n\n            if half_token:\n                block = half_token + block  # stitch half_token to new block\n\n            tokens = block.split()\n\n            if block and not block[-1:].isspace():  # block might split token\n                half_token = tokens.pop()  # save half token for later\n                if len(half_token) > max_len:  # prevent buildup of half_token\n                    msg = (\n                        b\"Token too long found in data: %s\" % half_token[: max_len + 1]\n                    )\n                    raise ValueError(msg)\n\n            for token in tokens:\n                if len(token) > max_len:\n                    msg = b\"Token too long found in data: %s\" % token[: max_len + 1]\n                    raise ValueError(msg)\n                value = int(token)\n                if value > maxval:\n                    msg = f\"Channel value too large for this mode: {value}\"\n                    raise ValueError(msg)\n                value = round(value / maxval * out_max)\n                data += o32(value) if self.mode == \"I\" else o8(value)\n                if len(data) == total_bytes:  # finished!\n                    break\n        return data\n\n    def decode(self, buffer):\n        self._comment_spans = False\n        if self.mode == \"1\":\n            data = self._decode_bitonal()\n            rawmode = \"1;8\"\n        else:\n            maxval = self.args[-1]\n            data = self._decode_blocks(maxval)\n            rawmode = \"I;32\" if self.mode == \"I\" else self.mode\n        self.set_as_raw(bytes(data), rawmode)\n        return -1, 0\n\n\nclass PpmDecoder(ImageFile.PyDecoder):\n    _pulls_fd = True\n\n    def decode(self, buffer):\n        data = bytearray()\n        maxval = self.args[-1]\n        in_byte_count = 1 if maxval < 256 else 2\n        out_byte_count = 4 if self.mode == \"I\" else 1\n        out_max = 65535 if self.mode == \"I\" else 255\n        bands = Image.getmodebands(self.mode)\n        while len(data) < self.state.xsize * self.state.ysize * bands * out_byte_count:\n            pixels = self.fd.read(in_byte_count * bands)\n            if len(pixels) < in_byte_count * bands:\n                # eof\n                break\n            for b in range(bands):\n                value = (\n                    pixels[b] if in_byte_count == 1 else i16(pixels, b * in_byte_count)\n                )\n                value = min(out_max, round(value / maxval * out_max))\n                data += o32(value) if self.mode == \"I\" else o8(value)\n        rawmode = \"I;32\" if self.mode == \"I\" else self.mode\n        self.set_as_raw(bytes(data), rawmode)\n        return -1, 0\n\n\n#\n# --------------------------------------------------------------------\n\n\ndef _save(im, fp, filename):\n    if im.mode == \"1\":\n        rawmode, head = \"1;I\", b\"P4\"\n    elif im.mode == \"L\":\n        rawmode, head = \"L\", b\"P5\"\n    elif im.mode == \"I\":\n        rawmode, head = \"I;16B\", b\"P5\"\n    elif im.mode in (\"RGB\", \"RGBA\"):\n        rawmode, head = \"RGB\", b\"P6\"\n    else:\n        msg = f\"cannot write mode {im.mode} as PPM\"\n        raise OSError(msg)\n    fp.write(head + b\"\\n%d %d\\n\" % im.size)\n    if head == b\"P6\":\n        fp.write(b\"255\\n\")\n    elif head == b\"P5\":\n        if rawmode == \"L\":\n            fp.write(b\"255\\n\")\n        else:\n            fp.write(b\"65535\\n\")\n    ImageFile._save(im, fp, [(\"raw\", (0, 0) + im.size, 0, (rawmode, 0, 1))])\n\n    # ALTERNATIVE: save via builtin debug function\n    # im._dump(filename)\n\n\n#\n# --------------------------------------------------------------------\n\n\nImage.register_open(PpmImageFile.format, PpmImageFile, _accept)\nImage.register_save(PpmImageFile.format, _save)\n\nImage.register_decoder(\"ppm\", PpmDecoder)\nImage.register_decoder(\"ppm_plain\", PpmPlainDecoder)\n\nImage.register_extensions(PpmImageFile.format, [\".pbm\", \".pgm\", \".ppm\", \".pnm\"])\n\nImage.register_mime(PpmImageFile.format, \"image/x-portable-anymap\")\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

Your response format must follow the template below:
<think>
Your thoughts or/and draft, like working through an exercise on scratch paper. Be as casual and as long as you want until you are confident to generate a correct solution.
</think>
<solution>
Final solution presented to the user.
</solution>
system
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]

**Title:** PPM Decoder Does Not Raise ValueError for Incomplete Image Data

**Description:**
When attempting to load a PPM image with insufficient pixel data, the decoder fails to raise a `ValueError`. This can lead to unexpected behavior or crashes downstream when the image data is used without proper validation.

**Example Code:**
```python
path = "temp.ppm"
with open(path, "wb") as f:
    f.write(b"P2 1 2 255 255")  # Incomplete pixel data

with Image.open(path) as im:
    im.load()  # Expected to raise ValueError due to insufficient data
```

**Expected Behavior:**
Loading the image should raise a `ValueError` indicating that there is not enough image data to complete the loading process.

**Actual Behavior:**
No error is raised, allowing the program to proceed despite the incomplete image data.

[/ISSUE]
--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.

--- BEGIN FILE ---
```
### src/PIL/PpmImagePlugin.py
#
# The Python Imaging Library.
# $Id$
#
# PPM support for PIL
#
# History:
#       96-03-24 fl     Created
#       98-03-06 fl     Write RGBA images (as RGB, that is)
#
# Copyright (c) Secret Labs AB 1997-98.
# Copyright (c) Fredrik Lundh 1996.
#
# See the README file for information on usage and redistribution.
#


from . import Image, ImageFile
from ._binary import i16be as i16
from ._binary import o8
from ._binary import o32le as o32

#
# --------------------------------------------------------------------

b_whitespace = b"\x20\x09\x0a\x0b\x0c\x0d"

MODES = {
    # standard
    b"P1": "1",
    b"P2": "L",
    b"P3": "RGB",
    b"P4": "1",
    b"P5": "L",
    b"P6": "RGB",
    # extensions
    b"P0CMYK": "CMYK",
    # PIL extensions (for test purposes only)
    b"PyP": "P",
    b"PyRGBA": "RGBA",
    b"PyCMYK": "CMYK",
}


def _accept(prefix):
    return prefix[0:1] == b"P" and prefix[1] in b"0123456y"


##
# Image plugin for PBM, PGM, and PPM images.


class PpmImageFile(ImageFile.ImageFile):
    format = "PPM"
    format_description = "Pbmplus image"

    def _read_magic(self):
        magic = b""
        # read until whitespace or longest available magic number
        for _ in range(6):
            c = self.fp.read(1)
            if not c or c in b_whitespace:
                break
            magic += c
        return magic

    def _read_token(self):
        token = b""
        while len(token) <= 10:  # read until next whitespace or limit of 10 characters
            c = self.fp.read(1)
            if not c:
                break
            elif c in b_whitespace:  # token ended
                if not token:
                    # skip whitespace at start
                    continue
                break
            elif c == b"#":
                # ignores rest of the line; stops at CR, LF or EOF
                while self.fp.read(1) not in b"\r\n":
                    pass
                continue
            token += c
        if not token:
            # Token was not even 1 byte
            msg = "Reached EOF while reading header"
            raise ValueError(msg)
        elif len(token) > 10:
            msg = f"Token too long in file header: {token.decode()}"
            raise ValueError(msg)
        return token

    def _open(self):
        magic_number = self._read_magic()
        try:
            mode = MODES[magic_number]
        except KeyError:
            msg = "not a PPM file"
            raise SyntaxError(msg)

        if magic_number in (b"P1", b"P4"):
            self.custom_mimetype = "image/x-portable-bitmap"
        elif magic_number in (b"P2", b"P5"):
            self.custom_mimetype = "image/x-portable-graymap"
        elif magic_number in (b"P3", b"P6"):
            self.custom_mimetype = "image/x-portable-pixmap"

        maxval = None
        decoder_name = "raw"
        if magic_number in (b"P1", b"P2", b"P3"):
            decoder_name = "ppm_plain"
        for ix in range(3):
            token = int(self._read_token())
            if ix == 0:  # token is the x size
                xsize = token
            elif ix == 1:  # token is the y size
                ysize = token
                if mode == "1":
                    self.mode = "1"
                    rawmode = "1;I"
                    break
                else:
                    self.mode = rawmode = mode
            elif ix == 2:  # token is maxval
                maxval = token
                if not 0 < maxval < 65536:
                    msg = "maxval must be greater than 0 and less than 65536"
                    raise ValueError(msg)
                if maxval > 255 and mode == "L":
                    self.mode = "I"

                if decoder_name != "ppm_plain":
                    # If maxval matches a bit depth, use the raw decoder directly
                    if maxval == 65535 and mode == "L":
                        rawmode = "I;16B"
                    elif maxval != 255:
                        decoder_name = "ppm"

        args = (rawmode, 0, 1) if decoder_name == "raw" else (rawmode, maxval)
        self._size = xsize, ysize
        self.tile = [(decoder_name, (0, 0, xsize, ysize), self.fp.tell(), args)]


#
# --------------------------------------------------------------------


class PpmPlainDecoder(ImageFile.PyDecoder):
    _pulls_fd = True

    def _read_block(self):
        return self.fd.read(ImageFile.SAFEBLOCK)

    def _find_comment_end(self, block, start=0):
        a = block.find(b"\n", start)
        b = block.find(b"\r", start)
        return min(a, b) if a * b > 0 else max(a, b)  # lowest nonnegative index (or -1)

    def _ignore_comments(self, block):
        if self._comment_spans:
            # Finish current comment
            while block:
                comment_end = self._find_comment_end(block)
                if comment_end != -1:
                    # Comment ends in this block
                    # Delete tail of comment
                    block = block[comment_end + 1 :]
                    break
                else:
                    # Comment spans whole block
                    # So read the next block, looking for the end
                    block = self._read_block()

        # Search for any further comments
        self._comment_spans = False
        while True:
            comment_start = block.find(b"#")
            if comment_start == -1:
                # No comment found
                break
            comment_end = self._find_comment_end(block, comment_start)
            if comment_end != -1:
                # Comment ends in this block
                # Delete comment
                block = block[:comment_start] + block[comment_end + 1 :]
            else:
                # Comment continues to next block(s)
                block = block[:comment_start]
                self._comment_spans = True
                break
        return block

    def _decode_bitonal(self):
        """
        This is a separate method because in the plain PBM format, all data tokens are
        exactly one byte, so the inter-token whitespace is optional.
        """
        data = bytearray()
        total_bytes = self.state.xsize * self.state.ysize

        while len(data) != total_bytes:
            block = self._read_block()  # read next block
            if not block:
                # eof
                break

            block = self._ignore_comments(block)

            tokens = b"".join(block.split())
            for token in tokens:
                if token not in (48, 49):
                    msg = b"Invalid token for this mode: %s" % bytes([token])
                    raise ValueError(msg)
            data = (data + tokens)[:total_bytes]
        invert = bytes.maketrans(b"01", b"\xFF\x00")
        return data.translate(invert)

    def _decode_blocks(self, maxval):
        data = bytearray()
        max_len = 10
        out_byte_count = 4 if self.mode == "I" else 1
        out_max = 65535 if self.mode == "I" else 255
        bands = Image.getmodebands(self.mode)
        total_bytes = self.state.xsize * self.state.ysize * bands * out_byte_count

        half_token = False
        while len(data) != total_bytes:
            block = self._read_block()  # read next block
            if not block:
                if half_token:
                    block = bytearray(b" ")  # flush half_token
                else:
                    # eof
                    break

            block = self._ignore_comments(block)

            if half_token:
                block = half_token + block  # stitch half_token to new block

            tokens = block.split()

            if block and not block[-1:].isspace():  # block might split token
                half_token = tokens.pop()  # save half token for later
                if len(half_token) > max_len:  # prevent buildup of half_token
                    msg = (
                        b"Token too long found in data: %s" % half_token[: max_len + 1]
                    )
                    raise ValueError(msg)

            for token in tokens:
                if len(token) > max_len:
                    msg = b"Token too long found in data: %s" % token[: max_len + 1]
                    raise ValueError(msg)
                value = int(token)
                if value > maxval:
                    msg = f"Channel value too large for this mode: {value}"
                    raise ValueError(msg)
                value = round(value / maxval * out_max)
                data += o32(value) if self.mode == "I" else o8(value)
                if len(data) == total_bytes:  # finished!
                    break
        return data

    def decode(self, buffer):
        self._comment_spans = False
        if self.mode == "1":
            data = self._decode_bitonal()
            rawmode = "1;8"
        else:
            maxval = self.args[-1]
            data = self._decode_blocks(maxval)
            rawmode = "I;32" if self.mode == "I" else self.mode
        self.set_as_raw(bytes(data), rawmode)
        return -1, 0


class PpmDecoder(ImageFile.PyDecoder):
    _pulls_fd = True

    def decode(self, buffer):
        data = bytearray()
        maxval = self.args[-1]
        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)
        return -1, 0


#
# --------------------------------------------------------------------


def _save(im, fp, filename):
    if im.mode == "1":
        rawmode, head = "1;I", b"P4"
    elif im.mode == "L":
        rawmode, head = "L", b"P5"
    elif im.mode == "I":
        rawmode, head = "I;16B", b"P5"
    elif im.mode in ("RGB", "RGBA"):
        rawmode, head = "RGB", b"P6"
    else:
        msg = f"cannot write mode {im.mode} as PPM"
        raise OSError(msg)
    fp.write(head + b"\n%d %d\n" % im.size)
    if head == b"P6":
        fp.write(b"255\n")
    elif head == b"P5":
        if rawmode == "L":
            fp.write(b"255\n")
        else:
            fp.write(b"65535\n")
    ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))])

    # ALTERNATIVE: save via builtin debug function
    # im._dump(filename)


#
# --------------------------------------------------------------------


Image.register_open(PpmImageFile.format, PpmImageFile, _accept)
Image.register_save(PpmImageFile.format, _save)

Image.register_decoder("ppm", PpmDecoder)
Image.register_decoder("ppm_plain", PpmPlainDecoder)

Image.register_extensions(PpmImageFile.format, [".pbm", ".pgm", ".ppm", ".pnm"])

Image.register_mime(PpmImageFile.format, "image/x-portable-anymap")

```
--- END FILE ---

Please first localize the bug based on the issue statement, and then generate *SEARCH/REPLACE* edits to fix the issue.

Every *SEARCH/REPLACE* edit must use this format:
1. The file path
2. The start of search block: <<<<<<< SEARCH
3. A contiguous chunk of lines to search for in the existing source code
4. The dividing line: =======
5. The lines to replace into the source code
6. The end of the replace block: >>>>>>> REPLACE

Here is an example:

```python
### mathweb/flask/app.py
<<<<<<< SEARCH
from flask import Flask
=======
import math
from flask import Flask
>>>>>>> REPLACE
```
</solution>

Please note that the *SEARCH/REPLACE* edit REQUIRES PROPER INDENTATION. If you would like to add the line '        print(x)', you must fully write that out, with all those spaces before the code!
Wrap each *SEARCH/REPLACE* edit in a code block as shown in the example above. If you have multiple *SEARCH/REPLACE* edits, use a separate code block for each one.
user