{
    "file_diffs": [
        {
            "old_file_content": "import pytest\n\nfrom PIL import Image, ImageFile, PcxImagePlugin\n\nfrom .helper import assert_image_equal, hopper\n\n\ndef _roundtrip(tmp_path, im):\n    f = str(tmp_path / \"temp.pcx\")\n    im.save(f)\n    with Image.open(f) as im2:\n        assert im2.mode == im.mode\n        assert im2.size == im.size\n        assert im2.format == \"PCX\"\n        assert im2.get_format_mimetype() == \"image/x-pcx\"\n        assert_image_equal(im2, im)\n\n\ndef test_sanity(tmp_path):\n    for mode in (\"1\", \"L\", \"P\", \"RGB\"):\n        _roundtrip(tmp_path, hopper(mode))\n\n    # Test an unsupported mode\n    f = str(tmp_path / \"temp.pcx\")\n    im = hopper(\"RGBA\")\n    with pytest.raises(ValueError):\n        im.save(f)\n\n\ndef test_invalid_file():\n    invalid_file = \"Tests/images/flower.jpg\"\n\n    with pytest.raises(SyntaxError):\n        PcxImagePlugin.PcxImageFile(invalid_file)\n\n\ndef test_odd(tmp_path):\n    # See issue #523, odd sized images should have a stride that's even.\n    # Not that ImageMagick or GIMP write PCX that way.\n    # We were not handling properly.\n    for mode in (\"1\", \"L\", \"P\", \"RGB\"):\n        # larger, odd sized images are better here to ensure that\n        # we handle interrupted scan lines properly.\n        _roundtrip(tmp_path, hopper(mode).resize((511, 511)))\n\n\ndef test_odd_read():\n    # Reading an image with an odd stride, making it malformed\n    with Image.open(\"Tests/images/odd_stride.pcx\") as im:\n        im.load()\n\n        assert im.size == (371, 150)\n\n\ndef test_pil184():\n    # Check reading of files where xmin/xmax is not zero.\n\n    test_file = \"Tests/images/pil184.pcx\"\n    with Image.open(test_file) as im:\n        assert im.size == (447, 144)\n        assert im.tile[0][1] == (0, 0, 447, 144)\n\n        # Make sure all pixels are either 0 or 255.\n        assert im.histogram()[0] + im.histogram()[255] == 447 * 144\n\n\ndef test_1px_width(tmp_path):\n    im = Image.new(\"L\", (1, 256))\n    px = im.load()\n    for y in range(256):\n        px[0, y] = y\n    _roundtrip(tmp_path, im)\n\n\ndef test_large_count(tmp_path):\n    im = Image.new(\"L\", (256, 1))\n    px = im.load()\n    for x in range(256):\n        px[x, 0] = x // 67 * 67\n    _roundtrip(tmp_path, im)\n\n\ndef _test_buffer_overflow(tmp_path, im, size=1024):\n    _last = ImageFile.MAXBLOCK\n    ImageFile.MAXBLOCK = size\n    try:\n        _roundtrip(tmp_path, im)\n    finally:\n        ImageFile.MAXBLOCK = _last\n\n\ndef test_break_in_count_overflow(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(4):\n        for x in range(256):\n            px[x, y] = x % 128\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_one_in_loop(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(5):\n        for x in range(256):\n            px[x, y] = x % 128\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_many_in_loop(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(4):\n        for x in range(256):\n            px[x, y] = x % 128\n    for x in range(8):\n        px[x, 4] = 16\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_one_at_end(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(5):\n        for x in range(256):\n            px[x, y] = x % 128\n    px[0, 3] = 128 + 64\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_many_at_end(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(5):\n        for x in range(256):\n            px[x, y] = x % 128\n    for x in range(4):\n        px[x * 2, 3] = 128 + 64\n        px[x + 256 - 4, 3] = 0\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_padding(tmp_path):\n    im = Image.new(\"L\", (257, 5))\n    px = im.load()\n    for y in range(5):\n        for x in range(257):\n            px[x, y] = x % 128\n    for x in range(5):\n        px[x, 3] = 0\n    _test_buffer_overflow(tmp_path, im)\n",
            "new_file_content": "import pytest\n\nfrom PIL import Image, ImageFile, PcxImagePlugin\n\nfrom .helper import assert_image_equal, hopper\n\n\ndef _roundtrip(tmp_path, im):\n    f = str(tmp_path / \"temp.pcx\")\n    im.save(f)\n    with Image.open(f) as im2:\n        assert im2.mode == im.mode\n        assert im2.size == im.size\n        assert im2.format == \"PCX\"\n        assert im2.get_format_mimetype() == \"image/x-pcx\"\n        assert_image_equal(im2, im)\n\n\ndef test_sanity(tmp_path):\n    for mode in (\"1\", \"L\", \"P\", \"RGB\"):\n        _roundtrip(tmp_path, hopper(mode))\n\n    # Test a palette with less than 256 colors\n    im = Image.new(\"P\", (1, 1))\n    im.putpalette((255, 0, 0))\n    _roundtrip(tmp_path, im)\n\n    # Test an unsupported mode\n    f = str(tmp_path / \"temp.pcx\")\n    im = hopper(\"RGBA\")\n    with pytest.raises(ValueError):\n        im.save(f)\n\n\ndef test_invalid_file():\n    invalid_file = \"Tests/images/flower.jpg\"\n\n    with pytest.raises(SyntaxError):\n        PcxImagePlugin.PcxImageFile(invalid_file)\n\n\ndef test_odd(tmp_path):\n    # See issue #523, odd sized images should have a stride that's even.\n    # Not that ImageMagick or GIMP write PCX that way.\n    # We were not handling properly.\n    for mode in (\"1\", \"L\", \"P\", \"RGB\"):\n        # larger, odd sized images are better here to ensure that\n        # we handle interrupted scan lines properly.\n        _roundtrip(tmp_path, hopper(mode).resize((511, 511)))\n\n\ndef test_odd_read():\n    # Reading an image with an odd stride, making it malformed\n    with Image.open(\"Tests/images/odd_stride.pcx\") as im:\n        im.load()\n\n        assert im.size == (371, 150)\n\n\ndef test_pil184():\n    # Check reading of files where xmin/xmax is not zero.\n\n    test_file = \"Tests/images/pil184.pcx\"\n    with Image.open(test_file) as im:\n        assert im.size == (447, 144)\n        assert im.tile[0][1] == (0, 0, 447, 144)\n\n        # Make sure all pixels are either 0 or 255.\n        assert im.histogram()[0] + im.histogram()[255] == 447 * 144\n\n\ndef test_1px_width(tmp_path):\n    im = Image.new(\"L\", (1, 256))\n    px = im.load()\n    for y in range(256):\n        px[0, y] = y\n    _roundtrip(tmp_path, im)\n\n\ndef test_large_count(tmp_path):\n    im = Image.new(\"L\", (256, 1))\n    px = im.load()\n    for x in range(256):\n        px[x, 0] = x // 67 * 67\n    _roundtrip(tmp_path, im)\n\n\ndef _test_buffer_overflow(tmp_path, im, size=1024):\n    _last = ImageFile.MAXBLOCK\n    ImageFile.MAXBLOCK = size\n    try:\n        _roundtrip(tmp_path, im)\n    finally:\n        ImageFile.MAXBLOCK = _last\n\n\ndef test_break_in_count_overflow(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(4):\n        for x in range(256):\n            px[x, y] = x % 128\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_one_in_loop(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(5):\n        for x in range(256):\n            px[x, y] = x % 128\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_many_in_loop(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(4):\n        for x in range(256):\n            px[x, y] = x % 128\n    for x in range(8):\n        px[x, 4] = 16\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_one_at_end(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(5):\n        for x in range(256):\n            px[x, y] = x % 128\n    px[0, 3] = 128 + 64\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_many_at_end(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(5):\n        for x in range(256):\n            px[x, y] = x % 128\n    for x in range(4):\n        px[x * 2, 3] = 128 + 64\n        px[x + 256 - 4, 3] = 0\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_padding(tmp_path):\n    im = Image.new(\"L\", (257, 5))\n    px = im.load()\n    for y in range(5):\n        for x in range(257):\n            px[x, y] = x % 128\n    for x in range(5):\n        px[x, 3] = 0\n    _test_buffer_overflow(tmp_path, im)\n",
            "header": {
                "file": {
                    "path": "Tests/test_file_pcx.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "61e33a57b",
                "new_commit_hash": "ba6663cd3",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Tests/test_file_pcx.py"
            },
            "plus_file": {
                "path": "b/Tests/test_file_pcx.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 20,
                            "length": 6
                        },
                        "new_range": {
                            "start": 20,
                            "length": 11
                        },
                        "section": "def test_sanity(tmp_path):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    for mode in (\"1\", \"L\", \"P\", \"RGB\"):",
                                "type": "context"
                            },
                            {
                                "content": "        _roundtrip(tmp_path, hopper(mode))",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    # Test a palette with less than 256 colors",
                                "type": "added"
                            },
                            {
                                "content": "    im = Image.new(\"P\", (1, 1))",
                                "type": "added"
                            },
                            {
                                "content": "    im.putpalette((255, 0, 0))",
                                "type": "added"
                            },
                            {
                                "content": "    _roundtrip(tmp_path, im)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    # Test an unsupported mode",
                                "type": "context"
                            },
                            {
                                "content": "    f = str(tmp_path / \"temp.pcx\")",
                                "type": "context"
                            },
                            {
                                "content": "    im = hopper(\"RGBA\")",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Tests/test_file_pcx.py",
                            "type": "function",
                            "name": "test_sanity",
                            "content": "def test_sanity(tmp_path):\n    for mode in (\"1\", \"L\", \"P\", \"RGB\"):\n        _roundtrip(tmp_path, hopper(mode))\n\n    # Test a palette with less than 256 colors\n    im = Image.new(\"P\", (1, 1))\n    im.putpalette((255, 0, 0))\n    _roundtrip(tmp_path, im)\n\n    # Test an unsupported mode\n    f = str(tmp_path / \"temp.pcx\")\n    im = hopper(\"RGBA\")\n    with pytest.raises(ValueError):\n        im.save(f)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 19,
                            "end_lineno": 32,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "#\n# The Python Imaging Library.\n# $Id$\n#\n# PCX file handling\n#\n# This format was originally used by ZSoft's popular PaintBrush\n# program for the IBM PC.  It is also supported by many MS-DOS and\n# Windows applications, including the Windows PaintBrush program in\n# Windows 3.\n#\n# history:\n# 1995-09-01 fl   Created\n# 1996-05-20 fl   Fixed RGB support\n# 1997-01-03 fl   Fixed 2-bit and 4-bit support\n# 1999-02-03 fl   Fixed 8-bit support (broken in 1.0b1)\n# 1999-02-07 fl   Added write support\n# 2002-06-09 fl   Made 2-bit and 4-bit support a bit more robust\n# 2002-07-30 fl   Seek from to current position, not beginning of file\n# 2003-06-03 fl   Extract DPI settings (info[\"dpi\"])\n#\n# Copyright (c) 1997-2003 by Secret Labs AB.\n# Copyright (c) 1995-2003 by Fredrik Lundh.\n#\n# See the README file for information on usage and redistribution.\n#\n\nimport io\nimport logging\n\nfrom . import Image, ImageFile, ImagePalette\nfrom ._binary import i16le as i16\nfrom ._binary import o8\nfrom ._binary import o16le as o16\n\nlogger = logging.getLogger(__name__)\n\n\ndef _accept(prefix):\n    return prefix[0] == 10 and prefix[1] in [0, 2, 3, 5]\n\n\n##\n# Image plugin for Paintbrush images.\n\n\nclass PcxImageFile(ImageFile.ImageFile):\n\n    format = \"PCX\"\n    format_description = \"Paintbrush\"\n\n    def _open(self):\n\n        # header\n        s = self.fp.read(128)\n        if not _accept(s):\n            raise SyntaxError(\"not a PCX file\")\n\n        # image\n        bbox = i16(s, 4), i16(s, 6), i16(s, 8) + 1, i16(s, 10) + 1\n        if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:\n            raise SyntaxError(\"bad PCX image size\")\n        logger.debug(\"BBox: %s %s %s %s\", *bbox)\n\n        # format\n        version = s[1]\n        bits = s[3]\n        planes = s[65]\n        provided_stride = i16(s, 66)\n        logger.debug(\n            \"PCX version %s, bits %s, planes %s, stride %s\",\n            version,\n            bits,\n            planes,\n            provided_stride,\n        )\n\n        self.info[\"dpi\"] = i16(s, 12), i16(s, 14)\n\n        if bits == 1 and planes == 1:\n            mode = rawmode = \"1\"\n\n        elif bits == 1 and planes in (2, 4):\n            mode = \"P\"\n            rawmode = \"P;%dL\" % planes\n            self.palette = ImagePalette.raw(\"RGB\", s[16:64])\n\n        elif version == 5 and bits == 8 and planes == 1:\n            mode = rawmode = \"L\"\n            # FIXME: hey, this doesn't work with the incremental loader !!!\n            self.fp.seek(-769, io.SEEK_END)\n            s = self.fp.read(769)\n            if len(s) == 769 and s[0] == 12:\n                # check if the palette is linear greyscale\n                for i in range(256):\n                    if s[i * 3 + 1 : i * 3 + 4] != o8(i) * 3:\n                        mode = rawmode = \"P\"\n                        break\n                if mode == \"P\":\n                    self.palette = ImagePalette.raw(\"RGB\", s[1:])\n            self.fp.seek(128)\n\n        elif version == 5 and bits == 8 and planes == 3:\n            mode = \"RGB\"\n            rawmode = \"RGB;L\"\n\n        else:\n            raise OSError(\"unknown PCX mode\")\n\n        self.mode = mode\n        self._size = bbox[2] - bbox[0], bbox[3] - bbox[1]\n\n        # Don't trust the passed in stride.\n        # Calculate the approximate position for ourselves.\n        # CVE-2020-35653\n        stride = (self._size[0] * bits + 7) // 8\n\n        # While the specification states that this must be even,\n        # not all images follow this\n        if provided_stride != stride:\n            stride += stride % 2\n\n        bbox = (0, 0) + self.size\n        logger.debug(\"size: %sx%s\", *self.size)\n\n        self.tile = [(\"pcx\", bbox, self.fp.tell(), (rawmode, planes * stride))]\n\n\n# --------------------------------------------------------------------\n# save PCX files\n\n\nSAVE = {\n    # mode: (version, bits, planes, raw mode)\n    \"1\": (2, 1, 1, \"1\"),\n    \"L\": (5, 8, 1, \"L\"),\n    \"P\": (5, 8, 1, \"P\"),\n    \"RGB\": (5, 8, 3, \"RGB;L\"),\n}\n\n\ndef _save(im, fp, filename):\n\n    try:\n        version, bits, planes, rawmode = SAVE[im.mode]\n    except KeyError as e:\n        raise ValueError(f\"Cannot save {im.mode} images as PCX\") from e\n\n    # bytes per plane\n    stride = (im.size[0] * bits + 7) // 8\n    # stride should be even\n    stride += stride % 2\n    # Stride needs to be kept in sync with the PcxEncode.c version.\n    # Ideally it should be passed in in the state, but the bytes value\n    # gets overwritten.\n\n    logger.debug(\n        \"PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d\",\n        im.size[0],\n        bits,\n        stride,\n    )\n\n    # under windows, we could determine the current screen size with\n    # \"Image.core.display_mode()[1]\", but I think that's overkill...\n\n    screen = im.size\n\n    dpi = 100, 100\n\n    # PCX header\n    fp.write(\n        o8(10)\n        + o8(version)\n        + o8(1)\n        + o8(bits)\n        + o16(0)\n        + o16(0)\n        + o16(im.size[0] - 1)\n        + o16(im.size[1] - 1)\n        + o16(dpi[0])\n        + o16(dpi[1])\n        + b\"\\0\" * 24\n        + b\"\\xFF\" * 24\n        + b\"\\0\"\n        + o8(planes)\n        + o16(stride)\n        + o16(1)\n        + o16(screen[0])\n        + o16(screen[1])\n        + b\"\\0\" * 54\n    )\n\n    assert fp.tell() == 128\n\n    ImageFile._save(im, fp, [(\"pcx\", (0, 0) + im.size, 0, (rawmode, bits * planes))])\n\n    if im.mode == \"P\":\n        # colour palette\n        fp.write(o8(12))\n        fp.write(im.im.getpalette(\"RGB\", \"RGB\"))  # 768 bytes\n    elif im.mode == \"L\":\n        # greyscale palette\n        fp.write(o8(12))\n        for i in range(256):\n            fp.write(o8(i) * 3)\n\n\n# --------------------------------------------------------------------\n# registry\n\n\nImage.register_open(PcxImageFile.format, PcxImageFile, _accept)\nImage.register_save(PcxImageFile.format, _save)\n\nImage.register_extension(PcxImageFile.format, \".pcx\")\n\nImage.register_mime(PcxImageFile.format, \"image/x-pcx\")\n",
            "new_file_content": "#\n# The Python Imaging Library.\n# $Id$\n#\n# PCX file handling\n#\n# This format was originally used by ZSoft's popular PaintBrush\n# program for the IBM PC.  It is also supported by many MS-DOS and\n# Windows applications, including the Windows PaintBrush program in\n# Windows 3.\n#\n# history:\n# 1995-09-01 fl   Created\n# 1996-05-20 fl   Fixed RGB support\n# 1997-01-03 fl   Fixed 2-bit and 4-bit support\n# 1999-02-03 fl   Fixed 8-bit support (broken in 1.0b1)\n# 1999-02-07 fl   Added write support\n# 2002-06-09 fl   Made 2-bit and 4-bit support a bit more robust\n# 2002-07-30 fl   Seek from to current position, not beginning of file\n# 2003-06-03 fl   Extract DPI settings (info[\"dpi\"])\n#\n# Copyright (c) 1997-2003 by Secret Labs AB.\n# Copyright (c) 1995-2003 by Fredrik Lundh.\n#\n# See the README file for information on usage and redistribution.\n#\n\nimport io\nimport logging\n\nfrom . import Image, ImageFile, ImagePalette\nfrom ._binary import i16le as i16\nfrom ._binary import o8\nfrom ._binary import o16le as o16\n\nlogger = logging.getLogger(__name__)\n\n\ndef _accept(prefix):\n    return prefix[0] == 10 and prefix[1] in [0, 2, 3, 5]\n\n\n##\n# Image plugin for Paintbrush images.\n\n\nclass PcxImageFile(ImageFile.ImageFile):\n\n    format = \"PCX\"\n    format_description = \"Paintbrush\"\n\n    def _open(self):\n\n        # header\n        s = self.fp.read(128)\n        if not _accept(s):\n            raise SyntaxError(\"not a PCX file\")\n\n        # image\n        bbox = i16(s, 4), i16(s, 6), i16(s, 8) + 1, i16(s, 10) + 1\n        if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:\n            raise SyntaxError(\"bad PCX image size\")\n        logger.debug(\"BBox: %s %s %s %s\", *bbox)\n\n        # format\n        version = s[1]\n        bits = s[3]\n        planes = s[65]\n        provided_stride = i16(s, 66)\n        logger.debug(\n            \"PCX version %s, bits %s, planes %s, stride %s\",\n            version,\n            bits,\n            planes,\n            provided_stride,\n        )\n\n        self.info[\"dpi\"] = i16(s, 12), i16(s, 14)\n\n        if bits == 1 and planes == 1:\n            mode = rawmode = \"1\"\n\n        elif bits == 1 and planes in (2, 4):\n            mode = \"P\"\n            rawmode = \"P;%dL\" % planes\n            self.palette = ImagePalette.raw(\"RGB\", s[16:64])\n\n        elif version == 5 and bits == 8 and planes == 1:\n            mode = rawmode = \"L\"\n            # FIXME: hey, this doesn't work with the incremental loader !!!\n            self.fp.seek(-769, io.SEEK_END)\n            s = self.fp.read(769)\n            if len(s) == 769 and s[0] == 12:\n                # check if the palette is linear greyscale\n                for i in range(256):\n                    if s[i * 3 + 1 : i * 3 + 4] != o8(i) * 3:\n                        mode = rawmode = \"P\"\n                        break\n                if mode == \"P\":\n                    self.palette = ImagePalette.raw(\"RGB\", s[1:])\n            self.fp.seek(128)\n\n        elif version == 5 and bits == 8 and planes == 3:\n            mode = \"RGB\"\n            rawmode = \"RGB;L\"\n\n        else:\n            raise OSError(\"unknown PCX mode\")\n\n        self.mode = mode\n        self._size = bbox[2] - bbox[0], bbox[3] - bbox[1]\n\n        # Don't trust the passed in stride.\n        # Calculate the approximate position for ourselves.\n        # CVE-2020-35653\n        stride = (self._size[0] * bits + 7) // 8\n\n        # While the specification states that this must be even,\n        # not all images follow this\n        if provided_stride != stride:\n            stride += stride % 2\n\n        bbox = (0, 0) + self.size\n        logger.debug(\"size: %sx%s\", *self.size)\n\n        self.tile = [(\"pcx\", bbox, self.fp.tell(), (rawmode, planes * stride))]\n\n\n# --------------------------------------------------------------------\n# save PCX files\n\n\nSAVE = {\n    # mode: (version, bits, planes, raw mode)\n    \"1\": (2, 1, 1, \"1\"),\n    \"L\": (5, 8, 1, \"L\"),\n    \"P\": (5, 8, 1, \"P\"),\n    \"RGB\": (5, 8, 3, \"RGB;L\"),\n}\n\n\ndef _save(im, fp, filename):\n\n    try:\n        version, bits, planes, rawmode = SAVE[im.mode]\n    except KeyError as e:\n        raise ValueError(f\"Cannot save {im.mode} images as PCX\") from e\n\n    # bytes per plane\n    stride = (im.size[0] * bits + 7) // 8\n    # stride should be even\n    stride += stride % 2\n    # Stride needs to be kept in sync with the PcxEncode.c version.\n    # Ideally it should be passed in in the state, but the bytes value\n    # gets overwritten.\n\n    logger.debug(\n        \"PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d\",\n        im.size[0],\n        bits,\n        stride,\n    )\n\n    # under windows, we could determine the current screen size with\n    # \"Image.core.display_mode()[1]\", but I think that's overkill...\n\n    screen = im.size\n\n    dpi = 100, 100\n\n    # PCX header\n    fp.write(\n        o8(10)\n        + o8(version)\n        + o8(1)\n        + o8(bits)\n        + o16(0)\n        + o16(0)\n        + o16(im.size[0] - 1)\n        + o16(im.size[1] - 1)\n        + o16(dpi[0])\n        + o16(dpi[1])\n        + b\"\\0\" * 24\n        + b\"\\xFF\" * 24\n        + b\"\\0\"\n        + o8(planes)\n        + o16(stride)\n        + o16(1)\n        + o16(screen[0])\n        + o16(screen[1])\n        + b\"\\0\" * 54\n    )\n\n    assert fp.tell() == 128\n\n    ImageFile._save(im, fp, [(\"pcx\", (0, 0) + im.size, 0, (rawmode, bits * planes))])\n\n    if im.mode == \"P\":\n        # colour palette\n        fp.write(o8(12))\n        palette = im.im.getpalette(\"RGB\", \"RGB\")\n        palette += b\"\\x00\" * (768 - len(palette))\n        fp.write(palette)  # 768 bytes\n    elif im.mode == \"L\":\n        # greyscale palette\n        fp.write(o8(12))\n        for i in range(256):\n            fp.write(o8(i) * 3)\n\n\n# --------------------------------------------------------------------\n# registry\n\n\nImage.register_open(PcxImageFile.format, PcxImageFile, _accept)\nImage.register_save(PcxImageFile.format, _save)\n\nImage.register_extension(PcxImageFile.format, \".pcx\")\n\nImage.register_mime(PcxImageFile.format, \"image/x-pcx\")\n",
            "header": {
                "file": {
                    "path": "src/PIL/PcxImagePlugin.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "d2e166bdd",
                "new_commit_hash": "841c18a22",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/src/PIL/PcxImagePlugin.py"
            },
            "plus_file": {
                "path": "b/src/PIL/PcxImagePlugin.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 198,
                            "length": 7
                        },
                        "new_range": {
                            "start": 198,
                            "length": 9
                        },
                        "section": "def _save(im, fp, filename):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    if im.mode == \"P\":",
                                "type": "context"
                            },
                            {
                                "content": "        # colour palette",
                                "type": "context"
                            },
                            {
                                "content": "        fp.write(o8(12))",
                                "type": "context"
                            },
                            {
                                "content": "        fp.write(im.im.getpalette(\"RGB\", \"RGB\"))  # 768 bytes",
                                "type": "deleted"
                            },
                            {
                                "content": "        palette = im.im.getpalette(\"RGB\", \"RGB\")",
                                "type": "added"
                            },
                            {
                                "content": "        palette += b\"\\x00\" * (768 - len(palette))",
                                "type": "added"
                            },
                            {
                                "content": "        fp.write(palette)  # 768 bytes",
                                "type": "added"
                            },
                            {
                                "content": "    elif im.mode == \"L\":",
                                "type": "context"
                            },
                            {
                                "content": "        # greyscale palette",
                                "type": "context"
                            },
                            {
                                "content": "        fp.write(o8(12))",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/PcxImagePlugin.py",
                            "type": "function",
                            "name": "_save",
                            "content": "def _save(im, fp, filename):\n\n    try:\n        version, bits, planes, rawmode = SAVE[im.mode]\n    except KeyError as e:\n        raise ValueError(f\"Cannot save {im.mode} images as PCX\") from e\n\n    # bytes per plane\n    stride = (im.size[0] * bits + 7) // 8\n    # stride should be even\n    stride += stride % 2\n    # Stride needs to be kept in sync with the PcxEncode.c version.\n    # Ideally it should be passed in in the state, but the bytes value\n    # gets overwritten.\n\n    logger.debug(\n        \"PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d\",\n        im.size[0],\n        bits,\n        stride,\n    )\n\n    # under windows, we could determine the current screen size with\n    # \"Image.core.display_mode()[1]\", but I think that's overkill...\n\n    screen = im.size\n\n    dpi = 100, 100\n\n    # PCX header\n    fp.write(\n        o8(10)\n        + o8(version)\n        + o8(1)\n        + o8(bits)\n        + o16(0)\n        + o16(0)\n        + o16(im.size[0] - 1)\n        + o16(im.size[1] - 1)\n        + o16(dpi[0])\n        + o16(dpi[1])\n        + b\"\\0\" * 24\n        + b\"\\xFF\" * 24\n        + b\"\\0\"\n        + o8(planes)\n        + o16(stride)\n        + o16(1)\n        + o16(screen[0])\n        + o16(screen[1])\n        + b\"\\0\" * 54\n    )\n\n    assert fp.tell() == 128\n\n    ImageFile._save(im, fp, [(\"pcx\", (0, 0) + im.size, 0, (rawmode, bits * planes))])\n\n    if im.mode == \"P\":\n        # colour palette\n        fp.write(o8(12))\n        fp.write(im.im.getpalette(\"RGB\", \"RGB\"))  # 768 bytes\n    elif im.mode == \"L\":\n        # greyscale palette\n        fp.write(o8(12))\n        for i in range(256):\n            fp.write(o8(i) * 3)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 142,
                            "end_lineno": 206,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "e774be01fe43598e94a160f0e0279c7ed8b07b21^",
    "new_commit_hash": "e774be01fe43598e94a160f0e0279c7ed8b07b21",
    "commit_message": "Merge pull request #6391 from radarhere/pcx\n\nPad PCX palette to 768 bytes when saving",
    "commit_date": "2022-06-23T09:50:22+03:00",
    "metadata": {}
}
{
    "repo_name": "pillow",
    "new_commit_hash": "e774be01fe43598e94a160f0e0279c7ed8b07b21",
    "test_file_codes": [
        "import pytest\n\nfrom PIL import Image, ImageFile, PcxImagePlugin\n\nfrom .helper import assert_image_equal, hopper\n\n\ndef _roundtrip(tmp_path, im):\n    f = str(tmp_path / \"temp.pcx\")\n    im.save(f)\n    with Image.open(f) as im2:\n        assert im2.mode == im.mode\n        assert im2.size == im.size\n        assert im2.format == \"PCX\"\n        assert im2.get_format_mimetype() == \"image/x-pcx\"\n        assert_image_equal(im2, im)\n\n\ndef test_sanity(tmp_path):\n    for mode in (\"1\", \"L\", \"P\", \"RGB\"):\n        _roundtrip(tmp_path, hopper(mode))\n\n    # Test a palette with less than 256 colors\n    im = Image.new(\"P\", (1, 1))\n    im.putpalette((255, 0, 0))\n    _roundtrip(tmp_path, im)\n\n    # Test an unsupported mode\n    f = str(tmp_path / \"temp.pcx\")\n    im = hopper(\"RGBA\")\n    with pytest.raises(ValueError):\n        im.save(f)\n\n\ndef test_invalid_file():\n    invalid_file = \"Tests/images/flower.jpg\"\n\n    with pytest.raises(SyntaxError):\n        PcxImagePlugin.PcxImageFile(invalid_file)\n\n\ndef test_odd(tmp_path):\n    # See issue #523, odd sized images should have a stride that's even.\n    # Not that ImageMagick or GIMP write PCX that way.\n    # We were not handling properly.\n    for mode in (\"1\", \"L\", \"P\", \"RGB\"):\n        # larger, odd sized images are better here to ensure that\n        # we handle interrupted scan lines properly.\n        _roundtrip(tmp_path, hopper(mode).resize((511, 511)))\n\n\ndef test_odd_read():\n    # Reading an image with an odd stride, making it malformed\n    with Image.open(\"Tests/images/odd_stride.pcx\") as im:\n        im.load()\n\n        assert im.size == (371, 150)\n\n\ndef test_pil184():\n    # Check reading of files where xmin/xmax is not zero.\n\n    test_file = \"Tests/images/pil184.pcx\"\n    with Image.open(test_file) as im:\n        assert im.size == (447, 144)\n        assert im.tile[0][1] == (0, 0, 447, 144)\n\n        # Make sure all pixels are either 0 or 255.\n        assert im.histogram()[0] + im.histogram()[255] == 447 * 144\n\n\ndef test_1px_width(tmp_path):\n    im = Image.new(\"L\", (1, 256))\n    px = im.load()\n    for y in range(256):\n        px[0, y] = y\n    _roundtrip(tmp_path, im)\n\n\ndef test_large_count(tmp_path):\n    im = Image.new(\"L\", (256, 1))\n    px = im.load()\n    for x in range(256):\n        px[x, 0] = x // 67 * 67\n    _roundtrip(tmp_path, im)\n\n\ndef _test_buffer_overflow(tmp_path, im, size=1024):\n    _last = ImageFile.MAXBLOCK\n    ImageFile.MAXBLOCK = size\n    try:\n        _roundtrip(tmp_path, im)\n    finally:\n        ImageFile.MAXBLOCK = _last\n\n\ndef test_break_in_count_overflow(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(4):\n        for x in range(256):\n            px[x, y] = x % 128\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_one_in_loop(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(5):\n        for x in range(256):\n            px[x, y] = x % 128\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_many_in_loop(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(4):\n        for x in range(256):\n            px[x, y] = x % 128\n    for x in range(8):\n        px[x, 4] = 16\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_one_at_end(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(5):\n        for x in range(256):\n            px[x, y] = x % 128\n    px[0, 3] = 128 + 64\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_many_at_end(tmp_path):\n    im = Image.new(\"L\", (256, 5))\n    px = im.load()\n    for y in range(5):\n        for x in range(256):\n            px[x, y] = x % 128\n    for x in range(4):\n        px[x * 2, 3] = 128 + 64\n        px[x + 256 - 4, 3] = 0\n    _test_buffer_overflow(tmp_path, im)\n\n\ndef test_break_padding(tmp_path):\n    im = Image.new(\"L\", (257, 5))\n    px = im.load()\n    for y in range(5):\n        for x in range(257):\n            px[x, y] = x % 128\n    for x in range(5):\n        px[x, 3] = 0\n    _test_buffer_overflow(tmp_path, im)\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 113ms\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 770ms\nPrepared 1 package in 35.81s\nInstalled 1 package in 1ms\n + pillow==9.2.0.dev0 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_e774be01fe43598e94a160f0e0279c7ed8b07b21)\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_e774be01fe43598e94a160f0e0279c7ed8b07b21\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 13 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                                        [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_invalid_file\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_odd\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_odd_read\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pil184\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_1px_width\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_large_count\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_break_in_count_overflow\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_break_one_in_loop\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_break_many_in_loop\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_break_one_at_end\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_break_many_at_end\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_break_padding\u001b[0m\n\u001b[32m============================== \u001b[32m\u001b[1m13 passed\u001b[0m\u001b[32m in 0.16s\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_e774be01fe43598e94a160f0e0279c7ed8b07b21\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 13 items\n\nr2e_tests/test_1.py \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[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_sanity __________________________________\u001b[0m\n\ntmp_path = PosixPath('/tmp/pytest-of-gcpuser/pytest-684/test_sanity0')\n\n    def test_sanity(tmp_path):\n        for mode in (\"1\", \"L\", \"P\", \"RGB\"):\n            _roundtrip(tmp_path, hopper(mode))\n    \n        # Test a palette with less than 256 colors\n        im = Image.new(\"P\", (1, 1))\n        im.putpalette((255, 0, 0))\n>       _roundtrip(tmp_path, im)\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:26: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:11: in _roundtrip\n    with Image.open(f) as im2:\n\u001b[1m\u001b[31msrc/PIL/Image.py\u001b[0m:3139: in open\n    im = _open_core(fp, filename, prefix, formats)\n\u001b[1m\u001b[31msrc/PIL/Image.py\u001b[0m:3125: in _open_core\n    im = factory(fp, filename)\n\u001b[1m\u001b[31msrc/PIL/ImageFile.py\u001b[0m:116: in __init__\n    self._open()\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = <PIL.PcxImagePlugin.PcxImageFile image mode= size=0x0 at 0x701229F455E0>\n\n    def _open(self):\n    \n        # header\n        s = self.fp.read(128)\n        if not _accept(s):\n            raise SyntaxError(\"not a PCX file\")\n    \n        # image\n        bbox = i16(s, 4), i16(s, 6), i16(s, 8) + 1, i16(s, 10) + 1\n        if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:\n            raise SyntaxError(\"bad PCX image size\")\n        logger.debug(\"BBox: %s %s %s %s\", *bbox)\n    \n        # format\n        version = s[1]\n        bits = s[3]\n        planes = s[65]\n        provided_stride = i16(s, 66)\n        logger.debug(\n            \"PCX version %s, bits %s, planes %s, stride %s\",\n            version,\n            bits,\n            planes,\n            provided_stride,\n        )\n    \n        self.info[\"dpi\"] = i16(s, 12), i16(s, 14)\n    \n        if bits == 1 and planes == 1:\n            mode = rawmode = \"1\"\n    \n        elif bits == 1 and planes in (2, 4):\n            mode = \"P\"\n            rawmode = \"P;%dL\" % planes\n            self.palette = ImagePalette.raw(\"RGB\", s[16:64])\n    \n        elif version == 5 and bits == 8 and planes == 1:\n            mode = rawmode = \"L\"\n            # FIXME: hey, this doesn't work with the incremental loader !!!\n>           self.fp.seek(-769, io.SEEK_END)\n\u001b[1m\u001b[31mE           OSError: [Errno 22] Invalid argument\u001b[0m\n\n\u001b[1m\u001b[31msrc/PIL/PcxImagePlugin.py\u001b[0m:91: OSError\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_invalid_file\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_odd\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_odd_read\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_pil184\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_1px_width\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_large_count\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_break_in_count_overflow\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_break_one_in_loop\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_break_many_in_loop\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_break_one_at_end\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_break_many_at_end\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_break_padding\u001b[0m\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mtest_sanity\u001b[0m - OSError: [Errno 22] Invalid argument\n\u001b[31m========================= \u001b[31m\u001b[1m1 failed\u001b[0m, \u001b[32m12 passed\u001b[0m\u001b[31m in 0.20s\u001b[0m\u001b[31m =========================\u001b[0m\n",
    "old_commit_res_stderr": ""
}
Tests/test_file_pcx.py src/PIL/PcxImagePlugin.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** Saving PCX images with incomplete palettes causes OSError

**Description:**
When saving a PCX image with a palette that contains fewer than 256 colors, an `OSError: [Errno 22] Invalid argument` is raised. This error occurs because the palette does not meet the required 768-byte length expected by the PCX format.

**Example Code:**
```python
from PIL import Image

# Create a palette-based image with an incomplete palette
im = Image.new("P", (1, 1))
im.putpalette((255, 0, 0))  # Only one color instead of 256
im.save("test.pcx")
```

**Expected Behavior:**
The PCX image should be saved successfully, with the palette being correctly handled even if it contains fewer than 256 colors, possibly by padding the palette to the required size.

**Actual Behavior:**
An error is raised during the save operation:
```
OSError: [Errno 22] Invalid argument
```
[/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/PcxImagePlugin.py
#
# The Python Imaging Library.
# $Id$
#
# PCX file handling
#
# This format was originally used by ZSoft's popular PaintBrush
# program for the IBM PC.  It is also supported by many MS-DOS and
# Windows applications, including the Windows PaintBrush program in
# Windows 3.
#
# history:
# 1995-09-01 fl   Created
# 1996-05-20 fl   Fixed RGB support
# 1997-01-03 fl   Fixed 2-bit and 4-bit support
# 1999-02-03 fl   Fixed 8-bit support (broken in 1.0b1)
# 1999-02-07 fl   Added write support
# 2002-06-09 fl   Made 2-bit and 4-bit support a bit more robust
# 2002-07-30 fl   Seek from to current position, not beginning of file
# 2003-06-03 fl   Extract DPI settings (info["dpi"])
#
# Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-2003 by Fredrik Lundh.
#
# See the README file for information on usage and redistribution.
#

import io
import logging

from . import Image, ImageFile, ImagePalette
from ._binary import i16le as i16
from ._binary import o8
from ._binary import o16le as o16

logger = logging.getLogger(__name__)


def _accept(prefix):
    return prefix[0] == 10 and prefix[1] in [0, 2, 3, 5]


##
# Image plugin for Paintbrush images.


class PcxImageFile(ImageFile.ImageFile):

    format = "PCX"
    format_description = "Paintbrush"

    def _open(self):

        # header
        s = self.fp.read(128)
        if not _accept(s):
            raise SyntaxError("not a PCX file")

        # image
        bbox = i16(s, 4), i16(s, 6), i16(s, 8) + 1, i16(s, 10) + 1
        if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
            raise SyntaxError("bad PCX image size")
        logger.debug("BBox: %s %s %s %s", *bbox)

        # format
        version = s[1]
        bits = s[3]
        planes = s[65]
        provided_stride = i16(s, 66)
        logger.debug(
            "PCX version %s, bits %s, planes %s, stride %s",
            version,
            bits,
            planes,
            provided_stride,
        )

        self.info["dpi"] = i16(s, 12), i16(s, 14)

        if bits == 1 and planes == 1:
            mode = rawmode = "1"

        elif bits == 1 and planes in (2, 4):
            mode = "P"
            rawmode = "P;%dL" % planes
            self.palette = ImagePalette.raw("RGB", s[16:64])

        elif version == 5 and bits == 8 and planes == 1:
            mode = rawmode = "L"
            # FIXME: hey, this doesn't work with the incremental loader !!!
            self.fp.seek(-769, io.SEEK_END)
            s = self.fp.read(769)
            if len(s) == 769 and s[0] == 12:
                # check if the palette is linear greyscale
                for i in range(256):
                    if s[i * 3 + 1 : i * 3 + 4] != o8(i) * 3:
                        mode = rawmode = "P"
                        break
                if mode == "P":
                    self.palette = ImagePalette.raw("RGB", s[1:])
            self.fp.seek(128)

        elif version == 5 and bits == 8 and planes == 3:
            mode = "RGB"
            rawmode = "RGB;L"

        else:
            raise OSError("unknown PCX mode")

        self.mode = mode
        self._size = bbox[2] - bbox[0], bbox[3] - bbox[1]

        # Don't trust the passed in stride.
        # Calculate the approximate position for ourselves.
        # CVE-2020-35653
        stride = (self._size[0] * bits + 7) // 8

        # While the specification states that this must be even,
        # not all images follow this
        if provided_stride != stride:
            stride += stride % 2

        bbox = (0, 0) + self.size
        logger.debug("size: %sx%s", *self.size)

        self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]


# --------------------------------------------------------------------
# save PCX files


SAVE = {
    # mode: (version, bits, planes, raw mode)
    "1": (2, 1, 1, "1"),
    "L": (5, 8, 1, "L"),
    "P": (5, 8, 1, "P"),
    "RGB": (5, 8, 3, "RGB;L"),
}


def _save(im, fp, filename):

    try:
        version, bits, planes, rawmode = SAVE[im.mode]
    except KeyError as e:
        raise ValueError(f"Cannot save {im.mode} images as PCX") from e

    # bytes per plane
    stride = (im.size[0] * bits + 7) // 8
    # stride should be even
    stride += stride % 2
    # Stride needs to be kept in sync with the PcxEncode.c version.
    # Ideally it should be passed in in the state, but the bytes value
    # gets overwritten.

    logger.debug(
        "PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d",
        im.size[0],
        bits,
        stride,
    )

    # under windows, we could determine the current screen size with
    # "Image.core.display_mode()[1]", but I think that's overkill...

    screen = im.size

    dpi = 100, 100

    # PCX header
    fp.write(
        o8(10)
        + o8(version)
        + o8(1)
        + o8(bits)
        + o16(0)
        + o16(0)
        + o16(im.size[0] - 1)
        + o16(im.size[1] - 1)
        + o16(dpi[0])
        + o16(dpi[1])
        + b"\0" * 24
        + b"\xFF" * 24
        + b"\0"
        + o8(planes)
        + o16(stride)
        + o16(1)
        + o16(screen[0])
        + o16(screen[1])
        + b"\0" * 54
    )

    assert fp.tell() == 128

    ImageFile._save(im, fp, [("pcx", (0, 0) + im.size, 0, (rawmode, bits * planes))])

    if im.mode == "P":
        # colour palette
        fp.write(o8(12))
        fp.write(im.im.getpalette("RGB", "RGB"))  # 768 bytes
    elif im.mode == "L":
        # greyscale palette
        fp.write(o8(12))
        for i in range(256):
            fp.write(o8(i) * 3)


# --------------------------------------------------------------------
# registry


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

Image.register_extension(PcxImageFile.format, ".pcx")

Image.register_mime(PcxImageFile.format, "image/x-pcx")

```
--- 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:** Saving PCX images with incomplete palettes causes OSError

**Description:**
When saving a PCX image with a palette that contains fewer than 256 colors, an `OSError: [Errno 22] Invalid argument` is raised. This error occurs because the palette does not meet the required 768-byte length expected by the PCX format.

**Example Code:**
```python
from PIL import Image

# Create a palette-based image with an incomplete palette
im = Image.new("P", (1, 1))
im.putpalette((255, 0, 0))  # Only one color instead of 256
im.save("test.pcx")
```

**Expected Behavior:**
The PCX image should be saved successfully, with the palette being correctly handled even if it contains fewer than 256 colors, possibly by padding the palette to the required size.

**Actual Behavior:**
An error is raised during the save operation:
```
OSError: [Errno 22] Invalid argument
```
[/ISSUE]
{
    "\u001b[1mtest_sanity\u001b[0m": "PASSED",
    "\u001b[1mtest_invalid_file\u001b[0m": "PASSED",
    "\u001b[1mtest_odd\u001b[0m": "PASSED",
    "\u001b[1mtest_odd_read\u001b[0m": "PASSED",
    "\u001b[1mtest_pil184\u001b[0m": "PASSED",
    "\u001b[1mtest_1px_width\u001b[0m": "PASSED",
    "\u001b[1mtest_large_count\u001b[0m": "PASSED",
    "\u001b[1mtest_break_in_count_overflow\u001b[0m": "PASSED",
    "\u001b[1mtest_break_one_in_loop\u001b[0m": "PASSED",
    "\u001b[1mtest_break_many_in_loop\u001b[0m": "PASSED",
    "\u001b[1mtest_break_one_at_end\u001b[0m": "PASSED",
    "\u001b[1mtest_break_many_at_end\u001b[0m": "PASSED",
    "\u001b[1mtest_break_padding\u001b[0m": "PASSED"
}
diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py
index d2e166bdd..841c18a22 100644
--- a/src/PIL/PcxImagePlugin.py
+++ b/src/PIL/PcxImagePlugin.py
@@ -198,7 +198,9 @@ def _save(im, fp, filename):
     if im.mode == "P":
         # colour palette
         fp.write(o8(12))
-        fp.write(im.im.getpalette("RGB", "RGB"))  # 768 bytes
+        palette = im.im.getpalette("RGB", "RGB")
+        palette += b"\x00" * (768 - len(palette))
+        fp.write(palette)  # 768 bytes
     elif im.mode == "L":
         # greyscale palette
         fp.write(o8(12))
 

{"src/PIL/PcxImagePlugin.py": "#\n# The Python Imaging Library.\n# $Id$\n#\n# PCX file handling\n#\n# This format was originally used by ZSoft's popular PaintBrush\n# program for the IBM PC.  It is also supported by many MS-DOS and\n# Windows applications, including the Windows PaintBrush program in\n# Windows 3.\n#\n# history:\n# 1995-09-01 fl   Created\n# 1996-05-20 fl   Fixed RGB support\n# 1997-01-03 fl   Fixed 2-bit and 4-bit support\n# 1999-02-03 fl   Fixed 8-bit support (broken in 1.0b1)\n# 1999-02-07 fl   Added write support\n# 2002-06-09 fl   Made 2-bit and 4-bit support a bit more robust\n# 2002-07-30 fl   Seek from to current position, not beginning of file\n# 2003-06-03 fl   Extract DPI settings (info[\"dpi\"])\n#\n# Copyright (c) 1997-2003 by Secret Labs AB.\n# Copyright (c) 1995-2003 by Fredrik Lundh.\n#\n# See the README file for information on usage and redistribution.\n#\n\nimport io\nimport logging\n\nfrom . import Image, ImageFile, ImagePalette\nfrom ._binary import i16le as i16\nfrom ._binary import o8\nfrom ._binary import o16le as o16\n\nlogger = logging.getLogger(__name__)\n\n\ndef _accept(prefix):\n    return prefix[0] == 10 and prefix[1] in [0, 2, 3, 5]\n\n\n##\n# Image plugin for Paintbrush images.\n\n\nclass PcxImageFile(ImageFile.ImageFile):\n\n    format = \"PCX\"\n    format_description = \"Paintbrush\"\n\n    def _open(self):\n\n        # header\n        s = self.fp.read(128)\n        if not _accept(s):\n            raise SyntaxError(\"not a PCX file\")\n\n        # image\n        bbox = i16(s, 4), i16(s, 6), i16(s, 8) + 1, i16(s, 10) + 1\n        if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:\n            raise SyntaxError(\"bad PCX image size\")\n        logger.debug(\"BBox: %s %s %s %s\", *bbox)\n\n        # format\n        version = s[1]\n        bits = s[3]\n        planes = s[65]\n        provided_stride = i16(s, 66)\n        logger.debug(\n            \"PCX version %s, bits %s, planes %s, stride %s\",\n            version,\n            bits,\n            planes,\n            provided_stride,\n        )\n\n        self.info[\"dpi\"] = i16(s, 12), i16(s, 14)\n\n        if bits == 1 and planes == 1:\n            mode = rawmode = \"1\"\n\n        elif bits == 1 and planes in (2, 4):\n            mode = \"P\"\n            rawmode = \"P;%dL\" % planes\n            self.palette = ImagePalette.raw(\"RGB\", s[16:64])\n\n        elif version == 5 and bits == 8 and planes == 1:\n            mode = rawmode = \"L\"\n            # FIXME: hey, this doesn't work with the incremental loader !!!\n            self.fp.seek(-769, io.SEEK_END)\n            s = self.fp.read(769)\n            if len(s) == 769 and s[0] == 12:\n                # check if the palette is linear greyscale\n                for i in range(256):\n                    if s[i * 3 + 1 : i * 3 + 4] != o8(i) * 3:\n                        mode = rawmode = \"P\"\n                        break\n                if mode == \"P\":\n                    self.palette = ImagePalette.raw(\"RGB\", s[1:])\n            self.fp.seek(128)\n\n        elif version == 5 and bits == 8 and planes == 3:\n            mode = \"RGB\"\n            rawmode = \"RGB;L\"\n\n        else:\n            raise OSError(\"unknown PCX mode\")\n\n        self.mode = mode\n        self._size = bbox[2] - bbox[0], bbox[3] - bbox[1]\n\n        # Don't trust the passed in stride.\n        # Calculate the approximate position for ourselves.\n        # CVE-2020-35653\n        stride = (self._size[0] * bits + 7) // 8\n\n        # While the specification states that this must be even,\n        # not all images follow this\n        if provided_stride != stride:\n            stride += stride % 2\n\n        bbox = (0, 0) + self.size\n        logger.debug(\"size: %sx%s\", *self.size)\n\n        self.tile = [(\"pcx\", bbox, self.fp.tell(), (rawmode, planes * stride))]\n\n\n# --------------------------------------------------------------------\n# save PCX files\n\n\nSAVE = {\n    # mode: (version, bits, planes, raw mode)\n    \"1\": (2, 1, 1, \"1\"),\n    \"L\": (5, 8, 1, \"L\"),\n    \"P\": (5, 8, 1, \"P\"),\n    \"RGB\": (5, 8, 3, \"RGB;L\"),\n}\n\n\ndef _save(im, fp, filename):\n\n    try:\n        version, bits, planes, rawmode = SAVE[im.mode]\n    except KeyError as e:\n        raise ValueError(f\"Cannot save {im.mode} images as PCX\") from e\n\n    # bytes per plane\n    stride = (im.size[0] * bits + 7) // 8\n    # stride should be even\n    stride += stride % 2\n    # Stride needs to be kept in sync with the PcxEncode.c version.\n    # Ideally it should be passed in in the state, but the bytes value\n    # gets overwritten.\n\n    logger.debug(\n        \"PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d\",\n        im.size[0],\n        bits,\n        stride,\n    )\n\n    # under windows, we could determine the current screen size with\n    # \"Image.core.display_mode()[1]\", but I think that's overkill...\n\n    screen = im.size\n\n    dpi = 100, 100\n\n    # PCX header\n    fp.write(\n        o8(10)\n        + o8(version)\n        + o8(1)\n        + o8(bits)\n        + o16(0)\n        + o16(0)\n        + o16(im.size[0] - 1)\n        + o16(im.size[1] - 1)\n        + o16(dpi[0])\n        + o16(dpi[1])\n        + b\"\\0\" * 24\n        + b\"\\xFF\" * 24\n        + b\"\\0\"\n        + o8(planes)\n        + o16(stride)\n        + o16(1)\n        + o16(screen[0])\n        + o16(screen[1])\n        + b\"\\0\" * 54\n    )\n\n    assert fp.tell() == 128\n\n    ImageFile._save(im, fp, [(\"pcx\", (0, 0) + im.size, 0, (rawmode, bits * planes))])\n\n    if im.mode == \"P\":\n        # colour palette\n        fp.write(o8(12))\n        fp.write(im.im.getpalette(\"RGB\", \"RGB\"))  # 768 bytes\n    elif im.mode == \"L\":\n        # greyscale palette\n        fp.write(o8(12))\n        for i in range(256):\n            fp.write(o8(i) * 3)\n\n\n# --------------------------------------------------------------------\n# registry\n\n\nImage.register_open(PcxImageFile.format, PcxImageFile, _accept)\nImage.register_save(PcxImageFile.format, _save)\n\nImage.register_extension(PcxImageFile.format, \".pcx\")\n\nImage.register_mime(PcxImageFile.format, \"image/x-pcx\")\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:** Saving PCX images with incomplete palettes causes OSError

**Description:**
When saving a PCX image with a palette that contains fewer than 256 colors, an `OSError: [Errno 22] Invalid argument` is raised. This error occurs because the palette does not meet the required 768-byte length expected by the PCX format.

**Example Code:**
```python
from PIL import Image

# Create a palette-based image with an incomplete palette
im = Image.new("P", (1, 1))
im.putpalette((255, 0, 0))  # Only one color instead of 256
im.save("test.pcx")
```

**Expected Behavior:**
The PCX image should be saved successfully, with the palette being correctly handled even if it contains fewer than 256 colors, possibly by padding the palette to the required size.

**Actual Behavior:**
An error is raised during the save operation:
```
OSError: [Errno 22] Invalid argument
```
[/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/PcxImagePlugin.py
#
# The Python Imaging Library.
# $Id$
#
# PCX file handling
#
# This format was originally used by ZSoft's popular PaintBrush
# program for the IBM PC.  It is also supported by many MS-DOS and
# Windows applications, including the Windows PaintBrush program in
# Windows 3.
#
# history:
# 1995-09-01 fl   Created
# 1996-05-20 fl   Fixed RGB support
# 1997-01-03 fl   Fixed 2-bit and 4-bit support
# 1999-02-03 fl   Fixed 8-bit support (broken in 1.0b1)
# 1999-02-07 fl   Added write support
# 2002-06-09 fl   Made 2-bit and 4-bit support a bit more robust
# 2002-07-30 fl   Seek from to current position, not beginning of file
# 2003-06-03 fl   Extract DPI settings (info["dpi"])
#
# Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-2003 by Fredrik Lundh.
#
# See the README file for information on usage and redistribution.
#

import io
import logging

from . import Image, ImageFile, ImagePalette
from ._binary import i16le as i16
from ._binary import o8
from ._binary import o16le as o16

logger = logging.getLogger(__name__)


def _accept(prefix):
    return prefix[0] == 10 and prefix[1] in [0, 2, 3, 5]


##
# Image plugin for Paintbrush images.


class PcxImageFile(ImageFile.ImageFile):

    format = "PCX"
    format_description = "Paintbrush"

    def _open(self):

        # header
        s = self.fp.read(128)
        if not _accept(s):
            raise SyntaxError("not a PCX file")

        # image
        bbox = i16(s, 4), i16(s, 6), i16(s, 8) + 1, i16(s, 10) + 1
        if bbox[2] <= bbox[0] or bbox[3] <= bbox[1]:
            raise SyntaxError("bad PCX image size")
        logger.debug("BBox: %s %s %s %s", *bbox)

        # format
        version = s[1]
        bits = s[3]
        planes = s[65]
        provided_stride = i16(s, 66)
        logger.debug(
            "PCX version %s, bits %s, planes %s, stride %s",
            version,
            bits,
            planes,
            provided_stride,
        )

        self.info["dpi"] = i16(s, 12), i16(s, 14)

        if bits == 1 and planes == 1:
            mode = rawmode = "1"

        elif bits == 1 and planes in (2, 4):
            mode = "P"
            rawmode = "P;%dL" % planes
            self.palette = ImagePalette.raw("RGB", s[16:64])

        elif version == 5 and bits == 8 and planes == 1:
            mode = rawmode = "L"
            # FIXME: hey, this doesn't work with the incremental loader !!!
            self.fp.seek(-769, io.SEEK_END)
            s = self.fp.read(769)
            if len(s) == 769 and s[0] == 12:
                # check if the palette is linear greyscale
                for i in range(256):
                    if s[i * 3 + 1 : i * 3 + 4] != o8(i) * 3:
                        mode = rawmode = "P"
                        break
                if mode == "P":
                    self.palette = ImagePalette.raw("RGB", s[1:])
            self.fp.seek(128)

        elif version == 5 and bits == 8 and planes == 3:
            mode = "RGB"
            rawmode = "RGB;L"

        else:
            raise OSError("unknown PCX mode")

        self.mode = mode
        self._size = bbox[2] - bbox[0], bbox[3] - bbox[1]

        # Don't trust the passed in stride.
        # Calculate the approximate position for ourselves.
        # CVE-2020-35653
        stride = (self._size[0] * bits + 7) // 8

        # While the specification states that this must be even,
        # not all images follow this
        if provided_stride != stride:
            stride += stride % 2

        bbox = (0, 0) + self.size
        logger.debug("size: %sx%s", *self.size)

        self.tile = [("pcx", bbox, self.fp.tell(), (rawmode, planes * stride))]


# --------------------------------------------------------------------
# save PCX files


SAVE = {
    # mode: (version, bits, planes, raw mode)
    "1": (2, 1, 1, "1"),
    "L": (5, 8, 1, "L"),
    "P": (5, 8, 1, "P"),
    "RGB": (5, 8, 3, "RGB;L"),
}


def _save(im, fp, filename):

    try:
        version, bits, planes, rawmode = SAVE[im.mode]
    except KeyError as e:
        raise ValueError(f"Cannot save {im.mode} images as PCX") from e

    # bytes per plane
    stride = (im.size[0] * bits + 7) // 8
    # stride should be even
    stride += stride % 2
    # Stride needs to be kept in sync with the PcxEncode.c version.
    # Ideally it should be passed in in the state, but the bytes value
    # gets overwritten.

    logger.debug(
        "PcxImagePlugin._save: xwidth: %d, bits: %d, stride: %d",
        im.size[0],
        bits,
        stride,
    )

    # under windows, we could determine the current screen size with
    # "Image.core.display_mode()[1]", but I think that's overkill...

    screen = im.size

    dpi = 100, 100

    # PCX header
    fp.write(
        o8(10)
        + o8(version)
        + o8(1)
        + o8(bits)
        + o16(0)
        + o16(0)
        + o16(im.size[0] - 1)
        + o16(im.size[1] - 1)
        + o16(dpi[0])
        + o16(dpi[1])
        + b"\0" * 24
        + b"\xFF" * 24
        + b"\0"
        + o8(planes)
        + o16(stride)
        + o16(1)
        + o16(screen[0])
        + o16(screen[1])
        + b"\0" * 54
    )

    assert fp.tell() == 128

    ImageFile._save(im, fp, [("pcx", (0, 0) + im.size, 0, (rawmode, bits * planes))])

    if im.mode == "P":
        # colour palette
        fp.write(o8(12))
        fp.write(im.im.getpalette("RGB", "RGB"))  # 768 bytes
    elif im.mode == "L":
        # greyscale palette
        fp.write(o8(12))
        for i in range(256):
            fp.write(o8(i) * 3)


# --------------------------------------------------------------------
# registry


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

Image.register_extension(PcxImageFile.format, ".pcx")

Image.register_mime(PcxImageFile.format, "image/x-pcx")

```
--- 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