{
    "file_diffs": [
        {
            "old_file_content": "import os\nimport re\nimport warnings\nfrom io import BytesIO\n\nimport pytest\n\nfrom PIL import (\n    ExifTags,\n    Image,\n    ImageFile,\n    ImageOps,\n    JpegImagePlugin,\n    UnidentifiedImageError,\n    features,\n)\n\nfrom .helper import (\n    assert_image,\n    assert_image_equal,\n    assert_image_equal_tofile,\n    assert_image_similar,\n    assert_image_similar_tofile,\n    cjpeg_available,\n    djpeg_available,\n    hopper,\n    is_win32,\n    mark_if_feature_version,\n    skip_unless_feature,\n)\n\ntry:\n    from defusedxml import ElementTree\nexcept ImportError:\n    ElementTree = None\n\nTEST_FILE = \"Tests/images/hopper.jpg\"\n\n\n@skip_unless_feature(\"jpg\")\nclass TestFileJpeg:\n    def roundtrip(self, im, **options):\n        out = BytesIO()\n        im.save(out, \"JPEG\", **options)\n        test_bytes = out.tell()\n        out.seek(0)\n        im = Image.open(out)\n        im.bytes = test_bytes  # for testing only\n        return im\n\n    def gen_random_image(self, size, mode=\"RGB\"):\n        \"\"\"Generates a very hard to compress file\n        :param size: tuple\n        :param mode: optional image mode\n\n        \"\"\"\n        return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode)))\n\n    def test_sanity(self):\n        # internal version number\n        assert re.search(r\"\\d+\\.\\d+$\", features.version_codec(\"jpg\"))\n\n        with Image.open(TEST_FILE) as im:\n            im.load()\n            assert im.mode == \"RGB\"\n            assert im.size == (128, 128)\n            assert im.format == \"JPEG\"\n            assert im.get_format_mimetype() == \"image/jpeg\"\n\n    @pytest.mark.parametrize(\"size\", ((1, 0), (0, 1), (0, 0)))\n    def test_zero(self, size, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        im = Image.new(\"RGB\", size)\n        with pytest.raises(ValueError):\n            im.save(f)\n\n    def test_app(self):\n        # Test APP/COM reader (@PIL135)\n        with Image.open(TEST_FILE) as im:\n            assert im.applist[0] == (\"APP0\", b\"JFIF\\x00\\x01\\x01\\x01\\x00`\\x00`\\x00\\x00\")\n            assert im.applist[1] == (\n                \"COM\",\n                b\"File written by Adobe Photoshop\\xa8 4.0\\x00\",\n            )\n            assert len(im.applist) == 2\n\n            assert im.info[\"comment\"] == b\"File written by Adobe Photoshop\\xa8 4.0\\x00\"\n            assert im.app[\"COM\"] == im.info[\"comment\"]\n\n    def test_comment_write(self):\n        with Image.open(TEST_FILE) as im:\n            assert im.info[\"comment\"] == b\"File written by Adobe Photoshop\\xa8 4.0\\x00\"\n\n            # Test that existing comment is saved by default\n            out = BytesIO()\n            im.save(out, format=\"JPEG\")\n            with Image.open(out) as reloaded:\n                assert im.info[\"comment\"] == reloaded.info[\"comment\"]\n\n            # Ensure that a blank comment causes any existing comment to be removed\n            for comment in (\"\", b\"\", None):\n                out = BytesIO()\n                im.save(out, format=\"JPEG\", comment=comment)\n                with Image.open(out) as reloaded:\n                    assert \"comment\" not in reloaded.info\n\n            # Test that a comment argument overrides the default comment\n            for comment in (\"Test comment text\", b\"Text comment text\"):\n                out = BytesIO()\n                im.save(out, format=\"JPEG\", comment=comment)\n                with Image.open(out) as reloaded:\n                    if not isinstance(comment, bytes):\n                        comment = comment.encode()\n                    assert reloaded.info[\"comment\"] == comment\n\n    def test_cmyk(self):\n        # Test CMYK handling.  Thanks to Tim and Charlie for test data,\n        # Michael for getting me to look one more time.\n        f = \"Tests/images/pil_sample_cmyk.jpg\"\n        with Image.open(f) as im:\n            # the source image has red pixels in the upper left corner.\n            c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))\n            assert c == 0.0\n            assert m > 0.8\n            assert y > 0.8\n            assert k == 0.0\n            # the opposite corner is black\n            c, m, y, k = (\n                x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))\n            )\n            assert k > 0.9\n            # roundtrip, and check again\n            im = self.roundtrip(im)\n            c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))\n            assert c == 0.0\n            assert m > 0.8\n            assert y > 0.8\n            assert k == 0.0\n            c, m, y, k = (\n                x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))\n            )\n            assert k > 0.9\n\n    @pytest.mark.parametrize(\n        \"test_image_path\",\n        [TEST_FILE, \"Tests/images/pil_sample_cmyk.jpg\"],\n    )\n    def test_dpi(self, test_image_path):\n        def test(xdpi, ydpi=None):\n            with Image.open(test_image_path) as im:\n                im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi))\n            return im.info.get(\"dpi\")\n\n        assert test(72) == (72, 72)\n        assert test(300) == (300, 300)\n        assert test(100, 200) == (100, 200)\n        assert test(0) is None  # square pixels\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_icc(self, tmp_path):\n        # Test ICC support\n        with Image.open(\"Tests/images/rgb.jpg\") as im1:\n            icc_profile = im1.info[\"icc_profile\"]\n            assert len(icc_profile) == 3144\n            # Roundtrip via physical file.\n            f = str(tmp_path / \"temp.jpg\")\n            im1.save(f, icc_profile=icc_profile)\n        with Image.open(f) as im2:\n            assert im2.info.get(\"icc_profile\") == icc_profile\n            # Roundtrip via memory buffer.\n            im1 = self.roundtrip(hopper())\n            im2 = self.roundtrip(hopper(), icc_profile=icc_profile)\n            assert_image_equal(im1, im2)\n            assert not im1.info.get(\"icc_profile\")\n            assert im2.info.get(\"icc_profile\")\n\n    @pytest.mark.parametrize(\n        \"n\",\n        (\n            0,\n            1,\n            3,\n            4,\n            5,\n            65533 - 14,  # full JPEG marker block\n            65533 - 14 + 1,  # full block plus one byte\n            ImageFile.MAXBLOCK,  # full buffer block\n            ImageFile.MAXBLOCK + 1,  # full buffer block plus one byte\n            ImageFile.MAXBLOCK * 4 + 3,  # large block\n        ),\n    )\n    def test_icc_big(self, n):\n        # Make sure that the \"extra\" support handles large blocks\n        # The ICC APP marker can store 65519 bytes per marker, so\n        # using a 4-byte test code should allow us to detect out of\n        # order issues.\n        icc_profile = (b\"Test\" * int(n / 4 + 1))[:n]\n        assert len(icc_profile) == n  # sanity\n        im1 = self.roundtrip(hopper(), icc_profile=icc_profile)\n        assert im1.info.get(\"icc_profile\") == (icc_profile or None)\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_large_icc_meta(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        # Sometimes the meta data on the icc_profile block is bigger than\n        # Image.MAXBLOCK or the image size.\n        with Image.open(\"Tests/images/icc_profile_big.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            icc_profile = im.info[\"icc_profile\"]\n            # Should not raise OSError for image with icc larger than image size.\n            im.save(\n                f,\n                format=\"JPEG\",\n                progressive=True,\n                quality=95,\n                icc_profile=icc_profile,\n                optimize=True,\n            )\n\n    def test_optimize(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), optimize=0)\n        im3 = self.roundtrip(hopper(), optimize=1)\n        assert_image_equal(im1, im2)\n        assert_image_equal(im1, im3)\n        assert im1.bytes >= im2.bytes\n        assert im1.bytes >= im3.bytes\n\n    def test_optimize_large_buffer(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        f = str(tmp_path / \"temp.jpg\")\n        # this requires ~ 1.5x Image.MAXBLOCK\n        im = Image.new(\"RGB\", (4096, 4096), 0xFF3333)\n        im.save(f, format=\"JPEG\", optimize=True)\n\n    def test_progressive(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), progressive=False)\n        im3 = self.roundtrip(hopper(), progressive=True)\n        assert not im1.info.get(\"progressive\")\n        assert not im2.info.get(\"progressive\")\n        assert im3.info.get(\"progressive\")\n\n        assert_image_equal(im1, im3)\n        assert im1.bytes >= im3.bytes\n\n    def test_progressive_large_buffer(self, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        # this requires ~ 1.5x Image.MAXBLOCK\n        im = Image.new(\"RGB\", (4096, 4096), 0xFF3333)\n        im.save(f, format=\"JPEG\", progressive=True)\n\n    def test_progressive_large_buffer_highest_quality(self, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        im = self.gen_random_image((255, 255))\n        # this requires more bytes than pixels in the image\n        im.save(f, format=\"JPEG\", progressive=True, quality=100)\n\n    def test_progressive_cmyk_buffer(self):\n        # Issue 2272, quality 90 cmyk image is tripping the large buffer bug.\n        f = BytesIO()\n        im = self.gen_random_image((256, 256), \"CMYK\")\n        im.save(f, format=\"JPEG\", progressive=True, quality=94)\n\n    def test_large_exif(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        f = str(tmp_path / \"temp.jpg\")\n        im = hopper()\n        im.save(f, \"JPEG\", quality=90, exif=b\"1\" * 65532)\n\n    def test_exif_typeerror(self):\n        with Image.open(\"Tests/images/exif_typeerror.jpg\") as im:\n            # Should not raise a TypeError\n            im._getexif()\n\n    def test_exif_gps(self, tmp_path):\n        expected_exif_gps = {\n            0: b\"\\x00\\x00\\x00\\x01\",\n            2: 4294967295,\n            5: b\"\\x01\",\n            30: 65535,\n            29: \"1999:99:99 99:99:99\",\n        }\n        gps_index = 34853\n\n        # Reading\n        with Image.open(\"Tests/images/exif_gps.jpg\") as im:\n            exif = im._getexif()\n            assert exif[gps_index] == expected_exif_gps\n\n        # Writing\n        f = str(tmp_path / \"temp.jpg\")\n        exif = Image.Exif()\n        exif[gps_index] = expected_exif_gps\n        hopper().save(f, exif=exif)\n\n        with Image.open(f) as reloaded:\n            exif = reloaded._getexif()\n            assert exif[gps_index] == expected_exif_gps\n\n    def test_empty_exif_gps(self):\n        with Image.open(\"Tests/images/empty_gps_ifd.jpg\") as im:\n            exif = im.getexif()\n            del exif[0x8769]\n\n            # Assert that it needs to be transposed\n            assert exif[0x0112] == Image.Transpose.TRANSVERSE\n\n            # Assert that the GPS IFD is present and empty\n            assert exif.get_ifd(0x8825) == {}\n\n            transposed = ImageOps.exif_transpose(im)\n        exif = transposed.getexif()\n        assert exif.get_ifd(0x8825) == {}\n\n        # Assert that it was transposed\n        assert 0x0112 not in exif\n\n    def test_exif_equality(self):\n        # In 7.2.0, Exif rationals were changed to be read as\n        # TiffImagePlugin.IFDRational. This class had a bug in __eq__,\n        # breaking the self-equality of Exif data\n        exifs = []\n        for i in range(2):\n            with Image.open(\"Tests/images/exif-200dpcm.jpg\") as im:\n                exifs.append(im._getexif())\n        assert exifs[0] == exifs[1]\n\n    def test_exif_rollback(self):\n        # rolling back exif support in 3.1 to pre-3.0 formatting.\n        # expected from 2.9, with b/u qualifiers switched for 3.2 compatibility\n        # this test passes on 2.9 and 3.1, but not 3.0\n        expected_exif = {\n            34867: 4294967295,\n            258: (24, 24, 24),\n            36867: \"2099:09:29 10:10:10\",\n            34853: {\n                0: b\"\\x00\\x00\\x00\\x01\",\n                2: 4294967295,\n                5: b\"\\x01\",\n                30: 65535,\n                29: \"1999:99:99 99:99:99\",\n            },\n            296: 65535,\n            34665: 185,\n            41994: 65535,\n            514: 4294967295,\n            271: \"Make\",\n            272: \"XXX-XXX\",\n            305: \"PIL\",\n            42034: (1, 1, 1, 1),\n            42035: \"LensMake\",\n            34856: b\"\\xaa\\xaa\\xaa\\xaa\\xaa\\xaa\",\n            282: 4294967295,\n            33434: 4294967295,\n        }\n\n        with Image.open(\"Tests/images/exif_gps.jpg\") as im:\n            exif = im._getexif()\n\n        for tag, value in expected_exif.items():\n            assert value == exif[tag]\n\n    def test_exif_gps_typeerror(self):\n        with Image.open(\"Tests/images/exif_gps_typeerror.jpg\") as im:\n            # Should not raise a TypeError\n            im._getexif()\n\n    def test_progressive_compat(self):\n        im1 = self.roundtrip(hopper())\n        assert not im1.info.get(\"progressive\")\n        assert not im1.info.get(\"progression\")\n\n        im2 = self.roundtrip(hopper(), progressive=0)\n        im3 = self.roundtrip(hopper(), progression=0)  # compatibility\n        assert not im2.info.get(\"progressive\")\n        assert not im2.info.get(\"progression\")\n        assert not im3.info.get(\"progressive\")\n        assert not im3.info.get(\"progression\")\n\n        im2 = self.roundtrip(hopper(), progressive=1)\n        im3 = self.roundtrip(hopper(), progression=1)  # compatibility\n        assert_image_equal(im1, im2)\n        assert_image_equal(im1, im3)\n        assert im2.info.get(\"progressive\")\n        assert im2.info.get(\"progression\")\n        assert im3.info.get(\"progressive\")\n        assert im3.info.get(\"progression\")\n\n    def test_quality(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), quality=50)\n        assert_image(im1, im2.mode, im2.size)\n        assert im1.bytes >= im2.bytes\n\n        im3 = self.roundtrip(hopper(), quality=0)\n        assert_image(im1, im3.mode, im3.size)\n        assert im2.bytes > im3.bytes\n\n    def test_smooth(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), smooth=100)\n        assert_image(im1, im2.mode, im2.size)\n\n    def test_subsampling(self):\n        def getsampling(im):\n            layer = im.layer\n            return layer[0][1:3] + layer[1][1:3] + layer[2][1:3]\n\n        # experimental API\n        im = self.roundtrip(hopper(), subsampling=-1)  # default\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=0)  # 4:4:4\n        assert getsampling(im) == (1, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=1)  # 4:2:2\n        assert getsampling(im) == (2, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=2)  # 4:2:0\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=3)  # default (undefined)\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n\n        im = self.roundtrip(hopper(), subsampling=\"4:4:4\")\n        assert getsampling(im) == (1, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:2:2\")\n        assert getsampling(im) == (2, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:2:0\")\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:1:1\")\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n\n        with pytest.raises(TypeError):\n            self.roundtrip(hopper(), subsampling=\"1:1:1\")\n\n    def test_exif(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            info = im._getexif()\n            assert info[305] == \"Adobe Photoshop CS Macintosh\"\n\n    def test_get_child_images(self):\n        with Image.open(\"Tests/images/flower.jpg\") as im:\n            ims = im.get_child_images()\n\n        assert len(ims) == 1\n        assert_image_equal_tofile(ims[0], \"Tests/images/flower_thumbnail.png\")\n\n    def test_mp(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            assert im._getmp() is None\n\n    def test_quality_keep(self, tmp_path):\n        # RGB\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n        # Grayscale\n        with Image.open(\"Tests/images/hopper_gray.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n        # CMYK\n        with Image.open(\"Tests/images/pil_sample_cmyk.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n\n    def test_junk_jpeg_header(self):\n        # https://github.com/python-pillow/Pillow/issues/630\n        filename = \"Tests/images/junk_jpeg_header.jpg\"\n        with Image.open(filename):\n            pass\n\n    def test_ff00_jpeg_header(self):\n        filename = \"Tests/images/jpeg_ff00_header.jpg\"\n        with Image.open(filename):\n            pass\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_truncated_jpeg_should_read_all_the_data(self):\n        filename = \"Tests/images/truncated_jpeg.jpg\"\n        ImageFile.LOAD_TRUNCATED_IMAGES = True\n        with Image.open(filename) as im:\n            im.load()\n            ImageFile.LOAD_TRUNCATED_IMAGES = False\n            assert im.getbbox() is not None\n\n    def test_truncated_jpeg_throws_oserror(self):\n        filename = \"Tests/images/truncated_jpeg.jpg\"\n        with Image.open(filename) as im:\n            with pytest.raises(OSError):\n                im.load()\n\n            # Test that the error is raised if loaded a second time\n            with pytest.raises(OSError):\n                im.load()\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_qtables(self, tmp_path):\n        def _n_qtables_helper(n, test_file):\n            with Image.open(test_file) as im:\n                f = str(tmp_path / \"temp.jpg\")\n                im.save(f, qtables=[[n] * 64] * n)\n            with Image.open(f) as im:\n                assert len(im.quantization) == n\n                reloaded = self.roundtrip(im, qtables=\"keep\")\n                assert im.quantization == reloaded.quantization\n                assert max(reloaded.quantization[0]) <= 255\n\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            qtables = im.quantization\n            reloaded = self.roundtrip(im, qtables=qtables, subsampling=0)\n            assert im.quantization == reloaded.quantization\n            assert_image_similar(im, self.roundtrip(im, qtables=\"web_low\"), 30)\n            assert_image_similar(im, self.roundtrip(im, qtables=\"web_high\"), 30)\n            assert_image_similar(im, self.roundtrip(im, qtables=\"keep\"), 30)\n\n            # valid bounds for baseline qtable\n            bounds_qtable = [int(s) for s in (\"255 1 \" * 32).split(None)]\n            im2 = self.roundtrip(im, qtables=[bounds_qtable])\n            assert im2.quantization == {0: bounds_qtable}\n\n            # values from wizard.txt in jpeg9-a src package.\n            standard_l_qtable = [\n                int(s)\n                for s in \"\"\"\n                16  11  10  16  24  40  51  61\n                12  12  14  19  26  58  60  55\n                14  13  16  24  40  57  69  56\n                14  17  22  29  51  87  80  62\n                18  22  37  56  68 109 103  77\n                24  35  55  64  81 104 113  92\n                49  64  78  87 103 121 120 101\n                72  92  95  98 112 100 103  99\n                \"\"\".split(\n                    None\n                )\n            ]\n\n            standard_chrominance_qtable = [\n                int(s)\n                for s in \"\"\"\n                17  18  24  47  99  99  99  99\n                18  21  26  66  99  99  99  99\n                24  26  56  99  99  99  99  99\n                47  66  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                \"\"\".split(\n                    None\n                )\n            ]\n            # list of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables=[standard_l_qtable, standard_chrominance_qtable]\n                ),\n                30,\n            )\n\n            # tuple of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables=(standard_l_qtable, standard_chrominance_qtable)\n                ),\n                30,\n            )\n\n            # dict of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables={0: standard_l_qtable, 1: standard_chrominance_qtable}\n                ),\n                30,\n            )\n\n            _n_qtables_helper(1, \"Tests/images/hopper_gray.jpg\")\n            _n_qtables_helper(1, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(2, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(3, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(1, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(2, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(3, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(4, \"Tests/images/pil_sample_cmyk.jpg\")\n\n            # not a sequence\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=\"a\")\n            # sequence wrong length\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[])\n            # sequence wrong length\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[1, 2, 3, 4, 5])\n\n            # qtable entry not a sequence\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[1])\n            # qtable entry has wrong number of items\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[[1, 2, 3, 4]])\n\n    def test_load_16bit_qtables(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            assert len(im.quantization) == 2\n            assert len(im.quantization[0]) == 64\n            assert max(im.quantization[0]) > 255\n\n    def test_save_multiple_16bit_qtables(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            im2 = self.roundtrip(im, qtables=\"keep\")\n            assert im.quantization == im2.quantization\n\n    def test_save_single_16bit_qtable(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            im2 = self.roundtrip(im, qtables={0: im.quantization[0]})\n            assert len(im2.quantization) == 1\n            assert im2.quantization[0] == im.quantization[0]\n\n    def test_save_low_quality_baseline_qtables(self):\n        with Image.open(TEST_FILE) as im:\n            im2 = self.roundtrip(im, quality=10)\n            assert len(im2.quantization) == 2\n            assert max(im2.quantization[0]) <= 255\n            assert max(im2.quantization[1]) <= 255\n\n    def test_convert_dict_qtables_deprecation(self):\n        with pytest.warns(DeprecationWarning):\n            qtable = {0: [1, 2, 3, 4]}\n            qtable2 = JpegImagePlugin.convert_dict_qtables(qtable)\n            assert qtable == qtable2\n\n    @pytest.mark.skipif(not djpeg_available(), reason=\"djpeg not available\")\n    def test_load_djpeg(self):\n        with Image.open(TEST_FILE) as img:\n            img.load_djpeg()\n            assert_image_similar_tofile(img, TEST_FILE, 5)\n\n    @pytest.mark.skipif(not cjpeg_available(), reason=\"cjpeg not available\")\n    def test_save_cjpeg(self, tmp_path):\n        with Image.open(TEST_FILE) as img:\n            tempfile = str(tmp_path / \"temp.jpg\")\n            JpegImagePlugin._save_cjpeg(img, 0, tempfile)\n            # Default save quality is 75%, so a tiny bit of difference is alright\n            assert_image_similar_tofile(img, tempfile, 17)\n\n    def test_no_duplicate_0x1001_tag(self):\n        # Arrange\n        tag_ids = {v: k for k, v in ExifTags.TAGS.items()}\n\n        # Assert\n        assert tag_ids[\"RelatedImageWidth\"] == 0x1001\n        assert tag_ids[\"RelatedImageLength\"] == 0x1002\n\n    def test_MAXBLOCK_scaling(self, tmp_path):\n        im = self.gen_random_image((512, 512))\n        f = str(tmp_path / \"temp.jpeg\")\n        im.save(f, quality=100, optimize=True)\n\n        with Image.open(f) as reloaded:\n            # none of these should crash\n            reloaded.save(f, quality=\"keep\")\n            reloaded.save(f, quality=\"keep\", progressive=True)\n            reloaded.save(f, quality=\"keep\", optimize=True)\n\n    def test_bad_mpo_header(self):\n        \"\"\"Treat unknown MPO as JPEG\"\"\"\n        # Arrange\n\n        # Act\n        # Shouldn't raise error\n        fn = \"Tests/images/sugarshack_bad_mpo_header.jpg\"\n        with pytest.warns(UserWarning, Image.open, fn) as im:\n            # Assert\n            assert im.format == \"JPEG\"\n\n    @pytest.mark.parametrize(\"mode\", (\"1\", \"L\", \"RGB\", \"RGBX\", \"CMYK\", \"YCbCr\"))\n    def test_save_correct_modes(self, mode):\n        out = BytesIO()\n        img = Image.new(mode, (20, 20))\n        img.save(out, \"JPEG\")\n\n    @pytest.mark.parametrize(\"mode\", (\"LA\", \"La\", \"RGBA\", \"RGBa\", \"P\"))\n    def test_save_wrong_modes(self, mode):\n        # ref https://github.com/python-pillow/Pillow/issues/2005\n        out = BytesIO()\n        img = Image.new(mode, (20, 20))\n        with pytest.raises(OSError):\n            img.save(out, \"JPEG\")\n\n    def test_save_tiff_with_dpi(self, tmp_path):\n        # Arrange\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            # Act\n            im.save(outfile, \"JPEG\", dpi=im.info[\"dpi\"])\n\n            # Assert\n            with Image.open(outfile) as reloaded:\n                reloaded.load()\n                assert im.info[\"dpi\"] == reloaded.info[\"dpi\"]\n\n    def test_save_dpi_rounding(self, tmp_path):\n        outfile = str(tmp_path / \"temp.jpg\")\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            im.save(outfile, dpi=(72.2, 72.2))\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == (72, 72)\n\n            im.save(outfile, dpi=(72.8, 72.8))\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"dpi\"] == (73, 73)\n\n    def test_dpi_tuple_from_exif(self):\n        # Arrange\n        # This Photoshop CC 2017 image has DPI in EXIF not metadata\n        # EXIF XResolution is (2000000, 10000)\n        with Image.open(\"Tests/images/photoshop-200dpi.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (200, 200)\n\n    def test_dpi_int_from_exif(self):\n        # Arrange\n        # This image has DPI in EXIF not metadata\n        # EXIF XResolution is 72\n        with Image.open(\"Tests/images/exif-72dpi-int.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_dpi_from_dpcm_exif(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm:\n        # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/exif-200dpcm.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (508, 508)\n\n    def test_dpi_exif_zero_division(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with EXIF resolution set to 0/0:\n        # exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/exif-dpi-zerodivision.jpg\") as im:\n            # Act / Assert\n            # This should return the default, and not raise a ZeroDivisionError\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_dpi_exif_string(self):\n        # Arrange\n        # 0x011A tag in this exif contains string '300300\\x02'\n        with Image.open(\"Tests/images/broken_exif_dpi.jpg\") as im:\n            # Act / Assert\n            # This should return the default\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_no_dpi_in_exif(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with resolution removed from EXIF:\n        # exiftool \"-*resolution*\"= photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/no-dpi-in-exif.jpg\") as im:\n            # Act / Assert\n            # \"When the image resolution is unknown, 72 [dpi] is designated.\"\n            # https://exiv2.org/tags.html\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_invalid_exif(self):\n        # This is no-dpi-in-exif with the tiff header of the exif block\n        # hexedited from MM * to FF FF FF FF\n        with Image.open(\"Tests/images/invalid-exif.jpg\") as im:\n            # This should return the default, and not a SyntaxError or\n            # OSError for unidentified image.\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_exif_x_resolution(self, tmp_path):\n        with Image.open(\"Tests/images/flower.jpg\") as im:\n            exif = im.getexif()\n            assert exif[282] == 180\n\n            out = str(tmp_path / \"out.jpg\")\n            with warnings.catch_warnings():\n                im.save(out, exif=exif)\n\n        with Image.open(out) as reloaded:\n            assert reloaded.getexif()[282] == 180\n\n    def test_invalid_exif_x_resolution(self):\n        # When no x or y resolution is defined in EXIF\n        with Image.open(\"Tests/images/invalid-exif-without-x-resolution.jpg\") as im:\n            # This should return the default, and not a ValueError or\n            # OSError for an unidentified image.\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_ifd_offset_exif(self):\n        # Arrange\n        # This image has been manually hexedited to have an IFD offset of 10,\n        # in contrast to normal 8\n        with Image.open(\"Tests/images/exif-ifd-offset.jpg\") as im:\n            # Act / Assert\n            assert im._getexif()[306] == \"2017:03:13 23:03:09\"\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_photoshop(self):\n        with Image.open(\"Tests/images/photoshop-200dpi.jpg\") as im:\n            assert im.info[\"photoshop\"][0x03ED] == {\n                \"XResolution\": 200.0,\n                \"DisplayedUnitsX\": 1,\n                \"YResolution\": 200.0,\n                \"DisplayedUnitsY\": 1,\n            }\n\n            # Test that the image can still load, even with broken Photoshop data\n            # This image had the APP13 length hexedited to be smaller\n            assert_image_equal_tofile(im, \"Tests/images/photoshop-200dpi-broken.jpg\")\n\n        # This image does not contain a Photoshop header string\n        with Image.open(\"Tests/images/app13.jpg\") as im:\n            assert \"photoshop\" not in im.info\n\n    def test_photoshop_malformed_and_multiple(self):\n        with Image.open(\"Tests/images/app13-multiple.jpg\") as im:\n            assert \"photoshop\" in im.info\n            assert 24 == len(im.info[\"photoshop\"])\n            apps_13_lengths = [len(v) for k, v in im.applist if k == \"APP13\"]\n            assert [65504, 24] == apps_13_lengths\n\n    def test_adobe_transform(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            assert im.info[\"adobe_transform\"] == 1\n\n        with Image.open(\"Tests/images/pil_sample_cmyk.jpg\") as im:\n            assert im.info[\"adobe_transform\"] == 2\n\n        # This image has been manually hexedited\n        # so that the APP14 reports its length to be 11,\n        # leaving no room for \"adobe_transform\"\n        with Image.open(\"Tests/images/truncated_app14.jpg\") as im:\n            assert \"adobe\" in im.info\n            assert \"adobe_transform\" not in im.info\n\n    def test_icc_after_SOF(self):\n        with Image.open(\"Tests/images/icc-after-SOF.jpg\") as im:\n            assert im.info[\"icc_profile\"] == b\"profile\"\n\n    def test_jpeg_magic_number(self):\n        size = 4097\n        buffer = BytesIO(b\"\\xFF\" * size)  # Many xFF bytes\n        buffer.max_pos = 0\n        orig_read = buffer.read\n\n        def read(n=-1):\n            res = orig_read(n)\n            buffer.max_pos = max(buffer.max_pos, buffer.tell())\n            return res\n\n        buffer.read = read\n        with pytest.raises(UnidentifiedImageError):\n            with Image.open(buffer):\n                pass\n\n        # Assert the entire file has not been read\n        assert 0 < buffer.max_pos < size\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/xmp_test.jpg\") as im:\n            if ElementTree is None:\n                with pytest.warns(UserWarning):\n                    assert im.getxmp() == {}\n            else:\n                xmp = im.getxmp()\n\n                description = xmp[\"xmpmeta\"][\"RDF\"][\"Description\"]\n                assert description[\"DerivedFrom\"] == {\n                    \"documentID\": \"8367D410E636EA95B7DE7EBA1C43A412\",\n                    \"originalDocumentID\": \"8367D410E636EA95B7DE7EBA1C43A412\",\n                }\n                assert description[\"Look\"][\"Description\"][\"Group\"][\"Alt\"][\"li\"] == {\n                    \"lang\": \"x-default\",\n                    \"text\": \"Profiles\",\n                }\n                assert description[\"ToneCurve\"][\"Seq\"][\"li\"] == [\"0, 0\", \"255, 255\"]\n\n                # Attribute\n                assert description[\"Version\"] == \"10.4\"\n\n        if ElementTree is not None:\n            with Image.open(\"Tests/images/hopper.jpg\") as im:\n                assert im.getxmp() == {}\n\n    @pytest.mark.timeout(timeout=1)\n    def test_eof(self):\n        # Even though this decoder never says that it is finished\n        # the image should still end when there is no new data\n        class InfiniteMockPyDecoder(ImageFile.PyDecoder):\n            def decode(self, buffer):\n                return 0, 0\n\n        decoder = InfiniteMockPyDecoder(None)\n\n        def closure(mode, *args):\n            decoder.__init__(mode, *args)\n            return decoder\n\n        Image.register_decoder(\"INFINITE\", closure)\n\n        with Image.open(TEST_FILE) as im:\n            im.tile = [\n                (\"INFINITE\", (0, 0, 128, 128), 0, (\"RGB\", 0, 1)),\n            ]\n            ImageFile.LOAD_TRUNCATED_IMAGES = True\n            im.load()\n            ImageFile.LOAD_TRUNCATED_IMAGES = False\n\n\n@pytest.mark.skipif(not is_win32(), reason=\"Windows only\")\n@skip_unless_feature(\"jpg\")\nclass TestFileCloseW32:\n    def test_fd_leak(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.jpg\")\n\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            im.save(tmpfile)\n\n        im = Image.open(tmpfile)\n        fp = im.fp\n        assert not fp.closed\n        with pytest.raises(OSError):\n            os.remove(tmpfile)\n        im.load()\n        assert fp.closed\n        # this should not fail, as load should have closed the file.\n        os.remove(tmpfile)\n",
            "new_file_content": "import os\nimport re\nimport warnings\nfrom io import BytesIO\n\nimport pytest\n\nfrom PIL import (\n    ExifTags,\n    Image,\n    ImageFile,\n    ImageOps,\n    JpegImagePlugin,\n    UnidentifiedImageError,\n    features,\n)\n\nfrom .helper import (\n    assert_image,\n    assert_image_equal,\n    assert_image_equal_tofile,\n    assert_image_similar,\n    assert_image_similar_tofile,\n    cjpeg_available,\n    djpeg_available,\n    hopper,\n    is_win32,\n    mark_if_feature_version,\n    skip_unless_feature,\n)\n\ntry:\n    from defusedxml import ElementTree\nexcept ImportError:\n    ElementTree = None\n\nTEST_FILE = \"Tests/images/hopper.jpg\"\n\n\n@skip_unless_feature(\"jpg\")\nclass TestFileJpeg:\n    def roundtrip(self, im, **options):\n        out = BytesIO()\n        im.save(out, \"JPEG\", **options)\n        test_bytes = out.tell()\n        out.seek(0)\n        im = Image.open(out)\n        im.bytes = test_bytes  # for testing only\n        return im\n\n    def gen_random_image(self, size, mode=\"RGB\"):\n        \"\"\"Generates a very hard to compress file\n        :param size: tuple\n        :param mode: optional image mode\n\n        \"\"\"\n        return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode)))\n\n    def test_sanity(self):\n        # internal version number\n        assert re.search(r\"\\d+\\.\\d+$\", features.version_codec(\"jpg\"))\n\n        with Image.open(TEST_FILE) as im:\n            im.load()\n            assert im.mode == \"RGB\"\n            assert im.size == (128, 128)\n            assert im.format == \"JPEG\"\n            assert im.get_format_mimetype() == \"image/jpeg\"\n\n    @pytest.mark.parametrize(\"size\", ((1, 0), (0, 1), (0, 0)))\n    def test_zero(self, size, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        im = Image.new(\"RGB\", size)\n        with pytest.raises(ValueError):\n            im.save(f)\n\n    def test_app(self):\n        # Test APP/COM reader (@PIL135)\n        with Image.open(TEST_FILE) as im:\n            assert im.applist[0] == (\"APP0\", b\"JFIF\\x00\\x01\\x01\\x01\\x00`\\x00`\\x00\\x00\")\n            assert im.applist[1] == (\n                \"COM\",\n                b\"File written by Adobe Photoshop\\xa8 4.0\\x00\",\n            )\n            assert len(im.applist) == 2\n\n            assert im.info[\"comment\"] == b\"File written by Adobe Photoshop\\xa8 4.0\\x00\"\n            assert im.app[\"COM\"] == im.info[\"comment\"]\n\n    def test_comment_write(self):\n        with Image.open(TEST_FILE) as im:\n            assert im.info[\"comment\"] == b\"File written by Adobe Photoshop\\xa8 4.0\\x00\"\n\n            # Test that existing comment is saved by default\n            out = BytesIO()\n            im.save(out, format=\"JPEG\")\n            with Image.open(out) as reloaded:\n                assert im.info[\"comment\"] == reloaded.info[\"comment\"]\n\n            # Ensure that a blank comment causes any existing comment to be removed\n            for comment in (\"\", b\"\", None):\n                out = BytesIO()\n                im.save(out, format=\"JPEG\", comment=comment)\n                with Image.open(out) as reloaded:\n                    assert \"comment\" not in reloaded.info\n\n            # Test that a comment argument overrides the default comment\n            for comment in (\"Test comment text\", b\"Text comment text\"):\n                out = BytesIO()\n                im.save(out, format=\"JPEG\", comment=comment)\n                with Image.open(out) as reloaded:\n                    if not isinstance(comment, bytes):\n                        comment = comment.encode()\n                    assert reloaded.info[\"comment\"] == comment\n\n    def test_cmyk(self):\n        # Test CMYK handling.  Thanks to Tim and Charlie for test data,\n        # Michael for getting me to look one more time.\n        f = \"Tests/images/pil_sample_cmyk.jpg\"\n        with Image.open(f) as im:\n            # the source image has red pixels in the upper left corner.\n            c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))\n            assert c == 0.0\n            assert m > 0.8\n            assert y > 0.8\n            assert k == 0.0\n            # the opposite corner is black\n            c, m, y, k = (\n                x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))\n            )\n            assert k > 0.9\n            # roundtrip, and check again\n            im = self.roundtrip(im)\n            c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))\n            assert c == 0.0\n            assert m > 0.8\n            assert y > 0.8\n            assert k == 0.0\n            c, m, y, k = (\n                x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))\n            )\n            assert k > 0.9\n\n    @pytest.mark.parametrize(\n        \"test_image_path\",\n        [TEST_FILE, \"Tests/images/pil_sample_cmyk.jpg\"],\n    )\n    def test_dpi(self, test_image_path):\n        def test(xdpi, ydpi=None):\n            with Image.open(test_image_path) as im:\n                im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi))\n            return im.info.get(\"dpi\")\n\n        assert test(72) == (72, 72)\n        assert test(300) == (300, 300)\n        assert test(100, 200) == (100, 200)\n        assert test(0) is None  # square pixels\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_icc(self, tmp_path):\n        # Test ICC support\n        with Image.open(\"Tests/images/rgb.jpg\") as im1:\n            icc_profile = im1.info[\"icc_profile\"]\n            assert len(icc_profile) == 3144\n            # Roundtrip via physical file.\n            f = str(tmp_path / \"temp.jpg\")\n            im1.save(f, icc_profile=icc_profile)\n        with Image.open(f) as im2:\n            assert im2.info.get(\"icc_profile\") == icc_profile\n            # Roundtrip via memory buffer.\n            im1 = self.roundtrip(hopper())\n            im2 = self.roundtrip(hopper(), icc_profile=icc_profile)\n            assert_image_equal(im1, im2)\n            assert not im1.info.get(\"icc_profile\")\n            assert im2.info.get(\"icc_profile\")\n\n    @pytest.mark.parametrize(\n        \"n\",\n        (\n            0,\n            1,\n            3,\n            4,\n            5,\n            65533 - 14,  # full JPEG marker block\n            65533 - 14 + 1,  # full block plus one byte\n            ImageFile.MAXBLOCK,  # full buffer block\n            ImageFile.MAXBLOCK + 1,  # full buffer block plus one byte\n            ImageFile.MAXBLOCK * 4 + 3,  # large block\n        ),\n    )\n    def test_icc_big(self, n):\n        # Make sure that the \"extra\" support handles large blocks\n        # The ICC APP marker can store 65519 bytes per marker, so\n        # using a 4-byte test code should allow us to detect out of\n        # order issues.\n        icc_profile = (b\"Test\" * int(n / 4 + 1))[:n]\n        assert len(icc_profile) == n  # sanity\n        im1 = self.roundtrip(hopper(), icc_profile=icc_profile)\n        assert im1.info.get(\"icc_profile\") == (icc_profile or None)\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_large_icc_meta(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        # Sometimes the meta data on the icc_profile block is bigger than\n        # Image.MAXBLOCK or the image size.\n        with Image.open(\"Tests/images/icc_profile_big.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            icc_profile = im.info[\"icc_profile\"]\n            # Should not raise OSError for image with icc larger than image size.\n            im.save(\n                f,\n                format=\"JPEG\",\n                progressive=True,\n                quality=95,\n                icc_profile=icc_profile,\n                optimize=True,\n            )\n\n    def test_optimize(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), optimize=0)\n        im3 = self.roundtrip(hopper(), optimize=1)\n        assert_image_equal(im1, im2)\n        assert_image_equal(im1, im3)\n        assert im1.bytes >= im2.bytes\n        assert im1.bytes >= im3.bytes\n\n    def test_optimize_large_buffer(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        f = str(tmp_path / \"temp.jpg\")\n        # this requires ~ 1.5x Image.MAXBLOCK\n        im = Image.new(\"RGB\", (4096, 4096), 0xFF3333)\n        im.save(f, format=\"JPEG\", optimize=True)\n\n    def test_progressive(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), progressive=False)\n        im3 = self.roundtrip(hopper(), progressive=True)\n        assert not im1.info.get(\"progressive\")\n        assert not im2.info.get(\"progressive\")\n        assert im3.info.get(\"progressive\")\n\n        assert_image_equal(im1, im3)\n        assert im1.bytes >= im3.bytes\n\n    def test_progressive_large_buffer(self, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        # this requires ~ 1.5x Image.MAXBLOCK\n        im = Image.new(\"RGB\", (4096, 4096), 0xFF3333)\n        im.save(f, format=\"JPEG\", progressive=True)\n\n    def test_progressive_large_buffer_highest_quality(self, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        im = self.gen_random_image((255, 255))\n        # this requires more bytes than pixels in the image\n        im.save(f, format=\"JPEG\", progressive=True, quality=100)\n\n    def test_progressive_cmyk_buffer(self):\n        # Issue 2272, quality 90 cmyk image is tripping the large buffer bug.\n        f = BytesIO()\n        im = self.gen_random_image((256, 256), \"CMYK\")\n        im.save(f, format=\"JPEG\", progressive=True, quality=94)\n\n    def test_large_exif(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        f = str(tmp_path / \"temp.jpg\")\n        im = hopper()\n        im.save(f, \"JPEG\", quality=90, exif=b\"1\" * 65533)\n\n        with pytest.raises(ValueError):\n            im.save(f, \"JPEG\", quality=90, exif=b\"1\" * 65534)\n\n    def test_exif_typeerror(self):\n        with Image.open(\"Tests/images/exif_typeerror.jpg\") as im:\n            # Should not raise a TypeError\n            im._getexif()\n\n    def test_exif_gps(self, tmp_path):\n        expected_exif_gps = {\n            0: b\"\\x00\\x00\\x00\\x01\",\n            2: 4294967295,\n            5: b\"\\x01\",\n            30: 65535,\n            29: \"1999:99:99 99:99:99\",\n        }\n        gps_index = 34853\n\n        # Reading\n        with Image.open(\"Tests/images/exif_gps.jpg\") as im:\n            exif = im._getexif()\n            assert exif[gps_index] == expected_exif_gps\n\n        # Writing\n        f = str(tmp_path / \"temp.jpg\")\n        exif = Image.Exif()\n        exif[gps_index] = expected_exif_gps\n        hopper().save(f, exif=exif)\n\n        with Image.open(f) as reloaded:\n            exif = reloaded._getexif()\n            assert exif[gps_index] == expected_exif_gps\n\n    def test_empty_exif_gps(self):\n        with Image.open(\"Tests/images/empty_gps_ifd.jpg\") as im:\n            exif = im.getexif()\n            del exif[0x8769]\n\n            # Assert that it needs to be transposed\n            assert exif[0x0112] == Image.Transpose.TRANSVERSE\n\n            # Assert that the GPS IFD is present and empty\n            assert exif.get_ifd(0x8825) == {}\n\n            transposed = ImageOps.exif_transpose(im)\n        exif = transposed.getexif()\n        assert exif.get_ifd(0x8825) == {}\n\n        # Assert that it was transposed\n        assert 0x0112 not in exif\n\n    def test_exif_equality(self):\n        # In 7.2.0, Exif rationals were changed to be read as\n        # TiffImagePlugin.IFDRational. This class had a bug in __eq__,\n        # breaking the self-equality of Exif data\n        exifs = []\n        for i in range(2):\n            with Image.open(\"Tests/images/exif-200dpcm.jpg\") as im:\n                exifs.append(im._getexif())\n        assert exifs[0] == exifs[1]\n\n    def test_exif_rollback(self):\n        # rolling back exif support in 3.1 to pre-3.0 formatting.\n        # expected from 2.9, with b/u qualifiers switched for 3.2 compatibility\n        # this test passes on 2.9 and 3.1, but not 3.0\n        expected_exif = {\n            34867: 4294967295,\n            258: (24, 24, 24),\n            36867: \"2099:09:29 10:10:10\",\n            34853: {\n                0: b\"\\x00\\x00\\x00\\x01\",\n                2: 4294967295,\n                5: b\"\\x01\",\n                30: 65535,\n                29: \"1999:99:99 99:99:99\",\n            },\n            296: 65535,\n            34665: 185,\n            41994: 65535,\n            514: 4294967295,\n            271: \"Make\",\n            272: \"XXX-XXX\",\n            305: \"PIL\",\n            42034: (1, 1, 1, 1),\n            42035: \"LensMake\",\n            34856: b\"\\xaa\\xaa\\xaa\\xaa\\xaa\\xaa\",\n            282: 4294967295,\n            33434: 4294967295,\n        }\n\n        with Image.open(\"Tests/images/exif_gps.jpg\") as im:\n            exif = im._getexif()\n\n        for tag, value in expected_exif.items():\n            assert value == exif[tag]\n\n    def test_exif_gps_typeerror(self):\n        with Image.open(\"Tests/images/exif_gps_typeerror.jpg\") as im:\n            # Should not raise a TypeError\n            im._getexif()\n\n    def test_progressive_compat(self):\n        im1 = self.roundtrip(hopper())\n        assert not im1.info.get(\"progressive\")\n        assert not im1.info.get(\"progression\")\n\n        im2 = self.roundtrip(hopper(), progressive=0)\n        im3 = self.roundtrip(hopper(), progression=0)  # compatibility\n        assert not im2.info.get(\"progressive\")\n        assert not im2.info.get(\"progression\")\n        assert not im3.info.get(\"progressive\")\n        assert not im3.info.get(\"progression\")\n\n        im2 = self.roundtrip(hopper(), progressive=1)\n        im3 = self.roundtrip(hopper(), progression=1)  # compatibility\n        assert_image_equal(im1, im2)\n        assert_image_equal(im1, im3)\n        assert im2.info.get(\"progressive\")\n        assert im2.info.get(\"progression\")\n        assert im3.info.get(\"progressive\")\n        assert im3.info.get(\"progression\")\n\n    def test_quality(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), quality=50)\n        assert_image(im1, im2.mode, im2.size)\n        assert im1.bytes >= im2.bytes\n\n        im3 = self.roundtrip(hopper(), quality=0)\n        assert_image(im1, im3.mode, im3.size)\n        assert im2.bytes > im3.bytes\n\n    def test_smooth(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), smooth=100)\n        assert_image(im1, im2.mode, im2.size)\n\n    def test_subsampling(self):\n        def getsampling(im):\n            layer = im.layer\n            return layer[0][1:3] + layer[1][1:3] + layer[2][1:3]\n\n        # experimental API\n        im = self.roundtrip(hopper(), subsampling=-1)  # default\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=0)  # 4:4:4\n        assert getsampling(im) == (1, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=1)  # 4:2:2\n        assert getsampling(im) == (2, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=2)  # 4:2:0\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=3)  # default (undefined)\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n\n        im = self.roundtrip(hopper(), subsampling=\"4:4:4\")\n        assert getsampling(im) == (1, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:2:2\")\n        assert getsampling(im) == (2, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:2:0\")\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:1:1\")\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n\n        with pytest.raises(TypeError):\n            self.roundtrip(hopper(), subsampling=\"1:1:1\")\n\n    def test_exif(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            info = im._getexif()\n            assert info[305] == \"Adobe Photoshop CS Macintosh\"\n\n    def test_get_child_images(self):\n        with Image.open(\"Tests/images/flower.jpg\") as im:\n            ims = im.get_child_images()\n\n        assert len(ims) == 1\n        assert_image_equal_tofile(ims[0], \"Tests/images/flower_thumbnail.png\")\n\n    def test_mp(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            assert im._getmp() is None\n\n    def test_quality_keep(self, tmp_path):\n        # RGB\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n        # Grayscale\n        with Image.open(\"Tests/images/hopper_gray.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n        # CMYK\n        with Image.open(\"Tests/images/pil_sample_cmyk.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n\n    def test_junk_jpeg_header(self):\n        # https://github.com/python-pillow/Pillow/issues/630\n        filename = \"Tests/images/junk_jpeg_header.jpg\"\n        with Image.open(filename):\n            pass\n\n    def test_ff00_jpeg_header(self):\n        filename = \"Tests/images/jpeg_ff00_header.jpg\"\n        with Image.open(filename):\n            pass\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_truncated_jpeg_should_read_all_the_data(self):\n        filename = \"Tests/images/truncated_jpeg.jpg\"\n        ImageFile.LOAD_TRUNCATED_IMAGES = True\n        with Image.open(filename) as im:\n            im.load()\n            ImageFile.LOAD_TRUNCATED_IMAGES = False\n            assert im.getbbox() is not None\n\n    def test_truncated_jpeg_throws_oserror(self):\n        filename = \"Tests/images/truncated_jpeg.jpg\"\n        with Image.open(filename) as im:\n            with pytest.raises(OSError):\n                im.load()\n\n            # Test that the error is raised if loaded a second time\n            with pytest.raises(OSError):\n                im.load()\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_qtables(self, tmp_path):\n        def _n_qtables_helper(n, test_file):\n            with Image.open(test_file) as im:\n                f = str(tmp_path / \"temp.jpg\")\n                im.save(f, qtables=[[n] * 64] * n)\n            with Image.open(f) as im:\n                assert len(im.quantization) == n\n                reloaded = self.roundtrip(im, qtables=\"keep\")\n                assert im.quantization == reloaded.quantization\n                assert max(reloaded.quantization[0]) <= 255\n\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            qtables = im.quantization\n            reloaded = self.roundtrip(im, qtables=qtables, subsampling=0)\n            assert im.quantization == reloaded.quantization\n            assert_image_similar(im, self.roundtrip(im, qtables=\"web_low\"), 30)\n            assert_image_similar(im, self.roundtrip(im, qtables=\"web_high\"), 30)\n            assert_image_similar(im, self.roundtrip(im, qtables=\"keep\"), 30)\n\n            # valid bounds for baseline qtable\n            bounds_qtable = [int(s) for s in (\"255 1 \" * 32).split(None)]\n            im2 = self.roundtrip(im, qtables=[bounds_qtable])\n            assert im2.quantization == {0: bounds_qtable}\n\n            # values from wizard.txt in jpeg9-a src package.\n            standard_l_qtable = [\n                int(s)\n                for s in \"\"\"\n                16  11  10  16  24  40  51  61\n                12  12  14  19  26  58  60  55\n                14  13  16  24  40  57  69  56\n                14  17  22  29  51  87  80  62\n                18  22  37  56  68 109 103  77\n                24  35  55  64  81 104 113  92\n                49  64  78  87 103 121 120 101\n                72  92  95  98 112 100 103  99\n                \"\"\".split(\n                    None\n                )\n            ]\n\n            standard_chrominance_qtable = [\n                int(s)\n                for s in \"\"\"\n                17  18  24  47  99  99  99  99\n                18  21  26  66  99  99  99  99\n                24  26  56  99  99  99  99  99\n                47  66  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                \"\"\".split(\n                    None\n                )\n            ]\n            # list of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables=[standard_l_qtable, standard_chrominance_qtable]\n                ),\n                30,\n            )\n\n            # tuple of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables=(standard_l_qtable, standard_chrominance_qtable)\n                ),\n                30,\n            )\n\n            # dict of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables={0: standard_l_qtable, 1: standard_chrominance_qtable}\n                ),\n                30,\n            )\n\n            _n_qtables_helper(1, \"Tests/images/hopper_gray.jpg\")\n            _n_qtables_helper(1, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(2, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(3, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(1, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(2, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(3, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(4, \"Tests/images/pil_sample_cmyk.jpg\")\n\n            # not a sequence\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=\"a\")\n            # sequence wrong length\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[])\n            # sequence wrong length\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[1, 2, 3, 4, 5])\n\n            # qtable entry not a sequence\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[1])\n            # qtable entry has wrong number of items\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[[1, 2, 3, 4]])\n\n    def test_load_16bit_qtables(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            assert len(im.quantization) == 2\n            assert len(im.quantization[0]) == 64\n            assert max(im.quantization[0]) > 255\n\n    def test_save_multiple_16bit_qtables(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            im2 = self.roundtrip(im, qtables=\"keep\")\n            assert im.quantization == im2.quantization\n\n    def test_save_single_16bit_qtable(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            im2 = self.roundtrip(im, qtables={0: im.quantization[0]})\n            assert len(im2.quantization) == 1\n            assert im2.quantization[0] == im.quantization[0]\n\n    def test_save_low_quality_baseline_qtables(self):\n        with Image.open(TEST_FILE) as im:\n            im2 = self.roundtrip(im, quality=10)\n            assert len(im2.quantization) == 2\n            assert max(im2.quantization[0]) <= 255\n            assert max(im2.quantization[1]) <= 255\n\n    def test_convert_dict_qtables_deprecation(self):\n        with pytest.warns(DeprecationWarning):\n            qtable = {0: [1, 2, 3, 4]}\n            qtable2 = JpegImagePlugin.convert_dict_qtables(qtable)\n            assert qtable == qtable2\n\n    @pytest.mark.skipif(not djpeg_available(), reason=\"djpeg not available\")\n    def test_load_djpeg(self):\n        with Image.open(TEST_FILE) as img:\n            img.load_djpeg()\n            assert_image_similar_tofile(img, TEST_FILE, 5)\n\n    @pytest.mark.skipif(not cjpeg_available(), reason=\"cjpeg not available\")\n    def test_save_cjpeg(self, tmp_path):\n        with Image.open(TEST_FILE) as img:\n            tempfile = str(tmp_path / \"temp.jpg\")\n            JpegImagePlugin._save_cjpeg(img, 0, tempfile)\n            # Default save quality is 75%, so a tiny bit of difference is alright\n            assert_image_similar_tofile(img, tempfile, 17)\n\n    def test_no_duplicate_0x1001_tag(self):\n        # Arrange\n        tag_ids = {v: k for k, v in ExifTags.TAGS.items()}\n\n        # Assert\n        assert tag_ids[\"RelatedImageWidth\"] == 0x1001\n        assert tag_ids[\"RelatedImageLength\"] == 0x1002\n\n    def test_MAXBLOCK_scaling(self, tmp_path):\n        im = self.gen_random_image((512, 512))\n        f = str(tmp_path / \"temp.jpeg\")\n        im.save(f, quality=100, optimize=True)\n\n        with Image.open(f) as reloaded:\n            # none of these should crash\n            reloaded.save(f, quality=\"keep\")\n            reloaded.save(f, quality=\"keep\", progressive=True)\n            reloaded.save(f, quality=\"keep\", optimize=True)\n\n    def test_bad_mpo_header(self):\n        \"\"\"Treat unknown MPO as JPEG\"\"\"\n        # Arrange\n\n        # Act\n        # Shouldn't raise error\n        fn = \"Tests/images/sugarshack_bad_mpo_header.jpg\"\n        with pytest.warns(UserWarning, Image.open, fn) as im:\n            # Assert\n            assert im.format == \"JPEG\"\n\n    @pytest.mark.parametrize(\"mode\", (\"1\", \"L\", \"RGB\", \"RGBX\", \"CMYK\", \"YCbCr\"))\n    def test_save_correct_modes(self, mode):\n        out = BytesIO()\n        img = Image.new(mode, (20, 20))\n        img.save(out, \"JPEG\")\n\n    @pytest.mark.parametrize(\"mode\", (\"LA\", \"La\", \"RGBA\", \"RGBa\", \"P\"))\n    def test_save_wrong_modes(self, mode):\n        # ref https://github.com/python-pillow/Pillow/issues/2005\n        out = BytesIO()\n        img = Image.new(mode, (20, 20))\n        with pytest.raises(OSError):\n            img.save(out, \"JPEG\")\n\n    def test_save_tiff_with_dpi(self, tmp_path):\n        # Arrange\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            # Act\n            im.save(outfile, \"JPEG\", dpi=im.info[\"dpi\"])\n\n            # Assert\n            with Image.open(outfile) as reloaded:\n                reloaded.load()\n                assert im.info[\"dpi\"] == reloaded.info[\"dpi\"]\n\n    def test_save_dpi_rounding(self, tmp_path):\n        outfile = str(tmp_path / \"temp.jpg\")\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            im.save(outfile, dpi=(72.2, 72.2))\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == (72, 72)\n\n            im.save(outfile, dpi=(72.8, 72.8))\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"dpi\"] == (73, 73)\n\n    def test_dpi_tuple_from_exif(self):\n        # Arrange\n        # This Photoshop CC 2017 image has DPI in EXIF not metadata\n        # EXIF XResolution is (2000000, 10000)\n        with Image.open(\"Tests/images/photoshop-200dpi.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (200, 200)\n\n    def test_dpi_int_from_exif(self):\n        # Arrange\n        # This image has DPI in EXIF not metadata\n        # EXIF XResolution is 72\n        with Image.open(\"Tests/images/exif-72dpi-int.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_dpi_from_dpcm_exif(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm:\n        # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/exif-200dpcm.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (508, 508)\n\n    def test_dpi_exif_zero_division(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with EXIF resolution set to 0/0:\n        # exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/exif-dpi-zerodivision.jpg\") as im:\n            # Act / Assert\n            # This should return the default, and not raise a ZeroDivisionError\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_dpi_exif_string(self):\n        # Arrange\n        # 0x011A tag in this exif contains string '300300\\x02'\n        with Image.open(\"Tests/images/broken_exif_dpi.jpg\") as im:\n            # Act / Assert\n            # This should return the default\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_no_dpi_in_exif(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with resolution removed from EXIF:\n        # exiftool \"-*resolution*\"= photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/no-dpi-in-exif.jpg\") as im:\n            # Act / Assert\n            # \"When the image resolution is unknown, 72 [dpi] is designated.\"\n            # https://exiv2.org/tags.html\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_invalid_exif(self):\n        # This is no-dpi-in-exif with the tiff header of the exif block\n        # hexedited from MM * to FF FF FF FF\n        with Image.open(\"Tests/images/invalid-exif.jpg\") as im:\n            # This should return the default, and not a SyntaxError or\n            # OSError for unidentified image.\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_exif_x_resolution(self, tmp_path):\n        with Image.open(\"Tests/images/flower.jpg\") as im:\n            exif = im.getexif()\n            assert exif[282] == 180\n\n            out = str(tmp_path / \"out.jpg\")\n            with warnings.catch_warnings():\n                im.save(out, exif=exif)\n\n        with Image.open(out) as reloaded:\n            assert reloaded.getexif()[282] == 180\n\n    def test_invalid_exif_x_resolution(self):\n        # When no x or y resolution is defined in EXIF\n        with Image.open(\"Tests/images/invalid-exif-without-x-resolution.jpg\") as im:\n            # This should return the default, and not a ValueError or\n            # OSError for an unidentified image.\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_ifd_offset_exif(self):\n        # Arrange\n        # This image has been manually hexedited to have an IFD offset of 10,\n        # in contrast to normal 8\n        with Image.open(\"Tests/images/exif-ifd-offset.jpg\") as im:\n            # Act / Assert\n            assert im._getexif()[306] == \"2017:03:13 23:03:09\"\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_photoshop(self):\n        with Image.open(\"Tests/images/photoshop-200dpi.jpg\") as im:\n            assert im.info[\"photoshop\"][0x03ED] == {\n                \"XResolution\": 200.0,\n                \"DisplayedUnitsX\": 1,\n                \"YResolution\": 200.0,\n                \"DisplayedUnitsY\": 1,\n            }\n\n            # Test that the image can still load, even with broken Photoshop data\n            # This image had the APP13 length hexedited to be smaller\n            assert_image_equal_tofile(im, \"Tests/images/photoshop-200dpi-broken.jpg\")\n\n        # This image does not contain a Photoshop header string\n        with Image.open(\"Tests/images/app13.jpg\") as im:\n            assert \"photoshop\" not in im.info\n\n    def test_photoshop_malformed_and_multiple(self):\n        with Image.open(\"Tests/images/app13-multiple.jpg\") as im:\n            assert \"photoshop\" in im.info\n            assert 24 == len(im.info[\"photoshop\"])\n            apps_13_lengths = [len(v) for k, v in im.applist if k == \"APP13\"]\n            assert [65504, 24] == apps_13_lengths\n\n    def test_adobe_transform(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            assert im.info[\"adobe_transform\"] == 1\n\n        with Image.open(\"Tests/images/pil_sample_cmyk.jpg\") as im:\n            assert im.info[\"adobe_transform\"] == 2\n\n        # This image has been manually hexedited\n        # so that the APP14 reports its length to be 11,\n        # leaving no room for \"adobe_transform\"\n        with Image.open(\"Tests/images/truncated_app14.jpg\") as im:\n            assert \"adobe\" in im.info\n            assert \"adobe_transform\" not in im.info\n\n    def test_icc_after_SOF(self):\n        with Image.open(\"Tests/images/icc-after-SOF.jpg\") as im:\n            assert im.info[\"icc_profile\"] == b\"profile\"\n\n    def test_jpeg_magic_number(self):\n        size = 4097\n        buffer = BytesIO(b\"\\xFF\" * size)  # Many xFF bytes\n        buffer.max_pos = 0\n        orig_read = buffer.read\n\n        def read(n=-1):\n            res = orig_read(n)\n            buffer.max_pos = max(buffer.max_pos, buffer.tell())\n            return res\n\n        buffer.read = read\n        with pytest.raises(UnidentifiedImageError):\n            with Image.open(buffer):\n                pass\n\n        # Assert the entire file has not been read\n        assert 0 < buffer.max_pos < size\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/xmp_test.jpg\") as im:\n            if ElementTree is None:\n                with pytest.warns(UserWarning):\n                    assert im.getxmp() == {}\n            else:\n                xmp = im.getxmp()\n\n                description = xmp[\"xmpmeta\"][\"RDF\"][\"Description\"]\n                assert description[\"DerivedFrom\"] == {\n                    \"documentID\": \"8367D410E636EA95B7DE7EBA1C43A412\",\n                    \"originalDocumentID\": \"8367D410E636EA95B7DE7EBA1C43A412\",\n                }\n                assert description[\"Look\"][\"Description\"][\"Group\"][\"Alt\"][\"li\"] == {\n                    \"lang\": \"x-default\",\n                    \"text\": \"Profiles\",\n                }\n                assert description[\"ToneCurve\"][\"Seq\"][\"li\"] == [\"0, 0\", \"255, 255\"]\n\n                # Attribute\n                assert description[\"Version\"] == \"10.4\"\n\n        if ElementTree is not None:\n            with Image.open(\"Tests/images/hopper.jpg\") as im:\n                assert im.getxmp() == {}\n\n    @pytest.mark.timeout(timeout=1)\n    def test_eof(self):\n        # Even though this decoder never says that it is finished\n        # the image should still end when there is no new data\n        class InfiniteMockPyDecoder(ImageFile.PyDecoder):\n            def decode(self, buffer):\n                return 0, 0\n\n        decoder = InfiniteMockPyDecoder(None)\n\n        def closure(mode, *args):\n            decoder.__init__(mode, *args)\n            return decoder\n\n        Image.register_decoder(\"INFINITE\", closure)\n\n        with Image.open(TEST_FILE) as im:\n            im.tile = [\n                (\"INFINITE\", (0, 0, 128, 128), 0, (\"RGB\", 0, 1)),\n            ]\n            ImageFile.LOAD_TRUNCATED_IMAGES = True\n            im.load()\n            ImageFile.LOAD_TRUNCATED_IMAGES = False\n\n\n@pytest.mark.skipif(not is_win32(), reason=\"Windows only\")\n@skip_unless_feature(\"jpg\")\nclass TestFileCloseW32:\n    def test_fd_leak(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.jpg\")\n\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            im.save(tmpfile)\n\n        im = Image.open(tmpfile)\n        fp = im.fp\n        assert not fp.closed\n        with pytest.raises(OSError):\n            os.remove(tmpfile)\n        im.load()\n        assert fp.closed\n        # this should not fail, as load should have closed the file.\n        os.remove(tmpfile)\n",
            "header": {
                "file": {
                    "path": "Tests/test_file_jpeg.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "e3c5abcbd",
                "new_commit_hash": "b84661330",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Tests/test_file_jpeg.py"
            },
            "plus_file": {
                "path": "b/Tests/test_file_jpeg.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 270,
                            "length": 7
                        },
                        "new_range": {
                            "start": 270,
                            "length": 10
                        },
                        "section": "class TestFileJpeg:"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        # https://github.com/python-pillow/Pillow/issues/148",
                                "type": "context"
                            },
                            {
                                "content": "        f = str(tmp_path / \"temp.jpg\")",
                                "type": "context"
                            },
                            {
                                "content": "        im = hopper()",
                                "type": "context"
                            },
                            {
                                "content": "        im.save(f, \"JPEG\", quality=90, exif=b\"1\" * 65532)",
                                "type": "deleted"
                            },
                            {
                                "content": "        im.save(f, \"JPEG\", quality=90, exif=b\"1\" * 65533)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        with pytest.raises(ValueError):",
                                "type": "added"
                            },
                            {
                                "content": "            im.save(f, \"JPEG\", quality=90, exif=b\"1\" * 65534)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def test_exif_typeerror(self):",
                                "type": "context"
                            },
                            {
                                "content": "        with Image.open(\"Tests/images/exif_typeerror.jpg\") as im:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Tests/test_file_jpeg.py",
                            "type": "method",
                            "name": "TestFileJpeg.test_large_exif",
                            "content": "class TestFileJpeg:\n    def roundtrip(self, im, **options):\n        out = BytesIO()\n        im.save(out, \"JPEG\", **options)\n        test_bytes = out.tell()\n        out.seek(0)\n        im = Image.open(out)\n        im.bytes = test_bytes  # for testing only\n        return im\n\n    def gen_random_image(self, size, mode=\"RGB\"):\n        \"\"\"Generates a very hard to compress file\n        :param size: tuple\n        :param mode: optional image mode\n\n        \"\"\"\n        return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode)))\n\n    def test_sanity(self):\n        # internal version number\n        assert re.search(r\"\\d+\\.\\d+$\", features.version_codec(\"jpg\"))\n\n        with Image.open(TEST_FILE) as im:\n            im.load()\n            assert im.mode == \"RGB\"\n            assert im.size == (128, 128)\n            assert im.format == \"JPEG\"\n            assert im.get_format_mimetype() == \"image/jpeg\"\n\n    @pytest.mark.parametrize(\"size\", ((1, 0), (0, 1), (0, 0)))\n    def test_zero(self, size, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        im = Image.new(\"RGB\", size)\n        with pytest.raises(ValueError):\n            im.save(f)\n\n    def test_app(self):\n        # Test APP/COM reader (@PIL135)\n        with Image.open(TEST_FILE) as im:\n            assert im.applist[0] == (\"APP0\", b\"JFIF\\x00\\x01\\x01\\x01\\x00`\\x00`\\x00\\x00\")\n            assert im.applist[1] == (\n                \"COM\",\n                b\"File written by Adobe Photoshop\\xa8 4.0\\x00\",\n            )\n            assert len(im.applist) == 2\n\n            assert im.info[\"comment\"] == b\"File written by Adobe Photoshop\\xa8 4.0\\x00\"\n            assert im.app[\"COM\"] == im.info[\"comment\"]\n\n    def test_comment_write(self):\n        with Image.open(TEST_FILE) as im:\n            assert im.info[\"comment\"] == b\"File written by Adobe Photoshop\\xa8 4.0\\x00\"\n\n            # Test that existing comment is saved by default\n            out = BytesIO()\n            im.save(out, format=\"JPEG\")\n            with Image.open(out) as reloaded:\n                assert im.info[\"comment\"] == reloaded.info[\"comment\"]\n\n            # Ensure that a blank comment causes any existing comment to be removed\n            for comment in (\"\", b\"\", None):\n                out = BytesIO()\n                im.save(out, format=\"JPEG\", comment=comment)\n                with Image.open(out) as reloaded:\n                    assert \"comment\" not in reloaded.info\n\n            # Test that a comment argument overrides the default comment\n            for comment in (\"Test comment text\", b\"Text comment text\"):\n                out = BytesIO()\n                im.save(out, format=\"JPEG\", comment=comment)\n                with Image.open(out) as reloaded:\n                    if not isinstance(comment, bytes):\n                        comment = comment.encode()\n                    assert reloaded.info[\"comment\"] == comment\n\n    def test_cmyk(self):\n        # Test CMYK handling.  Thanks to Tim and Charlie for test data,\n        # Michael for getting me to look one more time.\n        f = \"Tests/images/pil_sample_cmyk.jpg\"\n        with Image.open(f) as im:\n            # the source image has red pixels in the upper left corner.\n            c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))\n            assert c == 0.0\n            assert m > 0.8\n            assert y > 0.8\n            assert k == 0.0\n            # the opposite corner is black\n            c, m, y, k = (\n                x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))\n            )\n            assert k > 0.9\n            # roundtrip, and check again\n            im = self.roundtrip(im)\n            c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))\n            assert c == 0.0\n            assert m > 0.8\n            assert y > 0.8\n            assert k == 0.0\n            c, m, y, k = (\n                x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))\n            )\n            assert k > 0.9\n\n    @pytest.mark.parametrize(\n        \"test_image_path\",\n        [TEST_FILE, \"Tests/images/pil_sample_cmyk.jpg\"],\n    )\n    def test_dpi(self, test_image_path):\n        def test(xdpi, ydpi=None):\n            with Image.open(test_image_path) as im:\n                im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi))\n            return im.info.get(\"dpi\")\n\n        assert test(72) == (72, 72)\n        assert test(300) == (300, 300)\n        assert test(100, 200) == (100, 200)\n        assert test(0) is None  # square pixels\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_icc(self, tmp_path):\n        # Test ICC support\n        with Image.open(\"Tests/images/rgb.jpg\") as im1:\n            icc_profile = im1.info[\"icc_profile\"]\n            assert len(icc_profile) == 3144\n            # Roundtrip via physical file.\n            f = str(tmp_path / \"temp.jpg\")\n            im1.save(f, icc_profile=icc_profile)\n        with Image.open(f) as im2:\n            assert im2.info.get(\"icc_profile\") == icc_profile\n            # Roundtrip via memory buffer.\n            im1 = self.roundtrip(hopper())\n            im2 = self.roundtrip(hopper(), icc_profile=icc_profile)\n            assert_image_equal(im1, im2)\n            assert not im1.info.get(\"icc_profile\")\n            assert im2.info.get(\"icc_profile\")\n\n    @pytest.mark.parametrize(\n        \"n\",\n        (\n            0,\n            1,\n            3,\n            4,\n            5,\n            65533 - 14,  # full JPEG marker block\n            65533 - 14 + 1,  # full block plus one byte\n            ImageFile.MAXBLOCK,  # full buffer block\n            ImageFile.MAXBLOCK + 1,  # full buffer block plus one byte\n            ImageFile.MAXBLOCK * 4 + 3,  # large block\n        ),\n    )\n    def test_icc_big(self, n):\n        # Make sure that the \"extra\" support handles large blocks\n        # The ICC APP marker can store 65519 bytes per marker, so\n        # using a 4-byte test code should allow us to detect out of\n        # order issues.\n        icc_profile = (b\"Test\" * int(n / 4 + 1))[:n]\n        assert len(icc_profile) == n  # sanity\n        im1 = self.roundtrip(hopper(), icc_profile=icc_profile)\n        assert im1.info.get(\"icc_profile\") == (icc_profile or None)\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_large_icc_meta(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        # Sometimes the meta data on the icc_profile block is bigger than\n        # Image.MAXBLOCK or the image size.\n        with Image.open(\"Tests/images/icc_profile_big.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            icc_profile = im.info[\"icc_profile\"]\n            # Should not raise OSError for image with icc larger than image size.\n            im.save(\n                f,\n                format=\"JPEG\",\n                progressive=True,\n                quality=95,\n                icc_profile=icc_profile,\n                optimize=True,\n            )\n\n    def test_optimize(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), optimize=0)\n        im3 = self.roundtrip(hopper(), optimize=1)\n        assert_image_equal(im1, im2)\n        assert_image_equal(im1, im3)\n        assert im1.bytes >= im2.bytes\n        assert im1.bytes >= im3.bytes\n\n    def test_optimize_large_buffer(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        f = str(tmp_path / \"temp.jpg\")\n        # this requires ~ 1.5x Image.MAXBLOCK\n        im = Image.new(\"RGB\", (4096, 4096), 0xFF3333)\n        im.save(f, format=\"JPEG\", optimize=True)\n\n    def test_progressive(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), progressive=False)\n        im3 = self.roundtrip(hopper(), progressive=True)\n        assert not im1.info.get(\"progressive\")\n        assert not im2.info.get(\"progressive\")\n        assert im3.info.get(\"progressive\")\n\n        assert_image_equal(im1, im3)\n        assert im1.bytes >= im3.bytes\n\n    def test_progressive_large_buffer(self, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        # this requires ~ 1.5x Image.MAXBLOCK\n        im = Image.new(\"RGB\", (4096, 4096), 0xFF3333)\n        im.save(f, format=\"JPEG\", progressive=True)\n\n    def test_progressive_large_buffer_highest_quality(self, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        im = self.gen_random_image((255, 255))\n        # this requires more bytes than pixels in the image\n        im.save(f, format=\"JPEG\", progressive=True, quality=100)\n\n    def test_progressive_cmyk_buffer(self):\n        # Issue 2272, quality 90 cmyk image is tripping the large buffer bug.\n        f = BytesIO()\n        im = self.gen_random_image((256, 256), \"CMYK\")\n        im.save(f, format=\"JPEG\", progressive=True, quality=94)\n\n    def test_large_exif(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        f = str(tmp_path / \"temp.jpg\")\n        im = hopper()\n        im.save(f, \"JPEG\", quality=90, exif=b\"1\" * 65532)\n\n    def test_exif_typeerror(self):\n        with Image.open(\"Tests/images/exif_typeerror.jpg\") as im:\n            # Should not raise a TypeError\n            im._getexif()\n\n    def test_exif_gps(self, tmp_path):\n        expected_exif_gps = {\n            0: b\"\\x00\\x00\\x00\\x01\",\n            2: 4294967295,\n            5: b\"\\x01\",\n            30: 65535,\n            29: \"1999:99:99 99:99:99\",\n        }\n        gps_index = 34853\n\n        # Reading\n        with Image.open(\"Tests/images/exif_gps.jpg\") as im:\n            exif = im._getexif()\n            assert exif[gps_index] == expected_exif_gps\n\n        # Writing\n        f = str(tmp_path / \"temp.jpg\")\n        exif = Image.Exif()\n        exif[gps_index] = expected_exif_gps\n        hopper().save(f, exif=exif)\n\n        with Image.open(f) as reloaded:\n            exif = reloaded._getexif()\n            assert exif[gps_index] == expected_exif_gps\n\n    def test_empty_exif_gps(self):\n        with Image.open(\"Tests/images/empty_gps_ifd.jpg\") as im:\n            exif = im.getexif()\n            del exif[0x8769]\n\n            # Assert that it needs to be transposed\n            assert exif[0x0112] == Image.Transpose.TRANSVERSE\n\n            # Assert that the GPS IFD is present and empty\n            assert exif.get_ifd(0x8825) == {}\n\n            transposed = ImageOps.exif_transpose(im)\n        exif = transposed.getexif()\n        assert exif.get_ifd(0x8825) == {}\n\n        # Assert that it was transposed\n        assert 0x0112 not in exif\n\n    def test_exif_equality(self):\n        # In 7.2.0, Exif rationals were changed to be read as\n        # TiffImagePlugin.IFDRational. This class had a bug in __eq__,\n        # breaking the self-equality of Exif data\n        exifs = []\n        for i in range(2):\n            with Image.open(\"Tests/images/exif-200dpcm.jpg\") as im:\n                exifs.append(im._getexif())\n        assert exifs[0] == exifs[1]\n\n    def test_exif_rollback(self):\n        # rolling back exif support in 3.1 to pre-3.0 formatting.\n        # expected from 2.9, with b/u qualifiers switched for 3.2 compatibility\n        # this test passes on 2.9 and 3.1, but not 3.0\n        expected_exif = {\n            34867: 4294967295,\n            258: (24, 24, 24),\n            36867: \"2099:09:29 10:10:10\",\n            34853: {\n                0: b\"\\x00\\x00\\x00\\x01\",\n                2: 4294967295,\n                5: b\"\\x01\",\n                30: 65535,\n                29: \"1999:99:99 99:99:99\",\n            },\n            296: 65535,\n            34665: 185,\n            41994: 65535,\n            514: 4294967295,\n            271: \"Make\",\n            272: \"XXX-XXX\",\n            305: \"PIL\",\n            42034: (1, 1, 1, 1),\n            42035: \"LensMake\",\n            34856: b\"\\xaa\\xaa\\xaa\\xaa\\xaa\\xaa\",\n            282: 4294967295,\n            33434: 4294967295,\n        }\n\n        with Image.open(\"Tests/images/exif_gps.jpg\") as im:\n            exif = im._getexif()\n\n        for tag, value in expected_exif.items():\n            assert value == exif[tag]\n\n    def test_exif_gps_typeerror(self):\n        with Image.open(\"Tests/images/exif_gps_typeerror.jpg\") as im:\n            # Should not raise a TypeError\n            im._getexif()\n\n    def test_progressive_compat(self):\n        im1 = self.roundtrip(hopper())\n        assert not im1.info.get(\"progressive\")\n        assert not im1.info.get(\"progression\")\n\n        im2 = self.roundtrip(hopper(), progressive=0)\n        im3 = self.roundtrip(hopper(), progression=0)  # compatibility\n        assert not im2.info.get(\"progressive\")\n        assert not im2.info.get(\"progression\")\n        assert not im3.info.get(\"progressive\")\n        assert not im3.info.get(\"progression\")\n\n        im2 = self.roundtrip(hopper(), progressive=1)\n        im3 = self.roundtrip(hopper(), progression=1)  # compatibility\n        assert_image_equal(im1, im2)\n        assert_image_equal(im1, im3)\n        assert im2.info.get(\"progressive\")\n        assert im2.info.get(\"progression\")\n        assert im3.info.get(\"progressive\")\n        assert im3.info.get(\"progression\")\n\n    def test_quality(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), quality=50)\n        assert_image(im1, im2.mode, im2.size)\n        assert im1.bytes >= im2.bytes\n\n        im3 = self.roundtrip(hopper(), quality=0)\n        assert_image(im1, im3.mode, im3.size)\n        assert im2.bytes > im3.bytes\n\n    def test_smooth(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), smooth=100)\n        assert_image(im1, im2.mode, im2.size)\n\n    def test_subsampling(self):\n        def getsampling(im):\n            layer = im.layer\n            return layer[0][1:3] + layer[1][1:3] + layer[2][1:3]\n\n        # experimental API\n        im = self.roundtrip(hopper(), subsampling=-1)  # default\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=0)  # 4:4:4\n        assert getsampling(im) == (1, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=1)  # 4:2:2\n        assert getsampling(im) == (2, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=2)  # 4:2:0\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=3)  # default (undefined)\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n\n        im = self.roundtrip(hopper(), subsampling=\"4:4:4\")\n        assert getsampling(im) == (1, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:2:2\")\n        assert getsampling(im) == (2, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:2:0\")\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:1:1\")\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n\n        with pytest.raises(TypeError):\n            self.roundtrip(hopper(), subsampling=\"1:1:1\")\n\n    def test_exif(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            info = im._getexif()\n            assert info[305] == \"Adobe Photoshop CS Macintosh\"\n\n    def test_get_child_images(self):\n        with Image.open(\"Tests/images/flower.jpg\") as im:\n            ims = im.get_child_images()\n\n        assert len(ims) == 1\n        assert_image_equal_tofile(ims[0], \"Tests/images/flower_thumbnail.png\")\n\n    def test_mp(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            assert im._getmp() is None\n\n    def test_quality_keep(self, tmp_path):\n        # RGB\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n        # Grayscale\n        with Image.open(\"Tests/images/hopper_gray.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n        # CMYK\n        with Image.open(\"Tests/images/pil_sample_cmyk.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n\n    def test_junk_jpeg_header(self):\n        # https://github.com/python-pillow/Pillow/issues/630\n        filename = \"Tests/images/junk_jpeg_header.jpg\"\n        with Image.open(filename):\n            pass\n\n    def test_ff00_jpeg_header(self):\n        filename = \"Tests/images/jpeg_ff00_header.jpg\"\n        with Image.open(filename):\n            pass\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_truncated_jpeg_should_read_all_the_data(self):\n        filename = \"Tests/images/truncated_jpeg.jpg\"\n        ImageFile.LOAD_TRUNCATED_IMAGES = True\n        with Image.open(filename) as im:\n            im.load()\n            ImageFile.LOAD_TRUNCATED_IMAGES = False\n            assert im.getbbox() is not None\n\n    def test_truncated_jpeg_throws_oserror(self):\n        filename = \"Tests/images/truncated_jpeg.jpg\"\n        with Image.open(filename) as im:\n            with pytest.raises(OSError):\n                im.load()\n\n            # Test that the error is raised if loaded a second time\n            with pytest.raises(OSError):\n                im.load()\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_qtables(self, tmp_path):\n        def _n_qtables_helper(n, test_file):\n            with Image.open(test_file) as im:\n                f = str(tmp_path / \"temp.jpg\")\n                im.save(f, qtables=[[n] * 64] * n)\n            with Image.open(f) as im:\n                assert len(im.quantization) == n\n                reloaded = self.roundtrip(im, qtables=\"keep\")\n                assert im.quantization == reloaded.quantization\n                assert max(reloaded.quantization[0]) <= 255\n\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            qtables = im.quantization\n            reloaded = self.roundtrip(im, qtables=qtables, subsampling=0)\n            assert im.quantization == reloaded.quantization\n            assert_image_similar(im, self.roundtrip(im, qtables=\"web_low\"), 30)\n            assert_image_similar(im, self.roundtrip(im, qtables=\"web_high\"), 30)\n            assert_image_similar(im, self.roundtrip(im, qtables=\"keep\"), 30)\n\n            # valid bounds for baseline qtable\n            bounds_qtable = [int(s) for s in (\"255 1 \" * 32).split(None)]\n            im2 = self.roundtrip(im, qtables=[bounds_qtable])\n            assert im2.quantization == {0: bounds_qtable}\n\n            # values from wizard.txt in jpeg9-a src package.\n            standard_l_qtable = [\n                int(s)\n                for s in \"\"\"\n                16  11  10  16  24  40  51  61\n                12  12  14  19  26  58  60  55\n                14  13  16  24  40  57  69  56\n                14  17  22  29  51  87  80  62\n                18  22  37  56  68 109 103  77\n                24  35  55  64  81 104 113  92\n                49  64  78  87 103 121 120 101\n                72  92  95  98 112 100 103  99\n                \"\"\".split(\n                    None\n                )\n            ]\n\n            standard_chrominance_qtable = [\n                int(s)\n                for s in \"\"\"\n                17  18  24  47  99  99  99  99\n                18  21  26  66  99  99  99  99\n                24  26  56  99  99  99  99  99\n                47  66  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                \"\"\".split(\n                    None\n                )\n            ]\n            # list of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables=[standard_l_qtable, standard_chrominance_qtable]\n                ),\n                30,\n            )\n\n            # tuple of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables=(standard_l_qtable, standard_chrominance_qtable)\n                ),\n                30,\n            )\n\n            # dict of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables={0: standard_l_qtable, 1: standard_chrominance_qtable}\n                ),\n                30,\n            )\n\n            _n_qtables_helper(1, \"Tests/images/hopper_gray.jpg\")\n            _n_qtables_helper(1, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(2, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(3, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(1, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(2, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(3, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(4, \"Tests/images/pil_sample_cmyk.jpg\")\n\n            # not a sequence\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=\"a\")\n            # sequence wrong length\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[])\n            # sequence wrong length\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[1, 2, 3, 4, 5])\n\n            # qtable entry not a sequence\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[1])\n            # qtable entry has wrong number of items\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[[1, 2, 3, 4]])\n\n    def test_load_16bit_qtables(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            assert len(im.quantization) == 2\n            assert len(im.quantization[0]) == 64\n            assert max(im.quantization[0]) > 255\n\n    def test_save_multiple_16bit_qtables(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            im2 = self.roundtrip(im, qtables=\"keep\")\n            assert im.quantization == im2.quantization\n\n    def test_save_single_16bit_qtable(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            im2 = self.roundtrip(im, qtables={0: im.quantization[0]})\n            assert len(im2.quantization) == 1\n            assert im2.quantization[0] == im.quantization[0]\n\n    def test_save_low_quality_baseline_qtables(self):\n        with Image.open(TEST_FILE) as im:\n            im2 = self.roundtrip(im, quality=10)\n            assert len(im2.quantization) == 2\n            assert max(im2.quantization[0]) <= 255\n            assert max(im2.quantization[1]) <= 255\n\n    def test_convert_dict_qtables_deprecation(self):\n        with pytest.warns(DeprecationWarning):\n            qtable = {0: [1, 2, 3, 4]}\n            qtable2 = JpegImagePlugin.convert_dict_qtables(qtable)\n            assert qtable == qtable2\n\n    @pytest.mark.skipif(not djpeg_available(), reason=\"djpeg not available\")\n    def test_load_djpeg(self):\n        with Image.open(TEST_FILE) as img:\n            img.load_djpeg()\n            assert_image_similar_tofile(img, TEST_FILE, 5)\n\n    @pytest.mark.skipif(not cjpeg_available(), reason=\"cjpeg not available\")\n    def test_save_cjpeg(self, tmp_path):\n        with Image.open(TEST_FILE) as img:\n            tempfile = str(tmp_path / \"temp.jpg\")\n            JpegImagePlugin._save_cjpeg(img, 0, tempfile)\n            # Default save quality is 75%, so a tiny bit of difference is alright\n            assert_image_similar_tofile(img, tempfile, 17)\n\n    def test_no_duplicate_0x1001_tag(self):\n        # Arrange\n        tag_ids = {v: k for k, v in ExifTags.TAGS.items()}\n\n        # Assert\n        assert tag_ids[\"RelatedImageWidth\"] == 0x1001\n        assert tag_ids[\"RelatedImageLength\"] == 0x1002\n\n    def test_MAXBLOCK_scaling(self, tmp_path):\n        im = self.gen_random_image((512, 512))\n        f = str(tmp_path / \"temp.jpeg\")\n        im.save(f, quality=100, optimize=True)\n\n        with Image.open(f) as reloaded:\n            # none of these should crash\n            reloaded.save(f, quality=\"keep\")\n            reloaded.save(f, quality=\"keep\", progressive=True)\n            reloaded.save(f, quality=\"keep\", optimize=True)\n\n    def test_bad_mpo_header(self):\n        \"\"\"Treat unknown MPO as JPEG\"\"\"\n        # Arrange\n\n        # Act\n        # Shouldn't raise error\n        fn = \"Tests/images/sugarshack_bad_mpo_header.jpg\"\n        with pytest.warns(UserWarning, Image.open, fn) as im:\n            # Assert\n            assert im.format == \"JPEG\"\n\n    @pytest.mark.parametrize(\"mode\", (\"1\", \"L\", \"RGB\", \"RGBX\", \"CMYK\", \"YCbCr\"))\n    def test_save_correct_modes(self, mode):\n        out = BytesIO()\n        img = Image.new(mode, (20, 20))\n        img.save(out, \"JPEG\")\n\n    @pytest.mark.parametrize(\"mode\", (\"LA\", \"La\", \"RGBA\", \"RGBa\", \"P\"))\n    def test_save_wrong_modes(self, mode):\n        # ref https://github.com/python-pillow/Pillow/issues/2005\n        out = BytesIO()\n        img = Image.new(mode, (20, 20))\n        with pytest.raises(OSError):\n            img.save(out, \"JPEG\")\n\n    def test_save_tiff_with_dpi(self, tmp_path):\n        # Arrange\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            # Act\n            im.save(outfile, \"JPEG\", dpi=im.info[\"dpi\"])\n\n            # Assert\n            with Image.open(outfile) as reloaded:\n                reloaded.load()\n                assert im.info[\"dpi\"] == reloaded.info[\"dpi\"]\n\n    def test_save_dpi_rounding(self, tmp_path):\n        outfile = str(tmp_path / \"temp.jpg\")\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            im.save(outfile, dpi=(72.2, 72.2))\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == (72, 72)\n\n            im.save(outfile, dpi=(72.8, 72.8))\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"dpi\"] == (73, 73)\n\n    def test_dpi_tuple_from_exif(self):\n        # Arrange\n        # This Photoshop CC 2017 image has DPI in EXIF not metadata\n        # EXIF XResolution is (2000000, 10000)\n        with Image.open(\"Tests/images/photoshop-200dpi.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (200, 200)\n\n    def test_dpi_int_from_exif(self):\n        # Arrange\n        # This image has DPI in EXIF not metadata\n        # EXIF XResolution is 72\n        with Image.open(\"Tests/images/exif-72dpi-int.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_dpi_from_dpcm_exif(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm:\n        # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/exif-200dpcm.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (508, 508)\n\n    def test_dpi_exif_zero_division(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with EXIF resolution set to 0/0:\n        # exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/exif-dpi-zerodivision.jpg\") as im:\n            # Act / Assert\n            # This should return the default, and not raise a ZeroDivisionError\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_dpi_exif_string(self):\n        # Arrange\n        # 0x011A tag in this exif contains string '300300\\x02'\n        with Image.open(\"Tests/images/broken_exif_dpi.jpg\") as im:\n            # Act / Assert\n            # This should return the default\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_no_dpi_in_exif(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with resolution removed from EXIF:\n        # exiftool \"-*resolution*\"= photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/no-dpi-in-exif.jpg\") as im:\n            # Act / Assert\n            # \"When the image resolution is unknown, 72 [dpi] is designated.\"\n            # https://exiv2.org/tags.html\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_invalid_exif(self):\n        # This is no-dpi-in-exif with the tiff header of the exif block\n        # hexedited from MM * to FF FF FF FF\n        with Image.open(\"Tests/images/invalid-exif.jpg\") as im:\n            # This should return the default, and not a SyntaxError or\n            # OSError for unidentified image.\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_exif_x_resolution(self, tmp_path):\n        with Image.open(\"Tests/images/flower.jpg\") as im:\n            exif = im.getexif()\n            assert exif[282] == 180\n\n            out = str(tmp_path / \"out.jpg\")\n            with warnings.catch_warnings():\n                im.save(out, exif=exif)\n\n        with Image.open(out) as reloaded:\n            assert reloaded.getexif()[282] == 180\n\n    def test_invalid_exif_x_resolution(self):\n        # When no x or y resolution is defined in EXIF\n        with Image.open(\"Tests/images/invalid-exif-without-x-resolution.jpg\") as im:\n            # This should return the default, and not a ValueError or\n            # OSError for an unidentified image.\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_ifd_offset_exif(self):\n        # Arrange\n        # This image has been manually hexedited to have an IFD offset of 10,\n        # in contrast to normal 8\n        with Image.open(\"Tests/images/exif-ifd-offset.jpg\") as im:\n            # Act / Assert\n            assert im._getexif()[306] == \"2017:03:13 23:03:09\"\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_photoshop(self):\n        with Image.open(\"Tests/images/photoshop-200dpi.jpg\") as im:\n            assert im.info[\"photoshop\"][0x03ED] == {\n                \"XResolution\": 200.0,\n                \"DisplayedUnitsX\": 1,\n                \"YResolution\": 200.0,\n                \"DisplayedUnitsY\": 1,\n            }\n\n            # Test that the image can still load, even with broken Photoshop data\n            # This image had the APP13 length hexedited to be smaller\n            assert_image_equal_tofile(im, \"Tests/images/photoshop-200dpi-broken.jpg\")\n\n        # This image does not contain a Photoshop header string\n        with Image.open(\"Tests/images/app13.jpg\") as im:\n            assert \"photoshop\" not in im.info\n\n    def test_photoshop_malformed_and_multiple(self):\n        with Image.open(\"Tests/images/app13-multiple.jpg\") as im:\n            assert \"photoshop\" in im.info\n            assert 24 == len(im.info[\"photoshop\"])\n            apps_13_lengths = [len(v) for k, v in im.applist if k == \"APP13\"]\n            assert [65504, 24] == apps_13_lengths\n\n    def test_adobe_transform(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            assert im.info[\"adobe_transform\"] == 1\n\n        with Image.open(\"Tests/images/pil_sample_cmyk.jpg\") as im:\n            assert im.info[\"adobe_transform\"] == 2\n\n        # This image has been manually hexedited\n        # so that the APP14 reports its length to be 11,\n        # leaving no room for \"adobe_transform\"\n        with Image.open(\"Tests/images/truncated_app14.jpg\") as im:\n            assert \"adobe\" in im.info\n            assert \"adobe_transform\" not in im.info\n\n    def test_icc_after_SOF(self):\n        with Image.open(\"Tests/images/icc-after-SOF.jpg\") as im:\n            assert im.info[\"icc_profile\"] == b\"profile\"\n\n    def test_jpeg_magic_number(self):\n        size = 4097\n        buffer = BytesIO(b\"\\xFF\" * size)  # Many xFF bytes\n        buffer.max_pos = 0\n        orig_read = buffer.read\n\n        def read(n=-1):\n            res = orig_read(n)\n            buffer.max_pos = max(buffer.max_pos, buffer.tell())\n            return res\n\n        buffer.read = read\n        with pytest.raises(UnidentifiedImageError):\n            with Image.open(buffer):\n                pass\n\n        # Assert the entire file has not been read\n        assert 0 < buffer.max_pos < size\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/xmp_test.jpg\") as im:\n            if ElementTree is None:\n                with pytest.warns(UserWarning):\n                    assert im.getxmp() == {}\n            else:\n                xmp = im.getxmp()\n\n                description = xmp[\"xmpmeta\"][\"RDF\"][\"Description\"]\n                assert description[\"DerivedFrom\"] == {\n                    \"documentID\": \"8367D410E636EA95B7DE7EBA1C43A412\",\n                    \"originalDocumentID\": \"8367D410E636EA95B7DE7EBA1C43A412\",\n                }\n                assert description[\"Look\"][\"Description\"][\"Group\"][\"Alt\"][\"li\"] == {\n                    \"lang\": \"x-default\",\n                    \"text\": \"Profiles\",\n                }\n                assert description[\"ToneCurve\"][\"Seq\"][\"li\"] == [\"0, 0\", \"255, 255\"]\n\n                # Attribute\n                assert description[\"Version\"] == \"10.4\"\n\n        if ElementTree is not None:\n            with Image.open(\"Tests/images/hopper.jpg\") as im:\n                assert im.getxmp() == {}\n\n    @pytest.mark.timeout(timeout=1)\n    def test_eof(self):\n        # Even though this decoder never says that it is finished\n        # the image should still end when there is no new data\n        class InfiniteMockPyDecoder(ImageFile.PyDecoder):\n            def decode(self, buffer):\n                return 0, 0\n\n        decoder = InfiniteMockPyDecoder(None)\n\n        def closure(mode, *args):\n            decoder.__init__(mode, *args)\n            return decoder\n\n        Image.register_decoder(\"INFINITE\", closure)\n\n        with Image.open(TEST_FILE) as im:\n            im.tile = [\n                (\"INFINITE\", (0, 0, 128, 128), 0, (\"RGB\", 0, 1)),\n            ]\n            ImageFile.LOAD_TRUNCATED_IMAGES = True\n            im.load()\n            ImageFile.LOAD_TRUNCATED_IMAGES = False",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 269,
                            "end_lineno": 273,
                            "parent": {
                                "file_name": "Tests/test_file_jpeg.py",
                                "type": "class",
                                "name": "TestFileJpeg",
                                "content": "class TestFileJpeg:\n    def roundtrip(self, im, **options):\n        out = BytesIO()\n        im.save(out, \"JPEG\", **options)\n        test_bytes = out.tell()\n        out.seek(0)\n        im = Image.open(out)\n        im.bytes = test_bytes  # for testing only\n        return im\n\n    def gen_random_image(self, size, mode=\"RGB\"):\n        \"\"\"Generates a very hard to compress file\n        :param size: tuple\n        :param mode: optional image mode\n\n        \"\"\"\n        return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode)))\n\n    def test_sanity(self):\n        # internal version number\n        assert re.search(r\"\\d+\\.\\d+$\", features.version_codec(\"jpg\"))\n\n        with Image.open(TEST_FILE) as im:\n            im.load()\n            assert im.mode == \"RGB\"\n            assert im.size == (128, 128)\n            assert im.format == \"JPEG\"\n            assert im.get_format_mimetype() == \"image/jpeg\"\n\n    @pytest.mark.parametrize(\"size\", ((1, 0), (0, 1), (0, 0)))\n    def test_zero(self, size, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        im = Image.new(\"RGB\", size)\n        with pytest.raises(ValueError):\n            im.save(f)\n\n    def test_app(self):\n        # Test APP/COM reader (@PIL135)\n        with Image.open(TEST_FILE) as im:\n            assert im.applist[0] == (\"APP0\", b\"JFIF\\x00\\x01\\x01\\x01\\x00`\\x00`\\x00\\x00\")\n            assert im.applist[1] == (\n                \"COM\",\n                b\"File written by Adobe Photoshop\\xa8 4.0\\x00\",\n            )\n            assert len(im.applist) == 2\n\n            assert im.info[\"comment\"] == b\"File written by Adobe Photoshop\\xa8 4.0\\x00\"\n            assert im.app[\"COM\"] == im.info[\"comment\"]\n\n    def test_comment_write(self):\n        with Image.open(TEST_FILE) as im:\n            assert im.info[\"comment\"] == b\"File written by Adobe Photoshop\\xa8 4.0\\x00\"\n\n            # Test that existing comment is saved by default\n            out = BytesIO()\n            im.save(out, format=\"JPEG\")\n            with Image.open(out) as reloaded:\n                assert im.info[\"comment\"] == reloaded.info[\"comment\"]\n\n            # Ensure that a blank comment causes any existing comment to be removed\n            for comment in (\"\", b\"\", None):\n                out = BytesIO()\n                im.save(out, format=\"JPEG\", comment=comment)\n                with Image.open(out) as reloaded:\n                    assert \"comment\" not in reloaded.info\n\n            # Test that a comment argument overrides the default comment\n            for comment in (\"Test comment text\", b\"Text comment text\"):\n                out = BytesIO()\n                im.save(out, format=\"JPEG\", comment=comment)\n                with Image.open(out) as reloaded:\n                    if not isinstance(comment, bytes):\n                        comment = comment.encode()\n                    assert reloaded.info[\"comment\"] == comment\n\n    def test_cmyk(self):\n        # Test CMYK handling.  Thanks to Tim and Charlie for test data,\n        # Michael for getting me to look one more time.\n        f = \"Tests/images/pil_sample_cmyk.jpg\"\n        with Image.open(f) as im:\n            # the source image has red pixels in the upper left corner.\n            c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))\n            assert c == 0.0\n            assert m > 0.8\n            assert y > 0.8\n            assert k == 0.0\n            # the opposite corner is black\n            c, m, y, k = (\n                x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))\n            )\n            assert k > 0.9\n            # roundtrip, and check again\n            im = self.roundtrip(im)\n            c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))\n            assert c == 0.0\n            assert m > 0.8\n            assert y > 0.8\n            assert k == 0.0\n            c, m, y, k = (\n                x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))\n            )\n            assert k > 0.9\n\n    @pytest.mark.parametrize(\n        \"test_image_path\",\n        [TEST_FILE, \"Tests/images/pil_sample_cmyk.jpg\"],\n    )\n    def test_dpi(self, test_image_path):\n        def test(xdpi, ydpi=None):\n            with Image.open(test_image_path) as im:\n                im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi))\n            return im.info.get(\"dpi\")\n\n        assert test(72) == (72, 72)\n        assert test(300) == (300, 300)\n        assert test(100, 200) == (100, 200)\n        assert test(0) is None  # square pixels\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_icc(self, tmp_path):\n        # Test ICC support\n        with Image.open(\"Tests/images/rgb.jpg\") as im1:\n            icc_profile = im1.info[\"icc_profile\"]\n            assert len(icc_profile) == 3144\n            # Roundtrip via physical file.\n            f = str(tmp_path / \"temp.jpg\")\n            im1.save(f, icc_profile=icc_profile)\n        with Image.open(f) as im2:\n            assert im2.info.get(\"icc_profile\") == icc_profile\n            # Roundtrip via memory buffer.\n            im1 = self.roundtrip(hopper())\n            im2 = self.roundtrip(hopper(), icc_profile=icc_profile)\n            assert_image_equal(im1, im2)\n            assert not im1.info.get(\"icc_profile\")\n            assert im2.info.get(\"icc_profile\")\n\n    @pytest.mark.parametrize(\n        \"n\",\n        (\n            0,\n            1,\n            3,\n            4,\n            5,\n            65533 - 14,  # full JPEG marker block\n            65533 - 14 + 1,  # full block plus one byte\n            ImageFile.MAXBLOCK,  # full buffer block\n            ImageFile.MAXBLOCK + 1,  # full buffer block plus one byte\n            ImageFile.MAXBLOCK * 4 + 3,  # large block\n        ),\n    )\n    def test_icc_big(self, n):\n        # Make sure that the \"extra\" support handles large blocks\n        # The ICC APP marker can store 65519 bytes per marker, so\n        # using a 4-byte test code should allow us to detect out of\n        # order issues.\n        icc_profile = (b\"Test\" * int(n / 4 + 1))[:n]\n        assert len(icc_profile) == n  # sanity\n        im1 = self.roundtrip(hopper(), icc_profile=icc_profile)\n        assert im1.info.get(\"icc_profile\") == (icc_profile or None)\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_large_icc_meta(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        # Sometimes the meta data on the icc_profile block is bigger than\n        # Image.MAXBLOCK or the image size.\n        with Image.open(\"Tests/images/icc_profile_big.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            icc_profile = im.info[\"icc_profile\"]\n            # Should not raise OSError for image with icc larger than image size.\n            im.save(\n                f,\n                format=\"JPEG\",\n                progressive=True,\n                quality=95,\n                icc_profile=icc_profile,\n                optimize=True,\n            )\n\n    def test_optimize(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), optimize=0)\n        im3 = self.roundtrip(hopper(), optimize=1)\n        assert_image_equal(im1, im2)\n        assert_image_equal(im1, im3)\n        assert im1.bytes >= im2.bytes\n        assert im1.bytes >= im3.bytes\n\n    def test_optimize_large_buffer(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        f = str(tmp_path / \"temp.jpg\")\n        # this requires ~ 1.5x Image.MAXBLOCK\n        im = Image.new(\"RGB\", (4096, 4096), 0xFF3333)\n        im.save(f, format=\"JPEG\", optimize=True)\n\n    def test_progressive(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), progressive=False)\n        im3 = self.roundtrip(hopper(), progressive=True)\n        assert not im1.info.get(\"progressive\")\n        assert not im2.info.get(\"progressive\")\n        assert im3.info.get(\"progressive\")\n\n        assert_image_equal(im1, im3)\n        assert im1.bytes >= im3.bytes\n\n    def test_progressive_large_buffer(self, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        # this requires ~ 1.5x Image.MAXBLOCK\n        im = Image.new(\"RGB\", (4096, 4096), 0xFF3333)\n        im.save(f, format=\"JPEG\", progressive=True)\n\n    def test_progressive_large_buffer_highest_quality(self, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        im = self.gen_random_image((255, 255))\n        # this requires more bytes than pixels in the image\n        im.save(f, format=\"JPEG\", progressive=True, quality=100)\n\n    def test_progressive_cmyk_buffer(self):\n        # Issue 2272, quality 90 cmyk image is tripping the large buffer bug.\n        f = BytesIO()\n        im = self.gen_random_image((256, 256), \"CMYK\")\n        im.save(f, format=\"JPEG\", progressive=True, quality=94)\n\n    def test_large_exif(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        f = str(tmp_path / \"temp.jpg\")\n        im = hopper()\n        im.save(f, \"JPEG\", quality=90, exif=b\"1\" * 65532)\n\n    def test_exif_typeerror(self):\n        with Image.open(\"Tests/images/exif_typeerror.jpg\") as im:\n            # Should not raise a TypeError\n            im._getexif()\n\n    def test_exif_gps(self, tmp_path):\n        expected_exif_gps = {\n            0: b\"\\x00\\x00\\x00\\x01\",\n            2: 4294967295,\n            5: b\"\\x01\",\n            30: 65535,\n            29: \"1999:99:99 99:99:99\",\n        }\n        gps_index = 34853\n\n        # Reading\n        with Image.open(\"Tests/images/exif_gps.jpg\") as im:\n            exif = im._getexif()\n            assert exif[gps_index] == expected_exif_gps\n\n        # Writing\n        f = str(tmp_path / \"temp.jpg\")\n        exif = Image.Exif()\n        exif[gps_index] = expected_exif_gps\n        hopper().save(f, exif=exif)\n\n        with Image.open(f) as reloaded:\n            exif = reloaded._getexif()\n            assert exif[gps_index] == expected_exif_gps\n\n    def test_empty_exif_gps(self):\n        with Image.open(\"Tests/images/empty_gps_ifd.jpg\") as im:\n            exif = im.getexif()\n            del exif[0x8769]\n\n            # Assert that it needs to be transposed\n            assert exif[0x0112] == Image.Transpose.TRANSVERSE\n\n            # Assert that the GPS IFD is present and empty\n            assert exif.get_ifd(0x8825) == {}\n\n            transposed = ImageOps.exif_transpose(im)\n        exif = transposed.getexif()\n        assert exif.get_ifd(0x8825) == {}\n\n        # Assert that it was transposed\n        assert 0x0112 not in exif\n\n    def test_exif_equality(self):\n        # In 7.2.0, Exif rationals were changed to be read as\n        # TiffImagePlugin.IFDRational. This class had a bug in __eq__,\n        # breaking the self-equality of Exif data\n        exifs = []\n        for i in range(2):\n            with Image.open(\"Tests/images/exif-200dpcm.jpg\") as im:\n                exifs.append(im._getexif())\n        assert exifs[0] == exifs[1]\n\n    def test_exif_rollback(self):\n        # rolling back exif support in 3.1 to pre-3.0 formatting.\n        # expected from 2.9, with b/u qualifiers switched for 3.2 compatibility\n        # this test passes on 2.9 and 3.1, but not 3.0\n        expected_exif = {\n            34867: 4294967295,\n            258: (24, 24, 24),\n            36867: \"2099:09:29 10:10:10\",\n            34853: {\n                0: b\"\\x00\\x00\\x00\\x01\",\n                2: 4294967295,\n                5: b\"\\x01\",\n                30: 65535,\n                29: \"1999:99:99 99:99:99\",\n            },\n            296: 65535,\n            34665: 185,\n            41994: 65535,\n            514: 4294967295,\n            271: \"Make\",\n            272: \"XXX-XXX\",\n            305: \"PIL\",\n            42034: (1, 1, 1, 1),\n            42035: \"LensMake\",\n            34856: b\"\\xaa\\xaa\\xaa\\xaa\\xaa\\xaa\",\n            282: 4294967295,\n            33434: 4294967295,\n        }\n\n        with Image.open(\"Tests/images/exif_gps.jpg\") as im:\n            exif = im._getexif()\n\n        for tag, value in expected_exif.items():\n            assert value == exif[tag]\n\n    def test_exif_gps_typeerror(self):\n        with Image.open(\"Tests/images/exif_gps_typeerror.jpg\") as im:\n            # Should not raise a TypeError\n            im._getexif()\n\n    def test_progressive_compat(self):\n        im1 = self.roundtrip(hopper())\n        assert not im1.info.get(\"progressive\")\n        assert not im1.info.get(\"progression\")\n\n        im2 = self.roundtrip(hopper(), progressive=0)\n        im3 = self.roundtrip(hopper(), progression=0)  # compatibility\n        assert not im2.info.get(\"progressive\")\n        assert not im2.info.get(\"progression\")\n        assert not im3.info.get(\"progressive\")\n        assert not im3.info.get(\"progression\")\n\n        im2 = self.roundtrip(hopper(), progressive=1)\n        im3 = self.roundtrip(hopper(), progression=1)  # compatibility\n        assert_image_equal(im1, im2)\n        assert_image_equal(im1, im3)\n        assert im2.info.get(\"progressive\")\n        assert im2.info.get(\"progression\")\n        assert im3.info.get(\"progressive\")\n        assert im3.info.get(\"progression\")\n\n    def test_quality(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), quality=50)\n        assert_image(im1, im2.mode, im2.size)\n        assert im1.bytes >= im2.bytes\n\n        im3 = self.roundtrip(hopper(), quality=0)\n        assert_image(im1, im3.mode, im3.size)\n        assert im2.bytes > im3.bytes\n\n    def test_smooth(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), smooth=100)\n        assert_image(im1, im2.mode, im2.size)\n\n    def test_subsampling(self):\n        def getsampling(im):\n            layer = im.layer\n            return layer[0][1:3] + layer[1][1:3] + layer[2][1:3]\n\n        # experimental API\n        im = self.roundtrip(hopper(), subsampling=-1)  # default\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=0)  # 4:4:4\n        assert getsampling(im) == (1, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=1)  # 4:2:2\n        assert getsampling(im) == (2, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=2)  # 4:2:0\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=3)  # default (undefined)\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n\n        im = self.roundtrip(hopper(), subsampling=\"4:4:4\")\n        assert getsampling(im) == (1, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:2:2\")\n        assert getsampling(im) == (2, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:2:0\")\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:1:1\")\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n\n        with pytest.raises(TypeError):\n            self.roundtrip(hopper(), subsampling=\"1:1:1\")\n\n    def test_exif(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            info = im._getexif()\n            assert info[305] == \"Adobe Photoshop CS Macintosh\"\n\n    def test_get_child_images(self):\n        with Image.open(\"Tests/images/flower.jpg\") as im:\n            ims = im.get_child_images()\n\n        assert len(ims) == 1\n        assert_image_equal_tofile(ims[0], \"Tests/images/flower_thumbnail.png\")\n\n    def test_mp(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            assert im._getmp() is None\n\n    def test_quality_keep(self, tmp_path):\n        # RGB\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n        # Grayscale\n        with Image.open(\"Tests/images/hopper_gray.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n        # CMYK\n        with Image.open(\"Tests/images/pil_sample_cmyk.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n\n    def test_junk_jpeg_header(self):\n        # https://github.com/python-pillow/Pillow/issues/630\n        filename = \"Tests/images/junk_jpeg_header.jpg\"\n        with Image.open(filename):\n            pass\n\n    def test_ff00_jpeg_header(self):\n        filename = \"Tests/images/jpeg_ff00_header.jpg\"\n        with Image.open(filename):\n            pass\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_truncated_jpeg_should_read_all_the_data(self):\n        filename = \"Tests/images/truncated_jpeg.jpg\"\n        ImageFile.LOAD_TRUNCATED_IMAGES = True\n        with Image.open(filename) as im:\n            im.load()\n            ImageFile.LOAD_TRUNCATED_IMAGES = False\n            assert im.getbbox() is not None\n\n    def test_truncated_jpeg_throws_oserror(self):\n        filename = \"Tests/images/truncated_jpeg.jpg\"\n        with Image.open(filename) as im:\n            with pytest.raises(OSError):\n                im.load()\n\n            # Test that the error is raised if loaded a second time\n            with pytest.raises(OSError):\n                im.load()\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_qtables(self, tmp_path):\n        def _n_qtables_helper(n, test_file):\n            with Image.open(test_file) as im:\n                f = str(tmp_path / \"temp.jpg\")\n                im.save(f, qtables=[[n] * 64] * n)\n            with Image.open(f) as im:\n                assert len(im.quantization) == n\n                reloaded = self.roundtrip(im, qtables=\"keep\")\n                assert im.quantization == reloaded.quantization\n                assert max(reloaded.quantization[0]) <= 255\n\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            qtables = im.quantization\n            reloaded = self.roundtrip(im, qtables=qtables, subsampling=0)\n            assert im.quantization == reloaded.quantization\n            assert_image_similar(im, self.roundtrip(im, qtables=\"web_low\"), 30)\n            assert_image_similar(im, self.roundtrip(im, qtables=\"web_high\"), 30)\n            assert_image_similar(im, self.roundtrip(im, qtables=\"keep\"), 30)\n\n            # valid bounds for baseline qtable\n            bounds_qtable = [int(s) for s in (\"255 1 \" * 32).split(None)]\n            im2 = self.roundtrip(im, qtables=[bounds_qtable])\n            assert im2.quantization == {0: bounds_qtable}\n\n            # values from wizard.txt in jpeg9-a src package.\n            standard_l_qtable = [\n                int(s)\n                for s in \"\"\"\n                16  11  10  16  24  40  51  61\n                12  12  14  19  26  58  60  55\n                14  13  16  24  40  57  69  56\n                14  17  22  29  51  87  80  62\n                18  22  37  56  68 109 103  77\n                24  35  55  64  81 104 113  92\n                49  64  78  87 103 121 120 101\n                72  92  95  98 112 100 103  99\n                \"\"\".split(\n                    None\n                )\n            ]\n\n            standard_chrominance_qtable = [\n                int(s)\n                for s in \"\"\"\n                17  18  24  47  99  99  99  99\n                18  21  26  66  99  99  99  99\n                24  26  56  99  99  99  99  99\n                47  66  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                \"\"\".split(\n                    None\n                )\n            ]\n            # list of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables=[standard_l_qtable, standard_chrominance_qtable]\n                ),\n                30,\n            )\n\n            # tuple of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables=(standard_l_qtable, standard_chrominance_qtable)\n                ),\n                30,\n            )\n\n            # dict of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables={0: standard_l_qtable, 1: standard_chrominance_qtable}\n                ),\n                30,\n            )\n\n            _n_qtables_helper(1, \"Tests/images/hopper_gray.jpg\")\n            _n_qtables_helper(1, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(2, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(3, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(1, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(2, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(3, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(4, \"Tests/images/pil_sample_cmyk.jpg\")\n\n            # not a sequence\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=\"a\")\n            # sequence wrong length\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[])\n            # sequence wrong length\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[1, 2, 3, 4, 5])\n\n            # qtable entry not a sequence\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[1])\n            # qtable entry has wrong number of items\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[[1, 2, 3, 4]])\n\n    def test_load_16bit_qtables(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            assert len(im.quantization) == 2\n            assert len(im.quantization[0]) == 64\n            assert max(im.quantization[0]) > 255\n\n    def test_save_multiple_16bit_qtables(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            im2 = self.roundtrip(im, qtables=\"keep\")\n            assert im.quantization == im2.quantization\n\n    def test_save_single_16bit_qtable(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            im2 = self.roundtrip(im, qtables={0: im.quantization[0]})\n            assert len(im2.quantization) == 1\n            assert im2.quantization[0] == im.quantization[0]\n\n    def test_save_low_quality_baseline_qtables(self):\n        with Image.open(TEST_FILE) as im:\n            im2 = self.roundtrip(im, quality=10)\n            assert len(im2.quantization) == 2\n            assert max(im2.quantization[0]) <= 255\n            assert max(im2.quantization[1]) <= 255\n\n    def test_convert_dict_qtables_deprecation(self):\n        with pytest.warns(DeprecationWarning):\n            qtable = {0: [1, 2, 3, 4]}\n            qtable2 = JpegImagePlugin.convert_dict_qtables(qtable)\n            assert qtable == qtable2\n\n    @pytest.mark.skipif(not djpeg_available(), reason=\"djpeg not available\")\n    def test_load_djpeg(self):\n        with Image.open(TEST_FILE) as img:\n            img.load_djpeg()\n            assert_image_similar_tofile(img, TEST_FILE, 5)\n\n    @pytest.mark.skipif(not cjpeg_available(), reason=\"cjpeg not available\")\n    def test_save_cjpeg(self, tmp_path):\n        with Image.open(TEST_FILE) as img:\n            tempfile = str(tmp_path / \"temp.jpg\")\n            JpegImagePlugin._save_cjpeg(img, 0, tempfile)\n            # Default save quality is 75%, so a tiny bit of difference is alright\n            assert_image_similar_tofile(img, tempfile, 17)\n\n    def test_no_duplicate_0x1001_tag(self):\n        # Arrange\n        tag_ids = {v: k for k, v in ExifTags.TAGS.items()}\n\n        # Assert\n        assert tag_ids[\"RelatedImageWidth\"] == 0x1001\n        assert tag_ids[\"RelatedImageLength\"] == 0x1002\n\n    def test_MAXBLOCK_scaling(self, tmp_path):\n        im = self.gen_random_image((512, 512))\n        f = str(tmp_path / \"temp.jpeg\")\n        im.save(f, quality=100, optimize=True)\n\n        with Image.open(f) as reloaded:\n            # none of these should crash\n            reloaded.save(f, quality=\"keep\")\n            reloaded.save(f, quality=\"keep\", progressive=True)\n            reloaded.save(f, quality=\"keep\", optimize=True)\n\n    def test_bad_mpo_header(self):\n        \"\"\"Treat unknown MPO as JPEG\"\"\"\n        # Arrange\n\n        # Act\n        # Shouldn't raise error\n        fn = \"Tests/images/sugarshack_bad_mpo_header.jpg\"\n        with pytest.warns(UserWarning, Image.open, fn) as im:\n            # Assert\n            assert im.format == \"JPEG\"\n\n    @pytest.mark.parametrize(\"mode\", (\"1\", \"L\", \"RGB\", \"RGBX\", \"CMYK\", \"YCbCr\"))\n    def test_save_correct_modes(self, mode):\n        out = BytesIO()\n        img = Image.new(mode, (20, 20))\n        img.save(out, \"JPEG\")\n\n    @pytest.mark.parametrize(\"mode\", (\"LA\", \"La\", \"RGBA\", \"RGBa\", \"P\"))\n    def test_save_wrong_modes(self, mode):\n        # ref https://github.com/python-pillow/Pillow/issues/2005\n        out = BytesIO()\n        img = Image.new(mode, (20, 20))\n        with pytest.raises(OSError):\n            img.save(out, \"JPEG\")\n\n    def test_save_tiff_with_dpi(self, tmp_path):\n        # Arrange\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            # Act\n            im.save(outfile, \"JPEG\", dpi=im.info[\"dpi\"])\n\n            # Assert\n            with Image.open(outfile) as reloaded:\n                reloaded.load()\n                assert im.info[\"dpi\"] == reloaded.info[\"dpi\"]\n\n    def test_save_dpi_rounding(self, tmp_path):\n        outfile = str(tmp_path / \"temp.jpg\")\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            im.save(outfile, dpi=(72.2, 72.2))\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == (72, 72)\n\n            im.save(outfile, dpi=(72.8, 72.8))\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"dpi\"] == (73, 73)\n\n    def test_dpi_tuple_from_exif(self):\n        # Arrange\n        # This Photoshop CC 2017 image has DPI in EXIF not metadata\n        # EXIF XResolution is (2000000, 10000)\n        with Image.open(\"Tests/images/photoshop-200dpi.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (200, 200)\n\n    def test_dpi_int_from_exif(self):\n        # Arrange\n        # This image has DPI in EXIF not metadata\n        # EXIF XResolution is 72\n        with Image.open(\"Tests/images/exif-72dpi-int.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_dpi_from_dpcm_exif(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm:\n        # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/exif-200dpcm.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (508, 508)\n\n    def test_dpi_exif_zero_division(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with EXIF resolution set to 0/0:\n        # exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/exif-dpi-zerodivision.jpg\") as im:\n            # Act / Assert\n            # This should return the default, and not raise a ZeroDivisionError\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_dpi_exif_string(self):\n        # Arrange\n        # 0x011A tag in this exif contains string '300300\\x02'\n        with Image.open(\"Tests/images/broken_exif_dpi.jpg\") as im:\n            # Act / Assert\n            # This should return the default\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_no_dpi_in_exif(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with resolution removed from EXIF:\n        # exiftool \"-*resolution*\"= photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/no-dpi-in-exif.jpg\") as im:\n            # Act / Assert\n            # \"When the image resolution is unknown, 72 [dpi] is designated.\"\n            # https://exiv2.org/tags.html\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_invalid_exif(self):\n        # This is no-dpi-in-exif with the tiff header of the exif block\n        # hexedited from MM * to FF FF FF FF\n        with Image.open(\"Tests/images/invalid-exif.jpg\") as im:\n            # This should return the default, and not a SyntaxError or\n            # OSError for unidentified image.\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_exif_x_resolution(self, tmp_path):\n        with Image.open(\"Tests/images/flower.jpg\") as im:\n            exif = im.getexif()\n            assert exif[282] == 180\n\n            out = str(tmp_path / \"out.jpg\")\n            with warnings.catch_warnings():\n                im.save(out, exif=exif)\n\n        with Image.open(out) as reloaded:\n            assert reloaded.getexif()[282] == 180\n\n    def test_invalid_exif_x_resolution(self):\n        # When no x or y resolution is defined in EXIF\n        with Image.open(\"Tests/images/invalid-exif-without-x-resolution.jpg\") as im:\n            # This should return the default, and not a ValueError or\n            # OSError for an unidentified image.\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_ifd_offset_exif(self):\n        # Arrange\n        # This image has been manually hexedited to have an IFD offset of 10,\n        # in contrast to normal 8\n        with Image.open(\"Tests/images/exif-ifd-offset.jpg\") as im:\n            # Act / Assert\n            assert im._getexif()[306] == \"2017:03:13 23:03:09\"\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_photoshop(self):\n        with Image.open(\"Tests/images/photoshop-200dpi.jpg\") as im:\n            assert im.info[\"photoshop\"][0x03ED] == {\n                \"XResolution\": 200.0,\n                \"DisplayedUnitsX\": 1,\n                \"YResolution\": 200.0,\n                \"DisplayedUnitsY\": 1,\n            }\n\n            # Test that the image can still load, even with broken Photoshop data\n            # This image had the APP13 length hexedited to be smaller\n            assert_image_equal_tofile(im, \"Tests/images/photoshop-200dpi-broken.jpg\")\n\n        # This image does not contain a Photoshop header string\n        with Image.open(\"Tests/images/app13.jpg\") as im:\n            assert \"photoshop\" not in im.info\n\n    def test_photoshop_malformed_and_multiple(self):\n        with Image.open(\"Tests/images/app13-multiple.jpg\") as im:\n            assert \"photoshop\" in im.info\n            assert 24 == len(im.info[\"photoshop\"])\n            apps_13_lengths = [len(v) for k, v in im.applist if k == \"APP13\"]\n            assert [65504, 24] == apps_13_lengths\n\n    def test_adobe_transform(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            assert im.info[\"adobe_transform\"] == 1\n\n        with Image.open(\"Tests/images/pil_sample_cmyk.jpg\") as im:\n            assert im.info[\"adobe_transform\"] == 2\n\n        # This image has been manually hexedited\n        # so that the APP14 reports its length to be 11,\n        # leaving no room for \"adobe_transform\"\n        with Image.open(\"Tests/images/truncated_app14.jpg\") as im:\n            assert \"adobe\" in im.info\n            assert \"adobe_transform\" not in im.info\n\n    def test_icc_after_SOF(self):\n        with Image.open(\"Tests/images/icc-after-SOF.jpg\") as im:\n            assert im.info[\"icc_profile\"] == b\"profile\"\n\n    def test_jpeg_magic_number(self):\n        size = 4097\n        buffer = BytesIO(b\"\\xFF\" * size)  # Many xFF bytes\n        buffer.max_pos = 0\n        orig_read = buffer.read\n\n        def read(n=-1):\n            res = orig_read(n)\n            buffer.max_pos = max(buffer.max_pos, buffer.tell())\n            return res\n\n        buffer.read = read\n        with pytest.raises(UnidentifiedImageError):\n            with Image.open(buffer):\n                pass\n\n        # Assert the entire file has not been read\n        assert 0 < buffer.max_pos < size\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/xmp_test.jpg\") as im:\n            if ElementTree is None:\n                with pytest.warns(UserWarning):\n                    assert im.getxmp() == {}\n            else:\n                xmp = im.getxmp()\n\n                description = xmp[\"xmpmeta\"][\"RDF\"][\"Description\"]\n                assert description[\"DerivedFrom\"] == {\n                    \"documentID\": \"8367D410E636EA95B7DE7EBA1C43A412\",\n                    \"originalDocumentID\": \"8367D410E636EA95B7DE7EBA1C43A412\",\n                }\n                assert description[\"Look\"][\"Description\"][\"Group\"][\"Alt\"][\"li\"] == {\n                    \"lang\": \"x-default\",\n                    \"text\": \"Profiles\",\n                }\n                assert description[\"ToneCurve\"][\"Seq\"][\"li\"] == [\"0, 0\", \"255, 255\"]\n\n                # Attribute\n                assert description[\"Version\"] == \"10.4\"\n\n        if ElementTree is not None:\n            with Image.open(\"Tests/images/hopper.jpg\") as im:\n                assert im.getxmp() == {}\n\n    @pytest.mark.timeout(timeout=1)\n    def test_eof(self):\n        # Even though this decoder never says that it is finished\n        # the image should still end when there is no new data\n        class InfiniteMockPyDecoder(ImageFile.PyDecoder):\n            def decode(self, buffer):\n                return 0, 0\n\n        decoder = InfiniteMockPyDecoder(None)\n\n        def closure(mode, *args):\n            decoder.__init__(mode, *args)\n            return decoder\n\n        Image.register_decoder(\"INFINITE\", closure)\n\n        with Image.open(TEST_FILE) as im:\n            im.tile = [\n                (\"INFINITE\", (0, 0, 128, 128), 0, (\"RGB\", 0, 1)),\n            ]\n            ImageFile.LOAD_TRUNCATED_IMAGES = True\n            im.load()\n            ImageFile.LOAD_TRUNCATED_IMAGES = False",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 41,
                                "end_lineno": 926,
                                "parent": null
                            }
                        },
                        {
                            "file_name": "Tests/test_file_jpeg.py",
                            "type": "class",
                            "name": "TestFileJpeg",
                            "content": "class TestFileJpeg:\n    def roundtrip(self, im, **options):\n        out = BytesIO()\n        im.save(out, \"JPEG\", **options)\n        test_bytes = out.tell()\n        out.seek(0)\n        im = Image.open(out)\n        im.bytes = test_bytes  # for testing only\n        return im\n\n    def gen_random_image(self, size, mode=\"RGB\"):\n        \"\"\"Generates a very hard to compress file\n        :param size: tuple\n        :param mode: optional image mode\n\n        \"\"\"\n        return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode)))\n\n    def test_sanity(self):\n        # internal version number\n        assert re.search(r\"\\d+\\.\\d+$\", features.version_codec(\"jpg\"))\n\n        with Image.open(TEST_FILE) as im:\n            im.load()\n            assert im.mode == \"RGB\"\n            assert im.size == (128, 128)\n            assert im.format == \"JPEG\"\n            assert im.get_format_mimetype() == \"image/jpeg\"\n\n    @pytest.mark.parametrize(\"size\", ((1, 0), (0, 1), (0, 0)))\n    def test_zero(self, size, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        im = Image.new(\"RGB\", size)\n        with pytest.raises(ValueError):\n            im.save(f)\n\n    def test_app(self):\n        # Test APP/COM reader (@PIL135)\n        with Image.open(TEST_FILE) as im:\n            assert im.applist[0] == (\"APP0\", b\"JFIF\\x00\\x01\\x01\\x01\\x00`\\x00`\\x00\\x00\")\n            assert im.applist[1] == (\n                \"COM\",\n                b\"File written by Adobe Photoshop\\xa8 4.0\\x00\",\n            )\n            assert len(im.applist) == 2\n\n            assert im.info[\"comment\"] == b\"File written by Adobe Photoshop\\xa8 4.0\\x00\"\n            assert im.app[\"COM\"] == im.info[\"comment\"]\n\n    def test_comment_write(self):\n        with Image.open(TEST_FILE) as im:\n            assert im.info[\"comment\"] == b\"File written by Adobe Photoshop\\xa8 4.0\\x00\"\n\n            # Test that existing comment is saved by default\n            out = BytesIO()\n            im.save(out, format=\"JPEG\")\n            with Image.open(out) as reloaded:\n                assert im.info[\"comment\"] == reloaded.info[\"comment\"]\n\n            # Ensure that a blank comment causes any existing comment to be removed\n            for comment in (\"\", b\"\", None):\n                out = BytesIO()\n                im.save(out, format=\"JPEG\", comment=comment)\n                with Image.open(out) as reloaded:\n                    assert \"comment\" not in reloaded.info\n\n            # Test that a comment argument overrides the default comment\n            for comment in (\"Test comment text\", b\"Text comment text\"):\n                out = BytesIO()\n                im.save(out, format=\"JPEG\", comment=comment)\n                with Image.open(out) as reloaded:\n                    if not isinstance(comment, bytes):\n                        comment = comment.encode()\n                    assert reloaded.info[\"comment\"] == comment\n\n    def test_cmyk(self):\n        # Test CMYK handling.  Thanks to Tim and Charlie for test data,\n        # Michael for getting me to look one more time.\n        f = \"Tests/images/pil_sample_cmyk.jpg\"\n        with Image.open(f) as im:\n            # the source image has red pixels in the upper left corner.\n            c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))\n            assert c == 0.0\n            assert m > 0.8\n            assert y > 0.8\n            assert k == 0.0\n            # the opposite corner is black\n            c, m, y, k = (\n                x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))\n            )\n            assert k > 0.9\n            # roundtrip, and check again\n            im = self.roundtrip(im)\n            c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))\n            assert c == 0.0\n            assert m > 0.8\n            assert y > 0.8\n            assert k == 0.0\n            c, m, y, k = (\n                x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))\n            )\n            assert k > 0.9\n\n    @pytest.mark.parametrize(\n        \"test_image_path\",\n        [TEST_FILE, \"Tests/images/pil_sample_cmyk.jpg\"],\n    )\n    def test_dpi(self, test_image_path):\n        def test(xdpi, ydpi=None):\n            with Image.open(test_image_path) as im:\n                im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi))\n            return im.info.get(\"dpi\")\n\n        assert test(72) == (72, 72)\n        assert test(300) == (300, 300)\n        assert test(100, 200) == (100, 200)\n        assert test(0) is None  # square pixels\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_icc(self, tmp_path):\n        # Test ICC support\n        with Image.open(\"Tests/images/rgb.jpg\") as im1:\n            icc_profile = im1.info[\"icc_profile\"]\n            assert len(icc_profile) == 3144\n            # Roundtrip via physical file.\n            f = str(tmp_path / \"temp.jpg\")\n            im1.save(f, icc_profile=icc_profile)\n        with Image.open(f) as im2:\n            assert im2.info.get(\"icc_profile\") == icc_profile\n            # Roundtrip via memory buffer.\n            im1 = self.roundtrip(hopper())\n            im2 = self.roundtrip(hopper(), icc_profile=icc_profile)\n            assert_image_equal(im1, im2)\n            assert not im1.info.get(\"icc_profile\")\n            assert im2.info.get(\"icc_profile\")\n\n    @pytest.mark.parametrize(\n        \"n\",\n        (\n            0,\n            1,\n            3,\n            4,\n            5,\n            65533 - 14,  # full JPEG marker block\n            65533 - 14 + 1,  # full block plus one byte\n            ImageFile.MAXBLOCK,  # full buffer block\n            ImageFile.MAXBLOCK + 1,  # full buffer block plus one byte\n            ImageFile.MAXBLOCK * 4 + 3,  # large block\n        ),\n    )\n    def test_icc_big(self, n):\n        # Make sure that the \"extra\" support handles large blocks\n        # The ICC APP marker can store 65519 bytes per marker, so\n        # using a 4-byte test code should allow us to detect out of\n        # order issues.\n        icc_profile = (b\"Test\" * int(n / 4 + 1))[:n]\n        assert len(icc_profile) == n  # sanity\n        im1 = self.roundtrip(hopper(), icc_profile=icc_profile)\n        assert im1.info.get(\"icc_profile\") == (icc_profile or None)\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_large_icc_meta(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        # Sometimes the meta data on the icc_profile block is bigger than\n        # Image.MAXBLOCK or the image size.\n        with Image.open(\"Tests/images/icc_profile_big.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            icc_profile = im.info[\"icc_profile\"]\n            # Should not raise OSError for image with icc larger than image size.\n            im.save(\n                f,\n                format=\"JPEG\",\n                progressive=True,\n                quality=95,\n                icc_profile=icc_profile,\n                optimize=True,\n            )\n\n    def test_optimize(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), optimize=0)\n        im3 = self.roundtrip(hopper(), optimize=1)\n        assert_image_equal(im1, im2)\n        assert_image_equal(im1, im3)\n        assert im1.bytes >= im2.bytes\n        assert im1.bytes >= im3.bytes\n\n    def test_optimize_large_buffer(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        f = str(tmp_path / \"temp.jpg\")\n        # this requires ~ 1.5x Image.MAXBLOCK\n        im = Image.new(\"RGB\", (4096, 4096), 0xFF3333)\n        im.save(f, format=\"JPEG\", optimize=True)\n\n    def test_progressive(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), progressive=False)\n        im3 = self.roundtrip(hopper(), progressive=True)\n        assert not im1.info.get(\"progressive\")\n        assert not im2.info.get(\"progressive\")\n        assert im3.info.get(\"progressive\")\n\n        assert_image_equal(im1, im3)\n        assert im1.bytes >= im3.bytes\n\n    def test_progressive_large_buffer(self, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        # this requires ~ 1.5x Image.MAXBLOCK\n        im = Image.new(\"RGB\", (4096, 4096), 0xFF3333)\n        im.save(f, format=\"JPEG\", progressive=True)\n\n    def test_progressive_large_buffer_highest_quality(self, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        im = self.gen_random_image((255, 255))\n        # this requires more bytes than pixels in the image\n        im.save(f, format=\"JPEG\", progressive=True, quality=100)\n\n    def test_progressive_cmyk_buffer(self):\n        # Issue 2272, quality 90 cmyk image is tripping the large buffer bug.\n        f = BytesIO()\n        im = self.gen_random_image((256, 256), \"CMYK\")\n        im.save(f, format=\"JPEG\", progressive=True, quality=94)\n\n    def test_large_exif(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        f = str(tmp_path / \"temp.jpg\")\n        im = hopper()\n        im.save(f, \"JPEG\", quality=90, exif=b\"1\" * 65532)\n\n    def test_exif_typeerror(self):\n        with Image.open(\"Tests/images/exif_typeerror.jpg\") as im:\n            # Should not raise a TypeError\n            im._getexif()\n\n    def test_exif_gps(self, tmp_path):\n        expected_exif_gps = {\n            0: b\"\\x00\\x00\\x00\\x01\",\n            2: 4294967295,\n            5: b\"\\x01\",\n            30: 65535,\n            29: \"1999:99:99 99:99:99\",\n        }\n        gps_index = 34853\n\n        # Reading\n        with Image.open(\"Tests/images/exif_gps.jpg\") as im:\n            exif = im._getexif()\n            assert exif[gps_index] == expected_exif_gps\n\n        # Writing\n        f = str(tmp_path / \"temp.jpg\")\n        exif = Image.Exif()\n        exif[gps_index] = expected_exif_gps\n        hopper().save(f, exif=exif)\n\n        with Image.open(f) as reloaded:\n            exif = reloaded._getexif()\n            assert exif[gps_index] == expected_exif_gps\n\n    def test_empty_exif_gps(self):\n        with Image.open(\"Tests/images/empty_gps_ifd.jpg\") as im:\n            exif = im.getexif()\n            del exif[0x8769]\n\n            # Assert that it needs to be transposed\n            assert exif[0x0112] == Image.Transpose.TRANSVERSE\n\n            # Assert that the GPS IFD is present and empty\n            assert exif.get_ifd(0x8825) == {}\n\n            transposed = ImageOps.exif_transpose(im)\n        exif = transposed.getexif()\n        assert exif.get_ifd(0x8825) == {}\n\n        # Assert that it was transposed\n        assert 0x0112 not in exif\n\n    def test_exif_equality(self):\n        # In 7.2.0, Exif rationals were changed to be read as\n        # TiffImagePlugin.IFDRational. This class had a bug in __eq__,\n        # breaking the self-equality of Exif data\n        exifs = []\n        for i in range(2):\n            with Image.open(\"Tests/images/exif-200dpcm.jpg\") as im:\n                exifs.append(im._getexif())\n        assert exifs[0] == exifs[1]\n\n    def test_exif_rollback(self):\n        # rolling back exif support in 3.1 to pre-3.0 formatting.\n        # expected from 2.9, with b/u qualifiers switched for 3.2 compatibility\n        # this test passes on 2.9 and 3.1, but not 3.0\n        expected_exif = {\n            34867: 4294967295,\n            258: (24, 24, 24),\n            36867: \"2099:09:29 10:10:10\",\n            34853: {\n                0: b\"\\x00\\x00\\x00\\x01\",\n                2: 4294967295,\n                5: b\"\\x01\",\n                30: 65535,\n                29: \"1999:99:99 99:99:99\",\n            },\n            296: 65535,\n            34665: 185,\n            41994: 65535,\n            514: 4294967295,\n            271: \"Make\",\n            272: \"XXX-XXX\",\n            305: \"PIL\",\n            42034: (1, 1, 1, 1),\n            42035: \"LensMake\",\n            34856: b\"\\xaa\\xaa\\xaa\\xaa\\xaa\\xaa\",\n            282: 4294967295,\n            33434: 4294967295,\n        }\n\n        with Image.open(\"Tests/images/exif_gps.jpg\") as im:\n            exif = im._getexif()\n\n        for tag, value in expected_exif.items():\n            assert value == exif[tag]\n\n    def test_exif_gps_typeerror(self):\n        with Image.open(\"Tests/images/exif_gps_typeerror.jpg\") as im:\n            # Should not raise a TypeError\n            im._getexif()\n\n    def test_progressive_compat(self):\n        im1 = self.roundtrip(hopper())\n        assert not im1.info.get(\"progressive\")\n        assert not im1.info.get(\"progression\")\n\n        im2 = self.roundtrip(hopper(), progressive=0)\n        im3 = self.roundtrip(hopper(), progression=0)  # compatibility\n        assert not im2.info.get(\"progressive\")\n        assert not im2.info.get(\"progression\")\n        assert not im3.info.get(\"progressive\")\n        assert not im3.info.get(\"progression\")\n\n        im2 = self.roundtrip(hopper(), progressive=1)\n        im3 = self.roundtrip(hopper(), progression=1)  # compatibility\n        assert_image_equal(im1, im2)\n        assert_image_equal(im1, im3)\n        assert im2.info.get(\"progressive\")\n        assert im2.info.get(\"progression\")\n        assert im3.info.get(\"progressive\")\n        assert im3.info.get(\"progression\")\n\n    def test_quality(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), quality=50)\n        assert_image(im1, im2.mode, im2.size)\n        assert im1.bytes >= im2.bytes\n\n        im3 = self.roundtrip(hopper(), quality=0)\n        assert_image(im1, im3.mode, im3.size)\n        assert im2.bytes > im3.bytes\n\n    def test_smooth(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), smooth=100)\n        assert_image(im1, im2.mode, im2.size)\n\n    def test_subsampling(self):\n        def getsampling(im):\n            layer = im.layer\n            return layer[0][1:3] + layer[1][1:3] + layer[2][1:3]\n\n        # experimental API\n        im = self.roundtrip(hopper(), subsampling=-1)  # default\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=0)  # 4:4:4\n        assert getsampling(im) == (1, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=1)  # 4:2:2\n        assert getsampling(im) == (2, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=2)  # 4:2:0\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=3)  # default (undefined)\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n\n        im = self.roundtrip(hopper(), subsampling=\"4:4:4\")\n        assert getsampling(im) == (1, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:2:2\")\n        assert getsampling(im) == (2, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:2:0\")\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:1:1\")\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n\n        with pytest.raises(TypeError):\n            self.roundtrip(hopper(), subsampling=\"1:1:1\")\n\n    def test_exif(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            info = im._getexif()\n            assert info[305] == \"Adobe Photoshop CS Macintosh\"\n\n    def test_get_child_images(self):\n        with Image.open(\"Tests/images/flower.jpg\") as im:\n            ims = im.get_child_images()\n\n        assert len(ims) == 1\n        assert_image_equal_tofile(ims[0], \"Tests/images/flower_thumbnail.png\")\n\n    def test_mp(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            assert im._getmp() is None\n\n    def test_quality_keep(self, tmp_path):\n        # RGB\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n        # Grayscale\n        with Image.open(\"Tests/images/hopper_gray.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n        # CMYK\n        with Image.open(\"Tests/images/pil_sample_cmyk.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n\n    def test_junk_jpeg_header(self):\n        # https://github.com/python-pillow/Pillow/issues/630\n        filename = \"Tests/images/junk_jpeg_header.jpg\"\n        with Image.open(filename):\n            pass\n\n    def test_ff00_jpeg_header(self):\n        filename = \"Tests/images/jpeg_ff00_header.jpg\"\n        with Image.open(filename):\n            pass\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_truncated_jpeg_should_read_all_the_data(self):\n        filename = \"Tests/images/truncated_jpeg.jpg\"\n        ImageFile.LOAD_TRUNCATED_IMAGES = True\n        with Image.open(filename) as im:\n            im.load()\n            ImageFile.LOAD_TRUNCATED_IMAGES = False\n            assert im.getbbox() is not None\n\n    def test_truncated_jpeg_throws_oserror(self):\n        filename = \"Tests/images/truncated_jpeg.jpg\"\n        with Image.open(filename) as im:\n            with pytest.raises(OSError):\n                im.load()\n\n            # Test that the error is raised if loaded a second time\n            with pytest.raises(OSError):\n                im.load()\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_qtables(self, tmp_path):\n        def _n_qtables_helper(n, test_file):\n            with Image.open(test_file) as im:\n                f = str(tmp_path / \"temp.jpg\")\n                im.save(f, qtables=[[n] * 64] * n)\n            with Image.open(f) as im:\n                assert len(im.quantization) == n\n                reloaded = self.roundtrip(im, qtables=\"keep\")\n                assert im.quantization == reloaded.quantization\n                assert max(reloaded.quantization[0]) <= 255\n\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            qtables = im.quantization\n            reloaded = self.roundtrip(im, qtables=qtables, subsampling=0)\n            assert im.quantization == reloaded.quantization\n            assert_image_similar(im, self.roundtrip(im, qtables=\"web_low\"), 30)\n            assert_image_similar(im, self.roundtrip(im, qtables=\"web_high\"), 30)\n            assert_image_similar(im, self.roundtrip(im, qtables=\"keep\"), 30)\n\n            # valid bounds for baseline qtable\n            bounds_qtable = [int(s) for s in (\"255 1 \" * 32).split(None)]\n            im2 = self.roundtrip(im, qtables=[bounds_qtable])\n            assert im2.quantization == {0: bounds_qtable}\n\n            # values from wizard.txt in jpeg9-a src package.\n            standard_l_qtable = [\n                int(s)\n                for s in \"\"\"\n                16  11  10  16  24  40  51  61\n                12  12  14  19  26  58  60  55\n                14  13  16  24  40  57  69  56\n                14  17  22  29  51  87  80  62\n                18  22  37  56  68 109 103  77\n                24  35  55  64  81 104 113  92\n                49  64  78  87 103 121 120 101\n                72  92  95  98 112 100 103  99\n                \"\"\".split(\n                    None\n                )\n            ]\n\n            standard_chrominance_qtable = [\n                int(s)\n                for s in \"\"\"\n                17  18  24  47  99  99  99  99\n                18  21  26  66  99  99  99  99\n                24  26  56  99  99  99  99  99\n                47  66  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                \"\"\".split(\n                    None\n                )\n            ]\n            # list of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables=[standard_l_qtable, standard_chrominance_qtable]\n                ),\n                30,\n            )\n\n            # tuple of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables=(standard_l_qtable, standard_chrominance_qtable)\n                ),\n                30,\n            )\n\n            # dict of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables={0: standard_l_qtable, 1: standard_chrominance_qtable}\n                ),\n                30,\n            )\n\n            _n_qtables_helper(1, \"Tests/images/hopper_gray.jpg\")\n            _n_qtables_helper(1, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(2, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(3, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(1, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(2, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(3, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(4, \"Tests/images/pil_sample_cmyk.jpg\")\n\n            # not a sequence\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=\"a\")\n            # sequence wrong length\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[])\n            # sequence wrong length\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[1, 2, 3, 4, 5])\n\n            # qtable entry not a sequence\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[1])\n            # qtable entry has wrong number of items\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[[1, 2, 3, 4]])\n\n    def test_load_16bit_qtables(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            assert len(im.quantization) == 2\n            assert len(im.quantization[0]) == 64\n            assert max(im.quantization[0]) > 255\n\n    def test_save_multiple_16bit_qtables(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            im2 = self.roundtrip(im, qtables=\"keep\")\n            assert im.quantization == im2.quantization\n\n    def test_save_single_16bit_qtable(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            im2 = self.roundtrip(im, qtables={0: im.quantization[0]})\n            assert len(im2.quantization) == 1\n            assert im2.quantization[0] == im.quantization[0]\n\n    def test_save_low_quality_baseline_qtables(self):\n        with Image.open(TEST_FILE) as im:\n            im2 = self.roundtrip(im, quality=10)\n            assert len(im2.quantization) == 2\n            assert max(im2.quantization[0]) <= 255\n            assert max(im2.quantization[1]) <= 255\n\n    def test_convert_dict_qtables_deprecation(self):\n        with pytest.warns(DeprecationWarning):\n            qtable = {0: [1, 2, 3, 4]}\n            qtable2 = JpegImagePlugin.convert_dict_qtables(qtable)\n            assert qtable == qtable2\n\n    @pytest.mark.skipif(not djpeg_available(), reason=\"djpeg not available\")\n    def test_load_djpeg(self):\n        with Image.open(TEST_FILE) as img:\n            img.load_djpeg()\n            assert_image_similar_tofile(img, TEST_FILE, 5)\n\n    @pytest.mark.skipif(not cjpeg_available(), reason=\"cjpeg not available\")\n    def test_save_cjpeg(self, tmp_path):\n        with Image.open(TEST_FILE) as img:\n            tempfile = str(tmp_path / \"temp.jpg\")\n            JpegImagePlugin._save_cjpeg(img, 0, tempfile)\n            # Default save quality is 75%, so a tiny bit of difference is alright\n            assert_image_similar_tofile(img, tempfile, 17)\n\n    def test_no_duplicate_0x1001_tag(self):\n        # Arrange\n        tag_ids = {v: k for k, v in ExifTags.TAGS.items()}\n\n        # Assert\n        assert tag_ids[\"RelatedImageWidth\"] == 0x1001\n        assert tag_ids[\"RelatedImageLength\"] == 0x1002\n\n    def test_MAXBLOCK_scaling(self, tmp_path):\n        im = self.gen_random_image((512, 512))\n        f = str(tmp_path / \"temp.jpeg\")\n        im.save(f, quality=100, optimize=True)\n\n        with Image.open(f) as reloaded:\n            # none of these should crash\n            reloaded.save(f, quality=\"keep\")\n            reloaded.save(f, quality=\"keep\", progressive=True)\n            reloaded.save(f, quality=\"keep\", optimize=True)\n\n    def test_bad_mpo_header(self):\n        \"\"\"Treat unknown MPO as JPEG\"\"\"\n        # Arrange\n\n        # Act\n        # Shouldn't raise error\n        fn = \"Tests/images/sugarshack_bad_mpo_header.jpg\"\n        with pytest.warns(UserWarning, Image.open, fn) as im:\n            # Assert\n            assert im.format == \"JPEG\"\n\n    @pytest.mark.parametrize(\"mode\", (\"1\", \"L\", \"RGB\", \"RGBX\", \"CMYK\", \"YCbCr\"))\n    def test_save_correct_modes(self, mode):\n        out = BytesIO()\n        img = Image.new(mode, (20, 20))\n        img.save(out, \"JPEG\")\n\n    @pytest.mark.parametrize(\"mode\", (\"LA\", \"La\", \"RGBA\", \"RGBa\", \"P\"))\n    def test_save_wrong_modes(self, mode):\n        # ref https://github.com/python-pillow/Pillow/issues/2005\n        out = BytesIO()\n        img = Image.new(mode, (20, 20))\n        with pytest.raises(OSError):\n            img.save(out, \"JPEG\")\n\n    def test_save_tiff_with_dpi(self, tmp_path):\n        # Arrange\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            # Act\n            im.save(outfile, \"JPEG\", dpi=im.info[\"dpi\"])\n\n            # Assert\n            with Image.open(outfile) as reloaded:\n                reloaded.load()\n                assert im.info[\"dpi\"] == reloaded.info[\"dpi\"]\n\n    def test_save_dpi_rounding(self, tmp_path):\n        outfile = str(tmp_path / \"temp.jpg\")\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            im.save(outfile, dpi=(72.2, 72.2))\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == (72, 72)\n\n            im.save(outfile, dpi=(72.8, 72.8))\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"dpi\"] == (73, 73)\n\n    def test_dpi_tuple_from_exif(self):\n        # Arrange\n        # This Photoshop CC 2017 image has DPI in EXIF not metadata\n        # EXIF XResolution is (2000000, 10000)\n        with Image.open(\"Tests/images/photoshop-200dpi.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (200, 200)\n\n    def test_dpi_int_from_exif(self):\n        # Arrange\n        # This image has DPI in EXIF not metadata\n        # EXIF XResolution is 72\n        with Image.open(\"Tests/images/exif-72dpi-int.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_dpi_from_dpcm_exif(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm:\n        # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/exif-200dpcm.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (508, 508)\n\n    def test_dpi_exif_zero_division(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with EXIF resolution set to 0/0:\n        # exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/exif-dpi-zerodivision.jpg\") as im:\n            # Act / Assert\n            # This should return the default, and not raise a ZeroDivisionError\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_dpi_exif_string(self):\n        # Arrange\n        # 0x011A tag in this exif contains string '300300\\x02'\n        with Image.open(\"Tests/images/broken_exif_dpi.jpg\") as im:\n            # Act / Assert\n            # This should return the default\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_no_dpi_in_exif(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with resolution removed from EXIF:\n        # exiftool \"-*resolution*\"= photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/no-dpi-in-exif.jpg\") as im:\n            # Act / Assert\n            # \"When the image resolution is unknown, 72 [dpi] is designated.\"\n            # https://exiv2.org/tags.html\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_invalid_exif(self):\n        # This is no-dpi-in-exif with the tiff header of the exif block\n        # hexedited from MM * to FF FF FF FF\n        with Image.open(\"Tests/images/invalid-exif.jpg\") as im:\n            # This should return the default, and not a SyntaxError or\n            # OSError for unidentified image.\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_exif_x_resolution(self, tmp_path):\n        with Image.open(\"Tests/images/flower.jpg\") as im:\n            exif = im.getexif()\n            assert exif[282] == 180\n\n            out = str(tmp_path / \"out.jpg\")\n            with warnings.catch_warnings():\n                im.save(out, exif=exif)\n\n        with Image.open(out) as reloaded:\n            assert reloaded.getexif()[282] == 180\n\n    def test_invalid_exif_x_resolution(self):\n        # When no x or y resolution is defined in EXIF\n        with Image.open(\"Tests/images/invalid-exif-without-x-resolution.jpg\") as im:\n            # This should return the default, and not a ValueError or\n            # OSError for an unidentified image.\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_ifd_offset_exif(self):\n        # Arrange\n        # This image has been manually hexedited to have an IFD offset of 10,\n        # in contrast to normal 8\n        with Image.open(\"Tests/images/exif-ifd-offset.jpg\") as im:\n            # Act / Assert\n            assert im._getexif()[306] == \"2017:03:13 23:03:09\"\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_photoshop(self):\n        with Image.open(\"Tests/images/photoshop-200dpi.jpg\") as im:\n            assert im.info[\"photoshop\"][0x03ED] == {\n                \"XResolution\": 200.0,\n                \"DisplayedUnitsX\": 1,\n                \"YResolution\": 200.0,\n                \"DisplayedUnitsY\": 1,\n            }\n\n            # Test that the image can still load, even with broken Photoshop data\n            # This image had the APP13 length hexedited to be smaller\n            assert_image_equal_tofile(im, \"Tests/images/photoshop-200dpi-broken.jpg\")\n\n        # This image does not contain a Photoshop header string\n        with Image.open(\"Tests/images/app13.jpg\") as im:\n            assert \"photoshop\" not in im.info\n\n    def test_photoshop_malformed_and_multiple(self):\n        with Image.open(\"Tests/images/app13-multiple.jpg\") as im:\n            assert \"photoshop\" in im.info\n            assert 24 == len(im.info[\"photoshop\"])\n            apps_13_lengths = [len(v) for k, v in im.applist if k == \"APP13\"]\n            assert [65504, 24] == apps_13_lengths\n\n    def test_adobe_transform(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            assert im.info[\"adobe_transform\"] == 1\n\n        with Image.open(\"Tests/images/pil_sample_cmyk.jpg\") as im:\n            assert im.info[\"adobe_transform\"] == 2\n\n        # This image has been manually hexedited\n        # so that the APP14 reports its length to be 11,\n        # leaving no room for \"adobe_transform\"\n        with Image.open(\"Tests/images/truncated_app14.jpg\") as im:\n            assert \"adobe\" in im.info\n            assert \"adobe_transform\" not in im.info\n\n    def test_icc_after_SOF(self):\n        with Image.open(\"Tests/images/icc-after-SOF.jpg\") as im:\n            assert im.info[\"icc_profile\"] == b\"profile\"\n\n    def test_jpeg_magic_number(self):\n        size = 4097\n        buffer = BytesIO(b\"\\xFF\" * size)  # Many xFF bytes\n        buffer.max_pos = 0\n        orig_read = buffer.read\n\n        def read(n=-1):\n            res = orig_read(n)\n            buffer.max_pos = max(buffer.max_pos, buffer.tell())\n            return res\n\n        buffer.read = read\n        with pytest.raises(UnidentifiedImageError):\n            with Image.open(buffer):\n                pass\n\n        # Assert the entire file has not been read\n        assert 0 < buffer.max_pos < size\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/xmp_test.jpg\") as im:\n            if ElementTree is None:\n                with pytest.warns(UserWarning):\n                    assert im.getxmp() == {}\n            else:\n                xmp = im.getxmp()\n\n                description = xmp[\"xmpmeta\"][\"RDF\"][\"Description\"]\n                assert description[\"DerivedFrom\"] == {\n                    \"documentID\": \"8367D410E636EA95B7DE7EBA1C43A412\",\n                    \"originalDocumentID\": \"8367D410E636EA95B7DE7EBA1C43A412\",\n                }\n                assert description[\"Look\"][\"Description\"][\"Group\"][\"Alt\"][\"li\"] == {\n                    \"lang\": \"x-default\",\n                    \"text\": \"Profiles\",\n                }\n                assert description[\"ToneCurve\"][\"Seq\"][\"li\"] == [\"0, 0\", \"255, 255\"]\n\n                # Attribute\n                assert description[\"Version\"] == \"10.4\"\n\n        if ElementTree is not None:\n            with Image.open(\"Tests/images/hopper.jpg\") as im:\n                assert im.getxmp() == {}\n\n    @pytest.mark.timeout(timeout=1)\n    def test_eof(self):\n        # Even though this decoder never says that it is finished\n        # the image should still end when there is no new data\n        class InfiniteMockPyDecoder(ImageFile.PyDecoder):\n            def decode(self, buffer):\n                return 0, 0\n\n        decoder = InfiniteMockPyDecoder(None)\n\n        def closure(mode, *args):\n            decoder.__init__(mode, *args)\n            return decoder\n\n        Image.register_decoder(\"INFINITE\", closure)\n\n        with Image.open(TEST_FILE) as im:\n            im.tile = [\n                (\"INFINITE\", (0, 0, 128, 128), 0, (\"RGB\", 0, 1)),\n            ]\n            ImageFile.LOAD_TRUNCATED_IMAGES = True\n            im.load()\n            ImageFile.LOAD_TRUNCATED_IMAGES = False",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 41,
                            "end_lineno": 926,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "#\n# The Python Imaging Library.\n# $Id$\n#\n# JPEG (JFIF) file handling\n#\n# See \"Digital Compression and Coding of Continuous-Tone Still Images,\n# Part 1, Requirements and Guidelines\" (CCITT T.81 / ISO 10918-1)\n#\n# History:\n# 1995-09-09 fl   Created\n# 1995-09-13 fl   Added full parser\n# 1996-03-25 fl   Added hack to use the IJG command line utilities\n# 1996-05-05 fl   Workaround Photoshop 2.5 CMYK polarity bug\n# 1996-05-28 fl   Added draft support, JFIF version (0.1)\n# 1996-12-30 fl   Added encoder options, added progression property (0.2)\n# 1997-08-27 fl   Save mode 1 images as BW (0.3)\n# 1998-07-12 fl   Added YCbCr to draft and save methods (0.4)\n# 1998-10-19 fl   Don't hang on files using 16-bit DQT's (0.4.1)\n# 2001-04-16 fl   Extract DPI settings from JFIF files (0.4.2)\n# 2002-07-01 fl   Skip pad bytes before markers; identify Exif files (0.4.3)\n# 2003-04-25 fl   Added experimental EXIF decoder (0.5)\n# 2003-06-06 fl   Added experimental EXIF GPSinfo decoder\n# 2003-09-13 fl   Extract COM markers\n# 2009-09-06 fl   Added icc_profile support (from Florian Hoech)\n# 2009-03-06 fl   Changed CMYK handling; always use Adobe polarity (0.6)\n# 2009-03-08 fl   Added subsampling support (from Justin Huff).\n#\n# Copyright (c) 1997-2003 by Secret Labs AB.\n# Copyright (c) 1995-1996 by Fredrik Lundh.\n#\n# See the README file for information on usage and redistribution.\n#\nimport array\nimport io\nimport math\nimport os\nimport struct\nimport subprocess\nimport sys\nimport tempfile\nimport warnings\n\nfrom . import Image, ImageFile\nfrom ._binary import i16be as i16\nfrom ._binary import i32be as i32\nfrom ._binary import o8\nfrom ._binary import o16be as o16\nfrom ._deprecate import deprecate\nfrom .JpegPresets import presets\n\n#\n# Parser\n\n\ndef Skip(self, marker):\n    n = i16(self.fp.read(2)) - 2\n    ImageFile._safe_read(self.fp, n)\n\n\ndef APP(self, marker):\n    #\n    # Application marker.  Store these in the APP dictionary.\n    # Also look for well-known application markers.\n\n    n = i16(self.fp.read(2)) - 2\n    s = ImageFile._safe_read(self.fp, n)\n\n    app = \"APP%d\" % (marker & 15)\n\n    self.app[app] = s  # compatibility\n    self.applist.append((app, s))\n\n    if marker == 0xFFE0 and s[:4] == b\"JFIF\":\n        # extract JFIF information\n        self.info[\"jfif\"] = version = i16(s, 5)  # version\n        self.info[\"jfif_version\"] = divmod(version, 256)\n        # extract JFIF properties\n        try:\n            jfif_unit = s[7]\n            jfif_density = i16(s, 8), i16(s, 10)\n        except Exception:\n            pass\n        else:\n            if jfif_unit == 1:\n                self.info[\"dpi\"] = jfif_density\n            self.info[\"jfif_unit\"] = jfif_unit\n            self.info[\"jfif_density\"] = jfif_density\n    elif marker == 0xFFE1 and s[:5] == b\"Exif\\0\":\n        if \"exif\" not in self.info:\n            # extract EXIF information (incomplete)\n            self.info[\"exif\"] = s  # FIXME: value will change\n            self._exif_offset = self.fp.tell() - n + 6\n    elif marker == 0xFFE2 and s[:5] == b\"FPXR\\0\":\n        # extract FlashPix information (incomplete)\n        self.info[\"flashpix\"] = s  # FIXME: value will change\n    elif marker == 0xFFE2 and s[:12] == b\"ICC_PROFILE\\0\":\n        # Since an ICC profile can be larger than the maximum size of\n        # a JPEG marker (64K), we need provisions to split it into\n        # multiple markers. The format defined by the ICC specifies\n        # one or more APP2 markers containing the following data:\n        #   Identifying string      ASCII \"ICC_PROFILE\\0\"  (12 bytes)\n        #   Marker sequence number  1, 2, etc (1 byte)\n        #   Number of markers       Total of APP2's used (1 byte)\n        #   Profile data            (remainder of APP2 data)\n        # Decoders should use the marker sequence numbers to\n        # reassemble the profile, rather than assuming that the APP2\n        # markers appear in the correct sequence.\n        self.icclist.append(s)\n    elif marker == 0xFFED and s[:14] == b\"Photoshop 3.0\\x00\":\n        # parse the image resource block\n        offset = 14\n        photoshop = self.info.setdefault(\"photoshop\", {})\n        while s[offset : offset + 4] == b\"8BIM\":\n            try:\n                offset += 4\n                # resource code\n                code = i16(s, offset)\n                offset += 2\n                # resource name (usually empty)\n                name_len = s[offset]\n                # name = s[offset+1:offset+1+name_len]\n                offset += 1 + name_len\n                offset += offset & 1  # align\n                # resource data block\n                size = i32(s, offset)\n                offset += 4\n                data = s[offset : offset + size]\n                if code == 0x03ED:  # ResolutionInfo\n                    data = {\n                        \"XResolution\": i32(data, 0) / 65536,\n                        \"DisplayedUnitsX\": i16(data, 4),\n                        \"YResolution\": i32(data, 8) / 65536,\n                        \"DisplayedUnitsY\": i16(data, 12),\n                    }\n                photoshop[code] = data\n                offset += size\n                offset += offset & 1  # align\n            except struct.error:\n                break  # insufficient data\n\n    elif marker == 0xFFEE and s[:5] == b\"Adobe\":\n        self.info[\"adobe\"] = i16(s, 5)\n        # extract Adobe custom properties\n        try:\n            adobe_transform = s[11]\n        except IndexError:\n            pass\n        else:\n            self.info[\"adobe_transform\"] = adobe_transform\n    elif marker == 0xFFE2 and s[:4] == b\"MPF\\0\":\n        # extract MPO information\n        self.info[\"mp\"] = s[4:]\n        # offset is current location minus buffer size\n        # plus constant header size\n        self.info[\"mpoffset\"] = self.fp.tell() - n + 4\n\n    # If DPI isn't in JPEG header, fetch from EXIF\n    if \"dpi\" not in self.info and \"exif\" in self.info:\n        try:\n            exif = self.getexif()\n            resolution_unit = exif[0x0128]\n            x_resolution = exif[0x011A]\n            try:\n                dpi = float(x_resolution[0]) / x_resolution[1]\n            except TypeError:\n                dpi = x_resolution\n            if math.isnan(dpi):\n                raise ValueError\n            if resolution_unit == 3:  # cm\n                # 1 dpcm = 2.54 dpi\n                dpi *= 2.54\n            self.info[\"dpi\"] = dpi, dpi\n        except (TypeError, KeyError, SyntaxError, ValueError, ZeroDivisionError):\n            # SyntaxError for invalid/unreadable EXIF\n            # KeyError for dpi not included\n            # ZeroDivisionError for invalid dpi rational value\n            # ValueError or TypeError for dpi being an invalid float\n            self.info[\"dpi\"] = 72, 72\n\n\ndef COM(self, marker):\n    #\n    # Comment marker.  Store these in the APP dictionary.\n    n = i16(self.fp.read(2)) - 2\n    s = ImageFile._safe_read(self.fp, n)\n\n    self.info[\"comment\"] = s\n    self.app[\"COM\"] = s  # compatibility\n    self.applist.append((\"COM\", s))\n\n\ndef SOF(self, marker):\n    #\n    # Start of frame marker.  Defines the size and mode of the\n    # image.  JPEG is colour blind, so we use some simple\n    # heuristics to map the number of layers to an appropriate\n    # mode.  Note that this could be made a bit brighter, by\n    # looking for JFIF and Adobe APP markers.\n\n    n = i16(self.fp.read(2)) - 2\n    s = ImageFile._safe_read(self.fp, n)\n    self._size = i16(s, 3), i16(s, 1)\n\n    self.bits = s[0]\n    if self.bits != 8:\n        msg = f\"cannot handle {self.bits}-bit layers\"\n        raise SyntaxError(msg)\n\n    self.layers = s[5]\n    if self.layers == 1:\n        self.mode = \"L\"\n    elif self.layers == 3:\n        self.mode = \"RGB\"\n    elif self.layers == 4:\n        self.mode = \"CMYK\"\n    else:\n        msg = f\"cannot handle {self.layers}-layer images\"\n        raise SyntaxError(msg)\n\n    if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]:\n        self.info[\"progressive\"] = self.info[\"progression\"] = 1\n\n    if self.icclist:\n        # fixup icc profile\n        self.icclist.sort()  # sort by sequence number\n        if self.icclist[0][13] == len(self.icclist):\n            profile = []\n            for p in self.icclist:\n                profile.append(p[14:])\n            icc_profile = b\"\".join(profile)\n        else:\n            icc_profile = None  # wrong number of fragments\n        self.info[\"icc_profile\"] = icc_profile\n        self.icclist = []\n\n    for i in range(6, len(s), 3):\n        t = s[i : i + 3]\n        # 4-tuples: id, vsamp, hsamp, qtable\n        self.layer.append((t[0], t[1] // 16, t[1] & 15, t[2]))\n\n\ndef DQT(self, marker):\n    #\n    # Define quantization table.  Note that there might be more\n    # than one table in each marker.\n\n    # FIXME: The quantization tables can be used to estimate the\n    # compression quality.\n\n    n = i16(self.fp.read(2)) - 2\n    s = ImageFile._safe_read(self.fp, n)\n    while len(s):\n        v = s[0]\n        precision = 1 if (v // 16 == 0) else 2  # in bytes\n        qt_length = 1 + precision * 64\n        if len(s) < qt_length:\n            msg = \"bad quantization table marker\"\n            raise SyntaxError(msg)\n        data = array.array(\"B\" if precision == 1 else \"H\", s[1:qt_length])\n        if sys.byteorder == \"little\" and precision > 1:\n            data.byteswap()  # the values are always big-endian\n        self.quantization[v & 15] = [data[i] for i in zigzag_index]\n        s = s[qt_length:]\n\n\n#\n# JPEG marker table\n\nMARKER = {\n    0xFFC0: (\"SOF0\", \"Baseline DCT\", SOF),\n    0xFFC1: (\"SOF1\", \"Extended Sequential DCT\", SOF),\n    0xFFC2: (\"SOF2\", \"Progressive DCT\", SOF),\n    0xFFC3: (\"SOF3\", \"Spatial lossless\", SOF),\n    0xFFC4: (\"DHT\", \"Define Huffman table\", Skip),\n    0xFFC5: (\"SOF5\", \"Differential sequential DCT\", SOF),\n    0xFFC6: (\"SOF6\", \"Differential progressive DCT\", SOF),\n    0xFFC7: (\"SOF7\", \"Differential spatial\", SOF),\n    0xFFC8: (\"JPG\", \"Extension\", None),\n    0xFFC9: (\"SOF9\", \"Extended sequential DCT (AC)\", SOF),\n    0xFFCA: (\"SOF10\", \"Progressive DCT (AC)\", SOF),\n    0xFFCB: (\"SOF11\", \"Spatial lossless DCT (AC)\", SOF),\n    0xFFCC: (\"DAC\", \"Define arithmetic coding conditioning\", Skip),\n    0xFFCD: (\"SOF13\", \"Differential sequential DCT (AC)\", SOF),\n    0xFFCE: (\"SOF14\", \"Differential progressive DCT (AC)\", SOF),\n    0xFFCF: (\"SOF15\", \"Differential spatial (AC)\", SOF),\n    0xFFD0: (\"RST0\", \"Restart 0\", None),\n    0xFFD1: (\"RST1\", \"Restart 1\", None),\n    0xFFD2: (\"RST2\", \"Restart 2\", None),\n    0xFFD3: (\"RST3\", \"Restart 3\", None),\n    0xFFD4: (\"RST4\", \"Restart 4\", None),\n    0xFFD5: (\"RST5\", \"Restart 5\", None),\n    0xFFD6: (\"RST6\", \"Restart 6\", None),\n    0xFFD7: (\"RST7\", \"Restart 7\", None),\n    0xFFD8: (\"SOI\", \"Start of image\", None),\n    0xFFD9: (\"EOI\", \"End of image\", None),\n    0xFFDA: (\"SOS\", \"Start of scan\", Skip),\n    0xFFDB: (\"DQT\", \"Define quantization table\", DQT),\n    0xFFDC: (\"DNL\", \"Define number of lines\", Skip),\n    0xFFDD: (\"DRI\", \"Define restart interval\", Skip),\n    0xFFDE: (\"DHP\", \"Define hierarchical progression\", SOF),\n    0xFFDF: (\"EXP\", \"Expand reference component\", Skip),\n    0xFFE0: (\"APP0\", \"Application segment 0\", APP),\n    0xFFE1: (\"APP1\", \"Application segment 1\", APP),\n    0xFFE2: (\"APP2\", \"Application segment 2\", APP),\n    0xFFE3: (\"APP3\", \"Application segment 3\", APP),\n    0xFFE4: (\"APP4\", \"Application segment 4\", APP),\n    0xFFE5: (\"APP5\", \"Application segment 5\", APP),\n    0xFFE6: (\"APP6\", \"Application segment 6\", APP),\n    0xFFE7: (\"APP7\", \"Application segment 7\", APP),\n    0xFFE8: (\"APP8\", \"Application segment 8\", APP),\n    0xFFE9: (\"APP9\", \"Application segment 9\", APP),\n    0xFFEA: (\"APP10\", \"Application segment 10\", APP),\n    0xFFEB: (\"APP11\", \"Application segment 11\", APP),\n    0xFFEC: (\"APP12\", \"Application segment 12\", APP),\n    0xFFED: (\"APP13\", \"Application segment 13\", APP),\n    0xFFEE: (\"APP14\", \"Application segment 14\", APP),\n    0xFFEF: (\"APP15\", \"Application segment 15\", APP),\n    0xFFF0: (\"JPG0\", \"Extension 0\", None),\n    0xFFF1: (\"JPG1\", \"Extension 1\", None),\n    0xFFF2: (\"JPG2\", \"Extension 2\", None),\n    0xFFF3: (\"JPG3\", \"Extension 3\", None),\n    0xFFF4: (\"JPG4\", \"Extension 4\", None),\n    0xFFF5: (\"JPG5\", \"Extension 5\", None),\n    0xFFF6: (\"JPG6\", \"Extension 6\", None),\n    0xFFF7: (\"JPG7\", \"Extension 7\", None),\n    0xFFF8: (\"JPG8\", \"Extension 8\", None),\n    0xFFF9: (\"JPG9\", \"Extension 9\", None),\n    0xFFFA: (\"JPG10\", \"Extension 10\", None),\n    0xFFFB: (\"JPG11\", \"Extension 11\", None),\n    0xFFFC: (\"JPG12\", \"Extension 12\", None),\n    0xFFFD: (\"JPG13\", \"Extension 13\", None),\n    0xFFFE: (\"COM\", \"Comment\", COM),\n}\n\n\ndef _accept(prefix):\n    # Magic number was taken from https://en.wikipedia.org/wiki/JPEG\n    return prefix[:3] == b\"\\xFF\\xD8\\xFF\"\n\n\n##\n# Image plugin for JPEG and JFIF images.\n\n\nclass JpegImageFile(ImageFile.ImageFile):\n    format = \"JPEG\"\n    format_description = \"JPEG (ISO 10918)\"\n\n    def _open(self):\n        s = self.fp.read(3)\n\n        if not _accept(s):\n            msg = \"not a JPEG file\"\n            raise SyntaxError(msg)\n        s = b\"\\xFF\"\n\n        # Create attributes\n        self.bits = self.layers = 0\n\n        # JPEG specifics (internal)\n        self.layer = []\n        self.huffman_dc = {}\n        self.huffman_ac = {}\n        self.quantization = {}\n        self.app = {}  # compatibility\n        self.applist = []\n        self.icclist = []\n\n        while True:\n            i = s[0]\n            if i == 0xFF:\n                s = s + self.fp.read(1)\n                i = i16(s)\n            else:\n                # Skip non-0xFF junk\n                s = self.fp.read(1)\n                continue\n\n            if i in MARKER:\n                name, description, handler = MARKER[i]\n                if handler is not None:\n                    handler(self, i)\n                if i == 0xFFDA:  # start of scan\n                    rawmode = self.mode\n                    if self.mode == \"CMYK\":\n                        rawmode = \"CMYK;I\"  # assume adobe conventions\n                    self.tile = [(\"jpeg\", (0, 0) + self.size, 0, (rawmode, \"\"))]\n                    # self.__offset = self.fp.tell()\n                    break\n                s = self.fp.read(1)\n            elif i == 0 or i == 0xFFFF:\n                # padded marker or junk; move on\n                s = b\"\\xff\"\n            elif i == 0xFF00:  # Skip extraneous data (escaped 0xFF)\n                s = self.fp.read(1)\n            else:\n                msg = \"no marker found\"\n                raise SyntaxError(msg)\n\n    def load_read(self, read_bytes):\n        \"\"\"\n        internal: read more image data\n        For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker\n        so libjpeg can finish decoding\n        \"\"\"\n        s = self.fp.read(read_bytes)\n\n        if not s and ImageFile.LOAD_TRUNCATED_IMAGES and not hasattr(self, \"_ended\"):\n            # Premature EOF.\n            # Pretend file is finished adding EOI marker\n            self._ended = True\n            return b\"\\xFF\\xD9\"\n\n        return s\n\n    def draft(self, mode, size):\n        if len(self.tile) != 1:\n            return\n\n        # Protect from second call\n        if self.decoderconfig:\n            return\n\n        d, e, o, a = self.tile[0]\n        scale = 1\n        original_size = self.size\n\n        if a[0] == \"RGB\" and mode in [\"L\", \"YCbCr\"]:\n            self.mode = mode\n            a = mode, \"\"\n\n        if size:\n            scale = min(self.size[0] // size[0], self.size[1] // size[1])\n            for s in [8, 4, 2, 1]:\n                if scale >= s:\n                    break\n            e = (\n                e[0],\n                e[1],\n                (e[2] - e[0] + s - 1) // s + e[0],\n                (e[3] - e[1] + s - 1) // s + e[1],\n            )\n            self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s)\n            scale = s\n\n        self.tile = [(d, e, o, a)]\n        self.decoderconfig = (scale, 0)\n\n        box = (0, 0, original_size[0] / scale, original_size[1] / scale)\n        return self.mode, box\n\n    def load_djpeg(self):\n        # ALTERNATIVE: handle JPEGs via the IJG command line utilities\n\n        f, path = tempfile.mkstemp()\n        os.close(f)\n        if os.path.exists(self.filename):\n            subprocess.check_call([\"djpeg\", \"-outfile\", path, self.filename])\n        else:\n            msg = \"Invalid Filename\"\n            raise ValueError(msg)\n\n        try:\n            with Image.open(path) as _im:\n                _im.load()\n                self.im = _im.im\n        finally:\n            try:\n                os.unlink(path)\n            except OSError:\n                pass\n\n        self.mode = self.im.mode\n        self._size = self.im.size\n\n        self.tile = []\n\n    def _getexif(self):\n        return _getexif(self)\n\n    def _getmp(self):\n        return _getmp(self)\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n\n        for segment, content in self.applist:\n            if segment == \"APP1\":\n                marker, xmp_tags = content.rsplit(b\"\\x00\", 1)\n                if marker == b\"http://ns.adobe.com/xap/1.0/\":\n                    return self._getxmp(xmp_tags)\n        return {}\n\n\ndef _getexif(self):\n    if \"exif\" not in self.info:\n        return None\n    return self.getexif()._get_merged_dict()\n\n\ndef _getmp(self):\n    # Extract MP information.  This method was inspired by the \"highly\n    # experimental\" _getexif version that's been in use for years now,\n    # itself based on the ImageFileDirectory class in the TIFF plugin.\n\n    # The MP record essentially consists of a TIFF file embedded in a JPEG\n    # application marker.\n    try:\n        data = self.info[\"mp\"]\n    except KeyError:\n        return None\n    file_contents = io.BytesIO(data)\n    head = file_contents.read(8)\n    endianness = \">\" if head[:4] == b\"\\x4d\\x4d\\x00\\x2a\" else \"<\"\n    # process dictionary\n    from . import TiffImagePlugin\n\n    try:\n        info = TiffImagePlugin.ImageFileDirectory_v2(head)\n        file_contents.seek(info.next)\n        info.load(file_contents)\n        mp = dict(info)\n    except Exception as e:\n        msg = \"malformed MP Index (unreadable directory)\"\n        raise SyntaxError(msg) from e\n    # it's an error not to have a number of images\n    try:\n        quant = mp[0xB001]\n    except KeyError as e:\n        msg = \"malformed MP Index (no number of images)\"\n        raise SyntaxError(msg) from e\n    # get MP entries\n    mpentries = []\n    try:\n        rawmpentries = mp[0xB002]\n        for entrynum in range(0, quant):\n            unpackedentry = struct.unpack_from(\n                f\"{endianness}LLLHH\", rawmpentries, entrynum * 16\n            )\n            labels = (\"Attribute\", \"Size\", \"DataOffset\", \"EntryNo1\", \"EntryNo2\")\n            mpentry = dict(zip(labels, unpackedentry))\n            mpentryattr = {\n                \"DependentParentImageFlag\": bool(mpentry[\"Attribute\"] & (1 << 31)),\n                \"DependentChildImageFlag\": bool(mpentry[\"Attribute\"] & (1 << 30)),\n                \"RepresentativeImageFlag\": bool(mpentry[\"Attribute\"] & (1 << 29)),\n                \"Reserved\": (mpentry[\"Attribute\"] & (3 << 27)) >> 27,\n                \"ImageDataFormat\": (mpentry[\"Attribute\"] & (7 << 24)) >> 24,\n                \"MPType\": mpentry[\"Attribute\"] & 0x00FFFFFF,\n            }\n            if mpentryattr[\"ImageDataFormat\"] == 0:\n                mpentryattr[\"ImageDataFormat\"] = \"JPEG\"\n            else:\n                msg = \"unsupported picture format in MPO\"\n                raise SyntaxError(msg)\n            mptypemap = {\n                0x000000: \"Undefined\",\n                0x010001: \"Large Thumbnail (VGA Equivalent)\",\n                0x010002: \"Large Thumbnail (Full HD Equivalent)\",\n                0x020001: \"Multi-Frame Image (Panorama)\",\n                0x020002: \"Multi-Frame Image: (Disparity)\",\n                0x020003: \"Multi-Frame Image: (Multi-Angle)\",\n                0x030000: \"Baseline MP Primary Image\",\n            }\n            mpentryattr[\"MPType\"] = mptypemap.get(mpentryattr[\"MPType\"], \"Unknown\")\n            mpentry[\"Attribute\"] = mpentryattr\n            mpentries.append(mpentry)\n        mp[0xB002] = mpentries\n    except KeyError as e:\n        msg = \"malformed MP Index (bad MP Entry)\"\n        raise SyntaxError(msg) from e\n    # Next we should try and parse the individual image unique ID list;\n    # we don't because I've never seen this actually used in a real MPO\n    # file and so can't test it.\n    return mp\n\n\n# --------------------------------------------------------------------\n# stuff to save JPEG files\n\nRAWMODE = {\n    \"1\": \"L\",\n    \"L\": \"L\",\n    \"RGB\": \"RGB\",\n    \"RGBX\": \"RGB\",\n    \"CMYK\": \"CMYK;I\",  # assume adobe conventions\n    \"YCbCr\": \"YCbCr\",\n}\n\n# fmt: off\nzigzag_index = (\n    0,  1,  5,  6, 14, 15, 27, 28,\n    2,  4,  7, 13, 16, 26, 29, 42,\n    3,  8, 12, 17, 25, 30, 41, 43,\n    9, 11, 18, 24, 31, 40, 44, 53,\n    10, 19, 23, 32, 39, 45, 52, 54,\n    20, 22, 33, 38, 46, 51, 55, 60,\n    21, 34, 37, 47, 50, 56, 59, 61,\n    35, 36, 48, 49, 57, 58, 62, 63,\n)\n\nsamplings = {\n    (1, 1, 1, 1, 1, 1): 0,\n    (2, 1, 1, 1, 1, 1): 1,\n    (2, 2, 1, 1, 1, 1): 2,\n}\n# fmt: on\n\n\ndef convert_dict_qtables(qtables):\n    deprecate(\"convert_dict_qtables\", 10, action=\"Conversion is no longer needed\")\n    return qtables\n\n\ndef get_sampling(im):\n    # There's no subsampling when images have only 1 layer\n    # (grayscale images) or when they are CMYK (4 layers),\n    # so set subsampling to the default value.\n    #\n    # NOTE: currently Pillow can't encode JPEG to YCCK format.\n    # If YCCK support is added in the future, subsampling code will have\n    # to be updated (here and in JpegEncode.c) to deal with 4 layers.\n    if not hasattr(im, \"layers\") or im.layers in (1, 4):\n        return -1\n    sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]\n    return samplings.get(sampling, -1)\n\n\ndef _save(im, fp, filename):\n    if im.width == 0 or im.height == 0:\n        msg = \"cannot write empty image as JPEG\"\n        raise ValueError(msg)\n\n    try:\n        rawmode = RAWMODE[im.mode]\n    except KeyError as e:\n        msg = f\"cannot write mode {im.mode} as JPEG\"\n        raise OSError(msg) from e\n\n    info = im.encoderinfo\n\n    dpi = [round(x) for x in info.get(\"dpi\", (0, 0))]\n\n    quality = info.get(\"quality\", -1)\n    subsampling = info.get(\"subsampling\", -1)\n    qtables = info.get(\"qtables\")\n\n    if quality == \"keep\":\n        quality = -1\n        subsampling = \"keep\"\n        qtables = \"keep\"\n    elif quality in presets:\n        preset = presets[quality]\n        quality = -1\n        subsampling = preset.get(\"subsampling\", -1)\n        qtables = preset.get(\"quantization\")\n    elif not isinstance(quality, int):\n        msg = \"Invalid quality setting\"\n        raise ValueError(msg)\n    else:\n        if subsampling in presets:\n            subsampling = presets[subsampling].get(\"subsampling\", -1)\n        if isinstance(qtables, str) and qtables in presets:\n            qtables = presets[qtables].get(\"quantization\")\n\n    if subsampling == \"4:4:4\":\n        subsampling = 0\n    elif subsampling == \"4:2:2\":\n        subsampling = 1\n    elif subsampling == \"4:2:0\":\n        subsampling = 2\n    elif subsampling == \"4:1:1\":\n        # For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0.\n        # Set 4:2:0 if someone is still using that value.\n        subsampling = 2\n    elif subsampling == \"keep\":\n        if im.format != \"JPEG\":\n            msg = \"Cannot use 'keep' when original image is not a JPEG\"\n            raise ValueError(msg)\n        subsampling = get_sampling(im)\n\n    def validate_qtables(qtables):\n        if qtables is None:\n            return qtables\n        if isinstance(qtables, str):\n            try:\n                lines = [\n                    int(num)\n                    for line in qtables.splitlines()\n                    for num in line.split(\"#\", 1)[0].split()\n                ]\n            except ValueError as e:\n                msg = \"Invalid quantization table\"\n                raise ValueError(msg) from e\n            else:\n                qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)]\n        if isinstance(qtables, (tuple, list, dict)):\n            if isinstance(qtables, dict):\n                qtables = [\n                    qtables[key] for key in range(len(qtables)) if key in qtables\n                ]\n            elif isinstance(qtables, tuple):\n                qtables = list(qtables)\n            if not (0 < len(qtables) < 5):\n                msg = \"None or too many quantization tables\"\n                raise ValueError(msg)\n            for idx, table in enumerate(qtables):\n                try:\n                    if len(table) != 64:\n                        raise TypeError\n                    table = array.array(\"H\", table)\n                except TypeError as e:\n                    msg = \"Invalid quantization table\"\n                    raise ValueError(msg) from e\n                else:\n                    qtables[idx] = list(table)\n            return qtables\n\n    if qtables == \"keep\":\n        if im.format != \"JPEG\":\n            msg = \"Cannot use 'keep' when original image is not a JPEG\"\n            raise ValueError(msg)\n        qtables = getattr(im, \"quantization\", None)\n    qtables = validate_qtables(qtables)\n\n    extra = info.get(\"extra\", b\"\")\n\n    icc_profile = info.get(\"icc_profile\")\n    if icc_profile:\n        ICC_OVERHEAD_LEN = 14\n        MAX_BYTES_IN_MARKER = 65533\n        MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN\n        markers = []\n        while icc_profile:\n            markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER])\n            icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:]\n        i = 1\n        for marker in markers:\n            size = o16(2 + ICC_OVERHEAD_LEN + len(marker))\n            extra += (\n                b\"\\xFF\\xE2\"\n                + size\n                + b\"ICC_PROFILE\\0\"\n                + o8(i)\n                + o8(len(markers))\n                + marker\n            )\n            i += 1\n\n    comment = info.get(\"comment\", im.info.get(\"comment\"))\n\n    # \"progressive\" is the official name, but older documentation\n    # says \"progression\"\n    # FIXME: issue a warning if the wrong form is used (post-1.1.7)\n    progressive = info.get(\"progressive\", False) or info.get(\"progression\", False)\n\n    optimize = info.get(\"optimize\", False)\n\n    exif = info.get(\"exif\", b\"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n\n    # get keyword arguments\n    im.encoderconfig = (\n        quality,\n        progressive,\n        info.get(\"smooth\", 0),\n        optimize,\n        info.get(\"streamtype\", 0),\n        dpi[0],\n        dpi[1],\n        subsampling,\n        qtables,\n        comment,\n        extra,\n        exif,\n    )\n\n    # if we optimize, libjpeg needs a buffer big enough to hold the whole image\n    # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is\n    # channels*size, this is a value that's been used in a django patch.\n    # https://github.com/matthewwithanm/django-imagekit/issues/50\n    bufsize = 0\n    if optimize or progressive:\n        # CMYK can be bigger\n        if im.mode == \"CMYK\":\n            bufsize = 4 * im.size[0] * im.size[1]\n        # keep sets quality to -1, but the actual value may be high.\n        elif quality >= 95 or quality == -1:\n            bufsize = 2 * im.size[0] * im.size[1]\n        else:\n            bufsize = im.size[0] * im.size[1]\n\n    # The EXIF info needs to be written as one block, + APP1, + one spare byte.\n    # Ensure that our buffer is big enough. Same with the icc_profile block.\n    bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, len(extra) + 1)\n\n    ImageFile._save(im, fp, [(\"jpeg\", (0, 0) + im.size, 0, rawmode)], bufsize)\n\n\ndef _save_cjpeg(im, fp, filename):\n    # ALTERNATIVE: handle JPEGs via the IJG command line utilities.\n    tempfile = im._dump()\n    subprocess.check_call([\"cjpeg\", \"-outfile\", filename, tempfile])\n    try:\n        os.unlink(tempfile)\n    except OSError:\n        pass\n\n\n##\n# Factory for making JPEG and MPO instances\ndef jpeg_factory(fp=None, filename=None):\n    im = JpegImageFile(fp, filename)\n    try:\n        mpheader = im._getmp()\n        if mpheader[45057] > 1:\n            # It's actually an MPO\n            from .MpoImagePlugin import MpoImageFile\n\n            # Don't reload everything, just convert it.\n            im = MpoImageFile.adopt(im, mpheader)\n    except (TypeError, IndexError):\n        # It is really a JPEG\n        pass\n    except SyntaxError:\n        warnings.warn(\n            \"Image appears to be a malformed MPO file, it will be \"\n            \"interpreted as a base JPEG file\"\n        )\n    return im\n\n\n# ---------------------------------------------------------------------\n# Registry stuff\n\nImage.register_open(JpegImageFile.format, jpeg_factory, _accept)\nImage.register_save(JpegImageFile.format, _save)\n\nImage.register_extensions(JpegImageFile.format, [\".jfif\", \".jpe\", \".jpg\", \".jpeg\"])\n\nImage.register_mime(JpegImageFile.format, \"image/jpeg\")\n",
            "new_file_content": "#\n# The Python Imaging Library.\n# $Id$\n#\n# JPEG (JFIF) file handling\n#\n# See \"Digital Compression and Coding of Continuous-Tone Still Images,\n# Part 1, Requirements and Guidelines\" (CCITT T.81 / ISO 10918-1)\n#\n# History:\n# 1995-09-09 fl   Created\n# 1995-09-13 fl   Added full parser\n# 1996-03-25 fl   Added hack to use the IJG command line utilities\n# 1996-05-05 fl   Workaround Photoshop 2.5 CMYK polarity bug\n# 1996-05-28 fl   Added draft support, JFIF version (0.1)\n# 1996-12-30 fl   Added encoder options, added progression property (0.2)\n# 1997-08-27 fl   Save mode 1 images as BW (0.3)\n# 1998-07-12 fl   Added YCbCr to draft and save methods (0.4)\n# 1998-10-19 fl   Don't hang on files using 16-bit DQT's (0.4.1)\n# 2001-04-16 fl   Extract DPI settings from JFIF files (0.4.2)\n# 2002-07-01 fl   Skip pad bytes before markers; identify Exif files (0.4.3)\n# 2003-04-25 fl   Added experimental EXIF decoder (0.5)\n# 2003-06-06 fl   Added experimental EXIF GPSinfo decoder\n# 2003-09-13 fl   Extract COM markers\n# 2009-09-06 fl   Added icc_profile support (from Florian Hoech)\n# 2009-03-06 fl   Changed CMYK handling; always use Adobe polarity (0.6)\n# 2009-03-08 fl   Added subsampling support (from Justin Huff).\n#\n# Copyright (c) 1997-2003 by Secret Labs AB.\n# Copyright (c) 1995-1996 by Fredrik Lundh.\n#\n# See the README file for information on usage and redistribution.\n#\nimport array\nimport io\nimport math\nimport os\nimport struct\nimport subprocess\nimport sys\nimport tempfile\nimport warnings\n\nfrom . import Image, ImageFile\nfrom ._binary import i16be as i16\nfrom ._binary import i32be as i32\nfrom ._binary import o8\nfrom ._binary import o16be as o16\nfrom ._deprecate import deprecate\nfrom .JpegPresets import presets\n\n#\n# Parser\n\n\ndef Skip(self, marker):\n    n = i16(self.fp.read(2)) - 2\n    ImageFile._safe_read(self.fp, n)\n\n\ndef APP(self, marker):\n    #\n    # Application marker.  Store these in the APP dictionary.\n    # Also look for well-known application markers.\n\n    n = i16(self.fp.read(2)) - 2\n    s = ImageFile._safe_read(self.fp, n)\n\n    app = \"APP%d\" % (marker & 15)\n\n    self.app[app] = s  # compatibility\n    self.applist.append((app, s))\n\n    if marker == 0xFFE0 and s[:4] == b\"JFIF\":\n        # extract JFIF information\n        self.info[\"jfif\"] = version = i16(s, 5)  # version\n        self.info[\"jfif_version\"] = divmod(version, 256)\n        # extract JFIF properties\n        try:\n            jfif_unit = s[7]\n            jfif_density = i16(s, 8), i16(s, 10)\n        except Exception:\n            pass\n        else:\n            if jfif_unit == 1:\n                self.info[\"dpi\"] = jfif_density\n            self.info[\"jfif_unit\"] = jfif_unit\n            self.info[\"jfif_density\"] = jfif_density\n    elif marker == 0xFFE1 and s[:5] == b\"Exif\\0\":\n        if \"exif\" not in self.info:\n            # extract EXIF information (incomplete)\n            self.info[\"exif\"] = s  # FIXME: value will change\n            self._exif_offset = self.fp.tell() - n + 6\n    elif marker == 0xFFE2 and s[:5] == b\"FPXR\\0\":\n        # extract FlashPix information (incomplete)\n        self.info[\"flashpix\"] = s  # FIXME: value will change\n    elif marker == 0xFFE2 and s[:12] == b\"ICC_PROFILE\\0\":\n        # Since an ICC profile can be larger than the maximum size of\n        # a JPEG marker (64K), we need provisions to split it into\n        # multiple markers. The format defined by the ICC specifies\n        # one or more APP2 markers containing the following data:\n        #   Identifying string      ASCII \"ICC_PROFILE\\0\"  (12 bytes)\n        #   Marker sequence number  1, 2, etc (1 byte)\n        #   Number of markers       Total of APP2's used (1 byte)\n        #   Profile data            (remainder of APP2 data)\n        # Decoders should use the marker sequence numbers to\n        # reassemble the profile, rather than assuming that the APP2\n        # markers appear in the correct sequence.\n        self.icclist.append(s)\n    elif marker == 0xFFED and s[:14] == b\"Photoshop 3.0\\x00\":\n        # parse the image resource block\n        offset = 14\n        photoshop = self.info.setdefault(\"photoshop\", {})\n        while s[offset : offset + 4] == b\"8BIM\":\n            try:\n                offset += 4\n                # resource code\n                code = i16(s, offset)\n                offset += 2\n                # resource name (usually empty)\n                name_len = s[offset]\n                # name = s[offset+1:offset+1+name_len]\n                offset += 1 + name_len\n                offset += offset & 1  # align\n                # resource data block\n                size = i32(s, offset)\n                offset += 4\n                data = s[offset : offset + size]\n                if code == 0x03ED:  # ResolutionInfo\n                    data = {\n                        \"XResolution\": i32(data, 0) / 65536,\n                        \"DisplayedUnitsX\": i16(data, 4),\n                        \"YResolution\": i32(data, 8) / 65536,\n                        \"DisplayedUnitsY\": i16(data, 12),\n                    }\n                photoshop[code] = data\n                offset += size\n                offset += offset & 1  # align\n            except struct.error:\n                break  # insufficient data\n\n    elif marker == 0xFFEE and s[:5] == b\"Adobe\":\n        self.info[\"adobe\"] = i16(s, 5)\n        # extract Adobe custom properties\n        try:\n            adobe_transform = s[11]\n        except IndexError:\n            pass\n        else:\n            self.info[\"adobe_transform\"] = adobe_transform\n    elif marker == 0xFFE2 and s[:4] == b\"MPF\\0\":\n        # extract MPO information\n        self.info[\"mp\"] = s[4:]\n        # offset is current location minus buffer size\n        # plus constant header size\n        self.info[\"mpoffset\"] = self.fp.tell() - n + 4\n\n    # If DPI isn't in JPEG header, fetch from EXIF\n    if \"dpi\" not in self.info and \"exif\" in self.info:\n        try:\n            exif = self.getexif()\n            resolution_unit = exif[0x0128]\n            x_resolution = exif[0x011A]\n            try:\n                dpi = float(x_resolution[0]) / x_resolution[1]\n            except TypeError:\n                dpi = x_resolution\n            if math.isnan(dpi):\n                raise ValueError\n            if resolution_unit == 3:  # cm\n                # 1 dpcm = 2.54 dpi\n                dpi *= 2.54\n            self.info[\"dpi\"] = dpi, dpi\n        except (TypeError, KeyError, SyntaxError, ValueError, ZeroDivisionError):\n            # SyntaxError for invalid/unreadable EXIF\n            # KeyError for dpi not included\n            # ZeroDivisionError for invalid dpi rational value\n            # ValueError or TypeError for dpi being an invalid float\n            self.info[\"dpi\"] = 72, 72\n\n\ndef COM(self, marker):\n    #\n    # Comment marker.  Store these in the APP dictionary.\n    n = i16(self.fp.read(2)) - 2\n    s = ImageFile._safe_read(self.fp, n)\n\n    self.info[\"comment\"] = s\n    self.app[\"COM\"] = s  # compatibility\n    self.applist.append((\"COM\", s))\n\n\ndef SOF(self, marker):\n    #\n    # Start of frame marker.  Defines the size and mode of the\n    # image.  JPEG is colour blind, so we use some simple\n    # heuristics to map the number of layers to an appropriate\n    # mode.  Note that this could be made a bit brighter, by\n    # looking for JFIF and Adobe APP markers.\n\n    n = i16(self.fp.read(2)) - 2\n    s = ImageFile._safe_read(self.fp, n)\n    self._size = i16(s, 3), i16(s, 1)\n\n    self.bits = s[0]\n    if self.bits != 8:\n        msg = f\"cannot handle {self.bits}-bit layers\"\n        raise SyntaxError(msg)\n\n    self.layers = s[5]\n    if self.layers == 1:\n        self.mode = \"L\"\n    elif self.layers == 3:\n        self.mode = \"RGB\"\n    elif self.layers == 4:\n        self.mode = \"CMYK\"\n    else:\n        msg = f\"cannot handle {self.layers}-layer images\"\n        raise SyntaxError(msg)\n\n    if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]:\n        self.info[\"progressive\"] = self.info[\"progression\"] = 1\n\n    if self.icclist:\n        # fixup icc profile\n        self.icclist.sort()  # sort by sequence number\n        if self.icclist[0][13] == len(self.icclist):\n            profile = []\n            for p in self.icclist:\n                profile.append(p[14:])\n            icc_profile = b\"\".join(profile)\n        else:\n            icc_profile = None  # wrong number of fragments\n        self.info[\"icc_profile\"] = icc_profile\n        self.icclist = []\n\n    for i in range(6, len(s), 3):\n        t = s[i : i + 3]\n        # 4-tuples: id, vsamp, hsamp, qtable\n        self.layer.append((t[0], t[1] // 16, t[1] & 15, t[2]))\n\n\ndef DQT(self, marker):\n    #\n    # Define quantization table.  Note that there might be more\n    # than one table in each marker.\n\n    # FIXME: The quantization tables can be used to estimate the\n    # compression quality.\n\n    n = i16(self.fp.read(2)) - 2\n    s = ImageFile._safe_read(self.fp, n)\n    while len(s):\n        v = s[0]\n        precision = 1 if (v // 16 == 0) else 2  # in bytes\n        qt_length = 1 + precision * 64\n        if len(s) < qt_length:\n            msg = \"bad quantization table marker\"\n            raise SyntaxError(msg)\n        data = array.array(\"B\" if precision == 1 else \"H\", s[1:qt_length])\n        if sys.byteorder == \"little\" and precision > 1:\n            data.byteswap()  # the values are always big-endian\n        self.quantization[v & 15] = [data[i] for i in zigzag_index]\n        s = s[qt_length:]\n\n\n#\n# JPEG marker table\n\nMARKER = {\n    0xFFC0: (\"SOF0\", \"Baseline DCT\", SOF),\n    0xFFC1: (\"SOF1\", \"Extended Sequential DCT\", SOF),\n    0xFFC2: (\"SOF2\", \"Progressive DCT\", SOF),\n    0xFFC3: (\"SOF3\", \"Spatial lossless\", SOF),\n    0xFFC4: (\"DHT\", \"Define Huffman table\", Skip),\n    0xFFC5: (\"SOF5\", \"Differential sequential DCT\", SOF),\n    0xFFC6: (\"SOF6\", \"Differential progressive DCT\", SOF),\n    0xFFC7: (\"SOF7\", \"Differential spatial\", SOF),\n    0xFFC8: (\"JPG\", \"Extension\", None),\n    0xFFC9: (\"SOF9\", \"Extended sequential DCT (AC)\", SOF),\n    0xFFCA: (\"SOF10\", \"Progressive DCT (AC)\", SOF),\n    0xFFCB: (\"SOF11\", \"Spatial lossless DCT (AC)\", SOF),\n    0xFFCC: (\"DAC\", \"Define arithmetic coding conditioning\", Skip),\n    0xFFCD: (\"SOF13\", \"Differential sequential DCT (AC)\", SOF),\n    0xFFCE: (\"SOF14\", \"Differential progressive DCT (AC)\", SOF),\n    0xFFCF: (\"SOF15\", \"Differential spatial (AC)\", SOF),\n    0xFFD0: (\"RST0\", \"Restart 0\", None),\n    0xFFD1: (\"RST1\", \"Restart 1\", None),\n    0xFFD2: (\"RST2\", \"Restart 2\", None),\n    0xFFD3: (\"RST3\", \"Restart 3\", None),\n    0xFFD4: (\"RST4\", \"Restart 4\", None),\n    0xFFD5: (\"RST5\", \"Restart 5\", None),\n    0xFFD6: (\"RST6\", \"Restart 6\", None),\n    0xFFD7: (\"RST7\", \"Restart 7\", None),\n    0xFFD8: (\"SOI\", \"Start of image\", None),\n    0xFFD9: (\"EOI\", \"End of image\", None),\n    0xFFDA: (\"SOS\", \"Start of scan\", Skip),\n    0xFFDB: (\"DQT\", \"Define quantization table\", DQT),\n    0xFFDC: (\"DNL\", \"Define number of lines\", Skip),\n    0xFFDD: (\"DRI\", \"Define restart interval\", Skip),\n    0xFFDE: (\"DHP\", \"Define hierarchical progression\", SOF),\n    0xFFDF: (\"EXP\", \"Expand reference component\", Skip),\n    0xFFE0: (\"APP0\", \"Application segment 0\", APP),\n    0xFFE1: (\"APP1\", \"Application segment 1\", APP),\n    0xFFE2: (\"APP2\", \"Application segment 2\", APP),\n    0xFFE3: (\"APP3\", \"Application segment 3\", APP),\n    0xFFE4: (\"APP4\", \"Application segment 4\", APP),\n    0xFFE5: (\"APP5\", \"Application segment 5\", APP),\n    0xFFE6: (\"APP6\", \"Application segment 6\", APP),\n    0xFFE7: (\"APP7\", \"Application segment 7\", APP),\n    0xFFE8: (\"APP8\", \"Application segment 8\", APP),\n    0xFFE9: (\"APP9\", \"Application segment 9\", APP),\n    0xFFEA: (\"APP10\", \"Application segment 10\", APP),\n    0xFFEB: (\"APP11\", \"Application segment 11\", APP),\n    0xFFEC: (\"APP12\", \"Application segment 12\", APP),\n    0xFFED: (\"APP13\", \"Application segment 13\", APP),\n    0xFFEE: (\"APP14\", \"Application segment 14\", APP),\n    0xFFEF: (\"APP15\", \"Application segment 15\", APP),\n    0xFFF0: (\"JPG0\", \"Extension 0\", None),\n    0xFFF1: (\"JPG1\", \"Extension 1\", None),\n    0xFFF2: (\"JPG2\", \"Extension 2\", None),\n    0xFFF3: (\"JPG3\", \"Extension 3\", None),\n    0xFFF4: (\"JPG4\", \"Extension 4\", None),\n    0xFFF5: (\"JPG5\", \"Extension 5\", None),\n    0xFFF6: (\"JPG6\", \"Extension 6\", None),\n    0xFFF7: (\"JPG7\", \"Extension 7\", None),\n    0xFFF8: (\"JPG8\", \"Extension 8\", None),\n    0xFFF9: (\"JPG9\", \"Extension 9\", None),\n    0xFFFA: (\"JPG10\", \"Extension 10\", None),\n    0xFFFB: (\"JPG11\", \"Extension 11\", None),\n    0xFFFC: (\"JPG12\", \"Extension 12\", None),\n    0xFFFD: (\"JPG13\", \"Extension 13\", None),\n    0xFFFE: (\"COM\", \"Comment\", COM),\n}\n\n\ndef _accept(prefix):\n    # Magic number was taken from https://en.wikipedia.org/wiki/JPEG\n    return prefix[:3] == b\"\\xFF\\xD8\\xFF\"\n\n\n##\n# Image plugin for JPEG and JFIF images.\n\n\nclass JpegImageFile(ImageFile.ImageFile):\n    format = \"JPEG\"\n    format_description = \"JPEG (ISO 10918)\"\n\n    def _open(self):\n        s = self.fp.read(3)\n\n        if not _accept(s):\n            msg = \"not a JPEG file\"\n            raise SyntaxError(msg)\n        s = b\"\\xFF\"\n\n        # Create attributes\n        self.bits = self.layers = 0\n\n        # JPEG specifics (internal)\n        self.layer = []\n        self.huffman_dc = {}\n        self.huffman_ac = {}\n        self.quantization = {}\n        self.app = {}  # compatibility\n        self.applist = []\n        self.icclist = []\n\n        while True:\n            i = s[0]\n            if i == 0xFF:\n                s = s + self.fp.read(1)\n                i = i16(s)\n            else:\n                # Skip non-0xFF junk\n                s = self.fp.read(1)\n                continue\n\n            if i in MARKER:\n                name, description, handler = MARKER[i]\n                if handler is not None:\n                    handler(self, i)\n                if i == 0xFFDA:  # start of scan\n                    rawmode = self.mode\n                    if self.mode == \"CMYK\":\n                        rawmode = \"CMYK;I\"  # assume adobe conventions\n                    self.tile = [(\"jpeg\", (0, 0) + self.size, 0, (rawmode, \"\"))]\n                    # self.__offset = self.fp.tell()\n                    break\n                s = self.fp.read(1)\n            elif i == 0 or i == 0xFFFF:\n                # padded marker or junk; move on\n                s = b\"\\xff\"\n            elif i == 0xFF00:  # Skip extraneous data (escaped 0xFF)\n                s = self.fp.read(1)\n            else:\n                msg = \"no marker found\"\n                raise SyntaxError(msg)\n\n    def load_read(self, read_bytes):\n        \"\"\"\n        internal: read more image data\n        For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker\n        so libjpeg can finish decoding\n        \"\"\"\n        s = self.fp.read(read_bytes)\n\n        if not s and ImageFile.LOAD_TRUNCATED_IMAGES and not hasattr(self, \"_ended\"):\n            # Premature EOF.\n            # Pretend file is finished adding EOI marker\n            self._ended = True\n            return b\"\\xFF\\xD9\"\n\n        return s\n\n    def draft(self, mode, size):\n        if len(self.tile) != 1:\n            return\n\n        # Protect from second call\n        if self.decoderconfig:\n            return\n\n        d, e, o, a = self.tile[0]\n        scale = 1\n        original_size = self.size\n\n        if a[0] == \"RGB\" and mode in [\"L\", \"YCbCr\"]:\n            self.mode = mode\n            a = mode, \"\"\n\n        if size:\n            scale = min(self.size[0] // size[0], self.size[1] // size[1])\n            for s in [8, 4, 2, 1]:\n                if scale >= s:\n                    break\n            e = (\n                e[0],\n                e[1],\n                (e[2] - e[0] + s - 1) // s + e[0],\n                (e[3] - e[1] + s - 1) // s + e[1],\n            )\n            self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s)\n            scale = s\n\n        self.tile = [(d, e, o, a)]\n        self.decoderconfig = (scale, 0)\n\n        box = (0, 0, original_size[0] / scale, original_size[1] / scale)\n        return self.mode, box\n\n    def load_djpeg(self):\n        # ALTERNATIVE: handle JPEGs via the IJG command line utilities\n\n        f, path = tempfile.mkstemp()\n        os.close(f)\n        if os.path.exists(self.filename):\n            subprocess.check_call([\"djpeg\", \"-outfile\", path, self.filename])\n        else:\n            msg = \"Invalid Filename\"\n            raise ValueError(msg)\n\n        try:\n            with Image.open(path) as _im:\n                _im.load()\n                self.im = _im.im\n        finally:\n            try:\n                os.unlink(path)\n            except OSError:\n                pass\n\n        self.mode = self.im.mode\n        self._size = self.im.size\n\n        self.tile = []\n\n    def _getexif(self):\n        return _getexif(self)\n\n    def _getmp(self):\n        return _getmp(self)\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n\n        for segment, content in self.applist:\n            if segment == \"APP1\":\n                marker, xmp_tags = content.rsplit(b\"\\x00\", 1)\n                if marker == b\"http://ns.adobe.com/xap/1.0/\":\n                    return self._getxmp(xmp_tags)\n        return {}\n\n\ndef _getexif(self):\n    if \"exif\" not in self.info:\n        return None\n    return self.getexif()._get_merged_dict()\n\n\ndef _getmp(self):\n    # Extract MP information.  This method was inspired by the \"highly\n    # experimental\" _getexif version that's been in use for years now,\n    # itself based on the ImageFileDirectory class in the TIFF plugin.\n\n    # The MP record essentially consists of a TIFF file embedded in a JPEG\n    # application marker.\n    try:\n        data = self.info[\"mp\"]\n    except KeyError:\n        return None\n    file_contents = io.BytesIO(data)\n    head = file_contents.read(8)\n    endianness = \">\" if head[:4] == b\"\\x4d\\x4d\\x00\\x2a\" else \"<\"\n    # process dictionary\n    from . import TiffImagePlugin\n\n    try:\n        info = TiffImagePlugin.ImageFileDirectory_v2(head)\n        file_contents.seek(info.next)\n        info.load(file_contents)\n        mp = dict(info)\n    except Exception as e:\n        msg = \"malformed MP Index (unreadable directory)\"\n        raise SyntaxError(msg) from e\n    # it's an error not to have a number of images\n    try:\n        quant = mp[0xB001]\n    except KeyError as e:\n        msg = \"malformed MP Index (no number of images)\"\n        raise SyntaxError(msg) from e\n    # get MP entries\n    mpentries = []\n    try:\n        rawmpentries = mp[0xB002]\n        for entrynum in range(0, quant):\n            unpackedentry = struct.unpack_from(\n                f\"{endianness}LLLHH\", rawmpentries, entrynum * 16\n            )\n            labels = (\"Attribute\", \"Size\", \"DataOffset\", \"EntryNo1\", \"EntryNo2\")\n            mpentry = dict(zip(labels, unpackedentry))\n            mpentryattr = {\n                \"DependentParentImageFlag\": bool(mpentry[\"Attribute\"] & (1 << 31)),\n                \"DependentChildImageFlag\": bool(mpentry[\"Attribute\"] & (1 << 30)),\n                \"RepresentativeImageFlag\": bool(mpentry[\"Attribute\"] & (1 << 29)),\n                \"Reserved\": (mpentry[\"Attribute\"] & (3 << 27)) >> 27,\n                \"ImageDataFormat\": (mpentry[\"Attribute\"] & (7 << 24)) >> 24,\n                \"MPType\": mpentry[\"Attribute\"] & 0x00FFFFFF,\n            }\n            if mpentryattr[\"ImageDataFormat\"] == 0:\n                mpentryattr[\"ImageDataFormat\"] = \"JPEG\"\n            else:\n                msg = \"unsupported picture format in MPO\"\n                raise SyntaxError(msg)\n            mptypemap = {\n                0x000000: \"Undefined\",\n                0x010001: \"Large Thumbnail (VGA Equivalent)\",\n                0x010002: \"Large Thumbnail (Full HD Equivalent)\",\n                0x020001: \"Multi-Frame Image (Panorama)\",\n                0x020002: \"Multi-Frame Image: (Disparity)\",\n                0x020003: \"Multi-Frame Image: (Multi-Angle)\",\n                0x030000: \"Baseline MP Primary Image\",\n            }\n            mpentryattr[\"MPType\"] = mptypemap.get(mpentryattr[\"MPType\"], \"Unknown\")\n            mpentry[\"Attribute\"] = mpentryattr\n            mpentries.append(mpentry)\n        mp[0xB002] = mpentries\n    except KeyError as e:\n        msg = \"malformed MP Index (bad MP Entry)\"\n        raise SyntaxError(msg) from e\n    # Next we should try and parse the individual image unique ID list;\n    # we don't because I've never seen this actually used in a real MPO\n    # file and so can't test it.\n    return mp\n\n\n# --------------------------------------------------------------------\n# stuff to save JPEG files\n\nRAWMODE = {\n    \"1\": \"L\",\n    \"L\": \"L\",\n    \"RGB\": \"RGB\",\n    \"RGBX\": \"RGB\",\n    \"CMYK\": \"CMYK;I\",  # assume adobe conventions\n    \"YCbCr\": \"YCbCr\",\n}\n\n# fmt: off\nzigzag_index = (\n    0,  1,  5,  6, 14, 15, 27, 28,\n    2,  4,  7, 13, 16, 26, 29, 42,\n    3,  8, 12, 17, 25, 30, 41, 43,\n    9, 11, 18, 24, 31, 40, 44, 53,\n    10, 19, 23, 32, 39, 45, 52, 54,\n    20, 22, 33, 38, 46, 51, 55, 60,\n    21, 34, 37, 47, 50, 56, 59, 61,\n    35, 36, 48, 49, 57, 58, 62, 63,\n)\n\nsamplings = {\n    (1, 1, 1, 1, 1, 1): 0,\n    (2, 1, 1, 1, 1, 1): 1,\n    (2, 2, 1, 1, 1, 1): 2,\n}\n# fmt: on\n\n\ndef convert_dict_qtables(qtables):\n    deprecate(\"convert_dict_qtables\", 10, action=\"Conversion is no longer needed\")\n    return qtables\n\n\ndef get_sampling(im):\n    # There's no subsampling when images have only 1 layer\n    # (grayscale images) or when they are CMYK (4 layers),\n    # so set subsampling to the default value.\n    #\n    # NOTE: currently Pillow can't encode JPEG to YCCK format.\n    # If YCCK support is added in the future, subsampling code will have\n    # to be updated (here and in JpegEncode.c) to deal with 4 layers.\n    if not hasattr(im, \"layers\") or im.layers in (1, 4):\n        return -1\n    sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]\n    return samplings.get(sampling, -1)\n\n\ndef _save(im, fp, filename):\n    if im.width == 0 or im.height == 0:\n        msg = \"cannot write empty image as JPEG\"\n        raise ValueError(msg)\n\n    try:\n        rawmode = RAWMODE[im.mode]\n    except KeyError as e:\n        msg = f\"cannot write mode {im.mode} as JPEG\"\n        raise OSError(msg) from e\n\n    info = im.encoderinfo\n\n    dpi = [round(x) for x in info.get(\"dpi\", (0, 0))]\n\n    quality = info.get(\"quality\", -1)\n    subsampling = info.get(\"subsampling\", -1)\n    qtables = info.get(\"qtables\")\n\n    if quality == \"keep\":\n        quality = -1\n        subsampling = \"keep\"\n        qtables = \"keep\"\n    elif quality in presets:\n        preset = presets[quality]\n        quality = -1\n        subsampling = preset.get(\"subsampling\", -1)\n        qtables = preset.get(\"quantization\")\n    elif not isinstance(quality, int):\n        msg = \"Invalid quality setting\"\n        raise ValueError(msg)\n    else:\n        if subsampling in presets:\n            subsampling = presets[subsampling].get(\"subsampling\", -1)\n        if isinstance(qtables, str) and qtables in presets:\n            qtables = presets[qtables].get(\"quantization\")\n\n    if subsampling == \"4:4:4\":\n        subsampling = 0\n    elif subsampling == \"4:2:2\":\n        subsampling = 1\n    elif subsampling == \"4:2:0\":\n        subsampling = 2\n    elif subsampling == \"4:1:1\":\n        # For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0.\n        # Set 4:2:0 if someone is still using that value.\n        subsampling = 2\n    elif subsampling == \"keep\":\n        if im.format != \"JPEG\":\n            msg = \"Cannot use 'keep' when original image is not a JPEG\"\n            raise ValueError(msg)\n        subsampling = get_sampling(im)\n\n    def validate_qtables(qtables):\n        if qtables is None:\n            return qtables\n        if isinstance(qtables, str):\n            try:\n                lines = [\n                    int(num)\n                    for line in qtables.splitlines()\n                    for num in line.split(\"#\", 1)[0].split()\n                ]\n            except ValueError as e:\n                msg = \"Invalid quantization table\"\n                raise ValueError(msg) from e\n            else:\n                qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)]\n        if isinstance(qtables, (tuple, list, dict)):\n            if isinstance(qtables, dict):\n                qtables = [\n                    qtables[key] for key in range(len(qtables)) if key in qtables\n                ]\n            elif isinstance(qtables, tuple):\n                qtables = list(qtables)\n            if not (0 < len(qtables) < 5):\n                msg = \"None or too many quantization tables\"\n                raise ValueError(msg)\n            for idx, table in enumerate(qtables):\n                try:\n                    if len(table) != 64:\n                        raise TypeError\n                    table = array.array(\"H\", table)\n                except TypeError as e:\n                    msg = \"Invalid quantization table\"\n                    raise ValueError(msg) from e\n                else:\n                    qtables[idx] = list(table)\n            return qtables\n\n    if qtables == \"keep\":\n        if im.format != \"JPEG\":\n            msg = \"Cannot use 'keep' when original image is not a JPEG\"\n            raise ValueError(msg)\n        qtables = getattr(im, \"quantization\", None)\n    qtables = validate_qtables(qtables)\n\n    extra = info.get(\"extra\", b\"\")\n\n    MAX_BYTES_IN_MARKER = 65533\n    icc_profile = info.get(\"icc_profile\")\n    if icc_profile:\n        ICC_OVERHEAD_LEN = 14\n        MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN\n        markers = []\n        while icc_profile:\n            markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER])\n            icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:]\n        i = 1\n        for marker in markers:\n            size = o16(2 + ICC_OVERHEAD_LEN + len(marker))\n            extra += (\n                b\"\\xFF\\xE2\"\n                + size\n                + b\"ICC_PROFILE\\0\"\n                + o8(i)\n                + o8(len(markers))\n                + marker\n            )\n            i += 1\n\n    comment = info.get(\"comment\", im.info.get(\"comment\"))\n\n    # \"progressive\" is the official name, but older documentation\n    # says \"progression\"\n    # FIXME: issue a warning if the wrong form is used (post-1.1.7)\n    progressive = info.get(\"progressive\", False) or info.get(\"progression\", False)\n\n    optimize = info.get(\"optimize\", False)\n\n    exif = info.get(\"exif\", b\"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    if len(exif) > MAX_BYTES_IN_MARKER:\n        msg = \"EXIF data is too long\"\n        raise ValueError(msg)\n\n    # get keyword arguments\n    im.encoderconfig = (\n        quality,\n        progressive,\n        info.get(\"smooth\", 0),\n        optimize,\n        info.get(\"streamtype\", 0),\n        dpi[0],\n        dpi[1],\n        subsampling,\n        qtables,\n        comment,\n        extra,\n        exif,\n    )\n\n    # if we optimize, libjpeg needs a buffer big enough to hold the whole image\n    # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is\n    # channels*size, this is a value that's been used in a django patch.\n    # https://github.com/matthewwithanm/django-imagekit/issues/50\n    bufsize = 0\n    if optimize or progressive:\n        # CMYK can be bigger\n        if im.mode == \"CMYK\":\n            bufsize = 4 * im.size[0] * im.size[1]\n        # keep sets quality to -1, but the actual value may be high.\n        elif quality >= 95 or quality == -1:\n            bufsize = 2 * im.size[0] * im.size[1]\n        else:\n            bufsize = im.size[0] * im.size[1]\n\n    # The EXIF info needs to be written as one block, + APP1, + one spare byte.\n    # Ensure that our buffer is big enough. Same with the icc_profile block.\n    bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, len(extra) + 1)\n\n    ImageFile._save(im, fp, [(\"jpeg\", (0, 0) + im.size, 0, rawmode)], bufsize)\n\n\ndef _save_cjpeg(im, fp, filename):\n    # ALTERNATIVE: handle JPEGs via the IJG command line utilities.\n    tempfile = im._dump()\n    subprocess.check_call([\"cjpeg\", \"-outfile\", filename, tempfile])\n    try:\n        os.unlink(tempfile)\n    except OSError:\n        pass\n\n\n##\n# Factory for making JPEG and MPO instances\ndef jpeg_factory(fp=None, filename=None):\n    im = JpegImageFile(fp, filename)\n    try:\n        mpheader = im._getmp()\n        if mpheader[45057] > 1:\n            # It's actually an MPO\n            from .MpoImagePlugin import MpoImageFile\n\n            # Don't reload everything, just convert it.\n            im = MpoImageFile.adopt(im, mpheader)\n    except (TypeError, IndexError):\n        # It is really a JPEG\n        pass\n    except SyntaxError:\n        warnings.warn(\n            \"Image appears to be a malformed MPO file, it will be \"\n            \"interpreted as a base JPEG file\"\n        )\n    return im\n\n\n# ---------------------------------------------------------------------\n# Registry stuff\n\nImage.register_open(JpegImageFile.format, jpeg_factory, _accept)\nImage.register_save(JpegImageFile.format, _save)\n\nImage.register_extensions(JpegImageFile.format, [\".jfif\", \".jpe\", \".jpg\", \".jpeg\"])\n\nImage.register_mime(JpegImageFile.format, \"image/jpeg\")\n",
            "header": {
                "file": {
                    "path": "src/PIL/JpegImagePlugin.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "d7ddbe0d9",
                "new_commit_hash": "71ae84c04",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/src/PIL/JpegImagePlugin.py"
            },
            "plus_file": {
                "path": "b/src/PIL/JpegImagePlugin.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 730,
                            "length": 10
                        },
                        "new_range": {
                            "start": 730,
                            "length": 10
                        },
                        "section": "def _save(im, fp, filename):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    extra = info.get(\"extra\", b\"\")",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    MAX_BYTES_IN_MARKER = 65533",
                                "type": "added"
                            },
                            {
                                "content": "    icc_profile = info.get(\"icc_profile\")",
                                "type": "context"
                            },
                            {
                                "content": "    if icc_profile:",
                                "type": "context"
                            },
                            {
                                "content": "        ICC_OVERHEAD_LEN = 14",
                                "type": "context"
                            },
                            {
                                "content": "        MAX_BYTES_IN_MARKER = 65533",
                                "type": "deleted"
                            },
                            {
                                "content": "        MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN",
                                "type": "context"
                            },
                            {
                                "content": "        markers = []",
                                "type": "context"
                            },
                            {
                                "content": "        while icc_profile:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/JpegImagePlugin.py",
                            "type": "function",
                            "name": "_save",
                            "content": "def _save(im, fp, filename):\n    if im.width == 0 or im.height == 0:\n        msg = \"cannot write empty image as JPEG\"\n        raise ValueError(msg)\n\n    try:\n        rawmode = RAWMODE[im.mode]\n    except KeyError as e:\n        msg = f\"cannot write mode {im.mode} as JPEG\"\n        raise OSError(msg) from e\n\n    info = im.encoderinfo\n\n    dpi = [round(x) for x in info.get(\"dpi\", (0, 0))]\n\n    quality = info.get(\"quality\", -1)\n    subsampling = info.get(\"subsampling\", -1)\n    qtables = info.get(\"qtables\")\n\n    if quality == \"keep\":\n        quality = -1\n        subsampling = \"keep\"\n        qtables = \"keep\"\n    elif quality in presets:\n        preset = presets[quality]\n        quality = -1\n        subsampling = preset.get(\"subsampling\", -1)\n        qtables = preset.get(\"quantization\")\n    elif not isinstance(quality, int):\n        msg = \"Invalid quality setting\"\n        raise ValueError(msg)\n    else:\n        if subsampling in presets:\n            subsampling = presets[subsampling].get(\"subsampling\", -1)\n        if isinstance(qtables, str) and qtables in presets:\n            qtables = presets[qtables].get(\"quantization\")\n\n    if subsampling == \"4:4:4\":\n        subsampling = 0\n    elif subsampling == \"4:2:2\":\n        subsampling = 1\n    elif subsampling == \"4:2:0\":\n        subsampling = 2\n    elif subsampling == \"4:1:1\":\n        # For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0.\n        # Set 4:2:0 if someone is still using that value.\n        subsampling = 2\n    elif subsampling == \"keep\":\n        if im.format != \"JPEG\":\n            msg = \"Cannot use 'keep' when original image is not a JPEG\"\n            raise ValueError(msg)\n        subsampling = get_sampling(im)\n\n    def validate_qtables(qtables):\n        if qtables is None:\n            return qtables\n        if isinstance(qtables, str):\n            try:\n                lines = [\n                    int(num)\n                    for line in qtables.splitlines()\n                    for num in line.split(\"#\", 1)[0].split()\n                ]\n            except ValueError as e:\n                msg = \"Invalid quantization table\"\n                raise ValueError(msg) from e\n            else:\n                qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)]\n        if isinstance(qtables, (tuple, list, dict)):\n            if isinstance(qtables, dict):\n                qtables = [\n                    qtables[key] for key in range(len(qtables)) if key in qtables\n                ]\n            elif isinstance(qtables, tuple):\n                qtables = list(qtables)\n            if not (0 < len(qtables) < 5):\n                msg = \"None or too many quantization tables\"\n                raise ValueError(msg)\n            for idx, table in enumerate(qtables):\n                try:\n                    if len(table) != 64:\n                        raise TypeError\n                    table = array.array(\"H\", table)\n                except TypeError as e:\n                    msg = \"Invalid quantization table\"\n                    raise ValueError(msg) from e\n                else:\n                    qtables[idx] = list(table)\n            return qtables\n\n    if qtables == \"keep\":\n        if im.format != \"JPEG\":\n            msg = \"Cannot use 'keep' when original image is not a JPEG\"\n            raise ValueError(msg)\n        qtables = getattr(im, \"quantization\", None)\n    qtables = validate_qtables(qtables)\n\n    extra = info.get(\"extra\", b\"\")\n\n    icc_profile = info.get(\"icc_profile\")\n    if icc_profile:\n        ICC_OVERHEAD_LEN = 14\n        MAX_BYTES_IN_MARKER = 65533\n        MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN\n        markers = []\n        while icc_profile:\n            markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER])\n            icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:]\n        i = 1\n        for marker in markers:\n            size = o16(2 + ICC_OVERHEAD_LEN + len(marker))\n            extra += (\n                b\"\\xFF\\xE2\"\n                + size\n                + b\"ICC_PROFILE\\0\"\n                + o8(i)\n                + o8(len(markers))\n                + marker\n            )\n            i += 1\n\n    comment = info.get(\"comment\", im.info.get(\"comment\"))\n\n    # \"progressive\" is the official name, but older documentation\n    # says \"progression\"\n    # FIXME: issue a warning if the wrong form is used (post-1.1.7)\n    progressive = info.get(\"progressive\", False) or info.get(\"progression\", False)\n\n    optimize = info.get(\"optimize\", False)\n\n    exif = info.get(\"exif\", b\"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n\n    # get keyword arguments\n    im.encoderconfig = (\n        quality,\n        progressive,\n        info.get(\"smooth\", 0),\n        optimize,\n        info.get(\"streamtype\", 0),\n        dpi[0],\n        dpi[1],\n        subsampling,\n        qtables,\n        comment,\n        extra,\n        exif,\n    )\n\n    # if we optimize, libjpeg needs a buffer big enough to hold the whole image\n    # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is\n    # channels*size, this is a value that's been used in a django patch.\n    # https://github.com/matthewwithanm/django-imagekit/issues/50\n    bufsize = 0\n    if optimize or progressive:\n        # CMYK can be bigger\n        if im.mode == \"CMYK\":\n            bufsize = 4 * im.size[0] * im.size[1]\n        # keep sets quality to -1, but the actual value may be high.\n        elif quality >= 95 or quality == -1:\n            bufsize = 2 * im.size[0] * im.size[1]\n        else:\n            bufsize = im.size[0] * im.size[1]\n\n    # The EXIF info needs to be written as one block, + APP1, + one spare byte.\n    # Ensure that our buffer is big enough. Same with the icc_profile block.\n    bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, len(extra) + 1)\n\n    ImageFile._save(im, fp, [(\"jpeg\", (0, 0) + im.size, 0, rawmode)], bufsize)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 634,
                            "end_lineno": 803,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 764,
                            "length": 6
                        },
                        "new_range": {
                            "start": 764,
                            "length": 9
                        },
                        "section": "def _save(im, fp, filename):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    exif = info.get(\"exif\", b\"\")",
                                "type": "context"
                            },
                            {
                                "content": "    if isinstance(exif, Image.Exif):",
                                "type": "context"
                            },
                            {
                                "content": "        exif = exif.tobytes()",
                                "type": "context"
                            },
                            {
                                "content": "    if len(exif) > MAX_BYTES_IN_MARKER:",
                                "type": "added"
                            },
                            {
                                "content": "        msg = \"EXIF data is too long\"",
                                "type": "added"
                            },
                            {
                                "content": "        raise ValueError(msg)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    # get keyword arguments",
                                "type": "context"
                            },
                            {
                                "content": "    im.encoderconfig = (",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/JpegImagePlugin.py",
                            "type": "function",
                            "name": "_save",
                            "content": "def _save(im, fp, filename):\n    if im.width == 0 or im.height == 0:\n        msg = \"cannot write empty image as JPEG\"\n        raise ValueError(msg)\n\n    try:\n        rawmode = RAWMODE[im.mode]\n    except KeyError as e:\n        msg = f\"cannot write mode {im.mode} as JPEG\"\n        raise OSError(msg) from e\n\n    info = im.encoderinfo\n\n    dpi = [round(x) for x in info.get(\"dpi\", (0, 0))]\n\n    quality = info.get(\"quality\", -1)\n    subsampling = info.get(\"subsampling\", -1)\n    qtables = info.get(\"qtables\")\n\n    if quality == \"keep\":\n        quality = -1\n        subsampling = \"keep\"\n        qtables = \"keep\"\n    elif quality in presets:\n        preset = presets[quality]\n        quality = -1\n        subsampling = preset.get(\"subsampling\", -1)\n        qtables = preset.get(\"quantization\")\n    elif not isinstance(quality, int):\n        msg = \"Invalid quality setting\"\n        raise ValueError(msg)\n    else:\n        if subsampling in presets:\n            subsampling = presets[subsampling].get(\"subsampling\", -1)\n        if isinstance(qtables, str) and qtables in presets:\n            qtables = presets[qtables].get(\"quantization\")\n\n    if subsampling == \"4:4:4\":\n        subsampling = 0\n    elif subsampling == \"4:2:2\":\n        subsampling = 1\n    elif subsampling == \"4:2:0\":\n        subsampling = 2\n    elif subsampling == \"4:1:1\":\n        # For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0.\n        # Set 4:2:0 if someone is still using that value.\n        subsampling = 2\n    elif subsampling == \"keep\":\n        if im.format != \"JPEG\":\n            msg = \"Cannot use 'keep' when original image is not a JPEG\"\n            raise ValueError(msg)\n        subsampling = get_sampling(im)\n\n    def validate_qtables(qtables):\n        if qtables is None:\n            return qtables\n        if isinstance(qtables, str):\n            try:\n                lines = [\n                    int(num)\n                    for line in qtables.splitlines()\n                    for num in line.split(\"#\", 1)[0].split()\n                ]\n            except ValueError as e:\n                msg = \"Invalid quantization table\"\n                raise ValueError(msg) from e\n            else:\n                qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)]\n        if isinstance(qtables, (tuple, list, dict)):\n            if isinstance(qtables, dict):\n                qtables = [\n                    qtables[key] for key in range(len(qtables)) if key in qtables\n                ]\n            elif isinstance(qtables, tuple):\n                qtables = list(qtables)\n            if not (0 < len(qtables) < 5):\n                msg = \"None or too many quantization tables\"\n                raise ValueError(msg)\n            for idx, table in enumerate(qtables):\n                try:\n                    if len(table) != 64:\n                        raise TypeError\n                    table = array.array(\"H\", table)\n                except TypeError as e:\n                    msg = \"Invalid quantization table\"\n                    raise ValueError(msg) from e\n                else:\n                    qtables[idx] = list(table)\n            return qtables\n\n    if qtables == \"keep\":\n        if im.format != \"JPEG\":\n            msg = \"Cannot use 'keep' when original image is not a JPEG\"\n            raise ValueError(msg)\n        qtables = getattr(im, \"quantization\", None)\n    qtables = validate_qtables(qtables)\n\n    extra = info.get(\"extra\", b\"\")\n\n    MAX_BYTES_IN_MARKER = 65533\n    icc_profile = info.get(\"icc_profile\")\n    if icc_profile:\n        ICC_OVERHEAD_LEN = 14\n        MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN\n        markers = []\n        while icc_profile:\n            markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER])\n            icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:]\n        i = 1\n        for marker in markers:\n            size = o16(2 + ICC_OVERHEAD_LEN + len(marker))\n            extra += (\n                b\"\\xFF\\xE2\"\n                + size\n                + b\"ICC_PROFILE\\0\"\n                + o8(i)\n                + o8(len(markers))\n                + marker\n            )\n            i += 1\n\n    comment = info.get(\"comment\", im.info.get(\"comment\"))\n\n    # \"progressive\" is the official name, but older documentation\n    # says \"progression\"\n    # FIXME: issue a warning if the wrong form is used (post-1.1.7)\n    progressive = info.get(\"progressive\", False) or info.get(\"progression\", False)\n\n    optimize = info.get(\"optimize\", False)\n\n    exif = info.get(\"exif\", b\"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n    if len(exif) > MAX_BYTES_IN_MARKER:\n        msg = \"EXIF data is too long\"\n        raise ValueError(msg)\n\n    # get keyword arguments\n    im.encoderconfig = (\n        quality,\n        progressive,\n        info.get(\"smooth\", 0),\n        optimize,\n        info.get(\"streamtype\", 0),\n        dpi[0],\n        dpi[1],\n        subsampling,\n        qtables,\n        comment,\n        extra,\n        exif,\n    )\n\n    # if we optimize, libjpeg needs a buffer big enough to hold the whole image\n    # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is\n    # channels*size, this is a value that's been used in a django patch.\n    # https://github.com/matthewwithanm/django-imagekit/issues/50\n    bufsize = 0\n    if optimize or progressive:\n        # CMYK can be bigger\n        if im.mode == \"CMYK\":\n            bufsize = 4 * im.size[0] * im.size[1]\n        # keep sets quality to -1, but the actual value may be high.\n        elif quality >= 95 or quality == -1:\n            bufsize = 2 * im.size[0] * im.size[1]\n        else:\n            bufsize = im.size[0] * im.size[1]\n\n    # The EXIF info needs to be written as one block, + APP1, + one spare byte.\n    # Ensure that our buffer is big enough. Same with the icc_profile block.\n    bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, len(extra) + 1)\n\n    ImageFile._save(im, fp, [(\"jpeg\", (0, 0) + im.size, 0, rawmode)], bufsize)",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 634,
                            "end_lineno": 806,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "bb524018d35ea6450e56c3cd3db489eeb0f5a79c^",
    "new_commit_hash": "bb524018d35ea6450e56c3cd3db489eeb0f5a79c",
    "commit_message": "Raise an error when EXIF data is too long",
    "commit_date": "2023-02-11T16:20:27+11:00",
    "metadata": {}
}
{
    "repo_name": "pillow",
    "new_commit_hash": "bb524018d35ea6450e56c3cd3db489eeb0f5a79c",
    "test_file_codes": [
        "import os\nimport re\nimport warnings\nfrom io import BytesIO\n\nimport pytest\n\nfrom PIL import (\n    ExifTags,\n    Image,\n    ImageFile,\n    ImageOps,\n    JpegImagePlugin,\n    UnidentifiedImageError,\n    features,\n)\n\nfrom .helper import (\n    assert_image,\n    assert_image_equal,\n    assert_image_equal_tofile,\n    assert_image_similar,\n    assert_image_similar_tofile,\n    cjpeg_available,\n    djpeg_available,\n    hopper,\n    is_win32,\n    mark_if_feature_version,\n    skip_unless_feature,\n)\n\ntry:\n    from defusedxml import ElementTree\nexcept ImportError:\n    ElementTree = None\n\nTEST_FILE = \"Tests/images/hopper.jpg\"\n\n\n@skip_unless_feature(\"jpg\")\nclass TestFileJpeg:\n    def roundtrip(self, im, **options):\n        out = BytesIO()\n        im.save(out, \"JPEG\", **options)\n        test_bytes = out.tell()\n        out.seek(0)\n        im = Image.open(out)\n        im.bytes = test_bytes  # for testing only\n        return im\n\n    def gen_random_image(self, size, mode=\"RGB\"):\n        \"\"\"Generates a very hard to compress file\n        :param size: tuple\n        :param mode: optional image mode\n\n        \"\"\"\n        return Image.frombytes(mode, size, os.urandom(size[0] * size[1] * len(mode)))\n\n    def test_sanity(self):\n        # internal version number\n        assert re.search(r\"\\d+\\.\\d+$\", features.version_codec(\"jpg\"))\n\n        with Image.open(TEST_FILE) as im:\n            im.load()\n            assert im.mode == \"RGB\"\n            assert im.size == (128, 128)\n            assert im.format == \"JPEG\"\n            assert im.get_format_mimetype() == \"image/jpeg\"\n\n    @pytest.mark.parametrize(\"size\", ((1, 0), (0, 1), (0, 0)))\n    def test_zero(self, size, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        im = Image.new(\"RGB\", size)\n        with pytest.raises(ValueError):\n            im.save(f)\n\n    def test_app(self):\n        # Test APP/COM reader (@PIL135)\n        with Image.open(TEST_FILE) as im:\n            assert im.applist[0] == (\"APP0\", b\"JFIF\\x00\\x01\\x01\\x01\\x00`\\x00`\\x00\\x00\")\n            assert im.applist[1] == (\n                \"COM\",\n                b\"File written by Adobe Photoshop\\xa8 4.0\\x00\",\n            )\n            assert len(im.applist) == 2\n\n            assert im.info[\"comment\"] == b\"File written by Adobe Photoshop\\xa8 4.0\\x00\"\n            assert im.app[\"COM\"] == im.info[\"comment\"]\n\n    def test_comment_write(self):\n        with Image.open(TEST_FILE) as im:\n            assert im.info[\"comment\"] == b\"File written by Adobe Photoshop\\xa8 4.0\\x00\"\n\n            # Test that existing comment is saved by default\n            out = BytesIO()\n            im.save(out, format=\"JPEG\")\n            with Image.open(out) as reloaded:\n                assert im.info[\"comment\"] == reloaded.info[\"comment\"]\n\n            # Ensure that a blank comment causes any existing comment to be removed\n            for comment in (\"\", b\"\", None):\n                out = BytesIO()\n                im.save(out, format=\"JPEG\", comment=comment)\n                with Image.open(out) as reloaded:\n                    assert \"comment\" not in reloaded.info\n\n            # Test that a comment argument overrides the default comment\n            for comment in (\"Test comment text\", b\"Text comment text\"):\n                out = BytesIO()\n                im.save(out, format=\"JPEG\", comment=comment)\n                with Image.open(out) as reloaded:\n                    if not isinstance(comment, bytes):\n                        comment = comment.encode()\n                    assert reloaded.info[\"comment\"] == comment\n\n    def test_cmyk(self):\n        # Test CMYK handling.  Thanks to Tim and Charlie for test data,\n        # Michael for getting me to look one more time.\n        f = \"Tests/images/pil_sample_cmyk.jpg\"\n        with Image.open(f) as im:\n            # the source image has red pixels in the upper left corner.\n            c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))\n            assert c == 0.0\n            assert m > 0.8\n            assert y > 0.8\n            assert k == 0.0\n            # the opposite corner is black\n            c, m, y, k = (\n                x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))\n            )\n            assert k > 0.9\n            # roundtrip, and check again\n            im = self.roundtrip(im)\n            c, m, y, k = (x / 255.0 for x in im.getpixel((0, 0)))\n            assert c == 0.0\n            assert m > 0.8\n            assert y > 0.8\n            assert k == 0.0\n            c, m, y, k = (\n                x / 255.0 for x in im.getpixel((im.size[0] - 1, im.size[1] - 1))\n            )\n            assert k > 0.9\n\n    @pytest.mark.parametrize(\n        \"test_image_path\",\n        [TEST_FILE, \"Tests/images/pil_sample_cmyk.jpg\"],\n    )\n    def test_dpi(self, test_image_path):\n        def test(xdpi, ydpi=None):\n            with Image.open(test_image_path) as im:\n                im = self.roundtrip(im, dpi=(xdpi, ydpi or xdpi))\n            return im.info.get(\"dpi\")\n\n        assert test(72) == (72, 72)\n        assert test(300) == (300, 300)\n        assert test(100, 200) == (100, 200)\n        assert test(0) is None  # square pixels\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_icc(self, tmp_path):\n        # Test ICC support\n        with Image.open(\"Tests/images/rgb.jpg\") as im1:\n            icc_profile = im1.info[\"icc_profile\"]\n            assert len(icc_profile) == 3144\n            # Roundtrip via physical file.\n            f = str(tmp_path / \"temp.jpg\")\n            im1.save(f, icc_profile=icc_profile)\n        with Image.open(f) as im2:\n            assert im2.info.get(\"icc_profile\") == icc_profile\n            # Roundtrip via memory buffer.\n            im1 = self.roundtrip(hopper())\n            im2 = self.roundtrip(hopper(), icc_profile=icc_profile)\n            assert_image_equal(im1, im2)\n            assert not im1.info.get(\"icc_profile\")\n            assert im2.info.get(\"icc_profile\")\n\n    @pytest.mark.parametrize(\n        \"n\",\n        (\n            0,\n            1,\n            3,\n            4,\n            5,\n            65533 - 14,  # full JPEG marker block\n            65533 - 14 + 1,  # full block plus one byte\n            ImageFile.MAXBLOCK,  # full buffer block\n            ImageFile.MAXBLOCK + 1,  # full buffer block plus one byte\n            ImageFile.MAXBLOCK * 4 + 3,  # large block\n        ),\n    )\n    def test_icc_big(self, n):\n        # Make sure that the \"extra\" support handles large blocks\n        # The ICC APP marker can store 65519 bytes per marker, so\n        # using a 4-byte test code should allow us to detect out of\n        # order issues.\n        icc_profile = (b\"Test\" * int(n / 4 + 1))[:n]\n        assert len(icc_profile) == n  # sanity\n        im1 = self.roundtrip(hopper(), icc_profile=icc_profile)\n        assert im1.info.get(\"icc_profile\") == (icc_profile or None)\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_large_icc_meta(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        # Sometimes the meta data on the icc_profile block is bigger than\n        # Image.MAXBLOCK or the image size.\n        with Image.open(\"Tests/images/icc_profile_big.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            icc_profile = im.info[\"icc_profile\"]\n            # Should not raise OSError for image with icc larger than image size.\n            im.save(\n                f,\n                format=\"JPEG\",\n                progressive=True,\n                quality=95,\n                icc_profile=icc_profile,\n                optimize=True,\n            )\n\n    def test_optimize(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), optimize=0)\n        im3 = self.roundtrip(hopper(), optimize=1)\n        assert_image_equal(im1, im2)\n        assert_image_equal(im1, im3)\n        assert im1.bytes >= im2.bytes\n        assert im1.bytes >= im3.bytes\n\n    def test_optimize_large_buffer(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        f = str(tmp_path / \"temp.jpg\")\n        # this requires ~ 1.5x Image.MAXBLOCK\n        im = Image.new(\"RGB\", (4096, 4096), 0xFF3333)\n        im.save(f, format=\"JPEG\", optimize=True)\n\n    def test_progressive(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), progressive=False)\n        im3 = self.roundtrip(hopper(), progressive=True)\n        assert not im1.info.get(\"progressive\")\n        assert not im2.info.get(\"progressive\")\n        assert im3.info.get(\"progressive\")\n\n        assert_image_equal(im1, im3)\n        assert im1.bytes >= im3.bytes\n\n    def test_progressive_large_buffer(self, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        # this requires ~ 1.5x Image.MAXBLOCK\n        im = Image.new(\"RGB\", (4096, 4096), 0xFF3333)\n        im.save(f, format=\"JPEG\", progressive=True)\n\n    def test_progressive_large_buffer_highest_quality(self, tmp_path):\n        f = str(tmp_path / \"temp.jpg\")\n        im = self.gen_random_image((255, 255))\n        # this requires more bytes than pixels in the image\n        im.save(f, format=\"JPEG\", progressive=True, quality=100)\n\n    def test_progressive_cmyk_buffer(self):\n        # Issue 2272, quality 90 cmyk image is tripping the large buffer bug.\n        f = BytesIO()\n        im = self.gen_random_image((256, 256), \"CMYK\")\n        im.save(f, format=\"JPEG\", progressive=True, quality=94)\n\n    def test_large_exif(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        f = str(tmp_path / \"temp.jpg\")\n        im = hopper()\n        im.save(f, \"JPEG\", quality=90, exif=b\"1\" * 65533)\n\n        with pytest.raises(ValueError):\n            im.save(f, \"JPEG\", quality=90, exif=b\"1\" * 65534)\n\n    def test_exif_typeerror(self):\n        with Image.open(\"Tests/images/exif_typeerror.jpg\") as im:\n            # Should not raise a TypeError\n            im._getexif()\n\n    def test_exif_gps(self, tmp_path):\n        expected_exif_gps = {\n            0: b\"\\x00\\x00\\x00\\x01\",\n            2: 4294967295,\n            5: b\"\\x01\",\n            30: 65535,\n            29: \"1999:99:99 99:99:99\",\n        }\n        gps_index = 34853\n\n        # Reading\n        with Image.open(\"Tests/images/exif_gps.jpg\") as im:\n            exif = im._getexif()\n            assert exif[gps_index] == expected_exif_gps\n\n        # Writing\n        f = str(tmp_path / \"temp.jpg\")\n        exif = Image.Exif()\n        exif[gps_index] = expected_exif_gps\n        hopper().save(f, exif=exif)\n\n        with Image.open(f) as reloaded:\n            exif = reloaded._getexif()\n            assert exif[gps_index] == expected_exif_gps\n\n    def test_empty_exif_gps(self):\n        with Image.open(\"Tests/images/empty_gps_ifd.jpg\") as im:\n            exif = im.getexif()\n            del exif[0x8769]\n\n            # Assert that it needs to be transposed\n            assert exif[0x0112] == Image.Transpose.TRANSVERSE\n\n            # Assert that the GPS IFD is present and empty\n            assert exif.get_ifd(0x8825) == {}\n\n            transposed = ImageOps.exif_transpose(im)\n        exif = transposed.getexif()\n        assert exif.get_ifd(0x8825) == {}\n\n        # Assert that it was transposed\n        assert 0x0112 not in exif\n\n    def test_exif_equality(self):\n        # In 7.2.0, Exif rationals were changed to be read as\n        # TiffImagePlugin.IFDRational. This class had a bug in __eq__,\n        # breaking the self-equality of Exif data\n        exifs = []\n        for i in range(2):\n            with Image.open(\"Tests/images/exif-200dpcm.jpg\") as im:\n                exifs.append(im._getexif())\n        assert exifs[0] == exifs[1]\n\n    def test_exif_rollback(self):\n        # rolling back exif support in 3.1 to pre-3.0 formatting.\n        # expected from 2.9, with b/u qualifiers switched for 3.2 compatibility\n        # this test passes on 2.9 and 3.1, but not 3.0\n        expected_exif = {\n            34867: 4294967295,\n            258: (24, 24, 24),\n            36867: \"2099:09:29 10:10:10\",\n            34853: {\n                0: b\"\\x00\\x00\\x00\\x01\",\n                2: 4294967295,\n                5: b\"\\x01\",\n                30: 65535,\n                29: \"1999:99:99 99:99:99\",\n            },\n            296: 65535,\n            34665: 185,\n            41994: 65535,\n            514: 4294967295,\n            271: \"Make\",\n            272: \"XXX-XXX\",\n            305: \"PIL\",\n            42034: (1, 1, 1, 1),\n            42035: \"LensMake\",\n            34856: b\"\\xaa\\xaa\\xaa\\xaa\\xaa\\xaa\",\n            282: 4294967295,\n            33434: 4294967295,\n        }\n\n        with Image.open(\"Tests/images/exif_gps.jpg\") as im:\n            exif = im._getexif()\n\n        for tag, value in expected_exif.items():\n            assert value == exif[tag]\n\n    def test_exif_gps_typeerror(self):\n        with Image.open(\"Tests/images/exif_gps_typeerror.jpg\") as im:\n            # Should not raise a TypeError\n            im._getexif()\n\n    def test_progressive_compat(self):\n        im1 = self.roundtrip(hopper())\n        assert not im1.info.get(\"progressive\")\n        assert not im1.info.get(\"progression\")\n\n        im2 = self.roundtrip(hopper(), progressive=0)\n        im3 = self.roundtrip(hopper(), progression=0)  # compatibility\n        assert not im2.info.get(\"progressive\")\n        assert not im2.info.get(\"progression\")\n        assert not im3.info.get(\"progressive\")\n        assert not im3.info.get(\"progression\")\n\n        im2 = self.roundtrip(hopper(), progressive=1)\n        im3 = self.roundtrip(hopper(), progression=1)  # compatibility\n        assert_image_equal(im1, im2)\n        assert_image_equal(im1, im3)\n        assert im2.info.get(\"progressive\")\n        assert im2.info.get(\"progression\")\n        assert im3.info.get(\"progressive\")\n        assert im3.info.get(\"progression\")\n\n    def test_quality(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), quality=50)\n        assert_image(im1, im2.mode, im2.size)\n        assert im1.bytes >= im2.bytes\n\n        im3 = self.roundtrip(hopper(), quality=0)\n        assert_image(im1, im3.mode, im3.size)\n        assert im2.bytes > im3.bytes\n\n    def test_smooth(self):\n        im1 = self.roundtrip(hopper())\n        im2 = self.roundtrip(hopper(), smooth=100)\n        assert_image(im1, im2.mode, im2.size)\n\n    def test_subsampling(self):\n        def getsampling(im):\n            layer = im.layer\n            return layer[0][1:3] + layer[1][1:3] + layer[2][1:3]\n\n        # experimental API\n        im = self.roundtrip(hopper(), subsampling=-1)  # default\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=0)  # 4:4:4\n        assert getsampling(im) == (1, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=1)  # 4:2:2\n        assert getsampling(im) == (2, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=2)  # 4:2:0\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=3)  # default (undefined)\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n\n        im = self.roundtrip(hopper(), subsampling=\"4:4:4\")\n        assert getsampling(im) == (1, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:2:2\")\n        assert getsampling(im) == (2, 1, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:2:0\")\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n        im = self.roundtrip(hopper(), subsampling=\"4:1:1\")\n        assert getsampling(im) == (2, 2, 1, 1, 1, 1)\n\n        with pytest.raises(TypeError):\n            self.roundtrip(hopper(), subsampling=\"1:1:1\")\n\n    def test_exif(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            info = im._getexif()\n            assert info[305] == \"Adobe Photoshop CS Macintosh\"\n\n    def test_get_child_images(self):\n        with Image.open(\"Tests/images/flower.jpg\") as im:\n            ims = im.get_child_images()\n\n        assert len(ims) == 1\n        assert_image_equal_tofile(ims[0], \"Tests/images/flower_thumbnail.png\")\n\n    def test_mp(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            assert im._getmp() is None\n\n    def test_quality_keep(self, tmp_path):\n        # RGB\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n        # Grayscale\n        with Image.open(\"Tests/images/hopper_gray.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n        # CMYK\n        with Image.open(\"Tests/images/pil_sample_cmyk.jpg\") as im:\n            f = str(tmp_path / \"temp.jpg\")\n            im.save(f, quality=\"keep\")\n\n    def test_junk_jpeg_header(self):\n        # https://github.com/python-pillow/Pillow/issues/630\n        filename = \"Tests/images/junk_jpeg_header.jpg\"\n        with Image.open(filename):\n            pass\n\n    def test_ff00_jpeg_header(self):\n        filename = \"Tests/images/jpeg_ff00_header.jpg\"\n        with Image.open(filename):\n            pass\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_truncated_jpeg_should_read_all_the_data(self):\n        filename = \"Tests/images/truncated_jpeg.jpg\"\n        ImageFile.LOAD_TRUNCATED_IMAGES = True\n        with Image.open(filename) as im:\n            im.load()\n            ImageFile.LOAD_TRUNCATED_IMAGES = False\n            assert im.getbbox() is not None\n\n    def test_truncated_jpeg_throws_oserror(self):\n        filename = \"Tests/images/truncated_jpeg.jpg\"\n        with Image.open(filename) as im:\n            with pytest.raises(OSError):\n                im.load()\n\n            # Test that the error is raised if loaded a second time\n            with pytest.raises(OSError):\n                im.load()\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_qtables(self, tmp_path):\n        def _n_qtables_helper(n, test_file):\n            with Image.open(test_file) as im:\n                f = str(tmp_path / \"temp.jpg\")\n                im.save(f, qtables=[[n] * 64] * n)\n            with Image.open(f) as im:\n                assert len(im.quantization) == n\n                reloaded = self.roundtrip(im, qtables=\"keep\")\n                assert im.quantization == reloaded.quantization\n                assert max(reloaded.quantization[0]) <= 255\n\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            qtables = im.quantization\n            reloaded = self.roundtrip(im, qtables=qtables, subsampling=0)\n            assert im.quantization == reloaded.quantization\n            assert_image_similar(im, self.roundtrip(im, qtables=\"web_low\"), 30)\n            assert_image_similar(im, self.roundtrip(im, qtables=\"web_high\"), 30)\n            assert_image_similar(im, self.roundtrip(im, qtables=\"keep\"), 30)\n\n            # valid bounds for baseline qtable\n            bounds_qtable = [int(s) for s in (\"255 1 \" * 32).split(None)]\n            im2 = self.roundtrip(im, qtables=[bounds_qtable])\n            assert im2.quantization == {0: bounds_qtable}\n\n            # values from wizard.txt in jpeg9-a src package.\n            standard_l_qtable = [\n                int(s)\n                for s in \"\"\"\n                16  11  10  16  24  40  51  61\n                12  12  14  19  26  58  60  55\n                14  13  16  24  40  57  69  56\n                14  17  22  29  51  87  80  62\n                18  22  37  56  68 109 103  77\n                24  35  55  64  81 104 113  92\n                49  64  78  87 103 121 120 101\n                72  92  95  98 112 100 103  99\n                \"\"\".split(\n                    None\n                )\n            ]\n\n            standard_chrominance_qtable = [\n                int(s)\n                for s in \"\"\"\n                17  18  24  47  99  99  99  99\n                18  21  26  66  99  99  99  99\n                24  26  56  99  99  99  99  99\n                47  66  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                99  99  99  99  99  99  99  99\n                \"\"\".split(\n                    None\n                )\n            ]\n            # list of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables=[standard_l_qtable, standard_chrominance_qtable]\n                ),\n                30,\n            )\n\n            # tuple of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables=(standard_l_qtable, standard_chrominance_qtable)\n                ),\n                30,\n            )\n\n            # dict of qtable lists\n            assert_image_similar(\n                im,\n                self.roundtrip(\n                    im, qtables={0: standard_l_qtable, 1: standard_chrominance_qtable}\n                ),\n                30,\n            )\n\n            _n_qtables_helper(1, \"Tests/images/hopper_gray.jpg\")\n            _n_qtables_helper(1, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(2, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(3, \"Tests/images/pil_sample_rgb.jpg\")\n            _n_qtables_helper(1, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(2, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(3, \"Tests/images/pil_sample_cmyk.jpg\")\n            _n_qtables_helper(4, \"Tests/images/pil_sample_cmyk.jpg\")\n\n            # not a sequence\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=\"a\")\n            # sequence wrong length\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[])\n            # sequence wrong length\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[1, 2, 3, 4, 5])\n\n            # qtable entry not a sequence\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[1])\n            # qtable entry has wrong number of items\n            with pytest.raises(ValueError):\n                self.roundtrip(im, qtables=[[1, 2, 3, 4]])\n\n    def test_load_16bit_qtables(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            assert len(im.quantization) == 2\n            assert len(im.quantization[0]) == 64\n            assert max(im.quantization[0]) > 255\n\n    def test_save_multiple_16bit_qtables(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            im2 = self.roundtrip(im, qtables=\"keep\")\n            assert im.quantization == im2.quantization\n\n    def test_save_single_16bit_qtable(self):\n        with Image.open(\"Tests/images/hopper_16bit_qtables.jpg\") as im:\n            im2 = self.roundtrip(im, qtables={0: im.quantization[0]})\n            assert len(im2.quantization) == 1\n            assert im2.quantization[0] == im.quantization[0]\n\n    def test_save_low_quality_baseline_qtables(self):\n        with Image.open(TEST_FILE) as im:\n            im2 = self.roundtrip(im, quality=10)\n            assert len(im2.quantization) == 2\n            assert max(im2.quantization[0]) <= 255\n            assert max(im2.quantization[1]) <= 255\n\n    def test_convert_dict_qtables_deprecation(self):\n        with pytest.warns(DeprecationWarning):\n            qtable = {0: [1, 2, 3, 4]}\n            qtable2 = JpegImagePlugin.convert_dict_qtables(qtable)\n            assert qtable == qtable2\n\n    @pytest.mark.skipif(not djpeg_available(), reason=\"djpeg not available\")\n    def test_load_djpeg(self):\n        with Image.open(TEST_FILE) as img:\n            img.load_djpeg()\n            assert_image_similar_tofile(img, TEST_FILE, 5)\n\n    @pytest.mark.skipif(not cjpeg_available(), reason=\"cjpeg not available\")\n    def test_save_cjpeg(self, tmp_path):\n        with Image.open(TEST_FILE) as img:\n            tempfile = str(tmp_path / \"temp.jpg\")\n            JpegImagePlugin._save_cjpeg(img, 0, tempfile)\n            # Default save quality is 75%, so a tiny bit of difference is alright\n            assert_image_similar_tofile(img, tempfile, 17)\n\n    def test_no_duplicate_0x1001_tag(self):\n        # Arrange\n        tag_ids = {v: k for k, v in ExifTags.TAGS.items()}\n\n        # Assert\n        assert tag_ids[\"RelatedImageWidth\"] == 0x1001\n        assert tag_ids[\"RelatedImageLength\"] == 0x1002\n\n    def test_MAXBLOCK_scaling(self, tmp_path):\n        im = self.gen_random_image((512, 512))\n        f = str(tmp_path / \"temp.jpeg\")\n        im.save(f, quality=100, optimize=True)\n\n        with Image.open(f) as reloaded:\n            # none of these should crash\n            reloaded.save(f, quality=\"keep\")\n            reloaded.save(f, quality=\"keep\", progressive=True)\n            reloaded.save(f, quality=\"keep\", optimize=True)\n\n    def test_bad_mpo_header(self):\n        \"\"\"Treat unknown MPO as JPEG\"\"\"\n        # Arrange\n\n        # Act\n        # Shouldn't raise error\n        fn = \"Tests/images/sugarshack_bad_mpo_header.jpg\"\n        with pytest.warns(UserWarning, Image.open, fn) as im:\n            # Assert\n            assert im.format == \"JPEG\"\n\n    @pytest.mark.parametrize(\"mode\", (\"1\", \"L\", \"RGB\", \"RGBX\", \"CMYK\", \"YCbCr\"))\n    def test_save_correct_modes(self, mode):\n        out = BytesIO()\n        img = Image.new(mode, (20, 20))\n        img.save(out, \"JPEG\")\n\n    @pytest.mark.parametrize(\"mode\", (\"LA\", \"La\", \"RGBA\", \"RGBa\", \"P\"))\n    def test_save_wrong_modes(self, mode):\n        # ref https://github.com/python-pillow/Pillow/issues/2005\n        out = BytesIO()\n        img = Image.new(mode, (20, 20))\n        with pytest.raises(OSError):\n            img.save(out, \"JPEG\")\n\n    def test_save_tiff_with_dpi(self, tmp_path):\n        # Arrange\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            # Act\n            im.save(outfile, \"JPEG\", dpi=im.info[\"dpi\"])\n\n            # Assert\n            with Image.open(outfile) as reloaded:\n                reloaded.load()\n                assert im.info[\"dpi\"] == reloaded.info[\"dpi\"]\n\n    def test_save_dpi_rounding(self, tmp_path):\n        outfile = str(tmp_path / \"temp.jpg\")\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            im.save(outfile, dpi=(72.2, 72.2))\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == (72, 72)\n\n            im.save(outfile, dpi=(72.8, 72.8))\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"dpi\"] == (73, 73)\n\n    def test_dpi_tuple_from_exif(self):\n        # Arrange\n        # This Photoshop CC 2017 image has DPI in EXIF not metadata\n        # EXIF XResolution is (2000000, 10000)\n        with Image.open(\"Tests/images/photoshop-200dpi.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (200, 200)\n\n    def test_dpi_int_from_exif(self):\n        # Arrange\n        # This image has DPI in EXIF not metadata\n        # EXIF XResolution is 72\n        with Image.open(\"Tests/images/exif-72dpi-int.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_dpi_from_dpcm_exif(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with EXIF resolution unit set to cm:\n        # exiftool -exif:ResolutionUnit=cm photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/exif-200dpcm.jpg\") as im:\n            # Act / Assert\n            assert im.info.get(\"dpi\") == (508, 508)\n\n    def test_dpi_exif_zero_division(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with EXIF resolution set to 0/0:\n        # exiftool -XResolution=0/0 -YResolution=0/0 photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/exif-dpi-zerodivision.jpg\") as im:\n            # Act / Assert\n            # This should return the default, and not raise a ZeroDivisionError\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_dpi_exif_string(self):\n        # Arrange\n        # 0x011A tag in this exif contains string '300300\\x02'\n        with Image.open(\"Tests/images/broken_exif_dpi.jpg\") as im:\n            # Act / Assert\n            # This should return the default\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_no_dpi_in_exif(self):\n        # Arrange\n        # This is photoshop-200dpi.jpg with resolution removed from EXIF:\n        # exiftool \"-*resolution*\"= photoshop-200dpi.jpg\n        with Image.open(\"Tests/images/no-dpi-in-exif.jpg\") as im:\n            # Act / Assert\n            # \"When the image resolution is unknown, 72 [dpi] is designated.\"\n            # https://exiv2.org/tags.html\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_invalid_exif(self):\n        # This is no-dpi-in-exif with the tiff header of the exif block\n        # hexedited from MM * to FF FF FF FF\n        with Image.open(\"Tests/images/invalid-exif.jpg\") as im:\n            # This should return the default, and not a SyntaxError or\n            # OSError for unidentified image.\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_exif_x_resolution(self, tmp_path):\n        with Image.open(\"Tests/images/flower.jpg\") as im:\n            exif = im.getexif()\n            assert exif[282] == 180\n\n            out = str(tmp_path / \"out.jpg\")\n            with warnings.catch_warnings():\n                im.save(out, exif=exif)\n\n        with Image.open(out) as reloaded:\n            assert reloaded.getexif()[282] == 180\n\n    def test_invalid_exif_x_resolution(self):\n        # When no x or y resolution is defined in EXIF\n        with Image.open(\"Tests/images/invalid-exif-without-x-resolution.jpg\") as im:\n            # This should return the default, and not a ValueError or\n            # OSError for an unidentified image.\n            assert im.info.get(\"dpi\") == (72, 72)\n\n    def test_ifd_offset_exif(self):\n        # Arrange\n        # This image has been manually hexedited to have an IFD offset of 10,\n        # in contrast to normal 8\n        with Image.open(\"Tests/images/exif-ifd-offset.jpg\") as im:\n            # Act / Assert\n            assert im._getexif()[306] == \"2017:03:13 23:03:09\"\n\n    @mark_if_feature_version(\n        pytest.mark.valgrind_known_error, \"libjpeg_turbo\", \"2.0\", reason=\"Known Failing\"\n    )\n    def test_photoshop(self):\n        with Image.open(\"Tests/images/photoshop-200dpi.jpg\") as im:\n            assert im.info[\"photoshop\"][0x03ED] == {\n                \"XResolution\": 200.0,\n                \"DisplayedUnitsX\": 1,\n                \"YResolution\": 200.0,\n                \"DisplayedUnitsY\": 1,\n            }\n\n            # Test that the image can still load, even with broken Photoshop data\n            # This image had the APP13 length hexedited to be smaller\n            assert_image_equal_tofile(im, \"Tests/images/photoshop-200dpi-broken.jpg\")\n\n        # This image does not contain a Photoshop header string\n        with Image.open(\"Tests/images/app13.jpg\") as im:\n            assert \"photoshop\" not in im.info\n\n    def test_photoshop_malformed_and_multiple(self):\n        with Image.open(\"Tests/images/app13-multiple.jpg\") as im:\n            assert \"photoshop\" in im.info\n            assert 24 == len(im.info[\"photoshop\"])\n            apps_13_lengths = [len(v) for k, v in im.applist if k == \"APP13\"]\n            assert [65504, 24] == apps_13_lengths\n\n    def test_adobe_transform(self):\n        with Image.open(\"Tests/images/pil_sample_rgb.jpg\") as im:\n            assert im.info[\"adobe_transform\"] == 1\n\n        with Image.open(\"Tests/images/pil_sample_cmyk.jpg\") as im:\n            assert im.info[\"adobe_transform\"] == 2\n\n        # This image has been manually hexedited\n        # so that the APP14 reports its length to be 11,\n        # leaving no room for \"adobe_transform\"\n        with Image.open(\"Tests/images/truncated_app14.jpg\") as im:\n            assert \"adobe\" in im.info\n            assert \"adobe_transform\" not in im.info\n\n    def test_icc_after_SOF(self):\n        with Image.open(\"Tests/images/icc-after-SOF.jpg\") as im:\n            assert im.info[\"icc_profile\"] == b\"profile\"\n\n    def test_jpeg_magic_number(self):\n        size = 4097\n        buffer = BytesIO(b\"\\xFF\" * size)  # Many xFF bytes\n        buffer.max_pos = 0\n        orig_read = buffer.read\n\n        def read(n=-1):\n            res = orig_read(n)\n            buffer.max_pos = max(buffer.max_pos, buffer.tell())\n            return res\n\n        buffer.read = read\n        with pytest.raises(UnidentifiedImageError):\n            with Image.open(buffer):\n                pass\n\n        # Assert the entire file has not been read\n        assert 0 < buffer.max_pos < size\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/xmp_test.jpg\") as im:\n            if ElementTree is None:\n                with pytest.warns(UserWarning):\n                    assert im.getxmp() == {}\n            else:\n                xmp = im.getxmp()\n\n                description = xmp[\"xmpmeta\"][\"RDF\"][\"Description\"]\n                assert description[\"DerivedFrom\"] == {\n                    \"documentID\": \"8367D410E636EA95B7DE7EBA1C43A412\",\n                    \"originalDocumentID\": \"8367D410E636EA95B7DE7EBA1C43A412\",\n                }\n                assert description[\"Look\"][\"Description\"][\"Group\"][\"Alt\"][\"li\"] == {\n                    \"lang\": \"x-default\",\n                    \"text\": \"Profiles\",\n                }\n                assert description[\"ToneCurve\"][\"Seq\"][\"li\"] == [\"0, 0\", \"255, 255\"]\n\n                # Attribute\n                assert description[\"Version\"] == \"10.4\"\n\n        if ElementTree is not None:\n            with Image.open(\"Tests/images/hopper.jpg\") as im:\n                assert im.getxmp() == {}\n\n    @pytest.mark.timeout(timeout=1)\n    def test_eof(self):\n        # Even though this decoder never says that it is finished\n        # the image should still end when there is no new data\n        class InfiniteMockPyDecoder(ImageFile.PyDecoder):\n            def decode(self, buffer):\n                return 0, 0\n\n        decoder = InfiniteMockPyDecoder(None)\n\n        def closure(mode, *args):\n            decoder.__init__(mode, *args)\n            return decoder\n\n        Image.register_decoder(\"INFINITE\", closure)\n\n        with Image.open(TEST_FILE) as im:\n            im.tile = [\n                (\"INFINITE\", (0, 0, 128, 128), 0, (\"RGB\", 0, 1)),\n            ]\n            ImageFile.LOAD_TRUNCATED_IMAGES = True\n            im.load()\n            ImageFile.LOAD_TRUNCATED_IMAGES = False\n\n\n@pytest.mark.skipif(not is_win32(), reason=\"Windows only\")\n@skip_unless_feature(\"jpg\")\nclass TestFileCloseW32:\n    def test_fd_leak(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.jpg\")\n\n        with Image.open(\"Tests/images/hopper.jpg\") as im:\n            im.save(tmpfile)\n\n        im = Image.open(tmpfile)\n        fp = im.fp\n        assert not fp.closed\n        with pytest.raises(OSError):\n            os.remove(tmpfile)\n        im.load()\n        assert fp.closed\n        # this should not fail, as load should have closed the file.\n        os.remove(tmpfile)\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 17ms\nInstalled 12 packages in 145ms\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 835ms\nPrepared 1 package in 35.14s\nInstalled 1 package in 1ms\n + pillow==9.5.0.dev0 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_bb524018d35ea6450e56c3cd3db489eeb0f5a79c)\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_bb524018d35ea6450e56c3cd3db489eeb0f5a79c\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 88 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.\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 [ 59%]\n\u001b[0m\u001b[33ms\u001b[0m\u001b[33ms\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[33ms\u001b[0m\u001b[32m                                     [100%]\u001b[0m\n\n==================================== PASSES ====================================\n\u001b[32m\u001b[1m________________ TestFileJpeg.test_save_multiple_16bit_qtables _________________\u001b[0m\n----------------------------- Captured stderr call -----------------------------\nCaution: quantization tables are too coarse for baseline JPEG\n\u001b[32m\u001b[1m__________________ TestFileJpeg.test_save_single_16bit_qtable __________________\u001b[0m\n----------------------------- Captured stderr call -----------------------------\nCaution: quantization tables are too coarse for baseline JPEG\n\u001b[36m\u001b[1m=========================== short test summary info ============================\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_sanity\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_zero[size0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_zero[size1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_zero[size2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_app\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_comment_write\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_cmyk\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi[Tests/images/hopper.jpg]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi[Tests/images/pil_sample_cmyk.jpg]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[4]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[5]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[65519]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[65520]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[65536]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[65537]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[262147]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_large_icc_meta\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_optimize\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_optimize_large_buffer\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_progressive\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_progressive_large_buffer\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_progressive_large_buffer_highest_quality\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_progressive_cmyk_buffer\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_large_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif_typeerror\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif_gps\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_empty_exif_gps\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif_equality\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif_rollback\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif_gps_typeerror\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_progressive_compat\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_quality\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_smooth\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_subsampling\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_get_child_images\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_mp\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_quality_keep\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_junk_jpeg_header\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_ff00_jpeg_header\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_truncated_jpeg_should_read_all_the_data\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_truncated_jpeg_throws_oserror\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_qtables\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_load_16bit_qtables\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_multiple_16bit_qtables\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_single_16bit_qtable\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_low_quality_baseline_qtables\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_convert_dict_qtables_deprecation\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_no_duplicate_0x1001_tag\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_MAXBLOCK_scaling\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_bad_mpo_header\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_correct_modes[1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_correct_modes[L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_correct_modes[RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_correct_modes[RGBX]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_correct_modes[CMYK]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_correct_modes[YCbCr]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_wrong_modes[LA]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_wrong_modes[La]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_wrong_modes[RGBA]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_wrong_modes[RGBa]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_wrong_modes[P]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_tiff_with_dpi\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_dpi_rounding\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi_tuple_from_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi_int_from_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi_from_dpcm_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi_exif_zero_division\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi_exif_string\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_no_dpi_in_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_invalid_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif_x_resolution\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_invalid_exif_x_resolution\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_ifd_offset_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_photoshop\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_photoshop_malformed_and_multiple\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_adobe_transform\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_after_SOF\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_jpeg_magic_number\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_getxmp\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_eof\u001b[0m\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:645: djpeg not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:651: cjpeg not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:935: Windows only\n\u001b[32m======================== \u001b[32m\u001b[1m85 passed\u001b[0m, \u001b[33m3 skipped\u001b[0m\u001b[32m in 0.85s\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_bb524018d35ea6450e56c3cd3db489eeb0f5a79c\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 88 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[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[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 [ 59%]\n\u001b[0m\u001b[33ms\u001b[0m\u001b[33ms\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[33ms\u001b[0m\u001b[31m                                     [100%]\u001b[0m\n\n=================================== FAILURES ===================================\n\u001b[31m\u001b[1m_________________________ TestFileJpeg.test_large_exif _________________________\u001b[0m\n\nself = <r2e_tests.test_1.TestFileJpeg object at 0x7e7dd15c3310>\ntmp_path = PosixPath('/tmp/pytest-of-gcpuser/pytest-591/test_large_exif0')\n\n    def test_large_exif(self, tmp_path):\n        # https://github.com/python-pillow/Pillow/issues/148\n        f = str(tmp_path / \"temp.jpg\")\n        im = hopper()\n        im.save(f, \"JPEG\", quality=90, exif=b\"1\" * 65533)\n    \n        with pytest.raises(ValueError):\n>           im.save(f, \"JPEG\", quality=90, exif=b\"1\" * 65534)\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:276: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\u001b[1m\u001b[31msrc/PIL/Image.py\u001b[0m:2430: in save\n    save_handler(self, fp, filename)\n\u001b[1m\u001b[31msrc/PIL/JpegImagePlugin.py\u001b[0m:803: in _save\n    ImageFile._save(im, fp, [(\"jpeg\", (0, 0) + im.size, 0, rawmode)], bufsize)\n\u001b[1m\u001b[31msrc/PIL/ImageFile.py\u001b[0m:517: in _save\n    _encode_tile(im, fp, tile, bufsize, fh)\n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nim = <PIL.PpmImagePlugin.PpmImageFile image mode=RGB size=128x128 at 0x7E7DD1605610>\nfp = <_io.BufferedRandom name='/tmp/pytest-of-gcpuser/pytest-591/test_large_exif0/temp.jpg'>\ntile = [('jpeg', (0, 0, 128, 128), 0, 'RGB')], bufsize = 65539, fh = 12\nexc = None\n\n    def _encode_tile(im, fp, tile, bufsize, fh, exc=None):\n        for e, b, o, a in tile:\n            if o > 0:\n                fp.seek(o)\n            encoder = Image._getencoder(im.mode, e, a, im.encoderconfig)\n            try:\n                encoder.setimage(im.im, b)\n                if encoder.pushes_fd:\n                    encoder.setfd(fp)\n                    l, s = encoder.encode_to_pyfd()\n                else:\n                    if exc:\n                        # compress to Python file-compatible object\n                        while True:\n                            l, s, d = encoder.encode(bufsize)\n                            fp.write(d)\n                            if s:\n                                break\n                    else:\n                        # slight speedup: compress to real file object\n                        s = encoder.encode_to_file(fh, bufsize)\n                if s < 0:\n                    msg = f\"encoder error {s} when writing image file\"\n>                   raise OSError(msg) from exc\n\u001b[1m\u001b[31mE                   OSError: encoder error -2 when writing image file\u001b[0m\n\n\u001b[1m\u001b[31msrc/PIL/ImageFile.py\u001b[0m:547: OSError\n----------------------------- Captured stderr call -----------------------------\nBogus marker length\n==================================== PASSES ====================================\n\u001b[32m\u001b[1m________________ TestFileJpeg.test_save_multiple_16bit_qtables _________________\u001b[0m\n----------------------------- Captured stderr call -----------------------------\nCaution: quantization tables are too coarse for baseline JPEG\n\u001b[32m\u001b[1m__________________ TestFileJpeg.test_save_single_16bit_qtable __________________\u001b[0m\n----------------------------- Captured stderr call -----------------------------\nCaution: quantization tables are too coarse for baseline JPEG\n\u001b[36m\u001b[1m=========================== short test summary info ============================\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_sanity\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_zero[size0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_zero[size1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_zero[size2]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_app\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_comment_write\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_cmyk\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi[Tests/images/hopper.jpg]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi[Tests/images/pil_sample_cmyk.jpg]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[0]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[3]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[4]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[5]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[65519]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[65520]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[65536]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[65537]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_big[262147]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_large_icc_meta\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_optimize\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_optimize_large_buffer\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_progressive\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_progressive_large_buffer\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_progressive_large_buffer_highest_quality\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_progressive_cmyk_buffer\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif_typeerror\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif_gps\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_empty_exif_gps\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif_equality\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif_rollback\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif_gps_typeerror\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_progressive_compat\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_quality\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_smooth\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_subsampling\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_get_child_images\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_mp\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_quality_keep\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_junk_jpeg_header\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_ff00_jpeg_header\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_truncated_jpeg_should_read_all_the_data\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_truncated_jpeg_throws_oserror\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_qtables\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_load_16bit_qtables\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_multiple_16bit_qtables\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_single_16bit_qtable\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_low_quality_baseline_qtables\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_convert_dict_qtables_deprecation\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_no_duplicate_0x1001_tag\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_MAXBLOCK_scaling\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_bad_mpo_header\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_correct_modes[1]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_correct_modes[L]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_correct_modes[RGB]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_correct_modes[RGBX]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_correct_modes[CMYK]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_correct_modes[YCbCr]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_wrong_modes[LA]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_wrong_modes[La]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_wrong_modes[RGBA]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_wrong_modes[RGBa]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_wrong_modes[P]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_tiff_with_dpi\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_save_dpi_rounding\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi_tuple_from_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi_int_from_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi_from_dpcm_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi_exif_zero_division\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_dpi_exif_string\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_no_dpi_in_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_invalid_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_exif_x_resolution\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_invalid_exif_x_resolution\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_ifd_offset_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_photoshop\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_photoshop_malformed_and_multiple\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_adobe_transform\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_icc_after_SOF\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_jpeg_magic_number\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_getxmp\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_eof\u001b[0m\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:645: djpeg not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:651: cjpeg not available\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:935: Windows only\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileJpeg::test_large_exif\u001b[0m - OSError: encoder error -2 when writing image file\n\u001b[31m=================== \u001b[31m\u001b[1m1 failed\u001b[0m, \u001b[32m84 passed\u001b[0m, \u001b[33m3 skipped\u001b[0m\u001b[31m in 0.78s\u001b[0m\u001b[31m ====================\u001b[0m\n",
    "old_commit_res_stderr": ""
}
Tests/test_file_jpeg.py src/PIL/JpegImagePlugin.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
**Analysis:**

1. **Key Parts:**
   - **Commit Message:** "Raise an error when EXIF data is too long."
   - **Test Changes:** The test was updated to save a JPEG with `exif=b"1" * 65534` and expects a `ValueError`.
   - **Old Test Result:** A test named `test_large_exif` failed with `OSError: encoder error -2 when writing image file`.
   
2. **Main Problem:**
   - When saving a JPEG with EXIF data exceeding 65533 bytes, the system raises an unclear `OSError` instead of a specific, informative error.

3. **Expected Behavior:**
   - The system should raise a clear and specific error indicating that the EXIF data is too long when exceeding the maximum allowed size.

4. **Actual Behavior:**
   - An `OSError` with the message "encoder error -2 when writing image file" is raised, which does not clearly indicate the nature of the issue.

5. **Issue Description:**
   - The issue should clearly state that saving a JPEG with excessively long EXIF data results in an unclear error, making it difficult for developers to understand and handle the problem.

---

[ISSUE]
### Saving JPEG with EXIF Data Exceeding 65533 Bytes Raises an Unclear OSError

#### Description
When attempting to save a JPEG image with EXIF data longer than 65533 bytes, an unclear `OSError` is raised. This makes it difficult to understand the root cause of the failure and handle it appropriately.

#### Example Code
```python
from PIL import Image

# Open an existing image
im = Image.open("example.jpg")

# Create EXIF data exceeding the maximum allowed size
exif_data = b"1" * 65534

# Attempt to save the image with oversized EXIF data
im.save("output.jpg", "JPEG", exif=exif_data)
```

#### Expected Behavior
The system should raise a clear and specific error indicating that the EXIF data is too long, allowing developers to understand and address the issue effectively.

#### Actual Behavior
An `OSError` is raised with the message:
```
OSError: encoder error -2 when writing image file
```
This error does not clearly indicate that the EXIF data length is the cause of the failure.
[/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/JpegImagePlugin.py
#
# The Python Imaging Library.
# $Id$
#
# JPEG (JFIF) file handling
#
# See "Digital Compression and Coding of Continuous-Tone Still Images,
# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
#
# History:
# 1995-09-09 fl   Created
# 1995-09-13 fl   Added full parser
# 1996-03-25 fl   Added hack to use the IJG command line utilities
# 1996-05-05 fl   Workaround Photoshop 2.5 CMYK polarity bug
# 1996-05-28 fl   Added draft support, JFIF version (0.1)
# 1996-12-30 fl   Added encoder options, added progression property (0.2)
# 1997-08-27 fl   Save mode 1 images as BW (0.3)
# 1998-07-12 fl   Added YCbCr to draft and save methods (0.4)
# 1998-10-19 fl   Don't hang on files using 16-bit DQT's (0.4.1)
# 2001-04-16 fl   Extract DPI settings from JFIF files (0.4.2)
# 2002-07-01 fl   Skip pad bytes before markers; identify Exif files (0.4.3)
# 2003-04-25 fl   Added experimental EXIF decoder (0.5)
# 2003-06-06 fl   Added experimental EXIF GPSinfo decoder
# 2003-09-13 fl   Extract COM markers
# 2009-09-06 fl   Added icc_profile support (from Florian Hoech)
# 2009-03-06 fl   Changed CMYK handling; always use Adobe polarity (0.6)
# 2009-03-08 fl   Added subsampling support (from Justin Huff).
#
# Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-1996 by Fredrik Lundh.
#
# See the README file for information on usage and redistribution.
#
import array
import io
import math
import os
import struct
import subprocess
import sys
import tempfile
import warnings

from . import Image, ImageFile
from ._binary import i16be as i16
from ._binary import i32be as i32
from ._binary import o8
from ._binary import o16be as o16
from ._deprecate import deprecate
from .JpegPresets import presets

#
# Parser


def Skip(self, marker):
    n = i16(self.fp.read(2)) - 2
    ImageFile._safe_read(self.fp, n)


def APP(self, marker):
    #
    # Application marker.  Store these in the APP dictionary.
    # Also look for well-known application markers.

    n = i16(self.fp.read(2)) - 2
    s = ImageFile._safe_read(self.fp, n)

    app = "APP%d" % (marker & 15)

    self.app[app] = s  # compatibility
    self.applist.append((app, s))

    if marker == 0xFFE0 and s[:4] == b"JFIF":
        # extract JFIF information
        self.info["jfif"] = version = i16(s, 5)  # version
        self.info["jfif_version"] = divmod(version, 256)
        # extract JFIF properties
        try:
            jfif_unit = s[7]
            jfif_density = i16(s, 8), i16(s, 10)
        except Exception:
            pass
        else:
            if jfif_unit == 1:
                self.info["dpi"] = jfif_density
            self.info["jfif_unit"] = jfif_unit
            self.info["jfif_density"] = jfif_density
    elif marker == 0xFFE1 and s[:5] == b"Exif\0":
        if "exif" not in self.info:
            # extract EXIF information (incomplete)
            self.info["exif"] = s  # FIXME: value will change
            self._exif_offset = self.fp.tell() - n + 6
    elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
        # extract FlashPix information (incomplete)
        self.info["flashpix"] = s  # FIXME: value will change
    elif marker == 0xFFE2 and s[:12] == b"ICC_PROFILE\0":
        # Since an ICC profile can be larger than the maximum size of
        # a JPEG marker (64K), we need provisions to split it into
        # multiple markers. The format defined by the ICC specifies
        # one or more APP2 markers containing the following data:
        #   Identifying string      ASCII "ICC_PROFILE\0"  (12 bytes)
        #   Marker sequence number  1, 2, etc (1 byte)
        #   Number of markers       Total of APP2's used (1 byte)
        #   Profile data            (remainder of APP2 data)
        # Decoders should use the marker sequence numbers to
        # reassemble the profile, rather than assuming that the APP2
        # markers appear in the correct sequence.
        self.icclist.append(s)
    elif marker == 0xFFED and s[:14] == b"Photoshop 3.0\x00":
        # parse the image resource block
        offset = 14
        photoshop = self.info.setdefault("photoshop", {})
        while s[offset : offset + 4] == b"8BIM":
            try:
                offset += 4
                # resource code
                code = i16(s, offset)
                offset += 2
                # resource name (usually empty)
                name_len = s[offset]
                # name = s[offset+1:offset+1+name_len]
                offset += 1 + name_len
                offset += offset & 1  # align
                # resource data block
                size = i32(s, offset)
                offset += 4
                data = s[offset : offset + size]
                if code == 0x03ED:  # ResolutionInfo
                    data = {
                        "XResolution": i32(data, 0) / 65536,
                        "DisplayedUnitsX": i16(data, 4),
                        "YResolution": i32(data, 8) / 65536,
                        "DisplayedUnitsY": i16(data, 12),
                    }
                photoshop[code] = data
                offset += size
                offset += offset & 1  # align
            except struct.error:
                break  # insufficient data

    elif marker == 0xFFEE and s[:5] == b"Adobe":
        self.info["adobe"] = i16(s, 5)
        # extract Adobe custom properties
        try:
            adobe_transform = s[11]
        except IndexError:
            pass
        else:
            self.info["adobe_transform"] = adobe_transform
    elif marker == 0xFFE2 and s[:4] == b"MPF\0":
        # extract MPO information
        self.info["mp"] = s[4:]
        # offset is current location minus buffer size
        # plus constant header size
        self.info["mpoffset"] = self.fp.tell() - n + 4

    # If DPI isn't in JPEG header, fetch from EXIF
    if "dpi" not in self.info and "exif" in self.info:
        try:
            exif = self.getexif()
            resolution_unit = exif[0x0128]
            x_resolution = exif[0x011A]
            try:
                dpi = float(x_resolution[0]) / x_resolution[1]
            except TypeError:
                dpi = x_resolution
            if math.isnan(dpi):
                raise ValueError
            if resolution_unit == 3:  # cm
                # 1 dpcm = 2.54 dpi
                dpi *= 2.54
            self.info["dpi"] = dpi, dpi
        except (TypeError, KeyError, SyntaxError, ValueError, ZeroDivisionError):
            # SyntaxError for invalid/unreadable EXIF
            # KeyError for dpi not included
            # ZeroDivisionError for invalid dpi rational value
            # ValueError or TypeError for dpi being an invalid float
            self.info["dpi"] = 72, 72


def COM(self, marker):
    #
    # Comment marker.  Store these in the APP dictionary.
    n = i16(self.fp.read(2)) - 2
    s = ImageFile._safe_read(self.fp, n)

    self.info["comment"] = s
    self.app["COM"] = s  # compatibility
    self.applist.append(("COM", s))


def SOF(self, marker):
    #
    # Start of frame marker.  Defines the size and mode of the
    # image.  JPEG is colour blind, so we use some simple
    # heuristics to map the number of layers to an appropriate
    # mode.  Note that this could be made a bit brighter, by
    # looking for JFIF and Adobe APP markers.

    n = i16(self.fp.read(2)) - 2
    s = ImageFile._safe_read(self.fp, n)
    self._size = i16(s, 3), i16(s, 1)

    self.bits = s[0]
    if self.bits != 8:
        msg = f"cannot handle {self.bits}-bit layers"
        raise SyntaxError(msg)

    self.layers = s[5]
    if self.layers == 1:
        self.mode = "L"
    elif self.layers == 3:
        self.mode = "RGB"
    elif self.layers == 4:
        self.mode = "CMYK"
    else:
        msg = f"cannot handle {self.layers}-layer images"
        raise SyntaxError(msg)

    if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]:
        self.info["progressive"] = self.info["progression"] = 1

    if self.icclist:
        # fixup icc profile
        self.icclist.sort()  # sort by sequence number
        if self.icclist[0][13] == len(self.icclist):
            profile = []
            for p in self.icclist:
                profile.append(p[14:])
            icc_profile = b"".join(profile)
        else:
            icc_profile = None  # wrong number of fragments
        self.info["icc_profile"] = icc_profile
        self.icclist = []

    for i in range(6, len(s), 3):
        t = s[i : i + 3]
        # 4-tuples: id, vsamp, hsamp, qtable
        self.layer.append((t[0], t[1] // 16, t[1] & 15, t[2]))


def DQT(self, marker):
    #
    # Define quantization table.  Note that there might be more
    # than one table in each marker.

    # FIXME: The quantization tables can be used to estimate the
    # compression quality.

    n = i16(self.fp.read(2)) - 2
    s = ImageFile._safe_read(self.fp, n)
    while len(s):
        v = s[0]
        precision = 1 if (v // 16 == 0) else 2  # in bytes
        qt_length = 1 + precision * 64
        if len(s) < qt_length:
            msg = "bad quantization table marker"
            raise SyntaxError(msg)
        data = array.array("B" if precision == 1 else "H", s[1:qt_length])
        if sys.byteorder == "little" and precision > 1:
            data.byteswap()  # the values are always big-endian
        self.quantization[v & 15] = [data[i] for i in zigzag_index]
        s = s[qt_length:]


#
# JPEG marker table

MARKER = {
    0xFFC0: ("SOF0", "Baseline DCT", SOF),
    0xFFC1: ("SOF1", "Extended Sequential DCT", SOF),
    0xFFC2: ("SOF2", "Progressive DCT", SOF),
    0xFFC3: ("SOF3", "Spatial lossless", SOF),
    0xFFC4: ("DHT", "Define Huffman table", Skip),
    0xFFC5: ("SOF5", "Differential sequential DCT", SOF),
    0xFFC6: ("SOF6", "Differential progressive DCT", SOF),
    0xFFC7: ("SOF7", "Differential spatial", SOF),
    0xFFC8: ("JPG", "Extension", None),
    0xFFC9: ("SOF9", "Extended sequential DCT (AC)", SOF),
    0xFFCA: ("SOF10", "Progressive DCT (AC)", SOF),
    0xFFCB: ("SOF11", "Spatial lossless DCT (AC)", SOF),
    0xFFCC: ("DAC", "Define arithmetic coding conditioning", Skip),
    0xFFCD: ("SOF13", "Differential sequential DCT (AC)", SOF),
    0xFFCE: ("SOF14", "Differential progressive DCT (AC)", SOF),
    0xFFCF: ("SOF15", "Differential spatial (AC)", SOF),
    0xFFD0: ("RST0", "Restart 0", None),
    0xFFD1: ("RST1", "Restart 1", None),
    0xFFD2: ("RST2", "Restart 2", None),
    0xFFD3: ("RST3", "Restart 3", None),
    0xFFD4: ("RST4", "Restart 4", None),
    0xFFD5: ("RST5", "Restart 5", None),
    0xFFD6: ("RST6", "Restart 6", None),
    0xFFD7: ("RST7", "Restart 7", None),
    0xFFD8: ("SOI", "Start of image", None),
    0xFFD9: ("EOI", "End of image", None),
    0xFFDA: ("SOS", "Start of scan", Skip),
    0xFFDB: ("DQT", "Define quantization table", DQT),
    0xFFDC: ("DNL", "Define number of lines", Skip),
    0xFFDD: ("DRI", "Define restart interval", Skip),
    0xFFDE: ("DHP", "Define hierarchical progression", SOF),
    0xFFDF: ("EXP", "Expand reference component", Skip),
    0xFFE0: ("APP0", "Application segment 0", APP),
    0xFFE1: ("APP1", "Application segment 1", APP),
    0xFFE2: ("APP2", "Application segment 2", APP),
    0xFFE3: ("APP3", "Application segment 3", APP),
    0xFFE4: ("APP4", "Application segment 4", APP),
    0xFFE5: ("APP5", "Application segment 5", APP),
    0xFFE6: ("APP6", "Application segment 6", APP),
    0xFFE7: ("APP7", "Application segment 7", APP),
    0xFFE8: ("APP8", "Application segment 8", APP),
    0xFFE9: ("APP9", "Application segment 9", APP),
    0xFFEA: ("APP10", "Application segment 10", APP),
    0xFFEB: ("APP11", "Application segment 11", APP),
    0xFFEC: ("APP12", "Application segment 12", APP),
    0xFFED: ("APP13", "Application segment 13", APP),
    0xFFEE: ("APP14", "Application segment 14", APP),
    0xFFEF: ("APP15", "Application segment 15", APP),
    0xFFF0: ("JPG0", "Extension 0", None),
    0xFFF1: ("JPG1", "Extension 1", None),
    0xFFF2: ("JPG2", "Extension 2", None),
    0xFFF3: ("JPG3", "Extension 3", None),
    0xFFF4: ("JPG4", "Extension 4", None),
    0xFFF5: ("JPG5", "Extension 5", None),
    0xFFF6: ("JPG6", "Extension 6", None),
    0xFFF7: ("JPG7", "Extension 7", None),
    0xFFF8: ("JPG8", "Extension 8", None),
    0xFFF9: ("JPG9", "Extension 9", None),
    0xFFFA: ("JPG10", "Extension 10", None),
    0xFFFB: ("JPG11", "Extension 11", None),
    0xFFFC: ("JPG12", "Extension 12", None),
    0xFFFD: ("JPG13", "Extension 13", None),
    0xFFFE: ("COM", "Comment", COM),
}


def _accept(prefix):
    # Magic number was taken from https://en.wikipedia.org/wiki/JPEG
    return prefix[:3] == b"\xFF\xD8\xFF"


##
# Image plugin for JPEG and JFIF images.


class JpegImageFile(ImageFile.ImageFile):
    format = "JPEG"
    format_description = "JPEG (ISO 10918)"

    def _open(self):
        s = self.fp.read(3)

        if not _accept(s):
            msg = "not a JPEG file"
            raise SyntaxError(msg)
        s = b"\xFF"

        # Create attributes
        self.bits = self.layers = 0

        # JPEG specifics (internal)
        self.layer = []
        self.huffman_dc = {}
        self.huffman_ac = {}
        self.quantization = {}
        self.app = {}  # compatibility
        self.applist = []
        self.icclist = []

        while True:
            i = s[0]
            if i == 0xFF:
                s = s + self.fp.read(1)
                i = i16(s)
            else:
                # Skip non-0xFF junk
                s = self.fp.read(1)
                continue

            if i in MARKER:
                name, description, handler = MARKER[i]
                if handler is not None:
                    handler(self, i)
                if i == 0xFFDA:  # start of scan
                    rawmode = self.mode
                    if self.mode == "CMYK":
                        rawmode = "CMYK;I"  # assume adobe conventions
                    self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))]
                    # self.__offset = self.fp.tell()
                    break
                s = self.fp.read(1)
            elif i == 0 or i == 0xFFFF:
                # padded marker or junk; move on
                s = b"\xff"
            elif i == 0xFF00:  # Skip extraneous data (escaped 0xFF)
                s = self.fp.read(1)
            else:
                msg = "no marker found"
                raise SyntaxError(msg)

    def load_read(self, read_bytes):
        """
        internal: read more image data
        For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker
        so libjpeg can finish decoding
        """
        s = self.fp.read(read_bytes)

        if not s and ImageFile.LOAD_TRUNCATED_IMAGES and not hasattr(self, "_ended"):
            # Premature EOF.
            # Pretend file is finished adding EOI marker
            self._ended = True
            return b"\xFF\xD9"

        return s

    def draft(self, mode, size):
        if len(self.tile) != 1:
            return

        # Protect from second call
        if self.decoderconfig:
            return

        d, e, o, a = self.tile[0]
        scale = 1
        original_size = self.size

        if a[0] == "RGB" and mode in ["L", "YCbCr"]:
            self.mode = mode
            a = mode, ""

        if size:
            scale = min(self.size[0] // size[0], self.size[1] // size[1])
            for s in [8, 4, 2, 1]:
                if scale >= s:
                    break
            e = (
                e[0],
                e[1],
                (e[2] - e[0] + s - 1) // s + e[0],
                (e[3] - e[1] + s - 1) // s + e[1],
            )
            self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s)
            scale = s

        self.tile = [(d, e, o, a)]
        self.decoderconfig = (scale, 0)

        box = (0, 0, original_size[0] / scale, original_size[1] / scale)
        return self.mode, box

    def load_djpeg(self):
        # ALTERNATIVE: handle JPEGs via the IJG command line utilities

        f, path = tempfile.mkstemp()
        os.close(f)
        if os.path.exists(self.filename):
            subprocess.check_call(["djpeg", "-outfile", path, self.filename])
        else:
            msg = "Invalid Filename"
            raise ValueError(msg)

        try:
            with Image.open(path) as _im:
                _im.load()
                self.im = _im.im
        finally:
            try:
                os.unlink(path)
            except OSError:
                pass

        self.mode = self.im.mode
        self._size = self.im.size

        self.tile = []

    def _getexif(self):
        return _getexif(self)

    def _getmp(self):
        return _getmp(self)

    def getxmp(self):
        """
        Returns a dictionary containing the XMP tags.
        Requires defusedxml to be installed.

        :returns: XMP tags in a dictionary.
        """

        for segment, content in self.applist:
            if segment == "APP1":
                marker, xmp_tags = content.rsplit(b"\x00", 1)
                if marker == b"http://ns.adobe.com/xap/1.0/":
                    return self._getxmp(xmp_tags)
        return {}


def _getexif(self):
    if "exif" not in self.info:
        return None
    return self.getexif()._get_merged_dict()


def _getmp(self):
    # Extract MP information.  This method was inspired by the "highly
    # experimental" _getexif version that's been in use for years now,
    # itself based on the ImageFileDirectory class in the TIFF plugin.

    # The MP record essentially consists of a TIFF file embedded in a JPEG
    # application marker.
    try:
        data = self.info["mp"]
    except KeyError:
        return None
    file_contents = io.BytesIO(data)
    head = file_contents.read(8)
    endianness = ">" if head[:4] == b"\x4d\x4d\x00\x2a" else "<"
    # process dictionary
    from . import TiffImagePlugin

    try:
        info = TiffImagePlugin.ImageFileDirectory_v2(head)
        file_contents.seek(info.next)
        info.load(file_contents)
        mp = dict(info)
    except Exception as e:
        msg = "malformed MP Index (unreadable directory)"
        raise SyntaxError(msg) from e
    # it's an error not to have a number of images
    try:
        quant = mp[0xB001]
    except KeyError as e:
        msg = "malformed MP Index (no number of images)"
        raise SyntaxError(msg) from e
    # get MP entries
    mpentries = []
    try:
        rawmpentries = mp[0xB002]
        for entrynum in range(0, quant):
            unpackedentry = struct.unpack_from(
                f"{endianness}LLLHH", rawmpentries, entrynum * 16
            )
            labels = ("Attribute", "Size", "DataOffset", "EntryNo1", "EntryNo2")
            mpentry = dict(zip(labels, unpackedentry))
            mpentryattr = {
                "DependentParentImageFlag": bool(mpentry["Attribute"] & (1 << 31)),
                "DependentChildImageFlag": bool(mpentry["Attribute"] & (1 << 30)),
                "RepresentativeImageFlag": bool(mpentry["Attribute"] & (1 << 29)),
                "Reserved": (mpentry["Attribute"] & (3 << 27)) >> 27,
                "ImageDataFormat": (mpentry["Attribute"] & (7 << 24)) >> 24,
                "MPType": mpentry["Attribute"] & 0x00FFFFFF,
            }
            if mpentryattr["ImageDataFormat"] == 0:
                mpentryattr["ImageDataFormat"] = "JPEG"
            else:
                msg = "unsupported picture format in MPO"
                raise SyntaxError(msg)
            mptypemap = {
                0x000000: "Undefined",
                0x010001: "Large Thumbnail (VGA Equivalent)",
                0x010002: "Large Thumbnail (Full HD Equivalent)",
                0x020001: "Multi-Frame Image (Panorama)",
                0x020002: "Multi-Frame Image: (Disparity)",
                0x020003: "Multi-Frame Image: (Multi-Angle)",
                0x030000: "Baseline MP Primary Image",
            }
            mpentryattr["MPType"] = mptypemap.get(mpentryattr["MPType"], "Unknown")
            mpentry["Attribute"] = mpentryattr
            mpentries.append(mpentry)
        mp[0xB002] = mpentries
    except KeyError as e:
        msg = "malformed MP Index (bad MP Entry)"
        raise SyntaxError(msg) from e
    # Next we should try and parse the individual image unique ID list;
    # we don't because I've never seen this actually used in a real MPO
    # file and so can't test it.
    return mp


# --------------------------------------------------------------------
# stuff to save JPEG files

RAWMODE = {
    "1": "L",
    "L": "L",
    "RGB": "RGB",
    "RGBX": "RGB",
    "CMYK": "CMYK;I",  # assume adobe conventions
    "YCbCr": "YCbCr",
}

# fmt: off
zigzag_index = (
    0,  1,  5,  6, 14, 15, 27, 28,
    2,  4,  7, 13, 16, 26, 29, 42,
    3,  8, 12, 17, 25, 30, 41, 43,
    9, 11, 18, 24, 31, 40, 44, 53,
    10, 19, 23, 32, 39, 45, 52, 54,
    20, 22, 33, 38, 46, 51, 55, 60,
    21, 34, 37, 47, 50, 56, 59, 61,
    35, 36, 48, 49, 57, 58, 62, 63,
)

samplings = {
    (1, 1, 1, 1, 1, 1): 0,
    (2, 1, 1, 1, 1, 1): 1,
    (2, 2, 1, 1, 1, 1): 2,
}
# fmt: on


def convert_dict_qtables(qtables):
    deprecate("convert_dict_qtables", 10, action="Conversion is no longer needed")
    return qtables


def get_sampling(im):
    # There's no subsampling when images have only 1 layer
    # (grayscale images) or when they are CMYK (4 layers),
    # so set subsampling to the default value.
    #
    # NOTE: currently Pillow can't encode JPEG to YCCK format.
    # If YCCK support is added in the future, subsampling code will have
    # to be updated (here and in JpegEncode.c) to deal with 4 layers.
    if not hasattr(im, "layers") or im.layers in (1, 4):
        return -1
    sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
    return samplings.get(sampling, -1)


def _save(im, fp, filename):
    if im.width == 0 or im.height == 0:
        msg = "cannot write empty image as JPEG"
        raise ValueError(msg)

    try:
        rawmode = RAWMODE[im.mode]
    except KeyError as e:
        msg = f"cannot write mode {im.mode} as JPEG"
        raise OSError(msg) from e

    info = im.encoderinfo

    dpi = [round(x) for x in info.get("dpi", (0, 0))]

    quality = info.get("quality", -1)
    subsampling = info.get("subsampling", -1)
    qtables = info.get("qtables")

    if quality == "keep":
        quality = -1
        subsampling = "keep"
        qtables = "keep"
    elif quality in presets:
        preset = presets[quality]
        quality = -1
        subsampling = preset.get("subsampling", -1)
        qtables = preset.get("quantization")
    elif not isinstance(quality, int):
        msg = "Invalid quality setting"
        raise ValueError(msg)
    else:
        if subsampling in presets:
            subsampling = presets[subsampling].get("subsampling", -1)
        if isinstance(qtables, str) and qtables in presets:
            qtables = presets[qtables].get("quantization")

    if subsampling == "4:4:4":
        subsampling = 0
    elif subsampling == "4:2:2":
        subsampling = 1
    elif subsampling == "4:2:0":
        subsampling = 2
    elif subsampling == "4:1:1":
        # For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0.
        # Set 4:2:0 if someone is still using that value.
        subsampling = 2
    elif subsampling == "keep":
        if im.format != "JPEG":
            msg = "Cannot use 'keep' when original image is not a JPEG"
            raise ValueError(msg)
        subsampling = get_sampling(im)

    def validate_qtables(qtables):
        if qtables is None:
            return qtables
        if isinstance(qtables, str):
            try:
                lines = [
                    int(num)
                    for line in qtables.splitlines()
                    for num in line.split("#", 1)[0].split()
                ]
            except ValueError as e:
                msg = "Invalid quantization table"
                raise ValueError(msg) from e
            else:
                qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)]
        if isinstance(qtables, (tuple, list, dict)):
            if isinstance(qtables, dict):
                qtables = [
                    qtables[key] for key in range(len(qtables)) if key in qtables
                ]
            elif isinstance(qtables, tuple):
                qtables = list(qtables)
            if not (0 < len(qtables) < 5):
                msg = "None or too many quantization tables"
                raise ValueError(msg)
            for idx, table in enumerate(qtables):
                try:
                    if len(table) != 64:
                        raise TypeError
                    table = array.array("H", table)
                except TypeError as e:
                    msg = "Invalid quantization table"
                    raise ValueError(msg) from e
                else:
                    qtables[idx] = list(table)
            return qtables

    if qtables == "keep":
        if im.format != "JPEG":
            msg = "Cannot use 'keep' when original image is not a JPEG"
            raise ValueError(msg)
        qtables = getattr(im, "quantization", None)
    qtables = validate_qtables(qtables)

    extra = info.get("extra", b"")

    icc_profile = info.get("icc_profile")
    if icc_profile:
        ICC_OVERHEAD_LEN = 14
        MAX_BYTES_IN_MARKER = 65533
        MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN
        markers = []
        while icc_profile:
            markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER])
            icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:]
        i = 1
        for marker in markers:
            size = o16(2 + ICC_OVERHEAD_LEN + len(marker))
            extra += (
                b"\xFF\xE2"
                + size
                + b"ICC_PROFILE\0"
                + o8(i)
                + o8(len(markers))
                + marker
            )
            i += 1

    comment = info.get("comment", im.info.get("comment"))

    # "progressive" is the official name, but older documentation
    # says "progression"
    # FIXME: issue a warning if the wrong form is used (post-1.1.7)
    progressive = info.get("progressive", False) or info.get("progression", False)

    optimize = info.get("optimize", False)

    exif = info.get("exif", b"")
    if isinstance(exif, Image.Exif):
        exif = exif.tobytes()

    # get keyword arguments
    im.encoderconfig = (
        quality,
        progressive,
        info.get("smooth", 0),
        optimize,
        info.get("streamtype", 0),
        dpi[0],
        dpi[1],
        subsampling,
        qtables,
        comment,
        extra,
        exif,
    )

    # if we optimize, libjpeg needs a buffer big enough to hold the whole image
    # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is
    # channels*size, this is a value that's been used in a django patch.
    # https://github.com/matthewwithanm/django-imagekit/issues/50
    bufsize = 0
    if optimize or progressive:
        # CMYK can be bigger
        if im.mode == "CMYK":
            bufsize = 4 * im.size[0] * im.size[1]
        # keep sets quality to -1, but the actual value may be high.
        elif quality >= 95 or quality == -1:
            bufsize = 2 * im.size[0] * im.size[1]
        else:
            bufsize = im.size[0] * im.size[1]

    # The EXIF info needs to be written as one block, + APP1, + one spare byte.
    # Ensure that our buffer is big enough. Same with the icc_profile block.
    bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, len(extra) + 1)

    ImageFile._save(im, fp, [("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize)


def _save_cjpeg(im, fp, filename):
    # ALTERNATIVE: handle JPEGs via the IJG command line utilities.
    tempfile = im._dump()
    subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
    try:
        os.unlink(tempfile)
    except OSError:
        pass


##
# Factory for making JPEG and MPO instances
def jpeg_factory(fp=None, filename=None):
    im = JpegImageFile(fp, filename)
    try:
        mpheader = im._getmp()
        if mpheader[45057] > 1:
            # It's actually an MPO
            from .MpoImagePlugin import MpoImageFile

            # Don't reload everything, just convert it.
            im = MpoImageFile.adopt(im, mpheader)
    except (TypeError, IndexError):
        # It is really a JPEG
        pass
    except SyntaxError:
        warnings.warn(
            "Image appears to be a malformed MPO file, it will be "
            "interpreted as a base JPEG file"
        )
    return im


# ---------------------------------------------------------------------
# Registry stuff

Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
Image.register_save(JpegImageFile.format, _save)

Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".jpeg"])

Image.register_mime(JpegImageFile.format, "image/jpeg")

```
--- 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.
**Analysis:**

1. **Key Parts:**
   - **Commit Message:** "Raise an error when EXIF data is too long."
   - **Test Changes:** The test was updated to save a JPEG with `exif=b"1" * 65534` and expects a `ValueError`.
   - **Old Test Result:** A test named `test_large_exif` failed with `OSError: encoder error -2 when writing image file`.
   
2. **Main Problem:**
   - When saving a JPEG with EXIF data exceeding 65533 bytes, the system raises an unclear `OSError` instead of a specific, informative error.

3. **Expected Behavior:**
   - The system should raise a clear and specific error indicating that the EXIF data is too long when exceeding the maximum allowed size.

4. **Actual Behavior:**
   - An `OSError` with the message "encoder error -2 when writing image file" is raised, which does not clearly indicate the nature of the issue.

5. **Issue Description:**
   - The issue should clearly state that saving a JPEG with excessively long EXIF data results in an unclear error, making it difficult for developers to understand and handle the problem.

---

[ISSUE]
### Saving JPEG with EXIF Data Exceeding 65533 Bytes Raises an Unclear OSError

#### Description
When attempting to save a JPEG image with EXIF data longer than 65533 bytes, an unclear `OSError` is raised. This makes it difficult to understand the root cause of the failure and handle it appropriately.

#### Example Code
```python
from PIL import Image

# Open an existing image
im = Image.open("example.jpg")

# Create EXIF data exceeding the maximum allowed size
exif_data = b"1" * 65534

# Attempt to save the image with oversized EXIF data
im.save("output.jpg", "JPEG", exif=exif_data)
```

#### Expected Behavior
The system should raise a clear and specific error indicating that the EXIF data is too long, allowing developers to understand and address the issue effectively.

#### Actual Behavior
An `OSError` is raised with the message:
```
OSError: encoder error -2 when writing image file
```
This error does not clearly indicate that the EXIF data length is the cause of the failure.
[/ISSUE]
{
    "\u001b[1mTestFileJpeg.test_sanity\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_zero[size0]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_zero[size1]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_zero[size2]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_app\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_comment_write\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_cmyk\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_dpi[Tests/images/hopper.jpg]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_dpi[Tests/images/pil_sample_cmyk.jpg]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_icc\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_icc_big[0]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_icc_big[1]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_icc_big[3]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_icc_big[4]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_icc_big[5]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_icc_big[65519]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_icc_big[65520]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_icc_big[65536]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_icc_big[65537]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_icc_big[262147]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_large_icc_meta\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_optimize\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_optimize_large_buffer\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_progressive\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_progressive_large_buffer\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_progressive_large_buffer_highest_quality\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_progressive_cmyk_buffer\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_large_exif\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_exif_typeerror\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_exif_gps\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_empty_exif_gps\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_exif_equality\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_exif_rollback\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_exif_gps_typeerror\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_progressive_compat\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_quality\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_smooth\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_subsampling\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_exif\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_get_child_images\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_mp\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_quality_keep\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_junk_jpeg_header\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_ff00_jpeg_header\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_truncated_jpeg_should_read_all_the_data\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_truncated_jpeg_throws_oserror\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_qtables\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_load_16bit_qtables\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_multiple_16bit_qtables\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_single_16bit_qtable\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_low_quality_baseline_qtables\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_convert_dict_qtables_deprecation\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_no_duplicate_0x1001_tag\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_MAXBLOCK_scaling\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_bad_mpo_header\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_correct_modes[1]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_correct_modes[L]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_correct_modes[RGB]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_correct_modes[RGBX]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_correct_modes[CMYK]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_correct_modes[YCbCr]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_wrong_modes[LA]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_wrong_modes[La]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_wrong_modes[RGBA]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_wrong_modes[RGBa]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_wrong_modes[P]\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_tiff_with_dpi\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_save_dpi_rounding\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_dpi_tuple_from_exif\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_dpi_int_from_exif\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_dpi_from_dpcm_exif\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_dpi_exif_zero_division\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_dpi_exif_string\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_no_dpi_in_exif\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_invalid_exif\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_exif_x_resolution\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_invalid_exif_x_resolution\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_ifd_offset_exif\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_photoshop\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_photoshop_malformed_and_multiple\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_adobe_transform\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_icc_after_SOF\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_jpeg_magic_number\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_getxmp\u001b[0m": "PASSED",
    "\u001b[1mTestFileJpeg.test_eof\u001b[0m": "PASSED"
}
diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py
index d7ddbe0d9..71ae84c04 100644
--- a/src/PIL/JpegImagePlugin.py
+++ b/src/PIL/JpegImagePlugin.py
@@ -730,10 +730,10 @@ def _save(im, fp, filename):
 
     extra = info.get("extra", b"")
 
+    MAX_BYTES_IN_MARKER = 65533
     icc_profile = info.get("icc_profile")
     if icc_profile:
         ICC_OVERHEAD_LEN = 14
-        MAX_BYTES_IN_MARKER = 65533
         MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN
         markers = []
         while icc_profile:
@@ -764,6 +764,9 @@ def _save(im, fp, filename):
     exif = info.get("exif", b"")
     if isinstance(exif, Image.Exif):
         exif = exif.tobytes()
+    if len(exif) > MAX_BYTES_IN_MARKER:
+        msg = "EXIF data is too long"
+        raise ValueError(msg)
 
     # get keyword arguments
     im.encoderconfig = (
 

{"src/PIL/JpegImagePlugin.py": "#\n# The Python Imaging Library.\n# $Id$\n#\n# JPEG (JFIF) file handling\n#\n# See \"Digital Compression and Coding of Continuous-Tone Still Images,\n# Part 1, Requirements and Guidelines\" (CCITT T.81 / ISO 10918-1)\n#\n# History:\n# 1995-09-09 fl   Created\n# 1995-09-13 fl   Added full parser\n# 1996-03-25 fl   Added hack to use the IJG command line utilities\n# 1996-05-05 fl   Workaround Photoshop 2.5 CMYK polarity bug\n# 1996-05-28 fl   Added draft support, JFIF version (0.1)\n# 1996-12-30 fl   Added encoder options, added progression property (0.2)\n# 1997-08-27 fl   Save mode 1 images as BW (0.3)\n# 1998-07-12 fl   Added YCbCr to draft and save methods (0.4)\n# 1998-10-19 fl   Don't hang on files using 16-bit DQT's (0.4.1)\n# 2001-04-16 fl   Extract DPI settings from JFIF files (0.4.2)\n# 2002-07-01 fl   Skip pad bytes before markers; identify Exif files (0.4.3)\n# 2003-04-25 fl   Added experimental EXIF decoder (0.5)\n# 2003-06-06 fl   Added experimental EXIF GPSinfo decoder\n# 2003-09-13 fl   Extract COM markers\n# 2009-09-06 fl   Added icc_profile support (from Florian Hoech)\n# 2009-03-06 fl   Changed CMYK handling; always use Adobe polarity (0.6)\n# 2009-03-08 fl   Added subsampling support (from Justin Huff).\n#\n# Copyright (c) 1997-2003 by Secret Labs AB.\n# Copyright (c) 1995-1996 by Fredrik Lundh.\n#\n# See the README file for information on usage and redistribution.\n#\nimport array\nimport io\nimport math\nimport os\nimport struct\nimport subprocess\nimport sys\nimport tempfile\nimport warnings\n\nfrom . import Image, ImageFile\nfrom ._binary import i16be as i16\nfrom ._binary import i32be as i32\nfrom ._binary import o8\nfrom ._binary import o16be as o16\nfrom ._deprecate import deprecate\nfrom .JpegPresets import presets\n\n#\n# Parser\n\n\ndef Skip(self, marker):\n    n = i16(self.fp.read(2)) - 2\n    ImageFile._safe_read(self.fp, n)\n\n\ndef APP(self, marker):\n    #\n    # Application marker.  Store these in the APP dictionary.\n    # Also look for well-known application markers.\n\n    n = i16(self.fp.read(2)) - 2\n    s = ImageFile._safe_read(self.fp, n)\n\n    app = \"APP%d\" % (marker & 15)\n\n    self.app[app] = s  # compatibility\n    self.applist.append((app, s))\n\n    if marker == 0xFFE0 and s[:4] == b\"JFIF\":\n        # extract JFIF information\n        self.info[\"jfif\"] = version = i16(s, 5)  # version\n        self.info[\"jfif_version\"] = divmod(version, 256)\n        # extract JFIF properties\n        try:\n            jfif_unit = s[7]\n            jfif_density = i16(s, 8), i16(s, 10)\n        except Exception:\n            pass\n        else:\n            if jfif_unit == 1:\n                self.info[\"dpi\"] = jfif_density\n            self.info[\"jfif_unit\"] = jfif_unit\n            self.info[\"jfif_density\"] = jfif_density\n    elif marker == 0xFFE1 and s[:5] == b\"Exif\\0\":\n        if \"exif\" not in self.info:\n            # extract EXIF information (incomplete)\n            self.info[\"exif\"] = s  # FIXME: value will change\n            self._exif_offset = self.fp.tell() - n + 6\n    elif marker == 0xFFE2 and s[:5] == b\"FPXR\\0\":\n        # extract FlashPix information (incomplete)\n        self.info[\"flashpix\"] = s  # FIXME: value will change\n    elif marker == 0xFFE2 and s[:12] == b\"ICC_PROFILE\\0\":\n        # Since an ICC profile can be larger than the maximum size of\n        # a JPEG marker (64K), we need provisions to split it into\n        # multiple markers. The format defined by the ICC specifies\n        # one or more APP2 markers containing the following data:\n        #   Identifying string      ASCII \"ICC_PROFILE\\0\"  (12 bytes)\n        #   Marker sequence number  1, 2, etc (1 byte)\n        #   Number of markers       Total of APP2's used (1 byte)\n        #   Profile data            (remainder of APP2 data)\n        # Decoders should use the marker sequence numbers to\n        # reassemble the profile, rather than assuming that the APP2\n        # markers appear in the correct sequence.\n        self.icclist.append(s)\n    elif marker == 0xFFED and s[:14] == b\"Photoshop 3.0\\x00\":\n        # parse the image resource block\n        offset = 14\n        photoshop = self.info.setdefault(\"photoshop\", {})\n        while s[offset : offset + 4] == b\"8BIM\":\n            try:\n                offset += 4\n                # resource code\n                code = i16(s, offset)\n                offset += 2\n                # resource name (usually empty)\n                name_len = s[offset]\n                # name = s[offset+1:offset+1+name_len]\n                offset += 1 + name_len\n                offset += offset & 1  # align\n                # resource data block\n                size = i32(s, offset)\n                offset += 4\n                data = s[offset : offset + size]\n                if code == 0x03ED:  # ResolutionInfo\n                    data = {\n                        \"XResolution\": i32(data, 0) / 65536,\n                        \"DisplayedUnitsX\": i16(data, 4),\n                        \"YResolution\": i32(data, 8) / 65536,\n                        \"DisplayedUnitsY\": i16(data, 12),\n                    }\n                photoshop[code] = data\n                offset += size\n                offset += offset & 1  # align\n            except struct.error:\n                break  # insufficient data\n\n    elif marker == 0xFFEE and s[:5] == b\"Adobe\":\n        self.info[\"adobe\"] = i16(s, 5)\n        # extract Adobe custom properties\n        try:\n            adobe_transform = s[11]\n        except IndexError:\n            pass\n        else:\n            self.info[\"adobe_transform\"] = adobe_transform\n    elif marker == 0xFFE2 and s[:4] == b\"MPF\\0\":\n        # extract MPO information\n        self.info[\"mp\"] = s[4:]\n        # offset is current location minus buffer size\n        # plus constant header size\n        self.info[\"mpoffset\"] = self.fp.tell() - n + 4\n\n    # If DPI isn't in JPEG header, fetch from EXIF\n    if \"dpi\" not in self.info and \"exif\" in self.info:\n        try:\n            exif = self.getexif()\n            resolution_unit = exif[0x0128]\n            x_resolution = exif[0x011A]\n            try:\n                dpi = float(x_resolution[0]) / x_resolution[1]\n            except TypeError:\n                dpi = x_resolution\n            if math.isnan(dpi):\n                raise ValueError\n            if resolution_unit == 3:  # cm\n                # 1 dpcm = 2.54 dpi\n                dpi *= 2.54\n            self.info[\"dpi\"] = dpi, dpi\n        except (TypeError, KeyError, SyntaxError, ValueError, ZeroDivisionError):\n            # SyntaxError for invalid/unreadable EXIF\n            # KeyError for dpi not included\n            # ZeroDivisionError for invalid dpi rational value\n            # ValueError or TypeError for dpi being an invalid float\n            self.info[\"dpi\"] = 72, 72\n\n\ndef COM(self, marker):\n    #\n    # Comment marker.  Store these in the APP dictionary.\n    n = i16(self.fp.read(2)) - 2\n    s = ImageFile._safe_read(self.fp, n)\n\n    self.info[\"comment\"] = s\n    self.app[\"COM\"] = s  # compatibility\n    self.applist.append((\"COM\", s))\n\n\ndef SOF(self, marker):\n    #\n    # Start of frame marker.  Defines the size and mode of the\n    # image.  JPEG is colour blind, so we use some simple\n    # heuristics to map the number of layers to an appropriate\n    # mode.  Note that this could be made a bit brighter, by\n    # looking for JFIF and Adobe APP markers.\n\n    n = i16(self.fp.read(2)) - 2\n    s = ImageFile._safe_read(self.fp, n)\n    self._size = i16(s, 3), i16(s, 1)\n\n    self.bits = s[0]\n    if self.bits != 8:\n        msg = f\"cannot handle {self.bits}-bit layers\"\n        raise SyntaxError(msg)\n\n    self.layers = s[5]\n    if self.layers == 1:\n        self.mode = \"L\"\n    elif self.layers == 3:\n        self.mode = \"RGB\"\n    elif self.layers == 4:\n        self.mode = \"CMYK\"\n    else:\n        msg = f\"cannot handle {self.layers}-layer images\"\n        raise SyntaxError(msg)\n\n    if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]:\n        self.info[\"progressive\"] = self.info[\"progression\"] = 1\n\n    if self.icclist:\n        # fixup icc profile\n        self.icclist.sort()  # sort by sequence number\n        if self.icclist[0][13] == len(self.icclist):\n            profile = []\n            for p in self.icclist:\n                profile.append(p[14:])\n            icc_profile = b\"\".join(profile)\n        else:\n            icc_profile = None  # wrong number of fragments\n        self.info[\"icc_profile\"] = icc_profile\n        self.icclist = []\n\n    for i in range(6, len(s), 3):\n        t = s[i : i + 3]\n        # 4-tuples: id, vsamp, hsamp, qtable\n        self.layer.append((t[0], t[1] // 16, t[1] & 15, t[2]))\n\n\ndef DQT(self, marker):\n    #\n    # Define quantization table.  Note that there might be more\n    # than one table in each marker.\n\n    # FIXME: The quantization tables can be used to estimate the\n    # compression quality.\n\n    n = i16(self.fp.read(2)) - 2\n    s = ImageFile._safe_read(self.fp, n)\n    while len(s):\n        v = s[0]\n        precision = 1 if (v // 16 == 0) else 2  # in bytes\n        qt_length = 1 + precision * 64\n        if len(s) < qt_length:\n            msg = \"bad quantization table marker\"\n            raise SyntaxError(msg)\n        data = array.array(\"B\" if precision == 1 else \"H\", s[1:qt_length])\n        if sys.byteorder == \"little\" and precision > 1:\n            data.byteswap()  # the values are always big-endian\n        self.quantization[v & 15] = [data[i] for i in zigzag_index]\n        s = s[qt_length:]\n\n\n#\n# JPEG marker table\n\nMARKER = {\n    0xFFC0: (\"SOF0\", \"Baseline DCT\", SOF),\n    0xFFC1: (\"SOF1\", \"Extended Sequential DCT\", SOF),\n    0xFFC2: (\"SOF2\", \"Progressive DCT\", SOF),\n    0xFFC3: (\"SOF3\", \"Spatial lossless\", SOF),\n    0xFFC4: (\"DHT\", \"Define Huffman table\", Skip),\n    0xFFC5: (\"SOF5\", \"Differential sequential DCT\", SOF),\n    0xFFC6: (\"SOF6\", \"Differential progressive DCT\", SOF),\n    0xFFC7: (\"SOF7\", \"Differential spatial\", SOF),\n    0xFFC8: (\"JPG\", \"Extension\", None),\n    0xFFC9: (\"SOF9\", \"Extended sequential DCT (AC)\", SOF),\n    0xFFCA: (\"SOF10\", \"Progressive DCT (AC)\", SOF),\n    0xFFCB: (\"SOF11\", \"Spatial lossless DCT (AC)\", SOF),\n    0xFFCC: (\"DAC\", \"Define arithmetic coding conditioning\", Skip),\n    0xFFCD: (\"SOF13\", \"Differential sequential DCT (AC)\", SOF),\n    0xFFCE: (\"SOF14\", \"Differential progressive DCT (AC)\", SOF),\n    0xFFCF: (\"SOF15\", \"Differential spatial (AC)\", SOF),\n    0xFFD0: (\"RST0\", \"Restart 0\", None),\n    0xFFD1: (\"RST1\", \"Restart 1\", None),\n    0xFFD2: (\"RST2\", \"Restart 2\", None),\n    0xFFD3: (\"RST3\", \"Restart 3\", None),\n    0xFFD4: (\"RST4\", \"Restart 4\", None),\n    0xFFD5: (\"RST5\", \"Restart 5\", None),\n    0xFFD6: (\"RST6\", \"Restart 6\", None),\n    0xFFD7: (\"RST7\", \"Restart 7\", None),\n    0xFFD8: (\"SOI\", \"Start of image\", None),\n    0xFFD9: (\"EOI\", \"End of image\", None),\n    0xFFDA: (\"SOS\", \"Start of scan\", Skip),\n    0xFFDB: (\"DQT\", \"Define quantization table\", DQT),\n    0xFFDC: (\"DNL\", \"Define number of lines\", Skip),\n    0xFFDD: (\"DRI\", \"Define restart interval\", Skip),\n    0xFFDE: (\"DHP\", \"Define hierarchical progression\", SOF),\n    0xFFDF: (\"EXP\", \"Expand reference component\", Skip),\n    0xFFE0: (\"APP0\", \"Application segment 0\", APP),\n    0xFFE1: (\"APP1\", \"Application segment 1\", APP),\n    0xFFE2: (\"APP2\", \"Application segment 2\", APP),\n    0xFFE3: (\"APP3\", \"Application segment 3\", APP),\n    0xFFE4: (\"APP4\", \"Application segment 4\", APP),\n    0xFFE5: (\"APP5\", \"Application segment 5\", APP),\n    0xFFE6: (\"APP6\", \"Application segment 6\", APP),\n    0xFFE7: (\"APP7\", \"Application segment 7\", APP),\n    0xFFE8: (\"APP8\", \"Application segment 8\", APP),\n    0xFFE9: (\"APP9\", \"Application segment 9\", APP),\n    0xFFEA: (\"APP10\", \"Application segment 10\", APP),\n    0xFFEB: (\"APP11\", \"Application segment 11\", APP),\n    0xFFEC: (\"APP12\", \"Application segment 12\", APP),\n    0xFFED: (\"APP13\", \"Application segment 13\", APP),\n    0xFFEE: (\"APP14\", \"Application segment 14\", APP),\n    0xFFEF: (\"APP15\", \"Application segment 15\", APP),\n    0xFFF0: (\"JPG0\", \"Extension 0\", None),\n    0xFFF1: (\"JPG1\", \"Extension 1\", None),\n    0xFFF2: (\"JPG2\", \"Extension 2\", None),\n    0xFFF3: (\"JPG3\", \"Extension 3\", None),\n    0xFFF4: (\"JPG4\", \"Extension 4\", None),\n    0xFFF5: (\"JPG5\", \"Extension 5\", None),\n    0xFFF6: (\"JPG6\", \"Extension 6\", None),\n    0xFFF7: (\"JPG7\", \"Extension 7\", None),\n    0xFFF8: (\"JPG8\", \"Extension 8\", None),\n    0xFFF9: (\"JPG9\", \"Extension 9\", None),\n    0xFFFA: (\"JPG10\", \"Extension 10\", None),\n    0xFFFB: (\"JPG11\", \"Extension 11\", None),\n    0xFFFC: (\"JPG12\", \"Extension 12\", None),\n    0xFFFD: (\"JPG13\", \"Extension 13\", None),\n    0xFFFE: (\"COM\", \"Comment\", COM),\n}\n\n\ndef _accept(prefix):\n    # Magic number was taken from https://en.wikipedia.org/wiki/JPEG\n    return prefix[:3] == b\"\\xFF\\xD8\\xFF\"\n\n\n##\n# Image plugin for JPEG and JFIF images.\n\n\nclass JpegImageFile(ImageFile.ImageFile):\n    format = \"JPEG\"\n    format_description = \"JPEG (ISO 10918)\"\n\n    def _open(self):\n        s = self.fp.read(3)\n\n        if not _accept(s):\n            msg = \"not a JPEG file\"\n            raise SyntaxError(msg)\n        s = b\"\\xFF\"\n\n        # Create attributes\n        self.bits = self.layers = 0\n\n        # JPEG specifics (internal)\n        self.layer = []\n        self.huffman_dc = {}\n        self.huffman_ac = {}\n        self.quantization = {}\n        self.app = {}  # compatibility\n        self.applist = []\n        self.icclist = []\n\n        while True:\n            i = s[0]\n            if i == 0xFF:\n                s = s + self.fp.read(1)\n                i = i16(s)\n            else:\n                # Skip non-0xFF junk\n                s = self.fp.read(1)\n                continue\n\n            if i in MARKER:\n                name, description, handler = MARKER[i]\n                if handler is not None:\n                    handler(self, i)\n                if i == 0xFFDA:  # start of scan\n                    rawmode = self.mode\n                    if self.mode == \"CMYK\":\n                        rawmode = \"CMYK;I\"  # assume adobe conventions\n                    self.tile = [(\"jpeg\", (0, 0) + self.size, 0, (rawmode, \"\"))]\n                    # self.__offset = self.fp.tell()\n                    break\n                s = self.fp.read(1)\n            elif i == 0 or i == 0xFFFF:\n                # padded marker or junk; move on\n                s = b\"\\xff\"\n            elif i == 0xFF00:  # Skip extraneous data (escaped 0xFF)\n                s = self.fp.read(1)\n            else:\n                msg = \"no marker found\"\n                raise SyntaxError(msg)\n\n    def load_read(self, read_bytes):\n        \"\"\"\n        internal: read more image data\n        For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker\n        so libjpeg can finish decoding\n        \"\"\"\n        s = self.fp.read(read_bytes)\n\n        if not s and ImageFile.LOAD_TRUNCATED_IMAGES and not hasattr(self, \"_ended\"):\n            # Premature EOF.\n            # Pretend file is finished adding EOI marker\n            self._ended = True\n            return b\"\\xFF\\xD9\"\n\n        return s\n\n    def draft(self, mode, size):\n        if len(self.tile) != 1:\n            return\n\n        # Protect from second call\n        if self.decoderconfig:\n            return\n\n        d, e, o, a = self.tile[0]\n        scale = 1\n        original_size = self.size\n\n        if a[0] == \"RGB\" and mode in [\"L\", \"YCbCr\"]:\n            self.mode = mode\n            a = mode, \"\"\n\n        if size:\n            scale = min(self.size[0] // size[0], self.size[1] // size[1])\n            for s in [8, 4, 2, 1]:\n                if scale >= s:\n                    break\n            e = (\n                e[0],\n                e[1],\n                (e[2] - e[0] + s - 1) // s + e[0],\n                (e[3] - e[1] + s - 1) // s + e[1],\n            )\n            self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s)\n            scale = s\n\n        self.tile = [(d, e, o, a)]\n        self.decoderconfig = (scale, 0)\n\n        box = (0, 0, original_size[0] / scale, original_size[1] / scale)\n        return self.mode, box\n\n    def load_djpeg(self):\n        # ALTERNATIVE: handle JPEGs via the IJG command line utilities\n\n        f, path = tempfile.mkstemp()\n        os.close(f)\n        if os.path.exists(self.filename):\n            subprocess.check_call([\"djpeg\", \"-outfile\", path, self.filename])\n        else:\n            msg = \"Invalid Filename\"\n            raise ValueError(msg)\n\n        try:\n            with Image.open(path) as _im:\n                _im.load()\n                self.im = _im.im\n        finally:\n            try:\n                os.unlink(path)\n            except OSError:\n                pass\n\n        self.mode = self.im.mode\n        self._size = self.im.size\n\n        self.tile = []\n\n    def _getexif(self):\n        return _getexif(self)\n\n    def _getmp(self):\n        return _getmp(self)\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n\n        for segment, content in self.applist:\n            if segment == \"APP1\":\n                marker, xmp_tags = content.rsplit(b\"\\x00\", 1)\n                if marker == b\"http://ns.adobe.com/xap/1.0/\":\n                    return self._getxmp(xmp_tags)\n        return {}\n\n\ndef _getexif(self):\n    if \"exif\" not in self.info:\n        return None\n    return self.getexif()._get_merged_dict()\n\n\ndef _getmp(self):\n    # Extract MP information.  This method was inspired by the \"highly\n    # experimental\" _getexif version that's been in use for years now,\n    # itself based on the ImageFileDirectory class in the TIFF plugin.\n\n    # The MP record essentially consists of a TIFF file embedded in a JPEG\n    # application marker.\n    try:\n        data = self.info[\"mp\"]\n    except KeyError:\n        return None\n    file_contents = io.BytesIO(data)\n    head = file_contents.read(8)\n    endianness = \">\" if head[:4] == b\"\\x4d\\x4d\\x00\\x2a\" else \"<\"\n    # process dictionary\n    from . import TiffImagePlugin\n\n    try:\n        info = TiffImagePlugin.ImageFileDirectory_v2(head)\n        file_contents.seek(info.next)\n        info.load(file_contents)\n        mp = dict(info)\n    except Exception as e:\n        msg = \"malformed MP Index (unreadable directory)\"\n        raise SyntaxError(msg) from e\n    # it's an error not to have a number of images\n    try:\n        quant = mp[0xB001]\n    except KeyError as e:\n        msg = \"malformed MP Index (no number of images)\"\n        raise SyntaxError(msg) from e\n    # get MP entries\n    mpentries = []\n    try:\n        rawmpentries = mp[0xB002]\n        for entrynum in range(0, quant):\n            unpackedentry = struct.unpack_from(\n                f\"{endianness}LLLHH\", rawmpentries, entrynum * 16\n            )\n            labels = (\"Attribute\", \"Size\", \"DataOffset\", \"EntryNo1\", \"EntryNo2\")\n            mpentry = dict(zip(labels, unpackedentry))\n            mpentryattr = {\n                \"DependentParentImageFlag\": bool(mpentry[\"Attribute\"] & (1 << 31)),\n                \"DependentChildImageFlag\": bool(mpentry[\"Attribute\"] & (1 << 30)),\n                \"RepresentativeImageFlag\": bool(mpentry[\"Attribute\"] & (1 << 29)),\n                \"Reserved\": (mpentry[\"Attribute\"] & (3 << 27)) >> 27,\n                \"ImageDataFormat\": (mpentry[\"Attribute\"] & (7 << 24)) >> 24,\n                \"MPType\": mpentry[\"Attribute\"] & 0x00FFFFFF,\n            }\n            if mpentryattr[\"ImageDataFormat\"] == 0:\n                mpentryattr[\"ImageDataFormat\"] = \"JPEG\"\n            else:\n                msg = \"unsupported picture format in MPO\"\n                raise SyntaxError(msg)\n            mptypemap = {\n                0x000000: \"Undefined\",\n                0x010001: \"Large Thumbnail (VGA Equivalent)\",\n                0x010002: \"Large Thumbnail (Full HD Equivalent)\",\n                0x020001: \"Multi-Frame Image (Panorama)\",\n                0x020002: \"Multi-Frame Image: (Disparity)\",\n                0x020003: \"Multi-Frame Image: (Multi-Angle)\",\n                0x030000: \"Baseline MP Primary Image\",\n            }\n            mpentryattr[\"MPType\"] = mptypemap.get(mpentryattr[\"MPType\"], \"Unknown\")\n            mpentry[\"Attribute\"] = mpentryattr\n            mpentries.append(mpentry)\n        mp[0xB002] = mpentries\n    except KeyError as e:\n        msg = \"malformed MP Index (bad MP Entry)\"\n        raise SyntaxError(msg) from e\n    # Next we should try and parse the individual image unique ID list;\n    # we don't because I've never seen this actually used in a real MPO\n    # file and so can't test it.\n    return mp\n\n\n# --------------------------------------------------------------------\n# stuff to save JPEG files\n\nRAWMODE = {\n    \"1\": \"L\",\n    \"L\": \"L\",\n    \"RGB\": \"RGB\",\n    \"RGBX\": \"RGB\",\n    \"CMYK\": \"CMYK;I\",  # assume adobe conventions\n    \"YCbCr\": \"YCbCr\",\n}\n\n# fmt: off\nzigzag_index = (\n    0,  1,  5,  6, 14, 15, 27, 28,\n    2,  4,  7, 13, 16, 26, 29, 42,\n    3,  8, 12, 17, 25, 30, 41, 43,\n    9, 11, 18, 24, 31, 40, 44, 53,\n    10, 19, 23, 32, 39, 45, 52, 54,\n    20, 22, 33, 38, 46, 51, 55, 60,\n    21, 34, 37, 47, 50, 56, 59, 61,\n    35, 36, 48, 49, 57, 58, 62, 63,\n)\n\nsamplings = {\n    (1, 1, 1, 1, 1, 1): 0,\n    (2, 1, 1, 1, 1, 1): 1,\n    (2, 2, 1, 1, 1, 1): 2,\n}\n# fmt: on\n\n\ndef convert_dict_qtables(qtables):\n    deprecate(\"convert_dict_qtables\", 10, action=\"Conversion is no longer needed\")\n    return qtables\n\n\ndef get_sampling(im):\n    # There's no subsampling when images have only 1 layer\n    # (grayscale images) or when they are CMYK (4 layers),\n    # so set subsampling to the default value.\n    #\n    # NOTE: currently Pillow can't encode JPEG to YCCK format.\n    # If YCCK support is added in the future, subsampling code will have\n    # to be updated (here and in JpegEncode.c) to deal with 4 layers.\n    if not hasattr(im, \"layers\") or im.layers in (1, 4):\n        return -1\n    sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]\n    return samplings.get(sampling, -1)\n\n\ndef _save(im, fp, filename):\n    if im.width == 0 or im.height == 0:\n        msg = \"cannot write empty image as JPEG\"\n        raise ValueError(msg)\n\n    try:\n        rawmode = RAWMODE[im.mode]\n    except KeyError as e:\n        msg = f\"cannot write mode {im.mode} as JPEG\"\n        raise OSError(msg) from e\n\n    info = im.encoderinfo\n\n    dpi = [round(x) for x in info.get(\"dpi\", (0, 0))]\n\n    quality = info.get(\"quality\", -1)\n    subsampling = info.get(\"subsampling\", -1)\n    qtables = info.get(\"qtables\")\n\n    if quality == \"keep\":\n        quality = -1\n        subsampling = \"keep\"\n        qtables = \"keep\"\n    elif quality in presets:\n        preset = presets[quality]\n        quality = -1\n        subsampling = preset.get(\"subsampling\", -1)\n        qtables = preset.get(\"quantization\")\n    elif not isinstance(quality, int):\n        msg = \"Invalid quality setting\"\n        raise ValueError(msg)\n    else:\n        if subsampling in presets:\n            subsampling = presets[subsampling].get(\"subsampling\", -1)\n        if isinstance(qtables, str) and qtables in presets:\n            qtables = presets[qtables].get(\"quantization\")\n\n    if subsampling == \"4:4:4\":\n        subsampling = 0\n    elif subsampling == \"4:2:2\":\n        subsampling = 1\n    elif subsampling == \"4:2:0\":\n        subsampling = 2\n    elif subsampling == \"4:1:1\":\n        # For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0.\n        # Set 4:2:0 if someone is still using that value.\n        subsampling = 2\n    elif subsampling == \"keep\":\n        if im.format != \"JPEG\":\n            msg = \"Cannot use 'keep' when original image is not a JPEG\"\n            raise ValueError(msg)\n        subsampling = get_sampling(im)\n\n    def validate_qtables(qtables):\n        if qtables is None:\n            return qtables\n        if isinstance(qtables, str):\n            try:\n                lines = [\n                    int(num)\n                    for line in qtables.splitlines()\n                    for num in line.split(\"#\", 1)[0].split()\n                ]\n            except ValueError as e:\n                msg = \"Invalid quantization table\"\n                raise ValueError(msg) from e\n            else:\n                qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)]\n        if isinstance(qtables, (tuple, list, dict)):\n            if isinstance(qtables, dict):\n                qtables = [\n                    qtables[key] for key in range(len(qtables)) if key in qtables\n                ]\n            elif isinstance(qtables, tuple):\n                qtables = list(qtables)\n            if not (0 < len(qtables) < 5):\n                msg = \"None or too many quantization tables\"\n                raise ValueError(msg)\n            for idx, table in enumerate(qtables):\n                try:\n                    if len(table) != 64:\n                        raise TypeError\n                    table = array.array(\"H\", table)\n                except TypeError as e:\n                    msg = \"Invalid quantization table\"\n                    raise ValueError(msg) from e\n                else:\n                    qtables[idx] = list(table)\n            return qtables\n\n    if qtables == \"keep\":\n        if im.format != \"JPEG\":\n            msg = \"Cannot use 'keep' when original image is not a JPEG\"\n            raise ValueError(msg)\n        qtables = getattr(im, \"quantization\", None)\n    qtables = validate_qtables(qtables)\n\n    extra = info.get(\"extra\", b\"\")\n\n    icc_profile = info.get(\"icc_profile\")\n    if icc_profile:\n        ICC_OVERHEAD_LEN = 14\n        MAX_BYTES_IN_MARKER = 65533\n        MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN\n        markers = []\n        while icc_profile:\n            markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER])\n            icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:]\n        i = 1\n        for marker in markers:\n            size = o16(2 + ICC_OVERHEAD_LEN + len(marker))\n            extra += (\n                b\"\\xFF\\xE2\"\n                + size\n                + b\"ICC_PROFILE\\0\"\n                + o8(i)\n                + o8(len(markers))\n                + marker\n            )\n            i += 1\n\n    comment = info.get(\"comment\", im.info.get(\"comment\"))\n\n    # \"progressive\" is the official name, but older documentation\n    # says \"progression\"\n    # FIXME: issue a warning if the wrong form is used (post-1.1.7)\n    progressive = info.get(\"progressive\", False) or info.get(\"progression\", False)\n\n    optimize = info.get(\"optimize\", False)\n\n    exif = info.get(\"exif\", b\"\")\n    if isinstance(exif, Image.Exif):\n        exif = exif.tobytes()\n\n    # get keyword arguments\n    im.encoderconfig = (\n        quality,\n        progressive,\n        info.get(\"smooth\", 0),\n        optimize,\n        info.get(\"streamtype\", 0),\n        dpi[0],\n        dpi[1],\n        subsampling,\n        qtables,\n        comment,\n        extra,\n        exif,\n    )\n\n    # if we optimize, libjpeg needs a buffer big enough to hold the whole image\n    # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is\n    # channels*size, this is a value that's been used in a django patch.\n    # https://github.com/matthewwithanm/django-imagekit/issues/50\n    bufsize = 0\n    if optimize or progressive:\n        # CMYK can be bigger\n        if im.mode == \"CMYK\":\n            bufsize = 4 * im.size[0] * im.size[1]\n        # keep sets quality to -1, but the actual value may be high.\n        elif quality >= 95 or quality == -1:\n            bufsize = 2 * im.size[0] * im.size[1]\n        else:\n            bufsize = im.size[0] * im.size[1]\n\n    # The EXIF info needs to be written as one block, + APP1, + one spare byte.\n    # Ensure that our buffer is big enough. Same with the icc_profile block.\n    bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, len(extra) + 1)\n\n    ImageFile._save(im, fp, [(\"jpeg\", (0, 0) + im.size, 0, rawmode)], bufsize)\n\n\ndef _save_cjpeg(im, fp, filename):\n    # ALTERNATIVE: handle JPEGs via the IJG command line utilities.\n    tempfile = im._dump()\n    subprocess.check_call([\"cjpeg\", \"-outfile\", filename, tempfile])\n    try:\n        os.unlink(tempfile)\n    except OSError:\n        pass\n\n\n##\n# Factory for making JPEG and MPO instances\ndef jpeg_factory(fp=None, filename=None):\n    im = JpegImageFile(fp, filename)\n    try:\n        mpheader = im._getmp()\n        if mpheader[45057] > 1:\n            # It's actually an MPO\n            from .MpoImagePlugin import MpoImageFile\n\n            # Don't reload everything, just convert it.\n            im = MpoImageFile.adopt(im, mpheader)\n    except (TypeError, IndexError):\n        # It is really a JPEG\n        pass\n    except SyntaxError:\n        warnings.warn(\n            \"Image appears to be a malformed MPO file, it will be \"\n            \"interpreted as a base JPEG file\"\n        )\n    return im\n\n\n# ---------------------------------------------------------------------\n# Registry stuff\n\nImage.register_open(JpegImageFile.format, jpeg_factory, _accept)\nImage.register_save(JpegImageFile.format, _save)\n\nImage.register_extensions(JpegImageFile.format, [\".jfif\", \".jpe\", \".jpg\", \".jpeg\"])\n\nImage.register_mime(JpegImageFile.format, \"image/jpeg\")\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 ---
**Analysis:**

1. **Key Parts:**
   - **Commit Message:** "Raise an error when EXIF data is too long."
   - **Test Changes:** The test was updated to save a JPEG with `exif=b"1" * 65534` and expects a `ValueError`.
   - **Old Test Result:** A test named `test_large_exif` failed with `OSError: encoder error -2 when writing image file`.
   
2. **Main Problem:**
   - When saving a JPEG with EXIF data exceeding 65533 bytes, the system raises an unclear `OSError` instead of a specific, informative error.

3. **Expected Behavior:**
   - The system should raise a clear and specific error indicating that the EXIF data is too long when exceeding the maximum allowed size.

4. **Actual Behavior:**
   - An `OSError` with the message "encoder error -2 when writing image file" is raised, which does not clearly indicate the nature of the issue.

5. **Issue Description:**
   - The issue should clearly state that saving a JPEG with excessively long EXIF data results in an unclear error, making it difficult for developers to understand and handle the problem.

---

[ISSUE]
### Saving JPEG with EXIF Data Exceeding 65533 Bytes Raises an Unclear OSError

#### Description
When attempting to save a JPEG image with EXIF data longer than 65533 bytes, an unclear `OSError` is raised. This makes it difficult to understand the root cause of the failure and handle it appropriately.

#### Example Code
```python
from PIL import Image

# Open an existing image
im = Image.open("example.jpg")

# Create EXIF data exceeding the maximum allowed size
exif_data = b"1" * 65534

# Attempt to save the image with oversized EXIF data
im.save("output.jpg", "JPEG", exif=exif_data)
```

#### Expected Behavior
The system should raise a clear and specific error indicating that the EXIF data is too long, allowing developers to understand and address the issue effectively.

#### Actual Behavior
An `OSError` is raised with the message:
```
OSError: encoder error -2 when writing image file
```
This error does not clearly indicate that the EXIF data length is the cause of the failure.
[/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/JpegImagePlugin.py
#
# The Python Imaging Library.
# $Id$
#
# JPEG (JFIF) file handling
#
# See "Digital Compression and Coding of Continuous-Tone Still Images,
# Part 1, Requirements and Guidelines" (CCITT T.81 / ISO 10918-1)
#
# History:
# 1995-09-09 fl   Created
# 1995-09-13 fl   Added full parser
# 1996-03-25 fl   Added hack to use the IJG command line utilities
# 1996-05-05 fl   Workaround Photoshop 2.5 CMYK polarity bug
# 1996-05-28 fl   Added draft support, JFIF version (0.1)
# 1996-12-30 fl   Added encoder options, added progression property (0.2)
# 1997-08-27 fl   Save mode 1 images as BW (0.3)
# 1998-07-12 fl   Added YCbCr to draft and save methods (0.4)
# 1998-10-19 fl   Don't hang on files using 16-bit DQT's (0.4.1)
# 2001-04-16 fl   Extract DPI settings from JFIF files (0.4.2)
# 2002-07-01 fl   Skip pad bytes before markers; identify Exif files (0.4.3)
# 2003-04-25 fl   Added experimental EXIF decoder (0.5)
# 2003-06-06 fl   Added experimental EXIF GPSinfo decoder
# 2003-09-13 fl   Extract COM markers
# 2009-09-06 fl   Added icc_profile support (from Florian Hoech)
# 2009-03-06 fl   Changed CMYK handling; always use Adobe polarity (0.6)
# 2009-03-08 fl   Added subsampling support (from Justin Huff).
#
# Copyright (c) 1997-2003 by Secret Labs AB.
# Copyright (c) 1995-1996 by Fredrik Lundh.
#
# See the README file for information on usage and redistribution.
#
import array
import io
import math
import os
import struct
import subprocess
import sys
import tempfile
import warnings

from . import Image, ImageFile
from ._binary import i16be as i16
from ._binary import i32be as i32
from ._binary import o8
from ._binary import o16be as o16
from ._deprecate import deprecate
from .JpegPresets import presets

#
# Parser


def Skip(self, marker):
    n = i16(self.fp.read(2)) - 2
    ImageFile._safe_read(self.fp, n)


def APP(self, marker):
    #
    # Application marker.  Store these in the APP dictionary.
    # Also look for well-known application markers.

    n = i16(self.fp.read(2)) - 2
    s = ImageFile._safe_read(self.fp, n)

    app = "APP%d" % (marker & 15)

    self.app[app] = s  # compatibility
    self.applist.append((app, s))

    if marker == 0xFFE0 and s[:4] == b"JFIF":
        # extract JFIF information
        self.info["jfif"] = version = i16(s, 5)  # version
        self.info["jfif_version"] = divmod(version, 256)
        # extract JFIF properties
        try:
            jfif_unit = s[7]
            jfif_density = i16(s, 8), i16(s, 10)
        except Exception:
            pass
        else:
            if jfif_unit == 1:
                self.info["dpi"] = jfif_density
            self.info["jfif_unit"] = jfif_unit
            self.info["jfif_density"] = jfif_density
    elif marker == 0xFFE1 and s[:5] == b"Exif\0":
        if "exif" not in self.info:
            # extract EXIF information (incomplete)
            self.info["exif"] = s  # FIXME: value will change
            self._exif_offset = self.fp.tell() - n + 6
    elif marker == 0xFFE2 and s[:5] == b"FPXR\0":
        # extract FlashPix information (incomplete)
        self.info["flashpix"] = s  # FIXME: value will change
    elif marker == 0xFFE2 and s[:12] == b"ICC_PROFILE\0":
        # Since an ICC profile can be larger than the maximum size of
        # a JPEG marker (64K), we need provisions to split it into
        # multiple markers. The format defined by the ICC specifies
        # one or more APP2 markers containing the following data:
        #   Identifying string      ASCII "ICC_PROFILE\0"  (12 bytes)
        #   Marker sequence number  1, 2, etc (1 byte)
        #   Number of markers       Total of APP2's used (1 byte)
        #   Profile data            (remainder of APP2 data)
        # Decoders should use the marker sequence numbers to
        # reassemble the profile, rather than assuming that the APP2
        # markers appear in the correct sequence.
        self.icclist.append(s)
    elif marker == 0xFFED and s[:14] == b"Photoshop 3.0\x00":
        # parse the image resource block
        offset = 14
        photoshop = self.info.setdefault("photoshop", {})
        while s[offset : offset + 4] == b"8BIM":
            try:
                offset += 4
                # resource code
                code = i16(s, offset)
                offset += 2
                # resource name (usually empty)
                name_len = s[offset]
                # name = s[offset+1:offset+1+name_len]
                offset += 1 + name_len
                offset += offset & 1  # align
                # resource data block
                size = i32(s, offset)
                offset += 4
                data = s[offset : offset + size]
                if code == 0x03ED:  # ResolutionInfo
                    data = {
                        "XResolution": i32(data, 0) / 65536,
                        "DisplayedUnitsX": i16(data, 4),
                        "YResolution": i32(data, 8) / 65536,
                        "DisplayedUnitsY": i16(data, 12),
                    }
                photoshop[code] = data
                offset += size
                offset += offset & 1  # align
            except struct.error:
                break  # insufficient data

    elif marker == 0xFFEE and s[:5] == b"Adobe":
        self.info["adobe"] = i16(s, 5)
        # extract Adobe custom properties
        try:
            adobe_transform = s[11]
        except IndexError:
            pass
        else:
            self.info["adobe_transform"] = adobe_transform
    elif marker == 0xFFE2 and s[:4] == b"MPF\0":
        # extract MPO information
        self.info["mp"] = s[4:]
        # offset is current location minus buffer size
        # plus constant header size
        self.info["mpoffset"] = self.fp.tell() - n + 4

    # If DPI isn't in JPEG header, fetch from EXIF
    if "dpi" not in self.info and "exif" in self.info:
        try:
            exif = self.getexif()
            resolution_unit = exif[0x0128]
            x_resolution = exif[0x011A]
            try:
                dpi = float(x_resolution[0]) / x_resolution[1]
            except TypeError:
                dpi = x_resolution
            if math.isnan(dpi):
                raise ValueError
            if resolution_unit == 3:  # cm
                # 1 dpcm = 2.54 dpi
                dpi *= 2.54
            self.info["dpi"] = dpi, dpi
        except (TypeError, KeyError, SyntaxError, ValueError, ZeroDivisionError):
            # SyntaxError for invalid/unreadable EXIF
            # KeyError for dpi not included
            # ZeroDivisionError for invalid dpi rational value
            # ValueError or TypeError for dpi being an invalid float
            self.info["dpi"] = 72, 72


def COM(self, marker):
    #
    # Comment marker.  Store these in the APP dictionary.
    n = i16(self.fp.read(2)) - 2
    s = ImageFile._safe_read(self.fp, n)

    self.info["comment"] = s
    self.app["COM"] = s  # compatibility
    self.applist.append(("COM", s))


def SOF(self, marker):
    #
    # Start of frame marker.  Defines the size and mode of the
    # image.  JPEG is colour blind, so we use some simple
    # heuristics to map the number of layers to an appropriate
    # mode.  Note that this could be made a bit brighter, by
    # looking for JFIF and Adobe APP markers.

    n = i16(self.fp.read(2)) - 2
    s = ImageFile._safe_read(self.fp, n)
    self._size = i16(s, 3), i16(s, 1)

    self.bits = s[0]
    if self.bits != 8:
        msg = f"cannot handle {self.bits}-bit layers"
        raise SyntaxError(msg)

    self.layers = s[5]
    if self.layers == 1:
        self.mode = "L"
    elif self.layers == 3:
        self.mode = "RGB"
    elif self.layers == 4:
        self.mode = "CMYK"
    else:
        msg = f"cannot handle {self.layers}-layer images"
        raise SyntaxError(msg)

    if marker in [0xFFC2, 0xFFC6, 0xFFCA, 0xFFCE]:
        self.info["progressive"] = self.info["progression"] = 1

    if self.icclist:
        # fixup icc profile
        self.icclist.sort()  # sort by sequence number
        if self.icclist[0][13] == len(self.icclist):
            profile = []
            for p in self.icclist:
                profile.append(p[14:])
            icc_profile = b"".join(profile)
        else:
            icc_profile = None  # wrong number of fragments
        self.info["icc_profile"] = icc_profile
        self.icclist = []

    for i in range(6, len(s), 3):
        t = s[i : i + 3]
        # 4-tuples: id, vsamp, hsamp, qtable
        self.layer.append((t[0], t[1] // 16, t[1] & 15, t[2]))


def DQT(self, marker):
    #
    # Define quantization table.  Note that there might be more
    # than one table in each marker.

    # FIXME: The quantization tables can be used to estimate the
    # compression quality.

    n = i16(self.fp.read(2)) - 2
    s = ImageFile._safe_read(self.fp, n)
    while len(s):
        v = s[0]
        precision = 1 if (v // 16 == 0) else 2  # in bytes
        qt_length = 1 + precision * 64
        if len(s) < qt_length:
            msg = "bad quantization table marker"
            raise SyntaxError(msg)
        data = array.array("B" if precision == 1 else "H", s[1:qt_length])
        if sys.byteorder == "little" and precision > 1:
            data.byteswap()  # the values are always big-endian
        self.quantization[v & 15] = [data[i] for i in zigzag_index]
        s = s[qt_length:]


#
# JPEG marker table

MARKER = {
    0xFFC0: ("SOF0", "Baseline DCT", SOF),
    0xFFC1: ("SOF1", "Extended Sequential DCT", SOF),
    0xFFC2: ("SOF2", "Progressive DCT", SOF),
    0xFFC3: ("SOF3", "Spatial lossless", SOF),
    0xFFC4: ("DHT", "Define Huffman table", Skip),
    0xFFC5: ("SOF5", "Differential sequential DCT", SOF),
    0xFFC6: ("SOF6", "Differential progressive DCT", SOF),
    0xFFC7: ("SOF7", "Differential spatial", SOF),
    0xFFC8: ("JPG", "Extension", None),
    0xFFC9: ("SOF9", "Extended sequential DCT (AC)", SOF),
    0xFFCA: ("SOF10", "Progressive DCT (AC)", SOF),
    0xFFCB: ("SOF11", "Spatial lossless DCT (AC)", SOF),
    0xFFCC: ("DAC", "Define arithmetic coding conditioning", Skip),
    0xFFCD: ("SOF13", "Differential sequential DCT (AC)", SOF),
    0xFFCE: ("SOF14", "Differential progressive DCT (AC)", SOF),
    0xFFCF: ("SOF15", "Differential spatial (AC)", SOF),
    0xFFD0: ("RST0", "Restart 0", None),
    0xFFD1: ("RST1", "Restart 1", None),
    0xFFD2: ("RST2", "Restart 2", None),
    0xFFD3: ("RST3", "Restart 3", None),
    0xFFD4: ("RST4", "Restart 4", None),
    0xFFD5: ("RST5", "Restart 5", None),
    0xFFD6: ("RST6", "Restart 6", None),
    0xFFD7: ("RST7", "Restart 7", None),
    0xFFD8: ("SOI", "Start of image", None),
    0xFFD9: ("EOI", "End of image", None),
    0xFFDA: ("SOS", "Start of scan", Skip),
    0xFFDB: ("DQT", "Define quantization table", DQT),
    0xFFDC: ("DNL", "Define number of lines", Skip),
    0xFFDD: ("DRI", "Define restart interval", Skip),
    0xFFDE: ("DHP", "Define hierarchical progression", SOF),
    0xFFDF: ("EXP", "Expand reference component", Skip),
    0xFFE0: ("APP0", "Application segment 0", APP),
    0xFFE1: ("APP1", "Application segment 1", APP),
    0xFFE2: ("APP2", "Application segment 2", APP),
    0xFFE3: ("APP3", "Application segment 3", APP),
    0xFFE4: ("APP4", "Application segment 4", APP),
    0xFFE5: ("APP5", "Application segment 5", APP),
    0xFFE6: ("APP6", "Application segment 6", APP),
    0xFFE7: ("APP7", "Application segment 7", APP),
    0xFFE8: ("APP8", "Application segment 8", APP),
    0xFFE9: ("APP9", "Application segment 9", APP),
    0xFFEA: ("APP10", "Application segment 10", APP),
    0xFFEB: ("APP11", "Application segment 11", APP),
    0xFFEC: ("APP12", "Application segment 12", APP),
    0xFFED: ("APP13", "Application segment 13", APP),
    0xFFEE: ("APP14", "Application segment 14", APP),
    0xFFEF: ("APP15", "Application segment 15", APP),
    0xFFF0: ("JPG0", "Extension 0", None),
    0xFFF1: ("JPG1", "Extension 1", None),
    0xFFF2: ("JPG2", "Extension 2", None),
    0xFFF3: ("JPG3", "Extension 3", None),
    0xFFF4: ("JPG4", "Extension 4", None),
    0xFFF5: ("JPG5", "Extension 5", None),
    0xFFF6: ("JPG6", "Extension 6", None),
    0xFFF7: ("JPG7", "Extension 7", None),
    0xFFF8: ("JPG8", "Extension 8", None),
    0xFFF9: ("JPG9", "Extension 9", None),
    0xFFFA: ("JPG10", "Extension 10", None),
    0xFFFB: ("JPG11", "Extension 11", None),
    0xFFFC: ("JPG12", "Extension 12", None),
    0xFFFD: ("JPG13", "Extension 13", None),
    0xFFFE: ("COM", "Comment", COM),
}


def _accept(prefix):
    # Magic number was taken from https://en.wikipedia.org/wiki/JPEG
    return prefix[:3] == b"\xFF\xD8\xFF"


##
# Image plugin for JPEG and JFIF images.


class JpegImageFile(ImageFile.ImageFile):
    format = "JPEG"
    format_description = "JPEG (ISO 10918)"

    def _open(self):
        s = self.fp.read(3)

        if not _accept(s):
            msg = "not a JPEG file"
            raise SyntaxError(msg)
        s = b"\xFF"

        # Create attributes
        self.bits = self.layers = 0

        # JPEG specifics (internal)
        self.layer = []
        self.huffman_dc = {}
        self.huffman_ac = {}
        self.quantization = {}
        self.app = {}  # compatibility
        self.applist = []
        self.icclist = []

        while True:
            i = s[0]
            if i == 0xFF:
                s = s + self.fp.read(1)
                i = i16(s)
            else:
                # Skip non-0xFF junk
                s = self.fp.read(1)
                continue

            if i in MARKER:
                name, description, handler = MARKER[i]
                if handler is not None:
                    handler(self, i)
                if i == 0xFFDA:  # start of scan
                    rawmode = self.mode
                    if self.mode == "CMYK":
                        rawmode = "CMYK;I"  # assume adobe conventions
                    self.tile = [("jpeg", (0, 0) + self.size, 0, (rawmode, ""))]
                    # self.__offset = self.fp.tell()
                    break
                s = self.fp.read(1)
            elif i == 0 or i == 0xFFFF:
                # padded marker or junk; move on
                s = b"\xff"
            elif i == 0xFF00:  # Skip extraneous data (escaped 0xFF)
                s = self.fp.read(1)
            else:
                msg = "no marker found"
                raise SyntaxError(msg)

    def load_read(self, read_bytes):
        """
        internal: read more image data
        For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker
        so libjpeg can finish decoding
        """
        s = self.fp.read(read_bytes)

        if not s and ImageFile.LOAD_TRUNCATED_IMAGES and not hasattr(self, "_ended"):
            # Premature EOF.
            # Pretend file is finished adding EOI marker
            self._ended = True
            return b"\xFF\xD9"

        return s

    def draft(self, mode, size):
        if len(self.tile) != 1:
            return

        # Protect from second call
        if self.decoderconfig:
            return

        d, e, o, a = self.tile[0]
        scale = 1
        original_size = self.size

        if a[0] == "RGB" and mode in ["L", "YCbCr"]:
            self.mode = mode
            a = mode, ""

        if size:
            scale = min(self.size[0] // size[0], self.size[1] // size[1])
            for s in [8, 4, 2, 1]:
                if scale >= s:
                    break
            e = (
                e[0],
                e[1],
                (e[2] - e[0] + s - 1) // s + e[0],
                (e[3] - e[1] + s - 1) // s + e[1],
            )
            self._size = ((self.size[0] + s - 1) // s, (self.size[1] + s - 1) // s)
            scale = s

        self.tile = [(d, e, o, a)]
        self.decoderconfig = (scale, 0)

        box = (0, 0, original_size[0] / scale, original_size[1] / scale)
        return self.mode, box

    def load_djpeg(self):
        # ALTERNATIVE: handle JPEGs via the IJG command line utilities

        f, path = tempfile.mkstemp()
        os.close(f)
        if os.path.exists(self.filename):
            subprocess.check_call(["djpeg", "-outfile", path, self.filename])
        else:
            msg = "Invalid Filename"
            raise ValueError(msg)

        try:
            with Image.open(path) as _im:
                _im.load()
                self.im = _im.im
        finally:
            try:
                os.unlink(path)
            except OSError:
                pass

        self.mode = self.im.mode
        self._size = self.im.size

        self.tile = []

    def _getexif(self):
        return _getexif(self)

    def _getmp(self):
        return _getmp(self)

    def getxmp(self):
        """
        Returns a dictionary containing the XMP tags.
        Requires defusedxml to be installed.

        :returns: XMP tags in a dictionary.
        """

        for segment, content in self.applist:
            if segment == "APP1":
                marker, xmp_tags = content.rsplit(b"\x00", 1)
                if marker == b"http://ns.adobe.com/xap/1.0/":
                    return self._getxmp(xmp_tags)
        return {}


def _getexif(self):
    if "exif" not in self.info:
        return None
    return self.getexif()._get_merged_dict()


def _getmp(self):
    # Extract MP information.  This method was inspired by the "highly
    # experimental" _getexif version that's been in use for years now,
    # itself based on the ImageFileDirectory class in the TIFF plugin.

    # The MP record essentially consists of a TIFF file embedded in a JPEG
    # application marker.
    try:
        data = self.info["mp"]
    except KeyError:
        return None
    file_contents = io.BytesIO(data)
    head = file_contents.read(8)
    endianness = ">" if head[:4] == b"\x4d\x4d\x00\x2a" else "<"
    # process dictionary
    from . import TiffImagePlugin

    try:
        info = TiffImagePlugin.ImageFileDirectory_v2(head)
        file_contents.seek(info.next)
        info.load(file_contents)
        mp = dict(info)
    except Exception as e:
        msg = "malformed MP Index (unreadable directory)"
        raise SyntaxError(msg) from e
    # it's an error not to have a number of images
    try:
        quant = mp[0xB001]
    except KeyError as e:
        msg = "malformed MP Index (no number of images)"
        raise SyntaxError(msg) from e
    # get MP entries
    mpentries = []
    try:
        rawmpentries = mp[0xB002]
        for entrynum in range(0, quant):
            unpackedentry = struct.unpack_from(
                f"{endianness}LLLHH", rawmpentries, entrynum * 16
            )
            labels = ("Attribute", "Size", "DataOffset", "EntryNo1", "EntryNo2")
            mpentry = dict(zip(labels, unpackedentry))
            mpentryattr = {
                "DependentParentImageFlag": bool(mpentry["Attribute"] & (1 << 31)),
                "DependentChildImageFlag": bool(mpentry["Attribute"] & (1 << 30)),
                "RepresentativeImageFlag": bool(mpentry["Attribute"] & (1 << 29)),
                "Reserved": (mpentry["Attribute"] & (3 << 27)) >> 27,
                "ImageDataFormat": (mpentry["Attribute"] & (7 << 24)) >> 24,
                "MPType": mpentry["Attribute"] & 0x00FFFFFF,
            }
            if mpentryattr["ImageDataFormat"] == 0:
                mpentryattr["ImageDataFormat"] = "JPEG"
            else:
                msg = "unsupported picture format in MPO"
                raise SyntaxError(msg)
            mptypemap = {
                0x000000: "Undefined",
                0x010001: "Large Thumbnail (VGA Equivalent)",
                0x010002: "Large Thumbnail (Full HD Equivalent)",
                0x020001: "Multi-Frame Image (Panorama)",
                0x020002: "Multi-Frame Image: (Disparity)",
                0x020003: "Multi-Frame Image: (Multi-Angle)",
                0x030000: "Baseline MP Primary Image",
            }
            mpentryattr["MPType"] = mptypemap.get(mpentryattr["MPType"], "Unknown")
            mpentry["Attribute"] = mpentryattr
            mpentries.append(mpentry)
        mp[0xB002] = mpentries
    except KeyError as e:
        msg = "malformed MP Index (bad MP Entry)"
        raise SyntaxError(msg) from e
    # Next we should try and parse the individual image unique ID list;
    # we don't because I've never seen this actually used in a real MPO
    # file and so can't test it.
    return mp


# --------------------------------------------------------------------
# stuff to save JPEG files

RAWMODE = {
    "1": "L",
    "L": "L",
    "RGB": "RGB",
    "RGBX": "RGB",
    "CMYK": "CMYK;I",  # assume adobe conventions
    "YCbCr": "YCbCr",
}

# fmt: off
zigzag_index = (
    0,  1,  5,  6, 14, 15, 27, 28,
    2,  4,  7, 13, 16, 26, 29, 42,
    3,  8, 12, 17, 25, 30, 41, 43,
    9, 11, 18, 24, 31, 40, 44, 53,
    10, 19, 23, 32, 39, 45, 52, 54,
    20, 22, 33, 38, 46, 51, 55, 60,
    21, 34, 37, 47, 50, 56, 59, 61,
    35, 36, 48, 49, 57, 58, 62, 63,
)

samplings = {
    (1, 1, 1, 1, 1, 1): 0,
    (2, 1, 1, 1, 1, 1): 1,
    (2, 2, 1, 1, 1, 1): 2,
}
# fmt: on


def convert_dict_qtables(qtables):
    deprecate("convert_dict_qtables", 10, action="Conversion is no longer needed")
    return qtables


def get_sampling(im):
    # There's no subsampling when images have only 1 layer
    # (grayscale images) or when they are CMYK (4 layers),
    # so set subsampling to the default value.
    #
    # NOTE: currently Pillow can't encode JPEG to YCCK format.
    # If YCCK support is added in the future, subsampling code will have
    # to be updated (here and in JpegEncode.c) to deal with 4 layers.
    if not hasattr(im, "layers") or im.layers in (1, 4):
        return -1
    sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3]
    return samplings.get(sampling, -1)


def _save(im, fp, filename):
    if im.width == 0 or im.height == 0:
        msg = "cannot write empty image as JPEG"
        raise ValueError(msg)

    try:
        rawmode = RAWMODE[im.mode]
    except KeyError as e:
        msg = f"cannot write mode {im.mode} as JPEG"
        raise OSError(msg) from e

    info = im.encoderinfo

    dpi = [round(x) for x in info.get("dpi", (0, 0))]

    quality = info.get("quality", -1)
    subsampling = info.get("subsampling", -1)
    qtables = info.get("qtables")

    if quality == "keep":
        quality = -1
        subsampling = "keep"
        qtables = "keep"
    elif quality in presets:
        preset = presets[quality]
        quality = -1
        subsampling = preset.get("subsampling", -1)
        qtables = preset.get("quantization")
    elif not isinstance(quality, int):
        msg = "Invalid quality setting"
        raise ValueError(msg)
    else:
        if subsampling in presets:
            subsampling = presets[subsampling].get("subsampling", -1)
        if isinstance(qtables, str) and qtables in presets:
            qtables = presets[qtables].get("quantization")

    if subsampling == "4:4:4":
        subsampling = 0
    elif subsampling == "4:2:2":
        subsampling = 1
    elif subsampling == "4:2:0":
        subsampling = 2
    elif subsampling == "4:1:1":
        # For compatibility. Before Pillow 4.3, 4:1:1 actually meant 4:2:0.
        # Set 4:2:0 if someone is still using that value.
        subsampling = 2
    elif subsampling == "keep":
        if im.format != "JPEG":
            msg = "Cannot use 'keep' when original image is not a JPEG"
            raise ValueError(msg)
        subsampling = get_sampling(im)

    def validate_qtables(qtables):
        if qtables is None:
            return qtables
        if isinstance(qtables, str):
            try:
                lines = [
                    int(num)
                    for line in qtables.splitlines()
                    for num in line.split("#", 1)[0].split()
                ]
            except ValueError as e:
                msg = "Invalid quantization table"
                raise ValueError(msg) from e
            else:
                qtables = [lines[s : s + 64] for s in range(0, len(lines), 64)]
        if isinstance(qtables, (tuple, list, dict)):
            if isinstance(qtables, dict):
                qtables = [
                    qtables[key] for key in range(len(qtables)) if key in qtables
                ]
            elif isinstance(qtables, tuple):
                qtables = list(qtables)
            if not (0 < len(qtables) < 5):
                msg = "None or too many quantization tables"
                raise ValueError(msg)
            for idx, table in enumerate(qtables):
                try:
                    if len(table) != 64:
                        raise TypeError
                    table = array.array("H", table)
                except TypeError as e:
                    msg = "Invalid quantization table"
                    raise ValueError(msg) from e
                else:
                    qtables[idx] = list(table)
            return qtables

    if qtables == "keep":
        if im.format != "JPEG":
            msg = "Cannot use 'keep' when original image is not a JPEG"
            raise ValueError(msg)
        qtables = getattr(im, "quantization", None)
    qtables = validate_qtables(qtables)

    extra = info.get("extra", b"")

    icc_profile = info.get("icc_profile")
    if icc_profile:
        ICC_OVERHEAD_LEN = 14
        MAX_BYTES_IN_MARKER = 65533
        MAX_DATA_BYTES_IN_MARKER = MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN
        markers = []
        while icc_profile:
            markers.append(icc_profile[:MAX_DATA_BYTES_IN_MARKER])
            icc_profile = icc_profile[MAX_DATA_BYTES_IN_MARKER:]
        i = 1
        for marker in markers:
            size = o16(2 + ICC_OVERHEAD_LEN + len(marker))
            extra += (
                b"\xFF\xE2"
                + size
                + b"ICC_PROFILE\0"
                + o8(i)
                + o8(len(markers))
                + marker
            )
            i += 1

    comment = info.get("comment", im.info.get("comment"))

    # "progressive" is the official name, but older documentation
    # says "progression"
    # FIXME: issue a warning if the wrong form is used (post-1.1.7)
    progressive = info.get("progressive", False) or info.get("progression", False)

    optimize = info.get("optimize", False)

    exif = info.get("exif", b"")
    if isinstance(exif, Image.Exif):
        exif = exif.tobytes()

    # get keyword arguments
    im.encoderconfig = (
        quality,
        progressive,
        info.get("smooth", 0),
        optimize,
        info.get("streamtype", 0),
        dpi[0],
        dpi[1],
        subsampling,
        qtables,
        comment,
        extra,
        exif,
    )

    # if we optimize, libjpeg needs a buffer big enough to hold the whole image
    # in a shot. Guessing on the size, at im.size bytes. (raw pixel size is
    # channels*size, this is a value that's been used in a django patch.
    # https://github.com/matthewwithanm/django-imagekit/issues/50
    bufsize = 0
    if optimize or progressive:
        # CMYK can be bigger
        if im.mode == "CMYK":
            bufsize = 4 * im.size[0] * im.size[1]
        # keep sets quality to -1, but the actual value may be high.
        elif quality >= 95 or quality == -1:
            bufsize = 2 * im.size[0] * im.size[1]
        else:
            bufsize = im.size[0] * im.size[1]

    # The EXIF info needs to be written as one block, + APP1, + one spare byte.
    # Ensure that our buffer is big enough. Same with the icc_profile block.
    bufsize = max(ImageFile.MAXBLOCK, bufsize, len(exif) + 5, len(extra) + 1)

    ImageFile._save(im, fp, [("jpeg", (0, 0) + im.size, 0, rawmode)], bufsize)


def _save_cjpeg(im, fp, filename):
    # ALTERNATIVE: handle JPEGs via the IJG command line utilities.
    tempfile = im._dump()
    subprocess.check_call(["cjpeg", "-outfile", filename, tempfile])
    try:
        os.unlink(tempfile)
    except OSError:
        pass


##
# Factory for making JPEG and MPO instances
def jpeg_factory(fp=None, filename=None):
    im = JpegImageFile(fp, filename)
    try:
        mpheader = im._getmp()
        if mpheader[45057] > 1:
            # It's actually an MPO
            from .MpoImagePlugin import MpoImageFile

            # Don't reload everything, just convert it.
            im = MpoImageFile.adopt(im, mpheader)
    except (TypeError, IndexError):
        # It is really a JPEG
        pass
    except SyntaxError:
        warnings.warn(
            "Image appears to be a malformed MPO file, it will be "
            "interpreted as a base JPEG file"
        )
    return im


# ---------------------------------------------------------------------
# Registry stuff

Image.register_open(JpegImageFile.format, jpeg_factory, _accept)
Image.register_save(JpegImageFile.format, _save)

Image.register_extensions(JpegImageFile.format, [".jfif", ".jpe", ".jpg", ".jpeg"])

Image.register_mime(JpegImageFile.format, "image/jpeg")

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