{
    "file_diffs": [
        {
            "old_file_content": "import os\nfrom io import BytesIO\n\nimport pytest\n\nfrom PIL import Image, TiffImagePlugin\nfrom PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION\n\nfrom .helper import (\n    assert_image_equal,\n    assert_image_equal_tofile,\n    assert_image_similar,\n    assert_image_similar_tofile,\n    hopper,\n    is_pypy,\n    is_win32,\n)\n\ntry:\n    import defusedxml.ElementTree as ElementTree\nexcept ImportError:\n    ElementTree = None\n\n\nclass TestFileTiff:\n    def test_sanity(self, tmp_path):\n\n        filename = str(tmp_path / \"temp.tif\")\n\n        hopper(\"RGB\").save(filename)\n\n        with Image.open(filename) as im:\n            im.load()\n        assert im.mode == \"RGB\"\n        assert im.size == (128, 128)\n        assert im.format == \"TIFF\"\n\n        hopper(\"1\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"L\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"P\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"RGB\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"I\").save(filename)\n        with Image.open(filename):\n            pass\n\n    @pytest.mark.skipif(is_pypy(), reason=\"Requires CPython\")\n    def test_unclosed_file(self):\n        def open():\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n\n        pytest.warns(ResourceWarning, open)\n\n    def test_closed_file(self):\n        with pytest.warns(None) as record:\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n            im.close()\n\n        assert not record\n\n    def test_context_manager(self):\n        with pytest.warns(None) as record:\n            with Image.open(\"Tests/images/multipage.tiff\") as im:\n                im.load()\n\n        assert not record\n\n    def test_mac_tiff(self):\n        # Read RGBa images from macOS [@PIL136]\n\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (55, 43)\n            assert im.tile == [(\"raw\", (0, 0, 55, 43), 8, (\"RGBa\", 0, 1))]\n            im.load()\n\n            assert_image_similar_tofile(im, \"Tests/images/pil136.png\", 1)\n\n    def test_wrong_bits_per_sample(self):\n        with Image.open(\"Tests/images/tiff_wrong_bits_per_sample.tiff\") as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (52, 53)\n            assert im.tile == [(\"raw\", (0, 0, 52, 53), 160, (\"RGBA\", 0, 1))]\n            im.load()\n\n    def test_set_legacy_api(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        with pytest.raises(Exception) as e:\n            ifd.legacy_api = None\n        assert str(e.value) == \"Not allowing setting of legacy api\"\n\n    def test_xyres_tiff(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # legacy api\n            assert isinstance(im.tag[X_RESOLUTION][0], tuple)\n            assert isinstance(im.tag[Y_RESOLUTION][0], tuple)\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n\n            assert im.info[\"dpi\"] == (72.0, 72.0)\n\n    def test_xyres_fallback_tiff(self):\n        filename = \"Tests/images/compression.tif\"\n        with Image.open(filename) as im:\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n            with pytest.raises(KeyError):\n                im.tag_v2[RESOLUTION_UNIT]\n\n            # Legacy.\n            assert im.info[\"resolution\"] == (100.0, 100.0)\n            # Fallback \"inch\".\n            assert im.info[\"dpi\"] == (100.0, 100.0)\n\n    def test_int_resolution(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # Try to read a file where X,Y_RESOLUTION are ints\n            im.tag_v2[X_RESOLUTION] = 71\n            im.tag_v2[Y_RESOLUTION] = 71\n            im._setup()\n            assert im.info[\"dpi\"] == (71.0, 71.0)\n\n    @pytest.mark.parametrize(\n        \"resolutionUnit, dpi\",\n        [(None, 72.8), (2, 72.8), (3, 184.912)],\n    )\n    def test_load_float_dpi(self, resolutionUnit, dpi):\n        with Image.open(\n            \"Tests/images/hopper_float_dpi_\" + str(resolutionUnit) + \".tif\"\n        ) as im:\n            assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit\n            assert im.info[\"dpi\"] == (dpi, dpi)\n\n    def test_save_float_dpi(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            dpi = (72.2, 72.2)\n            im.save(outfile, dpi=dpi)\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == dpi\n\n    def test_save_setting_missing_resolution(self):\n        b = BytesIO()\n        with Image.open(\"Tests/images/10ct_32bit_128.tiff\") as im:\n            im.save(b, format=\"tiff\", resolution=123.45)\n        with Image.open(b) as im:\n            assert im.tag_v2[X_RESOLUTION] == 123.45\n            assert im.tag_v2[Y_RESOLUTION] == 123.45\n\n    def test_invalid_file(self):\n        invalid_file = \"Tests/images/flower.jpg\"\n\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n\n        TiffImagePlugin.PREFIXES.append(b\"\\xff\\xd8\\xff\\xe0\")\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n        TiffImagePlugin.PREFIXES.pop()\n\n    def test_bad_exif(self):\n        with Image.open(\"Tests/images/hopper_bad_exif.jpg\") as i:\n            # Should not raise struct.error.\n            pytest.warns(UserWarning, i._getexif)\n\n    def test_save_rgba(self, tmp_path):\n        im = hopper(\"RGBA\")\n        outfile = str(tmp_path / \"temp.tif\")\n        im.save(outfile)\n\n    def test_save_unsupported_mode(self, tmp_path):\n        im = hopper(\"HSV\")\n        outfile = str(tmp_path / \"temp.tif\")\n        with pytest.raises(OSError):\n            im.save(outfile)\n\n    def test_little_endian(self):\n        with Image.open(\"Tests/images/16bit.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (little endian)\n        assert b[0] == ord(b\"\\xe0\")\n        assert b[1] == ord(b\"\\x01\")\n\n    def test_big_endian(self):\n        with Image.open(\"Tests/images/16bit.MM.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16B\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (big endian)\n        assert b[0] == ord(b\"\\x01\")\n        assert b[1] == ord(b\"\\xe0\")\n\n    def test_16bit_s(self):\n        with Image.open(\"Tests/images/16bit.s.tif\") as im:\n            im.load()\n            assert im.mode == \"I\"\n            assert im.getpixel((0, 0)) == 32767\n            assert im.getpixel((0, 1)) == 0\n\n    def test_12bit_rawmode(self):\n        \"\"\"Are we generating the same interpretation\n        of the image as Imagemagick is?\"\"\"\n\n        with Image.open(\"Tests/images/12bit.cropped.tif\") as im:\n            # to make the target --\n            # convert 12bit.cropped.tif -depth 16 tmp.tif\n            # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif\n            # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0,\n            # so we need to unshift so that the integer values are the same.\n\n            assert_image_equal_tofile(im, \"Tests/images/12in16bit.tif\")\n\n    def test_32bit_float(self):\n        # Issue 614, specific 32-bit float format\n        path = \"Tests/images/10ct_32bit_128.tiff\"\n        with Image.open(path) as im:\n            im.load()\n\n            assert im.getpixel((0, 0)) == -0.4526388943195343\n            assert im.getextrema() == (-3.140936851501465, 3.140684127807617)\n\n    def test_unknown_pixel_mode(self):\n        with pytest.raises(OSError):\n            with Image.open(\"Tests/images/hopper_unknown_pixel_mode.tif\"):\n                pass\n\n    def test_n_frames(self):\n        for path, n_frames in [\n            [\"Tests/images/multipage-lastframe.tif\", 1],\n            [\"Tests/images/multipage.tiff\", 3],\n        ]:\n            with Image.open(path) as im:\n                assert im.n_frames == n_frames\n                assert im.is_animated == (n_frames != 1)\n\n    def test_eoferror(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            n_frames = im.n_frames\n\n            # Test seeking past the last frame\n            with pytest.raises(EOFError):\n                im.seek(n_frames)\n            assert im.tell() < n_frames\n\n            # Test that seeking to the last frame does not raise an error\n            im.seek(n_frames - 1)\n\n    def test_multipage(self):\n        # issue #862\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue\n\n            im.seek(0)\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(1)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (255, 0, 0)\n\n            im.seek(0)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(2)\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_multipage_last_frame(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_frame_order(self):\n        # A frame can't progress to itself after reading\n        with Image.open(\"Tests/images/multipage_single_frame_loop.tiff\") as im:\n            assert im.n_frames == 1\n\n        # A frame can't progress to a frame that has already been read\n        with Image.open(\"Tests/images/multipage_multiple_frame_loop.tiff\") as im:\n            assert im.n_frames == 2\n\n        # Frames don't have to be in sequence\n        with Image.open(\"Tests/images/multipage_out_of_order.tiff\") as im:\n            assert im.n_frames == 3\n\n    def test___str__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # Act\n            ret = str(im.ifd)\n\n            # Assert\n            assert isinstance(ret, str)\n\n    def test_dict(self):\n        # Arrange\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # v2 interface\n            v2_tags = {\n                256: 55,\n                257: 43,\n                258: (8, 8, 8, 8),\n                259: 1,\n                262: 2,\n                296: 2,\n                273: (8,),\n                338: (1,),\n                277: 4,\n                279: (9460,),\n                282: 72.0,\n                283: 72.0,\n                284: 1,\n            }\n            assert dict(im.tag_v2) == v2_tags\n\n            # legacy interface\n            legacy_tags = {\n                256: (55,),\n                257: (43,),\n                258: (8, 8, 8, 8),\n                259: (1,),\n                262: (2,),\n                296: (2,),\n                273: (8,),\n                338: (1,),\n                277: (4,),\n                279: (9460,),\n                282: ((720000, 10000),),\n                283: ((720000, 10000),),\n                284: (1,),\n            }\n            assert dict(im.tag) == legacy_tags\n\n    def test__delitem__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            len_before = len(dict(im.ifd))\n            del im.ifd[256]\n            len_after = len(dict(im.ifd))\n            assert len_before == len_after + 1\n\n    def test_load_byte(self):\n        for legacy_api in [False, True]:\n            ifd = TiffImagePlugin.ImageFileDirectory_v2()\n            data = b\"abc\"\n            ret = ifd.load_byte(data, legacy_api)\n            assert ret == b\"abc\"\n\n    def test_load_string(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abc\\0\"\n        ret = ifd.load_string(data, False)\n        assert ret == \"abc\"\n\n    def test_load_float(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdabcd\"\n        ret = ifd.load_float(data, False)\n        assert ret == (1.6777999408082104e22, 1.6777999408082104e22)\n\n    def test_load_double(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdefghabcdefgh\"\n        ret = ifd.load_double(data, False)\n        assert ret == (8.540883223036124e194, 8.540883223036124e194)\n\n    def test_ifd_tag_type(self):\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            assert 0x8825 in im.tag_v2\n\n    def test_exif(self):\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            exif = im.getexif()\n\n            assert sorted(exif.keys()) == [\n                256,\n                257,\n                258,\n                259,\n                262,\n                271,\n                272,\n                273,\n                277,\n                278,\n                279,\n                282,\n                283,\n                284,\n                296,\n                297,\n                305,\n                339,\n                700,\n                34665,\n                34853,\n                50735,\n            ]\n            assert exif[256] == 640\n            assert exif[271] == \"FLIR\"\n\n            gps = exif.get_ifd(0x8825)\n            assert list(gps.keys()) == [0, 1, 2, 3, 4, 5, 6, 18]\n            assert gps[0] == b\"\\x03\\x02\\x00\\x00\"\n            assert gps[18] == \"WGS-84\"\n\n    def test_exif_frames(self):\n        # Test that EXIF data can change across frames\n        with Image.open(\"Tests/images/g4-multi.tiff\") as im:\n            assert im.getexif()[273] == (328, 815)\n\n            im.seek(1)\n            assert im.getexif()[273] == (1408, 1907)\n\n    def test_seek(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            im.seek(0)\n            assert im.tell() == 0\n\n    def test_seek_eof(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.tell() == 0\n            with pytest.raises(EOFError):\n                im.seek(-1)\n            with pytest.raises(EOFError):\n                im.seek(1)\n\n    def test__limit_rational_int(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 34\n        ret = _limit_rational(value, 65536)\n        assert ret == (34, 1)\n\n    def test__limit_rational_float(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 22.3\n        ret = _limit_rational(value, 65536)\n        assert ret == (223, 10)\n\n    def test_4bit(self):\n        test_file = \"Tests/images/hopper_gray_4bpp.tif\"\n        original = hopper(\"L\")\n        with Image.open(test_file) as im:\n            assert im.size == (128, 128)\n            assert im.mode == \"L\"\n            assert_image_similar(im, original, 7.3)\n\n    def test_gray_semibyte_per_pixel(self):\n        test_files = (\n            (\n                24.8,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif\",\n                ),\n            ),\n            (\n                7.3,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif\",\n                ),\n            ),\n        )\n        original = hopper(\"L\")\n        for epsilon, group in test_files:\n            with Image.open(group[0]) as im:\n                assert im.size == (128, 128)\n                assert im.mode == \"L\"\n                assert_image_similar(im, original, epsilon)\n                for file in group[1:]:\n                    with Image.open(file) as im2:\n                        assert im2.size == (128, 128)\n                        assert im2.mode == \"L\"\n                        assert_image_equal(im, im2)\n\n    def test_with_underscores(self, tmp_path):\n        kwargs = {\"resolution_unit\": \"inch\", \"x_resolution\": 72, \"y_resolution\": 36}\n        filename = str(tmp_path / \"temp.tif\")\n        hopper(\"RGB\").save(filename, **kwargs)\n        with Image.open(filename) as im:\n\n            # legacy interface\n            assert im.tag[X_RESOLUTION][0][0] == 72\n            assert im.tag[Y_RESOLUTION][0][0] == 36\n\n            # v2 interface\n            assert im.tag_v2[X_RESOLUTION] == 72\n            assert im.tag_v2[Y_RESOLUTION] == 36\n\n    def test_roundtrip_tiff_uint16(self, tmp_path):\n        # Test an image of all '0' values\n        pixel_value = 0x1234\n        infile = \"Tests/images/uint16_1_4660.tif\"\n        with Image.open(infile) as im:\n            assert im.getpixel((0, 0)) == pixel_value\n\n            tmpfile = str(tmp_path / \"temp.tif\")\n            im.save(tmpfile)\n\n            assert_image_equal_tofile(im, tmpfile)\n\n    def test_strip_raw(self):\n        infile = \"Tests/images/tiff_strip_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw(self):\n        # gdal_translate -of GTiff -co INTERLEAVE=BAND \\\n        # tiff_strip_raw.tif tiff_strip_planar_raw.tiff\n        infile = \"Tests/images/tiff_strip_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw_with_overviews(self):\n        # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16\n        infile = \"Tests/images/tiff_strip_planar_raw_with_overviews.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_tiled_planar_raw(self):\n        # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \\\n        # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \\\n        # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff\n        infile = \"Tests/images/tiff_tiled_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_palette(self, tmp_path):\n        def roundtrip(mode):\n            outfile = str(tmp_path / \"temp.tif\")\n\n            im = hopper(mode)\n            im.save(outfile)\n\n            with Image.open(outfile) as reloaded:\n                assert_image_equal(im.convert(\"RGB\"), reloaded.convert(\"RGB\"))\n\n        for mode in [\"P\", \"PA\"]:\n            roundtrip(mode)\n\n    def test_tiff_save_all(self):\n        mp = BytesIO()\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            im.save(mp, format=\"tiff\", save_all=True)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as im:\n            assert im.n_frames == 3\n\n        # Test appending images\n        mp = BytesIO()\n        im = Image.new(\"RGB\", (100, 100), \"#f00\")\n        ims = [Image.new(\"RGB\", (100, 100), color) for color in [\"#0f0\", \"#00f\"]]\n        im.copy().save(mp, format=\"TIFF\", save_all=True, append_images=ims)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            yield from ims\n\n        mp = BytesIO()\n        im.save(mp, format=\"TIFF\", save_all=True, append_images=imGenerator(ims))\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n    def test_saving_icc_profile(self, tmp_path):\n        # Tests saving TIFF with icc_profile set.\n        # At the time of writing this will only work for non-compressed tiffs\n        # as libtiff does not support embedded ICC profiles,\n        # ImageFile._save(..) however does.\n        im = Image.new(\"RGB\", (1, 1))\n        im.info[\"icc_profile\"] = \"Dummy value\"\n\n        # Try save-load round trip to make sure both handle icc_profile.\n        tmpfile = str(tmp_path / \"temp.tif\")\n        im.save(tmpfile, \"TIFF\", compression=\"raw\")\n        with Image.open(tmpfile) as reloaded:\n            assert b\"Dummy value\" == reloaded.info[\"icc_profile\"]\n\n    def test_save_icc_profile(self, tmp_path):\n        im = hopper()\n        assert \"icc_profile\" not in im.info\n\n        outfile = str(tmp_path / \"temp.tif\")\n        icc_profile = b\"Dummy value\"\n        im.save(outfile, icc_profile=icc_profile)\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"icc_profile\"] == icc_profile\n\n    def test_discard_icc_profile(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/icc_profile.png\") as im:\n            assert \"icc_profile\" in im.info\n\n            im.save(outfile, icc_profile=None)\n\n        with Image.open(outfile) as reloaded:\n            assert \"icc_profile\" not in reloaded.info\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/lab.tif\") 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[0][\"format\"] == \"image/tiff\"\n                assert description[3][\"BitsPerSample\"][\"Seq\"][\"li\"] == [\"8\", \"8\", \"8\"]\n\n    def test_close_on_load_exclusive(self, tmp_path):\n        # similar to test_fd_leak, but runs on unixlike os\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        im = Image.open(tmpfile)\n        fp = im.fp\n        assert not fp.closed\n        im.load()\n        assert fp.closed\n\n    def test_close_on_load_nonexclusive(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        with open(tmpfile, \"rb\") as f:\n            im = Image.open(f)\n            fp = im.fp\n            assert not fp.closed\n            im.load()\n            assert not fp.closed\n\n    # Ignore this UserWarning which triggers for four tags:\n    # \"Possibly corrupt EXIF data.  Expecting to read 50404352 bytes but...\"\n    @pytest.mark.filterwarnings(\"ignore:Possibly corrupt EXIF data\")\n    @pytest.mark.skipif(\n        not os.path.exists(\"Tests/images/string_dimension.tiff\"),\n        reason=\"Extra image files not installed\",\n    )\n    def test_string_dimension(self):\n        # Assert that an error is raised if one of the dimensions is a string\n        with Image.open(\"Tests/images/string_dimension.tiff\") as im:\n            with pytest.raises(OSError):\n                im.load()\n\n\n@pytest.mark.skipif(not is_win32(), reason=\"Windows only\")\nclass TestFileTiffW32:\n    def test_fd_leak(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        # this is an mmaped file.\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") 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\n        # this closes the mmap\n        im.close()\n\n        # this should not fail, as load should have closed the file pointer,\n        # and close should have closed the mmap\n        os.remove(tmpfile)\n",
            "new_file_content": "import os\nfrom io import BytesIO\n\nimport pytest\n\nfrom PIL import Image, TiffImagePlugin\nfrom PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION\n\nfrom .helper import (\n    assert_image_equal,\n    assert_image_equal_tofile,\n    assert_image_similar,\n    assert_image_similar_tofile,\n    hopper,\n    is_pypy,\n    is_win32,\n)\n\ntry:\n    import defusedxml.ElementTree as ElementTree\nexcept ImportError:\n    ElementTree = None\n\n\nclass TestFileTiff:\n    def test_sanity(self, tmp_path):\n\n        filename = str(tmp_path / \"temp.tif\")\n\n        hopper(\"RGB\").save(filename)\n\n        with Image.open(filename) as im:\n            im.load()\n        assert im.mode == \"RGB\"\n        assert im.size == (128, 128)\n        assert im.format == \"TIFF\"\n\n        hopper(\"1\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"L\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"P\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"RGB\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"I\").save(filename)\n        with Image.open(filename):\n            pass\n\n    @pytest.mark.skipif(is_pypy(), reason=\"Requires CPython\")\n    def test_unclosed_file(self):\n        def open():\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n\n        pytest.warns(ResourceWarning, open)\n\n    def test_closed_file(self):\n        with pytest.warns(None) as record:\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n            im.close()\n\n        assert not record\n\n    def test_context_manager(self):\n        with pytest.warns(None) as record:\n            with Image.open(\"Tests/images/multipage.tiff\") as im:\n                im.load()\n\n        assert not record\n\n    def test_mac_tiff(self):\n        # Read RGBa images from macOS [@PIL136]\n\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (55, 43)\n            assert im.tile == [(\"raw\", (0, 0, 55, 43), 8, (\"RGBa\", 0, 1))]\n            im.load()\n\n            assert_image_similar_tofile(im, \"Tests/images/pil136.png\", 1)\n\n    def test_wrong_bits_per_sample(self):\n        with Image.open(\"Tests/images/tiff_wrong_bits_per_sample.tiff\") as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (52, 53)\n            assert im.tile == [(\"raw\", (0, 0, 52, 53), 160, (\"RGBA\", 0, 1))]\n            im.load()\n\n    def test_set_legacy_api(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        with pytest.raises(Exception) as e:\n            ifd.legacy_api = None\n        assert str(e.value) == \"Not allowing setting of legacy api\"\n\n    def test_xyres_tiff(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # legacy api\n            assert isinstance(im.tag[X_RESOLUTION][0], tuple)\n            assert isinstance(im.tag[Y_RESOLUTION][0], tuple)\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n\n            assert im.info[\"dpi\"] == (72.0, 72.0)\n\n    def test_xyres_fallback_tiff(self):\n        filename = \"Tests/images/compression.tif\"\n        with Image.open(filename) as im:\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n            with pytest.raises(KeyError):\n                im.tag_v2[RESOLUTION_UNIT]\n\n            # Legacy.\n            assert im.info[\"resolution\"] == (100.0, 100.0)\n            # Fallback \"inch\".\n            assert im.info[\"dpi\"] == (100.0, 100.0)\n\n    def test_int_resolution(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # Try to read a file where X,Y_RESOLUTION are ints\n            im.tag_v2[X_RESOLUTION] = 71\n            im.tag_v2[Y_RESOLUTION] = 71\n            im._setup()\n            assert im.info[\"dpi\"] == (71.0, 71.0)\n\n    @pytest.mark.parametrize(\n        \"resolutionUnit, dpi\",\n        [(None, 72.8), (2, 72.8), (3, 184.912)],\n    )\n    def test_load_float_dpi(self, resolutionUnit, dpi):\n        with Image.open(\n            \"Tests/images/hopper_float_dpi_\" + str(resolutionUnit) + \".tif\"\n        ) as im:\n            assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit\n            assert im.info[\"dpi\"] == (dpi, dpi)\n\n    def test_save_float_dpi(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            dpi = (72.2, 72.2)\n            im.save(outfile, dpi=dpi)\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == dpi\n\n    def test_save_setting_missing_resolution(self):\n        b = BytesIO()\n        with Image.open(\"Tests/images/10ct_32bit_128.tiff\") as im:\n            im.save(b, format=\"tiff\", resolution=123.45)\n        with Image.open(b) as im:\n            assert im.tag_v2[X_RESOLUTION] == 123.45\n            assert im.tag_v2[Y_RESOLUTION] == 123.45\n\n    def test_invalid_file(self):\n        invalid_file = \"Tests/images/flower.jpg\"\n\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n\n        TiffImagePlugin.PREFIXES.append(b\"\\xff\\xd8\\xff\\xe0\")\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n        TiffImagePlugin.PREFIXES.pop()\n\n    def test_bad_exif(self):\n        with Image.open(\"Tests/images/hopper_bad_exif.jpg\") as i:\n            # Should not raise struct.error.\n            pytest.warns(UserWarning, i._getexif)\n\n    def test_save_rgba(self, tmp_path):\n        im = hopper(\"RGBA\")\n        outfile = str(tmp_path / \"temp.tif\")\n        im.save(outfile)\n\n    def test_save_unsupported_mode(self, tmp_path):\n        im = hopper(\"HSV\")\n        outfile = str(tmp_path / \"temp.tif\")\n        with pytest.raises(OSError):\n            im.save(outfile)\n\n    def test_little_endian(self):\n        with Image.open(\"Tests/images/16bit.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (little endian)\n        assert b[0] == ord(b\"\\xe0\")\n        assert b[1] == ord(b\"\\x01\")\n\n    def test_big_endian(self):\n        with Image.open(\"Tests/images/16bit.MM.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16B\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (big endian)\n        assert b[0] == ord(b\"\\x01\")\n        assert b[1] == ord(b\"\\xe0\")\n\n    def test_16bit_s(self):\n        with Image.open(\"Tests/images/16bit.s.tif\") as im:\n            im.load()\n            assert im.mode == \"I\"\n            assert im.getpixel((0, 0)) == 32767\n            assert im.getpixel((0, 1)) == 0\n\n    def test_12bit_rawmode(self):\n        \"\"\"Are we generating the same interpretation\n        of the image as Imagemagick is?\"\"\"\n\n        with Image.open(\"Tests/images/12bit.cropped.tif\") as im:\n            # to make the target --\n            # convert 12bit.cropped.tif -depth 16 tmp.tif\n            # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif\n            # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0,\n            # so we need to unshift so that the integer values are the same.\n\n            assert_image_equal_tofile(im, \"Tests/images/12in16bit.tif\")\n\n    def test_32bit_float(self):\n        # Issue 614, specific 32-bit float format\n        path = \"Tests/images/10ct_32bit_128.tiff\"\n        with Image.open(path) as im:\n            im.load()\n\n            assert im.getpixel((0, 0)) == -0.4526388943195343\n            assert im.getextrema() == (-3.140936851501465, 3.140684127807617)\n\n    def test_unknown_pixel_mode(self):\n        with pytest.raises(OSError):\n            with Image.open(\"Tests/images/hopper_unknown_pixel_mode.tif\"):\n                pass\n\n    def test_n_frames(self):\n        for path, n_frames in [\n            [\"Tests/images/multipage-lastframe.tif\", 1],\n            [\"Tests/images/multipage.tiff\", 3],\n        ]:\n            with Image.open(path) as im:\n                assert im.n_frames == n_frames\n                assert im.is_animated == (n_frames != 1)\n\n    def test_eoferror(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            n_frames = im.n_frames\n\n            # Test seeking past the last frame\n            with pytest.raises(EOFError):\n                im.seek(n_frames)\n            assert im.tell() < n_frames\n\n            # Test that seeking to the last frame does not raise an error\n            im.seek(n_frames - 1)\n\n    def test_multipage(self):\n        # issue #862\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue\n\n            im.seek(0)\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(1)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (255, 0, 0)\n\n            im.seek(0)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(2)\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_multipage_last_frame(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_frame_order(self):\n        # A frame can't progress to itself after reading\n        with Image.open(\"Tests/images/multipage_single_frame_loop.tiff\") as im:\n            assert im.n_frames == 1\n\n        # A frame can't progress to a frame that has already been read\n        with Image.open(\"Tests/images/multipage_multiple_frame_loop.tiff\") as im:\n            assert im.n_frames == 2\n\n        # Frames don't have to be in sequence\n        with Image.open(\"Tests/images/multipage_out_of_order.tiff\") as im:\n            assert im.n_frames == 3\n\n    def test___str__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # Act\n            ret = str(im.ifd)\n\n            # Assert\n            assert isinstance(ret, str)\n\n    def test_dict(self):\n        # Arrange\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # v2 interface\n            v2_tags = {\n                256: 55,\n                257: 43,\n                258: (8, 8, 8, 8),\n                259: 1,\n                262: 2,\n                296: 2,\n                273: (8,),\n                338: (1,),\n                277: 4,\n                279: (9460,),\n                282: 72.0,\n                283: 72.0,\n                284: 1,\n            }\n            assert dict(im.tag_v2) == v2_tags\n\n            # legacy interface\n            legacy_tags = {\n                256: (55,),\n                257: (43,),\n                258: (8, 8, 8, 8),\n                259: (1,),\n                262: (2,),\n                296: (2,),\n                273: (8,),\n                338: (1,),\n                277: (4,),\n                279: (9460,),\n                282: ((720000, 10000),),\n                283: ((720000, 10000),),\n                284: (1,),\n            }\n            assert dict(im.tag) == legacy_tags\n\n    def test__delitem__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            len_before = len(dict(im.ifd))\n            del im.ifd[256]\n            len_after = len(dict(im.ifd))\n            assert len_before == len_after + 1\n\n    def test_load_byte(self):\n        for legacy_api in [False, True]:\n            ifd = TiffImagePlugin.ImageFileDirectory_v2()\n            data = b\"abc\"\n            ret = ifd.load_byte(data, legacy_api)\n            assert ret == b\"abc\"\n\n    def test_load_string(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abc\\0\"\n        ret = ifd.load_string(data, False)\n        assert ret == \"abc\"\n\n    def test_load_float(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdabcd\"\n        ret = ifd.load_float(data, False)\n        assert ret == (1.6777999408082104e22, 1.6777999408082104e22)\n\n    def test_load_double(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdefghabcdefgh\"\n        ret = ifd.load_double(data, False)\n        assert ret == (8.540883223036124e194, 8.540883223036124e194)\n\n    def test_ifd_tag_type(self):\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            assert 0x8825 in im.tag_v2\n\n    def test_exif(self, tmp_path):\n        def check_exif(exif):\n            assert sorted(exif.keys()) == [\n                256,\n                257,\n                258,\n                259,\n                262,\n                271,\n                272,\n                273,\n                277,\n                278,\n                279,\n                282,\n                283,\n                284,\n                296,\n                297,\n                305,\n                339,\n                700,\n                34665,\n                34853,\n                50735,\n            ]\n            assert exif[256] == 640\n            assert exif[271] == \"FLIR\"\n\n            gps = exif.get_ifd(0x8825)\n            assert list(gps.keys()) == [0, 1, 2, 3, 4, 5, 6, 18]\n            assert gps[0] == b\"\\x03\\x02\\x00\\x00\"\n            assert gps[18] == \"WGS-84\"\n\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            exif = im.getexif()\n            check_exif(exif)\n\n            im.save(outfile, exif=exif)\n\n        with Image.open(outfile) as im:\n            exif = im.getexif()\n            check_exif(exif)\n\n    def test_exif_frames(self):\n        # Test that EXIF data can change across frames\n        with Image.open(\"Tests/images/g4-multi.tiff\") as im:\n            assert im.getexif()[273] == (328, 815)\n\n            im.seek(1)\n            assert im.getexif()[273] == (1408, 1907)\n\n    def test_seek(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            im.seek(0)\n            assert im.tell() == 0\n\n    def test_seek_eof(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.tell() == 0\n            with pytest.raises(EOFError):\n                im.seek(-1)\n            with pytest.raises(EOFError):\n                im.seek(1)\n\n    def test__limit_rational_int(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 34\n        ret = _limit_rational(value, 65536)\n        assert ret == (34, 1)\n\n    def test__limit_rational_float(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 22.3\n        ret = _limit_rational(value, 65536)\n        assert ret == (223, 10)\n\n    def test_4bit(self):\n        test_file = \"Tests/images/hopper_gray_4bpp.tif\"\n        original = hopper(\"L\")\n        with Image.open(test_file) as im:\n            assert im.size == (128, 128)\n            assert im.mode == \"L\"\n            assert_image_similar(im, original, 7.3)\n\n    def test_gray_semibyte_per_pixel(self):\n        test_files = (\n            (\n                24.8,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif\",\n                ),\n            ),\n            (\n                7.3,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif\",\n                ),\n            ),\n        )\n        original = hopper(\"L\")\n        for epsilon, group in test_files:\n            with Image.open(group[0]) as im:\n                assert im.size == (128, 128)\n                assert im.mode == \"L\"\n                assert_image_similar(im, original, epsilon)\n                for file in group[1:]:\n                    with Image.open(file) as im2:\n                        assert im2.size == (128, 128)\n                        assert im2.mode == \"L\"\n                        assert_image_equal(im, im2)\n\n    def test_with_underscores(self, tmp_path):\n        kwargs = {\"resolution_unit\": \"inch\", \"x_resolution\": 72, \"y_resolution\": 36}\n        filename = str(tmp_path / \"temp.tif\")\n        hopper(\"RGB\").save(filename, **kwargs)\n        with Image.open(filename) as im:\n\n            # legacy interface\n            assert im.tag[X_RESOLUTION][0][0] == 72\n            assert im.tag[Y_RESOLUTION][0][0] == 36\n\n            # v2 interface\n            assert im.tag_v2[X_RESOLUTION] == 72\n            assert im.tag_v2[Y_RESOLUTION] == 36\n\n    def test_roundtrip_tiff_uint16(self, tmp_path):\n        # Test an image of all '0' values\n        pixel_value = 0x1234\n        infile = \"Tests/images/uint16_1_4660.tif\"\n        with Image.open(infile) as im:\n            assert im.getpixel((0, 0)) == pixel_value\n\n            tmpfile = str(tmp_path / \"temp.tif\")\n            im.save(tmpfile)\n\n            assert_image_equal_tofile(im, tmpfile)\n\n    def test_strip_raw(self):\n        infile = \"Tests/images/tiff_strip_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw(self):\n        # gdal_translate -of GTiff -co INTERLEAVE=BAND \\\n        # tiff_strip_raw.tif tiff_strip_planar_raw.tiff\n        infile = \"Tests/images/tiff_strip_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw_with_overviews(self):\n        # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16\n        infile = \"Tests/images/tiff_strip_planar_raw_with_overviews.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_tiled_planar_raw(self):\n        # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \\\n        # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \\\n        # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff\n        infile = \"Tests/images/tiff_tiled_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_palette(self, tmp_path):\n        def roundtrip(mode):\n            outfile = str(tmp_path / \"temp.tif\")\n\n            im = hopper(mode)\n            im.save(outfile)\n\n            with Image.open(outfile) as reloaded:\n                assert_image_equal(im.convert(\"RGB\"), reloaded.convert(\"RGB\"))\n\n        for mode in [\"P\", \"PA\"]:\n            roundtrip(mode)\n\n    def test_tiff_save_all(self):\n        mp = BytesIO()\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            im.save(mp, format=\"tiff\", save_all=True)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as im:\n            assert im.n_frames == 3\n\n        # Test appending images\n        mp = BytesIO()\n        im = Image.new(\"RGB\", (100, 100), \"#f00\")\n        ims = [Image.new(\"RGB\", (100, 100), color) for color in [\"#0f0\", \"#00f\"]]\n        im.copy().save(mp, format=\"TIFF\", save_all=True, append_images=ims)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            yield from ims\n\n        mp = BytesIO()\n        im.save(mp, format=\"TIFF\", save_all=True, append_images=imGenerator(ims))\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n    def test_saving_icc_profile(self, tmp_path):\n        # Tests saving TIFF with icc_profile set.\n        # At the time of writing this will only work for non-compressed tiffs\n        # as libtiff does not support embedded ICC profiles,\n        # ImageFile._save(..) however does.\n        im = Image.new(\"RGB\", (1, 1))\n        im.info[\"icc_profile\"] = \"Dummy value\"\n\n        # Try save-load round trip to make sure both handle icc_profile.\n        tmpfile = str(tmp_path / \"temp.tif\")\n        im.save(tmpfile, \"TIFF\", compression=\"raw\")\n        with Image.open(tmpfile) as reloaded:\n            assert b\"Dummy value\" == reloaded.info[\"icc_profile\"]\n\n    def test_save_icc_profile(self, tmp_path):\n        im = hopper()\n        assert \"icc_profile\" not in im.info\n\n        outfile = str(tmp_path / \"temp.tif\")\n        icc_profile = b\"Dummy value\"\n        im.save(outfile, icc_profile=icc_profile)\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"icc_profile\"] == icc_profile\n\n    def test_discard_icc_profile(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/icc_profile.png\") as im:\n            assert \"icc_profile\" in im.info\n\n            im.save(outfile, icc_profile=None)\n\n        with Image.open(outfile) as reloaded:\n            assert \"icc_profile\" not in reloaded.info\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/lab.tif\") 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[0][\"format\"] == \"image/tiff\"\n                assert description[3][\"BitsPerSample\"][\"Seq\"][\"li\"] == [\"8\", \"8\", \"8\"]\n\n    def test_close_on_load_exclusive(self, tmp_path):\n        # similar to test_fd_leak, but runs on unixlike os\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        im = Image.open(tmpfile)\n        fp = im.fp\n        assert not fp.closed\n        im.load()\n        assert fp.closed\n\n    def test_close_on_load_nonexclusive(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        with open(tmpfile, \"rb\") as f:\n            im = Image.open(f)\n            fp = im.fp\n            assert not fp.closed\n            im.load()\n            assert not fp.closed\n\n    # Ignore this UserWarning which triggers for four tags:\n    # \"Possibly corrupt EXIF data.  Expecting to read 50404352 bytes but...\"\n    @pytest.mark.filterwarnings(\"ignore:Possibly corrupt EXIF data\")\n    @pytest.mark.skipif(\n        not os.path.exists(\"Tests/images/string_dimension.tiff\"),\n        reason=\"Extra image files not installed\",\n    )\n    def test_string_dimension(self):\n        # Assert that an error is raised if one of the dimensions is a string\n        with Image.open(\"Tests/images/string_dimension.tiff\") as im:\n            with pytest.raises(OSError):\n                im.load()\n\n\n@pytest.mark.skipif(not is_win32(), reason=\"Windows only\")\nclass TestFileTiffW32:\n    def test_fd_leak(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        # this is an mmaped file.\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") 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\n        # this closes the mmap\n        im.close()\n\n        # this should not fail, as load should have closed the file pointer,\n        # and close should have closed the mmap\n        os.remove(tmpfile)\n",
            "header": {
                "file": {
                    "path": "Tests/test_file_tiff.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "57f45bd09",
                "new_commit_hash": "94a3851c9",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/Tests/test_file_tiff.py"
            },
            "plus_file": {
                "path": "b/Tests/test_file_tiff.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 403,
                            "length": 10
                        },
                        "new_range": {
                            "start": 403,
                            "length": 8
                        },
                        "section": "class TestFileTiff:"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:",
                                "type": "context"
                            },
                            {
                                "content": "            assert 0x8825 in im.tag_v2",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    def test_exif(self):",
                                "type": "deleted"
                            },
                            {
                                "content": "        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:",
                                "type": "deleted"
                            },
                            {
                                "content": "            exif = im.getexif()",
                                "type": "deleted"
                            },
                            {
                                "content": "",
                                "type": "deleted"
                            },
                            {
                                "content": "    def test_exif(self, tmp_path):",
                                "type": "added"
                            },
                            {
                                "content": "        def check_exif(exif):",
                                "type": "added"
                            },
                            {
                                "content": "            assert sorted(exif.keys()) == [",
                                "type": "context"
                            },
                            {
                                "content": "                256,",
                                "type": "context"
                            },
                            {
                                "content": "                257,",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Tests/test_file_tiff.py",
                            "type": "class",
                            "name": "TestFileTiff",
                            "content": "class TestFileTiff:\n    def test_sanity(self, tmp_path):\n\n        filename = str(tmp_path / \"temp.tif\")\n\n        hopper(\"RGB\").save(filename)\n\n        with Image.open(filename) as im:\n            im.load()\n        assert im.mode == \"RGB\"\n        assert im.size == (128, 128)\n        assert im.format == \"TIFF\"\n\n        hopper(\"1\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"L\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"P\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"RGB\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"I\").save(filename)\n        with Image.open(filename):\n            pass\n\n    @pytest.mark.skipif(is_pypy(), reason=\"Requires CPython\")\n    def test_unclosed_file(self):\n        def open():\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n\n        pytest.warns(ResourceWarning, open)\n\n    def test_closed_file(self):\n        with pytest.warns(None) as record:\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n            im.close()\n\n        assert not record\n\n    def test_context_manager(self):\n        with pytest.warns(None) as record:\n            with Image.open(\"Tests/images/multipage.tiff\") as im:\n                im.load()\n\n        assert not record\n\n    def test_mac_tiff(self):\n        # Read RGBa images from macOS [@PIL136]\n\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (55, 43)\n            assert im.tile == [(\"raw\", (0, 0, 55, 43), 8, (\"RGBa\", 0, 1))]\n            im.load()\n\n            assert_image_similar_tofile(im, \"Tests/images/pil136.png\", 1)\n\n    def test_wrong_bits_per_sample(self):\n        with Image.open(\"Tests/images/tiff_wrong_bits_per_sample.tiff\") as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (52, 53)\n            assert im.tile == [(\"raw\", (0, 0, 52, 53), 160, (\"RGBA\", 0, 1))]\n            im.load()\n\n    def test_set_legacy_api(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        with pytest.raises(Exception) as e:\n            ifd.legacy_api = None\n        assert str(e.value) == \"Not allowing setting of legacy api\"\n\n    def test_xyres_tiff(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # legacy api\n            assert isinstance(im.tag[X_RESOLUTION][0], tuple)\n            assert isinstance(im.tag[Y_RESOLUTION][0], tuple)\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n\n            assert im.info[\"dpi\"] == (72.0, 72.0)\n\n    def test_xyres_fallback_tiff(self):\n        filename = \"Tests/images/compression.tif\"\n        with Image.open(filename) as im:\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n            with pytest.raises(KeyError):\n                im.tag_v2[RESOLUTION_UNIT]\n\n            # Legacy.\n            assert im.info[\"resolution\"] == (100.0, 100.0)\n            # Fallback \"inch\".\n            assert im.info[\"dpi\"] == (100.0, 100.0)\n\n    def test_int_resolution(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # Try to read a file where X,Y_RESOLUTION are ints\n            im.tag_v2[X_RESOLUTION] = 71\n            im.tag_v2[Y_RESOLUTION] = 71\n            im._setup()\n            assert im.info[\"dpi\"] == (71.0, 71.0)\n\n    @pytest.mark.parametrize(\n        \"resolutionUnit, dpi\",\n        [(None, 72.8), (2, 72.8), (3, 184.912)],\n    )\n    def test_load_float_dpi(self, resolutionUnit, dpi):\n        with Image.open(\n            \"Tests/images/hopper_float_dpi_\" + str(resolutionUnit) + \".tif\"\n        ) as im:\n            assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit\n            assert im.info[\"dpi\"] == (dpi, dpi)\n\n    def test_save_float_dpi(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            dpi = (72.2, 72.2)\n            im.save(outfile, dpi=dpi)\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == dpi\n\n    def test_save_setting_missing_resolution(self):\n        b = BytesIO()\n        with Image.open(\"Tests/images/10ct_32bit_128.tiff\") as im:\n            im.save(b, format=\"tiff\", resolution=123.45)\n        with Image.open(b) as im:\n            assert im.tag_v2[X_RESOLUTION] == 123.45\n            assert im.tag_v2[Y_RESOLUTION] == 123.45\n\n    def test_invalid_file(self):\n        invalid_file = \"Tests/images/flower.jpg\"\n\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n\n        TiffImagePlugin.PREFIXES.append(b\"\\xff\\xd8\\xff\\xe0\")\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n        TiffImagePlugin.PREFIXES.pop()\n\n    def test_bad_exif(self):\n        with Image.open(\"Tests/images/hopper_bad_exif.jpg\") as i:\n            # Should not raise struct.error.\n            pytest.warns(UserWarning, i._getexif)\n\n    def test_save_rgba(self, tmp_path):\n        im = hopper(\"RGBA\")\n        outfile = str(tmp_path / \"temp.tif\")\n        im.save(outfile)\n\n    def test_save_unsupported_mode(self, tmp_path):\n        im = hopper(\"HSV\")\n        outfile = str(tmp_path / \"temp.tif\")\n        with pytest.raises(OSError):\n            im.save(outfile)\n\n    def test_little_endian(self):\n        with Image.open(\"Tests/images/16bit.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (little endian)\n        assert b[0] == ord(b\"\\xe0\")\n        assert b[1] == ord(b\"\\x01\")\n\n    def test_big_endian(self):\n        with Image.open(\"Tests/images/16bit.MM.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16B\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (big endian)\n        assert b[0] == ord(b\"\\x01\")\n        assert b[1] == ord(b\"\\xe0\")\n\n    def test_16bit_s(self):\n        with Image.open(\"Tests/images/16bit.s.tif\") as im:\n            im.load()\n            assert im.mode == \"I\"\n            assert im.getpixel((0, 0)) == 32767\n            assert im.getpixel((0, 1)) == 0\n\n    def test_12bit_rawmode(self):\n        \"\"\"Are we generating the same interpretation\n        of the image as Imagemagick is?\"\"\"\n\n        with Image.open(\"Tests/images/12bit.cropped.tif\") as im:\n            # to make the target --\n            # convert 12bit.cropped.tif -depth 16 tmp.tif\n            # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif\n            # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0,\n            # so we need to unshift so that the integer values are the same.\n\n            assert_image_equal_tofile(im, \"Tests/images/12in16bit.tif\")\n\n    def test_32bit_float(self):\n        # Issue 614, specific 32-bit float format\n        path = \"Tests/images/10ct_32bit_128.tiff\"\n        with Image.open(path) as im:\n            im.load()\n\n            assert im.getpixel((0, 0)) == -0.4526388943195343\n            assert im.getextrema() == (-3.140936851501465, 3.140684127807617)\n\n    def test_unknown_pixel_mode(self):\n        with pytest.raises(OSError):\n            with Image.open(\"Tests/images/hopper_unknown_pixel_mode.tif\"):\n                pass\n\n    def test_n_frames(self):\n        for path, n_frames in [\n            [\"Tests/images/multipage-lastframe.tif\", 1],\n            [\"Tests/images/multipage.tiff\", 3],\n        ]:\n            with Image.open(path) as im:\n                assert im.n_frames == n_frames\n                assert im.is_animated == (n_frames != 1)\n\n    def test_eoferror(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            n_frames = im.n_frames\n\n            # Test seeking past the last frame\n            with pytest.raises(EOFError):\n                im.seek(n_frames)\n            assert im.tell() < n_frames\n\n            # Test that seeking to the last frame does not raise an error\n            im.seek(n_frames - 1)\n\n    def test_multipage(self):\n        # issue #862\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue\n\n            im.seek(0)\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(1)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (255, 0, 0)\n\n            im.seek(0)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(2)\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_multipage_last_frame(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_frame_order(self):\n        # A frame can't progress to itself after reading\n        with Image.open(\"Tests/images/multipage_single_frame_loop.tiff\") as im:\n            assert im.n_frames == 1\n\n        # A frame can't progress to a frame that has already been read\n        with Image.open(\"Tests/images/multipage_multiple_frame_loop.tiff\") as im:\n            assert im.n_frames == 2\n\n        # Frames don't have to be in sequence\n        with Image.open(\"Tests/images/multipage_out_of_order.tiff\") as im:\n            assert im.n_frames == 3\n\n    def test___str__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # Act\n            ret = str(im.ifd)\n\n            # Assert\n            assert isinstance(ret, str)\n\n    def test_dict(self):\n        # Arrange\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # v2 interface\n            v2_tags = {\n                256: 55,\n                257: 43,\n                258: (8, 8, 8, 8),\n                259: 1,\n                262: 2,\n                296: 2,\n                273: (8,),\n                338: (1,),\n                277: 4,\n                279: (9460,),\n                282: 72.0,\n                283: 72.0,\n                284: 1,\n            }\n            assert dict(im.tag_v2) == v2_tags\n\n            # legacy interface\n            legacy_tags = {\n                256: (55,),\n                257: (43,),\n                258: (8, 8, 8, 8),\n                259: (1,),\n                262: (2,),\n                296: (2,),\n                273: (8,),\n                338: (1,),\n                277: (4,),\n                279: (9460,),\n                282: ((720000, 10000),),\n                283: ((720000, 10000),),\n                284: (1,),\n            }\n            assert dict(im.tag) == legacy_tags\n\n    def test__delitem__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            len_before = len(dict(im.ifd))\n            del im.ifd[256]\n            len_after = len(dict(im.ifd))\n            assert len_before == len_after + 1\n\n    def test_load_byte(self):\n        for legacy_api in [False, True]:\n            ifd = TiffImagePlugin.ImageFileDirectory_v2()\n            data = b\"abc\"\n            ret = ifd.load_byte(data, legacy_api)\n            assert ret == b\"abc\"\n\n    def test_load_string(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abc\\0\"\n        ret = ifd.load_string(data, False)\n        assert ret == \"abc\"\n\n    def test_load_float(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdabcd\"\n        ret = ifd.load_float(data, False)\n        assert ret == (1.6777999408082104e22, 1.6777999408082104e22)\n\n    def test_load_double(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdefghabcdefgh\"\n        ret = ifd.load_double(data, False)\n        assert ret == (8.540883223036124e194, 8.540883223036124e194)\n\n    def test_ifd_tag_type(self):\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            assert 0x8825 in im.tag_v2\n\n    def test_exif(self):\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            exif = im.getexif()\n\n            assert sorted(exif.keys()) == [\n                256,\n                257,\n                258,\n                259,\n                262,\n                271,\n                272,\n                273,\n                277,\n                278,\n                279,\n                282,\n                283,\n                284,\n                296,\n                297,\n                305,\n                339,\n                700,\n                34665,\n                34853,\n                50735,\n            ]\n            assert exif[256] == 640\n            assert exif[271] == \"FLIR\"\n\n            gps = exif.get_ifd(0x8825)\n            assert list(gps.keys()) == [0, 1, 2, 3, 4, 5, 6, 18]\n            assert gps[0] == b\"\\x03\\x02\\x00\\x00\"\n            assert gps[18] == \"WGS-84\"\n\n    def test_exif_frames(self):\n        # Test that EXIF data can change across frames\n        with Image.open(\"Tests/images/g4-multi.tiff\") as im:\n            assert im.getexif()[273] == (328, 815)\n\n            im.seek(1)\n            assert im.getexif()[273] == (1408, 1907)\n\n    def test_seek(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            im.seek(0)\n            assert im.tell() == 0\n\n    def test_seek_eof(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.tell() == 0\n            with pytest.raises(EOFError):\n                im.seek(-1)\n            with pytest.raises(EOFError):\n                im.seek(1)\n\n    def test__limit_rational_int(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 34\n        ret = _limit_rational(value, 65536)\n        assert ret == (34, 1)\n\n    def test__limit_rational_float(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 22.3\n        ret = _limit_rational(value, 65536)\n        assert ret == (223, 10)\n\n    def test_4bit(self):\n        test_file = \"Tests/images/hopper_gray_4bpp.tif\"\n        original = hopper(\"L\")\n        with Image.open(test_file) as im:\n            assert im.size == (128, 128)\n            assert im.mode == \"L\"\n            assert_image_similar(im, original, 7.3)\n\n    def test_gray_semibyte_per_pixel(self):\n        test_files = (\n            (\n                24.8,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif\",\n                ),\n            ),\n            (\n                7.3,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif\",\n                ),\n            ),\n        )\n        original = hopper(\"L\")\n        for epsilon, group in test_files:\n            with Image.open(group[0]) as im:\n                assert im.size == (128, 128)\n                assert im.mode == \"L\"\n                assert_image_similar(im, original, epsilon)\n                for file in group[1:]:\n                    with Image.open(file) as im2:\n                        assert im2.size == (128, 128)\n                        assert im2.mode == \"L\"\n                        assert_image_equal(im, im2)\n\n    def test_with_underscores(self, tmp_path):\n        kwargs = {\"resolution_unit\": \"inch\", \"x_resolution\": 72, \"y_resolution\": 36}\n        filename = str(tmp_path / \"temp.tif\")\n        hopper(\"RGB\").save(filename, **kwargs)\n        with Image.open(filename) as im:\n\n            # legacy interface\n            assert im.tag[X_RESOLUTION][0][0] == 72\n            assert im.tag[Y_RESOLUTION][0][0] == 36\n\n            # v2 interface\n            assert im.tag_v2[X_RESOLUTION] == 72\n            assert im.tag_v2[Y_RESOLUTION] == 36\n\n    def test_roundtrip_tiff_uint16(self, tmp_path):\n        # Test an image of all '0' values\n        pixel_value = 0x1234\n        infile = \"Tests/images/uint16_1_4660.tif\"\n        with Image.open(infile) as im:\n            assert im.getpixel((0, 0)) == pixel_value\n\n            tmpfile = str(tmp_path / \"temp.tif\")\n            im.save(tmpfile)\n\n            assert_image_equal_tofile(im, tmpfile)\n\n    def test_strip_raw(self):\n        infile = \"Tests/images/tiff_strip_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw(self):\n        # gdal_translate -of GTiff -co INTERLEAVE=BAND \\\n        # tiff_strip_raw.tif tiff_strip_planar_raw.tiff\n        infile = \"Tests/images/tiff_strip_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw_with_overviews(self):\n        # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16\n        infile = \"Tests/images/tiff_strip_planar_raw_with_overviews.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_tiled_planar_raw(self):\n        # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \\\n        # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \\\n        # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff\n        infile = \"Tests/images/tiff_tiled_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_palette(self, tmp_path):\n        def roundtrip(mode):\n            outfile = str(tmp_path / \"temp.tif\")\n\n            im = hopper(mode)\n            im.save(outfile)\n\n            with Image.open(outfile) as reloaded:\n                assert_image_equal(im.convert(\"RGB\"), reloaded.convert(\"RGB\"))\n\n        for mode in [\"P\", \"PA\"]:\n            roundtrip(mode)\n\n    def test_tiff_save_all(self):\n        mp = BytesIO()\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            im.save(mp, format=\"tiff\", save_all=True)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as im:\n            assert im.n_frames == 3\n\n        # Test appending images\n        mp = BytesIO()\n        im = Image.new(\"RGB\", (100, 100), \"#f00\")\n        ims = [Image.new(\"RGB\", (100, 100), color) for color in [\"#0f0\", \"#00f\"]]\n        im.copy().save(mp, format=\"TIFF\", save_all=True, append_images=ims)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            yield from ims\n\n        mp = BytesIO()\n        im.save(mp, format=\"TIFF\", save_all=True, append_images=imGenerator(ims))\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n    def test_saving_icc_profile(self, tmp_path):\n        # Tests saving TIFF with icc_profile set.\n        # At the time of writing this will only work for non-compressed tiffs\n        # as libtiff does not support embedded ICC profiles,\n        # ImageFile._save(..) however does.\n        im = Image.new(\"RGB\", (1, 1))\n        im.info[\"icc_profile\"] = \"Dummy value\"\n\n        # Try save-load round trip to make sure both handle icc_profile.\n        tmpfile = str(tmp_path / \"temp.tif\")\n        im.save(tmpfile, \"TIFF\", compression=\"raw\")\n        with Image.open(tmpfile) as reloaded:\n            assert b\"Dummy value\" == reloaded.info[\"icc_profile\"]\n\n    def test_save_icc_profile(self, tmp_path):\n        im = hopper()\n        assert \"icc_profile\" not in im.info\n\n        outfile = str(tmp_path / \"temp.tif\")\n        icc_profile = b\"Dummy value\"\n        im.save(outfile, icc_profile=icc_profile)\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"icc_profile\"] == icc_profile\n\n    def test_discard_icc_profile(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/icc_profile.png\") as im:\n            assert \"icc_profile\" in im.info\n\n            im.save(outfile, icc_profile=None)\n\n        with Image.open(outfile) as reloaded:\n            assert \"icc_profile\" not in reloaded.info\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/lab.tif\") 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[0][\"format\"] == \"image/tiff\"\n                assert description[3][\"BitsPerSample\"][\"Seq\"][\"li\"] == [\"8\", \"8\", \"8\"]\n\n    def test_close_on_load_exclusive(self, tmp_path):\n        # similar to test_fd_leak, but runs on unixlike os\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        im = Image.open(tmpfile)\n        fp = im.fp\n        assert not fp.closed\n        im.load()\n        assert fp.closed\n\n    def test_close_on_load_nonexclusive(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        with open(tmpfile, \"rb\") as f:\n            im = Image.open(f)\n            fp = im.fp\n            assert not fp.closed\n            im.load()\n            assert not fp.closed\n\n    # Ignore this UserWarning which triggers for four tags:\n    # \"Possibly corrupt EXIF data.  Expecting to read 50404352 bytes but...\"\n    @pytest.mark.filterwarnings(\"ignore:Possibly corrupt EXIF data\")\n    @pytest.mark.skipif(\n        not os.path.exists(\"Tests/images/string_dimension.tiff\"),\n        reason=\"Extra image files not installed\",\n    )\n    def test_string_dimension(self):\n        # Assert that an error is raised if one of the dimensions is a string\n        with Image.open(\"Tests/images/string_dimension.tiff\") as im:\n            with pytest.raises(OSError):\n                im.load()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 25,
                            "end_lineno": 700,
                            "parent": null
                        },
                        {
                            "file_name": "Tests/test_file_tiff.py",
                            "type": "method",
                            "name": "TestFileTiff.test_exif",
                            "content": "class TestFileTiff:\n    def test_sanity(self, tmp_path):\n\n        filename = str(tmp_path / \"temp.tif\")\n\n        hopper(\"RGB\").save(filename)\n\n        with Image.open(filename) as im:\n            im.load()\n        assert im.mode == \"RGB\"\n        assert im.size == (128, 128)\n        assert im.format == \"TIFF\"\n\n        hopper(\"1\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"L\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"P\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"RGB\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"I\").save(filename)\n        with Image.open(filename):\n            pass\n\n    @pytest.mark.skipif(is_pypy(), reason=\"Requires CPython\")\n    def test_unclosed_file(self):\n        def open():\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n\n        pytest.warns(ResourceWarning, open)\n\n    def test_closed_file(self):\n        with pytest.warns(None) as record:\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n            im.close()\n\n        assert not record\n\n    def test_context_manager(self):\n        with pytest.warns(None) as record:\n            with Image.open(\"Tests/images/multipage.tiff\") as im:\n                im.load()\n\n        assert not record\n\n    def test_mac_tiff(self):\n        # Read RGBa images from macOS [@PIL136]\n\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (55, 43)\n            assert im.tile == [(\"raw\", (0, 0, 55, 43), 8, (\"RGBa\", 0, 1))]\n            im.load()\n\n            assert_image_similar_tofile(im, \"Tests/images/pil136.png\", 1)\n\n    def test_wrong_bits_per_sample(self):\n        with Image.open(\"Tests/images/tiff_wrong_bits_per_sample.tiff\") as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (52, 53)\n            assert im.tile == [(\"raw\", (0, 0, 52, 53), 160, (\"RGBA\", 0, 1))]\n            im.load()\n\n    def test_set_legacy_api(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        with pytest.raises(Exception) as e:\n            ifd.legacy_api = None\n        assert str(e.value) == \"Not allowing setting of legacy api\"\n\n    def test_xyres_tiff(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # legacy api\n            assert isinstance(im.tag[X_RESOLUTION][0], tuple)\n            assert isinstance(im.tag[Y_RESOLUTION][0], tuple)\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n\n            assert im.info[\"dpi\"] == (72.0, 72.0)\n\n    def test_xyres_fallback_tiff(self):\n        filename = \"Tests/images/compression.tif\"\n        with Image.open(filename) as im:\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n            with pytest.raises(KeyError):\n                im.tag_v2[RESOLUTION_UNIT]\n\n            # Legacy.\n            assert im.info[\"resolution\"] == (100.0, 100.0)\n            # Fallback \"inch\".\n            assert im.info[\"dpi\"] == (100.0, 100.0)\n\n    def test_int_resolution(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # Try to read a file where X,Y_RESOLUTION are ints\n            im.tag_v2[X_RESOLUTION] = 71\n            im.tag_v2[Y_RESOLUTION] = 71\n            im._setup()\n            assert im.info[\"dpi\"] == (71.0, 71.0)\n\n    @pytest.mark.parametrize(\n        \"resolutionUnit, dpi\",\n        [(None, 72.8), (2, 72.8), (3, 184.912)],\n    )\n    def test_load_float_dpi(self, resolutionUnit, dpi):\n        with Image.open(\n            \"Tests/images/hopper_float_dpi_\" + str(resolutionUnit) + \".tif\"\n        ) as im:\n            assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit\n            assert im.info[\"dpi\"] == (dpi, dpi)\n\n    def test_save_float_dpi(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            dpi = (72.2, 72.2)\n            im.save(outfile, dpi=dpi)\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == dpi\n\n    def test_save_setting_missing_resolution(self):\n        b = BytesIO()\n        with Image.open(\"Tests/images/10ct_32bit_128.tiff\") as im:\n            im.save(b, format=\"tiff\", resolution=123.45)\n        with Image.open(b) as im:\n            assert im.tag_v2[X_RESOLUTION] == 123.45\n            assert im.tag_v2[Y_RESOLUTION] == 123.45\n\n    def test_invalid_file(self):\n        invalid_file = \"Tests/images/flower.jpg\"\n\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n\n        TiffImagePlugin.PREFIXES.append(b\"\\xff\\xd8\\xff\\xe0\")\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n        TiffImagePlugin.PREFIXES.pop()\n\n    def test_bad_exif(self):\n        with Image.open(\"Tests/images/hopper_bad_exif.jpg\") as i:\n            # Should not raise struct.error.\n            pytest.warns(UserWarning, i._getexif)\n\n    def test_save_rgba(self, tmp_path):\n        im = hopper(\"RGBA\")\n        outfile = str(tmp_path / \"temp.tif\")\n        im.save(outfile)\n\n    def test_save_unsupported_mode(self, tmp_path):\n        im = hopper(\"HSV\")\n        outfile = str(tmp_path / \"temp.tif\")\n        with pytest.raises(OSError):\n            im.save(outfile)\n\n    def test_little_endian(self):\n        with Image.open(\"Tests/images/16bit.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (little endian)\n        assert b[0] == ord(b\"\\xe0\")\n        assert b[1] == ord(b\"\\x01\")\n\n    def test_big_endian(self):\n        with Image.open(\"Tests/images/16bit.MM.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16B\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (big endian)\n        assert b[0] == ord(b\"\\x01\")\n        assert b[1] == ord(b\"\\xe0\")\n\n    def test_16bit_s(self):\n        with Image.open(\"Tests/images/16bit.s.tif\") as im:\n            im.load()\n            assert im.mode == \"I\"\n            assert im.getpixel((0, 0)) == 32767\n            assert im.getpixel((0, 1)) == 0\n\n    def test_12bit_rawmode(self):\n        \"\"\"Are we generating the same interpretation\n        of the image as Imagemagick is?\"\"\"\n\n        with Image.open(\"Tests/images/12bit.cropped.tif\") as im:\n            # to make the target --\n            # convert 12bit.cropped.tif -depth 16 tmp.tif\n            # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif\n            # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0,\n            # so we need to unshift so that the integer values are the same.\n\n            assert_image_equal_tofile(im, \"Tests/images/12in16bit.tif\")\n\n    def test_32bit_float(self):\n        # Issue 614, specific 32-bit float format\n        path = \"Tests/images/10ct_32bit_128.tiff\"\n        with Image.open(path) as im:\n            im.load()\n\n            assert im.getpixel((0, 0)) == -0.4526388943195343\n            assert im.getextrema() == (-3.140936851501465, 3.140684127807617)\n\n    def test_unknown_pixel_mode(self):\n        with pytest.raises(OSError):\n            with Image.open(\"Tests/images/hopper_unknown_pixel_mode.tif\"):\n                pass\n\n    def test_n_frames(self):\n        for path, n_frames in [\n            [\"Tests/images/multipage-lastframe.tif\", 1],\n            [\"Tests/images/multipage.tiff\", 3],\n        ]:\n            with Image.open(path) as im:\n                assert im.n_frames == n_frames\n                assert im.is_animated == (n_frames != 1)\n\n    def test_eoferror(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            n_frames = im.n_frames\n\n            # Test seeking past the last frame\n            with pytest.raises(EOFError):\n                im.seek(n_frames)\n            assert im.tell() < n_frames\n\n            # Test that seeking to the last frame does not raise an error\n            im.seek(n_frames - 1)\n\n    def test_multipage(self):\n        # issue #862\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue\n\n            im.seek(0)\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(1)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (255, 0, 0)\n\n            im.seek(0)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(2)\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_multipage_last_frame(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_frame_order(self):\n        # A frame can't progress to itself after reading\n        with Image.open(\"Tests/images/multipage_single_frame_loop.tiff\") as im:\n            assert im.n_frames == 1\n\n        # A frame can't progress to a frame that has already been read\n        with Image.open(\"Tests/images/multipage_multiple_frame_loop.tiff\") as im:\n            assert im.n_frames == 2\n\n        # Frames don't have to be in sequence\n        with Image.open(\"Tests/images/multipage_out_of_order.tiff\") as im:\n            assert im.n_frames == 3\n\n    def test___str__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # Act\n            ret = str(im.ifd)\n\n            # Assert\n            assert isinstance(ret, str)\n\n    def test_dict(self):\n        # Arrange\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # v2 interface\n            v2_tags = {\n                256: 55,\n                257: 43,\n                258: (8, 8, 8, 8),\n                259: 1,\n                262: 2,\n                296: 2,\n                273: (8,),\n                338: (1,),\n                277: 4,\n                279: (9460,),\n                282: 72.0,\n                283: 72.0,\n                284: 1,\n            }\n            assert dict(im.tag_v2) == v2_tags\n\n            # legacy interface\n            legacy_tags = {\n                256: (55,),\n                257: (43,),\n                258: (8, 8, 8, 8),\n                259: (1,),\n                262: (2,),\n                296: (2,),\n                273: (8,),\n                338: (1,),\n                277: (4,),\n                279: (9460,),\n                282: ((720000, 10000),),\n                283: ((720000, 10000),),\n                284: (1,),\n            }\n            assert dict(im.tag) == legacy_tags\n\n    def test__delitem__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            len_before = len(dict(im.ifd))\n            del im.ifd[256]\n            len_after = len(dict(im.ifd))\n            assert len_before == len_after + 1\n\n    def test_load_byte(self):\n        for legacy_api in [False, True]:\n            ifd = TiffImagePlugin.ImageFileDirectory_v2()\n            data = b\"abc\"\n            ret = ifd.load_byte(data, legacy_api)\n            assert ret == b\"abc\"\n\n    def test_load_string(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abc\\0\"\n        ret = ifd.load_string(data, False)\n        assert ret == \"abc\"\n\n    def test_load_float(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdabcd\"\n        ret = ifd.load_float(data, False)\n        assert ret == (1.6777999408082104e22, 1.6777999408082104e22)\n\n    def test_load_double(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdefghabcdefgh\"\n        ret = ifd.load_double(data, False)\n        assert ret == (8.540883223036124e194, 8.540883223036124e194)\n\n    def test_ifd_tag_type(self):\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            assert 0x8825 in im.tag_v2\n\n    def test_exif(self):\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            exif = im.getexif()\n\n            assert sorted(exif.keys()) == [\n                256,\n                257,\n                258,\n                259,\n                262,\n                271,\n                272,\n                273,\n                277,\n                278,\n                279,\n                282,\n                283,\n                284,\n                296,\n                297,\n                305,\n                339,\n                700,\n                34665,\n                34853,\n                50735,\n            ]\n            assert exif[256] == 640\n            assert exif[271] == \"FLIR\"\n\n            gps = exif.get_ifd(0x8825)\n            assert list(gps.keys()) == [0, 1, 2, 3, 4, 5, 6, 18]\n            assert gps[0] == b\"\\x03\\x02\\x00\\x00\"\n            assert gps[18] == \"WGS-84\"\n\n    def test_exif_frames(self):\n        # Test that EXIF data can change across frames\n        with Image.open(\"Tests/images/g4-multi.tiff\") as im:\n            assert im.getexif()[273] == (328, 815)\n\n            im.seek(1)\n            assert im.getexif()[273] == (1408, 1907)\n\n    def test_seek(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            im.seek(0)\n            assert im.tell() == 0\n\n    def test_seek_eof(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.tell() == 0\n            with pytest.raises(EOFError):\n                im.seek(-1)\n            with pytest.raises(EOFError):\n                im.seek(1)\n\n    def test__limit_rational_int(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 34\n        ret = _limit_rational(value, 65536)\n        assert ret == (34, 1)\n\n    def test__limit_rational_float(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 22.3\n        ret = _limit_rational(value, 65536)\n        assert ret == (223, 10)\n\n    def test_4bit(self):\n        test_file = \"Tests/images/hopper_gray_4bpp.tif\"\n        original = hopper(\"L\")\n        with Image.open(test_file) as im:\n            assert im.size == (128, 128)\n            assert im.mode == \"L\"\n            assert_image_similar(im, original, 7.3)\n\n    def test_gray_semibyte_per_pixel(self):\n        test_files = (\n            (\n                24.8,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif\",\n                ),\n            ),\n            (\n                7.3,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif\",\n                ),\n            ),\n        )\n        original = hopper(\"L\")\n        for epsilon, group in test_files:\n            with Image.open(group[0]) as im:\n                assert im.size == (128, 128)\n                assert im.mode == \"L\"\n                assert_image_similar(im, original, epsilon)\n                for file in group[1:]:\n                    with Image.open(file) as im2:\n                        assert im2.size == (128, 128)\n                        assert im2.mode == \"L\"\n                        assert_image_equal(im, im2)\n\n    def test_with_underscores(self, tmp_path):\n        kwargs = {\"resolution_unit\": \"inch\", \"x_resolution\": 72, \"y_resolution\": 36}\n        filename = str(tmp_path / \"temp.tif\")\n        hopper(\"RGB\").save(filename, **kwargs)\n        with Image.open(filename) as im:\n\n            # legacy interface\n            assert im.tag[X_RESOLUTION][0][0] == 72\n            assert im.tag[Y_RESOLUTION][0][0] == 36\n\n            # v2 interface\n            assert im.tag_v2[X_RESOLUTION] == 72\n            assert im.tag_v2[Y_RESOLUTION] == 36\n\n    def test_roundtrip_tiff_uint16(self, tmp_path):\n        # Test an image of all '0' values\n        pixel_value = 0x1234\n        infile = \"Tests/images/uint16_1_4660.tif\"\n        with Image.open(infile) as im:\n            assert im.getpixel((0, 0)) == pixel_value\n\n            tmpfile = str(tmp_path / \"temp.tif\")\n            im.save(tmpfile)\n\n            assert_image_equal_tofile(im, tmpfile)\n\n    def test_strip_raw(self):\n        infile = \"Tests/images/tiff_strip_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw(self):\n        # gdal_translate -of GTiff -co INTERLEAVE=BAND \\\n        # tiff_strip_raw.tif tiff_strip_planar_raw.tiff\n        infile = \"Tests/images/tiff_strip_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw_with_overviews(self):\n        # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16\n        infile = \"Tests/images/tiff_strip_planar_raw_with_overviews.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_tiled_planar_raw(self):\n        # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \\\n        # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \\\n        # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff\n        infile = \"Tests/images/tiff_tiled_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_palette(self, tmp_path):\n        def roundtrip(mode):\n            outfile = str(tmp_path / \"temp.tif\")\n\n            im = hopper(mode)\n            im.save(outfile)\n\n            with Image.open(outfile) as reloaded:\n                assert_image_equal(im.convert(\"RGB\"), reloaded.convert(\"RGB\"))\n\n        for mode in [\"P\", \"PA\"]:\n            roundtrip(mode)\n\n    def test_tiff_save_all(self):\n        mp = BytesIO()\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            im.save(mp, format=\"tiff\", save_all=True)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as im:\n            assert im.n_frames == 3\n\n        # Test appending images\n        mp = BytesIO()\n        im = Image.new(\"RGB\", (100, 100), \"#f00\")\n        ims = [Image.new(\"RGB\", (100, 100), color) for color in [\"#0f0\", \"#00f\"]]\n        im.copy().save(mp, format=\"TIFF\", save_all=True, append_images=ims)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            yield from ims\n\n        mp = BytesIO()\n        im.save(mp, format=\"TIFF\", save_all=True, append_images=imGenerator(ims))\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n    def test_saving_icc_profile(self, tmp_path):\n        # Tests saving TIFF with icc_profile set.\n        # At the time of writing this will only work for non-compressed tiffs\n        # as libtiff does not support embedded ICC profiles,\n        # ImageFile._save(..) however does.\n        im = Image.new(\"RGB\", (1, 1))\n        im.info[\"icc_profile\"] = \"Dummy value\"\n\n        # Try save-load round trip to make sure both handle icc_profile.\n        tmpfile = str(tmp_path / \"temp.tif\")\n        im.save(tmpfile, \"TIFF\", compression=\"raw\")\n        with Image.open(tmpfile) as reloaded:\n            assert b\"Dummy value\" == reloaded.info[\"icc_profile\"]\n\n    def test_save_icc_profile(self, tmp_path):\n        im = hopper()\n        assert \"icc_profile\" not in im.info\n\n        outfile = str(tmp_path / \"temp.tif\")\n        icc_profile = b\"Dummy value\"\n        im.save(outfile, icc_profile=icc_profile)\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"icc_profile\"] == icc_profile\n\n    def test_discard_icc_profile(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/icc_profile.png\") as im:\n            assert \"icc_profile\" in im.info\n\n            im.save(outfile, icc_profile=None)\n\n        with Image.open(outfile) as reloaded:\n            assert \"icc_profile\" not in reloaded.info\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/lab.tif\") 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[0][\"format\"] == \"image/tiff\"\n                assert description[3][\"BitsPerSample\"][\"Seq\"][\"li\"] == [\"8\", \"8\", \"8\"]\n\n    def test_close_on_load_exclusive(self, tmp_path):\n        # similar to test_fd_leak, but runs on unixlike os\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        im = Image.open(tmpfile)\n        fp = im.fp\n        assert not fp.closed\n        im.load()\n        assert fp.closed\n\n    def test_close_on_load_nonexclusive(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        with open(tmpfile, \"rb\") as f:\n            im = Image.open(f)\n            fp = im.fp\n            assert not fp.closed\n            im.load()\n            assert not fp.closed\n\n    # Ignore this UserWarning which triggers for four tags:\n    # \"Possibly corrupt EXIF data.  Expecting to read 50404352 bytes but...\"\n    @pytest.mark.filterwarnings(\"ignore:Possibly corrupt EXIF data\")\n    @pytest.mark.skipif(\n        not os.path.exists(\"Tests/images/string_dimension.tiff\"),\n        reason=\"Extra image files not installed\",\n    )\n    def test_string_dimension(self):\n        # Assert that an error is raised if one of the dimensions is a string\n        with Image.open(\"Tests/images/string_dimension.tiff\") as im:\n            with pytest.raises(OSError):\n                im.load()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 406,
                            "end_lineno": 440,
                            "parent": {
                                "file_name": "Tests/test_file_tiff.py",
                                "type": "class",
                                "name": "TestFileTiff",
                                "content": "class TestFileTiff:\n    def test_sanity(self, tmp_path):\n\n        filename = str(tmp_path / \"temp.tif\")\n\n        hopper(\"RGB\").save(filename)\n\n        with Image.open(filename) as im:\n            im.load()\n        assert im.mode == \"RGB\"\n        assert im.size == (128, 128)\n        assert im.format == \"TIFF\"\n\n        hopper(\"1\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"L\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"P\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"RGB\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"I\").save(filename)\n        with Image.open(filename):\n            pass\n\n    @pytest.mark.skipif(is_pypy(), reason=\"Requires CPython\")\n    def test_unclosed_file(self):\n        def open():\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n\n        pytest.warns(ResourceWarning, open)\n\n    def test_closed_file(self):\n        with pytest.warns(None) as record:\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n            im.close()\n\n        assert not record\n\n    def test_context_manager(self):\n        with pytest.warns(None) as record:\n            with Image.open(\"Tests/images/multipage.tiff\") as im:\n                im.load()\n\n        assert not record\n\n    def test_mac_tiff(self):\n        # Read RGBa images from macOS [@PIL136]\n\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (55, 43)\n            assert im.tile == [(\"raw\", (0, 0, 55, 43), 8, (\"RGBa\", 0, 1))]\n            im.load()\n\n            assert_image_similar_tofile(im, \"Tests/images/pil136.png\", 1)\n\n    def test_wrong_bits_per_sample(self):\n        with Image.open(\"Tests/images/tiff_wrong_bits_per_sample.tiff\") as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (52, 53)\n            assert im.tile == [(\"raw\", (0, 0, 52, 53), 160, (\"RGBA\", 0, 1))]\n            im.load()\n\n    def test_set_legacy_api(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        with pytest.raises(Exception) as e:\n            ifd.legacy_api = None\n        assert str(e.value) == \"Not allowing setting of legacy api\"\n\n    def test_xyres_tiff(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # legacy api\n            assert isinstance(im.tag[X_RESOLUTION][0], tuple)\n            assert isinstance(im.tag[Y_RESOLUTION][0], tuple)\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n\n            assert im.info[\"dpi\"] == (72.0, 72.0)\n\n    def test_xyres_fallback_tiff(self):\n        filename = \"Tests/images/compression.tif\"\n        with Image.open(filename) as im:\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n            with pytest.raises(KeyError):\n                im.tag_v2[RESOLUTION_UNIT]\n\n            # Legacy.\n            assert im.info[\"resolution\"] == (100.0, 100.0)\n            # Fallback \"inch\".\n            assert im.info[\"dpi\"] == (100.0, 100.0)\n\n    def test_int_resolution(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # Try to read a file where X,Y_RESOLUTION are ints\n            im.tag_v2[X_RESOLUTION] = 71\n            im.tag_v2[Y_RESOLUTION] = 71\n            im._setup()\n            assert im.info[\"dpi\"] == (71.0, 71.0)\n\n    @pytest.mark.parametrize(\n        \"resolutionUnit, dpi\",\n        [(None, 72.8), (2, 72.8), (3, 184.912)],\n    )\n    def test_load_float_dpi(self, resolutionUnit, dpi):\n        with Image.open(\n            \"Tests/images/hopper_float_dpi_\" + str(resolutionUnit) + \".tif\"\n        ) as im:\n            assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit\n            assert im.info[\"dpi\"] == (dpi, dpi)\n\n    def test_save_float_dpi(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            dpi = (72.2, 72.2)\n            im.save(outfile, dpi=dpi)\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == dpi\n\n    def test_save_setting_missing_resolution(self):\n        b = BytesIO()\n        with Image.open(\"Tests/images/10ct_32bit_128.tiff\") as im:\n            im.save(b, format=\"tiff\", resolution=123.45)\n        with Image.open(b) as im:\n            assert im.tag_v2[X_RESOLUTION] == 123.45\n            assert im.tag_v2[Y_RESOLUTION] == 123.45\n\n    def test_invalid_file(self):\n        invalid_file = \"Tests/images/flower.jpg\"\n\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n\n        TiffImagePlugin.PREFIXES.append(b\"\\xff\\xd8\\xff\\xe0\")\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n        TiffImagePlugin.PREFIXES.pop()\n\n    def test_bad_exif(self):\n        with Image.open(\"Tests/images/hopper_bad_exif.jpg\") as i:\n            # Should not raise struct.error.\n            pytest.warns(UserWarning, i._getexif)\n\n    def test_save_rgba(self, tmp_path):\n        im = hopper(\"RGBA\")\n        outfile = str(tmp_path / \"temp.tif\")\n        im.save(outfile)\n\n    def test_save_unsupported_mode(self, tmp_path):\n        im = hopper(\"HSV\")\n        outfile = str(tmp_path / \"temp.tif\")\n        with pytest.raises(OSError):\n            im.save(outfile)\n\n    def test_little_endian(self):\n        with Image.open(\"Tests/images/16bit.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (little endian)\n        assert b[0] == ord(b\"\\xe0\")\n        assert b[1] == ord(b\"\\x01\")\n\n    def test_big_endian(self):\n        with Image.open(\"Tests/images/16bit.MM.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16B\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (big endian)\n        assert b[0] == ord(b\"\\x01\")\n        assert b[1] == ord(b\"\\xe0\")\n\n    def test_16bit_s(self):\n        with Image.open(\"Tests/images/16bit.s.tif\") as im:\n            im.load()\n            assert im.mode == \"I\"\n            assert im.getpixel((0, 0)) == 32767\n            assert im.getpixel((0, 1)) == 0\n\n    def test_12bit_rawmode(self):\n        \"\"\"Are we generating the same interpretation\n        of the image as Imagemagick is?\"\"\"\n\n        with Image.open(\"Tests/images/12bit.cropped.tif\") as im:\n            # to make the target --\n            # convert 12bit.cropped.tif -depth 16 tmp.tif\n            # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif\n            # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0,\n            # so we need to unshift so that the integer values are the same.\n\n            assert_image_equal_tofile(im, \"Tests/images/12in16bit.tif\")\n\n    def test_32bit_float(self):\n        # Issue 614, specific 32-bit float format\n        path = \"Tests/images/10ct_32bit_128.tiff\"\n        with Image.open(path) as im:\n            im.load()\n\n            assert im.getpixel((0, 0)) == -0.4526388943195343\n            assert im.getextrema() == (-3.140936851501465, 3.140684127807617)\n\n    def test_unknown_pixel_mode(self):\n        with pytest.raises(OSError):\n            with Image.open(\"Tests/images/hopper_unknown_pixel_mode.tif\"):\n                pass\n\n    def test_n_frames(self):\n        for path, n_frames in [\n            [\"Tests/images/multipage-lastframe.tif\", 1],\n            [\"Tests/images/multipage.tiff\", 3],\n        ]:\n            with Image.open(path) as im:\n                assert im.n_frames == n_frames\n                assert im.is_animated == (n_frames != 1)\n\n    def test_eoferror(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            n_frames = im.n_frames\n\n            # Test seeking past the last frame\n            with pytest.raises(EOFError):\n                im.seek(n_frames)\n            assert im.tell() < n_frames\n\n            # Test that seeking to the last frame does not raise an error\n            im.seek(n_frames - 1)\n\n    def test_multipage(self):\n        # issue #862\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue\n\n            im.seek(0)\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(1)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (255, 0, 0)\n\n            im.seek(0)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(2)\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_multipage_last_frame(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_frame_order(self):\n        # A frame can't progress to itself after reading\n        with Image.open(\"Tests/images/multipage_single_frame_loop.tiff\") as im:\n            assert im.n_frames == 1\n\n        # A frame can't progress to a frame that has already been read\n        with Image.open(\"Tests/images/multipage_multiple_frame_loop.tiff\") as im:\n            assert im.n_frames == 2\n\n        # Frames don't have to be in sequence\n        with Image.open(\"Tests/images/multipage_out_of_order.tiff\") as im:\n            assert im.n_frames == 3\n\n    def test___str__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # Act\n            ret = str(im.ifd)\n\n            # Assert\n            assert isinstance(ret, str)\n\n    def test_dict(self):\n        # Arrange\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # v2 interface\n            v2_tags = {\n                256: 55,\n                257: 43,\n                258: (8, 8, 8, 8),\n                259: 1,\n                262: 2,\n                296: 2,\n                273: (8,),\n                338: (1,),\n                277: 4,\n                279: (9460,),\n                282: 72.0,\n                283: 72.0,\n                284: 1,\n            }\n            assert dict(im.tag_v2) == v2_tags\n\n            # legacy interface\n            legacy_tags = {\n                256: (55,),\n                257: (43,),\n                258: (8, 8, 8, 8),\n                259: (1,),\n                262: (2,),\n                296: (2,),\n                273: (8,),\n                338: (1,),\n                277: (4,),\n                279: (9460,),\n                282: ((720000, 10000),),\n                283: ((720000, 10000),),\n                284: (1,),\n            }\n            assert dict(im.tag) == legacy_tags\n\n    def test__delitem__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            len_before = len(dict(im.ifd))\n            del im.ifd[256]\n            len_after = len(dict(im.ifd))\n            assert len_before == len_after + 1\n\n    def test_load_byte(self):\n        for legacy_api in [False, True]:\n            ifd = TiffImagePlugin.ImageFileDirectory_v2()\n            data = b\"abc\"\n            ret = ifd.load_byte(data, legacy_api)\n            assert ret == b\"abc\"\n\n    def test_load_string(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abc\\0\"\n        ret = ifd.load_string(data, False)\n        assert ret == \"abc\"\n\n    def test_load_float(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdabcd\"\n        ret = ifd.load_float(data, False)\n        assert ret == (1.6777999408082104e22, 1.6777999408082104e22)\n\n    def test_load_double(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdefghabcdefgh\"\n        ret = ifd.load_double(data, False)\n        assert ret == (8.540883223036124e194, 8.540883223036124e194)\n\n    def test_ifd_tag_type(self):\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            assert 0x8825 in im.tag_v2\n\n    def test_exif(self):\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            exif = im.getexif()\n\n            assert sorted(exif.keys()) == [\n                256,\n                257,\n                258,\n                259,\n                262,\n                271,\n                272,\n                273,\n                277,\n                278,\n                279,\n                282,\n                283,\n                284,\n                296,\n                297,\n                305,\n                339,\n                700,\n                34665,\n                34853,\n                50735,\n            ]\n            assert exif[256] == 640\n            assert exif[271] == \"FLIR\"\n\n            gps = exif.get_ifd(0x8825)\n            assert list(gps.keys()) == [0, 1, 2, 3, 4, 5, 6, 18]\n            assert gps[0] == b\"\\x03\\x02\\x00\\x00\"\n            assert gps[18] == \"WGS-84\"\n\n    def test_exif_frames(self):\n        # Test that EXIF data can change across frames\n        with Image.open(\"Tests/images/g4-multi.tiff\") as im:\n            assert im.getexif()[273] == (328, 815)\n\n            im.seek(1)\n            assert im.getexif()[273] == (1408, 1907)\n\n    def test_seek(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            im.seek(0)\n            assert im.tell() == 0\n\n    def test_seek_eof(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.tell() == 0\n            with pytest.raises(EOFError):\n                im.seek(-1)\n            with pytest.raises(EOFError):\n                im.seek(1)\n\n    def test__limit_rational_int(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 34\n        ret = _limit_rational(value, 65536)\n        assert ret == (34, 1)\n\n    def test__limit_rational_float(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 22.3\n        ret = _limit_rational(value, 65536)\n        assert ret == (223, 10)\n\n    def test_4bit(self):\n        test_file = \"Tests/images/hopper_gray_4bpp.tif\"\n        original = hopper(\"L\")\n        with Image.open(test_file) as im:\n            assert im.size == (128, 128)\n            assert im.mode == \"L\"\n            assert_image_similar(im, original, 7.3)\n\n    def test_gray_semibyte_per_pixel(self):\n        test_files = (\n            (\n                24.8,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif\",\n                ),\n            ),\n            (\n                7.3,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif\",\n                ),\n            ),\n        )\n        original = hopper(\"L\")\n        for epsilon, group in test_files:\n            with Image.open(group[0]) as im:\n                assert im.size == (128, 128)\n                assert im.mode == \"L\"\n                assert_image_similar(im, original, epsilon)\n                for file in group[1:]:\n                    with Image.open(file) as im2:\n                        assert im2.size == (128, 128)\n                        assert im2.mode == \"L\"\n                        assert_image_equal(im, im2)\n\n    def test_with_underscores(self, tmp_path):\n        kwargs = {\"resolution_unit\": \"inch\", \"x_resolution\": 72, \"y_resolution\": 36}\n        filename = str(tmp_path / \"temp.tif\")\n        hopper(\"RGB\").save(filename, **kwargs)\n        with Image.open(filename) as im:\n\n            # legacy interface\n            assert im.tag[X_RESOLUTION][0][0] == 72\n            assert im.tag[Y_RESOLUTION][0][0] == 36\n\n            # v2 interface\n            assert im.tag_v2[X_RESOLUTION] == 72\n            assert im.tag_v2[Y_RESOLUTION] == 36\n\n    def test_roundtrip_tiff_uint16(self, tmp_path):\n        # Test an image of all '0' values\n        pixel_value = 0x1234\n        infile = \"Tests/images/uint16_1_4660.tif\"\n        with Image.open(infile) as im:\n            assert im.getpixel((0, 0)) == pixel_value\n\n            tmpfile = str(tmp_path / \"temp.tif\")\n            im.save(tmpfile)\n\n            assert_image_equal_tofile(im, tmpfile)\n\n    def test_strip_raw(self):\n        infile = \"Tests/images/tiff_strip_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw(self):\n        # gdal_translate -of GTiff -co INTERLEAVE=BAND \\\n        # tiff_strip_raw.tif tiff_strip_planar_raw.tiff\n        infile = \"Tests/images/tiff_strip_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw_with_overviews(self):\n        # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16\n        infile = \"Tests/images/tiff_strip_planar_raw_with_overviews.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_tiled_planar_raw(self):\n        # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \\\n        # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \\\n        # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff\n        infile = \"Tests/images/tiff_tiled_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_palette(self, tmp_path):\n        def roundtrip(mode):\n            outfile = str(tmp_path / \"temp.tif\")\n\n            im = hopper(mode)\n            im.save(outfile)\n\n            with Image.open(outfile) as reloaded:\n                assert_image_equal(im.convert(\"RGB\"), reloaded.convert(\"RGB\"))\n\n        for mode in [\"P\", \"PA\"]:\n            roundtrip(mode)\n\n    def test_tiff_save_all(self):\n        mp = BytesIO()\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            im.save(mp, format=\"tiff\", save_all=True)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as im:\n            assert im.n_frames == 3\n\n        # Test appending images\n        mp = BytesIO()\n        im = Image.new(\"RGB\", (100, 100), \"#f00\")\n        ims = [Image.new(\"RGB\", (100, 100), color) for color in [\"#0f0\", \"#00f\"]]\n        im.copy().save(mp, format=\"TIFF\", save_all=True, append_images=ims)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            yield from ims\n\n        mp = BytesIO()\n        im.save(mp, format=\"TIFF\", save_all=True, append_images=imGenerator(ims))\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n    def test_saving_icc_profile(self, tmp_path):\n        # Tests saving TIFF with icc_profile set.\n        # At the time of writing this will only work for non-compressed tiffs\n        # as libtiff does not support embedded ICC profiles,\n        # ImageFile._save(..) however does.\n        im = Image.new(\"RGB\", (1, 1))\n        im.info[\"icc_profile\"] = \"Dummy value\"\n\n        # Try save-load round trip to make sure both handle icc_profile.\n        tmpfile = str(tmp_path / \"temp.tif\")\n        im.save(tmpfile, \"TIFF\", compression=\"raw\")\n        with Image.open(tmpfile) as reloaded:\n            assert b\"Dummy value\" == reloaded.info[\"icc_profile\"]\n\n    def test_save_icc_profile(self, tmp_path):\n        im = hopper()\n        assert \"icc_profile\" not in im.info\n\n        outfile = str(tmp_path / \"temp.tif\")\n        icc_profile = b\"Dummy value\"\n        im.save(outfile, icc_profile=icc_profile)\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"icc_profile\"] == icc_profile\n\n    def test_discard_icc_profile(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/icc_profile.png\") as im:\n            assert \"icc_profile\" in im.info\n\n            im.save(outfile, icc_profile=None)\n\n        with Image.open(outfile) as reloaded:\n            assert \"icc_profile\" not in reloaded.info\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/lab.tif\") 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[0][\"format\"] == \"image/tiff\"\n                assert description[3][\"BitsPerSample\"][\"Seq\"][\"li\"] == [\"8\", \"8\", \"8\"]\n\n    def test_close_on_load_exclusive(self, tmp_path):\n        # similar to test_fd_leak, but runs on unixlike os\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        im = Image.open(tmpfile)\n        fp = im.fp\n        assert not fp.closed\n        im.load()\n        assert fp.closed\n\n    def test_close_on_load_nonexclusive(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        with open(tmpfile, \"rb\") as f:\n            im = Image.open(f)\n            fp = im.fp\n            assert not fp.closed\n            im.load()\n            assert not fp.closed\n\n    # Ignore this UserWarning which triggers for four tags:\n    # \"Possibly corrupt EXIF data.  Expecting to read 50404352 bytes but...\"\n    @pytest.mark.filterwarnings(\"ignore:Possibly corrupt EXIF data\")\n    @pytest.mark.skipif(\n        not os.path.exists(\"Tests/images/string_dimension.tiff\"),\n        reason=\"Extra image files not installed\",\n    )\n    def test_string_dimension(self):\n        # Assert that an error is raised if one of the dimensions is a string\n        with Image.open(\"Tests/images/string_dimension.tiff\") as im:\n            with pytest.raises(OSError):\n                im.load()",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 25,
                                "end_lineno": 700,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 439,
                            "length": 6
                        },
                        "new_range": {
                            "start": 437,
                            "length": 17
                        },
                        "section": "class TestFileTiff:"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "            assert gps[0] == b\"\\x03\\x02\\x00\\x00\"",
                                "type": "context"
                            },
                            {
                                "content": "            assert gps[18] == \"WGS-84\"",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "        outfile = str(tmp_path / \"temp.tif\")",
                                "type": "added"
                            },
                            {
                                "content": "        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:",
                                "type": "added"
                            },
                            {
                                "content": "            exif = im.getexif()",
                                "type": "added"
                            },
                            {
                                "content": "            check_exif(exif)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "            im.save(outfile, exif=exif)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "        with Image.open(outfile) as im:",
                                "type": "added"
                            },
                            {
                                "content": "            exif = im.getexif()",
                                "type": "added"
                            },
                            {
                                "content": "            check_exif(exif)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def test_exif_frames(self):",
                                "type": "context"
                            },
                            {
                                "content": "        # Test that EXIF data can change across frames",
                                "type": "context"
                            },
                            {
                                "content": "        with Image.open(\"Tests/images/g4-multi.tiff\") as im:",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "Tests/test_file_tiff.py",
                            "type": "class",
                            "name": "TestFileTiff",
                            "content": "class TestFileTiff:\n    def test_sanity(self, tmp_path):\n\n        filename = str(tmp_path / \"temp.tif\")\n\n        hopper(\"RGB\").save(filename)\n\n        with Image.open(filename) as im:\n            im.load()\n        assert im.mode == \"RGB\"\n        assert im.size == (128, 128)\n        assert im.format == \"TIFF\"\n\n        hopper(\"1\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"L\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"P\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"RGB\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"I\").save(filename)\n        with Image.open(filename):\n            pass\n\n    @pytest.mark.skipif(is_pypy(), reason=\"Requires CPython\")\n    def test_unclosed_file(self):\n        def open():\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n\n        pytest.warns(ResourceWarning, open)\n\n    def test_closed_file(self):\n        with pytest.warns(None) as record:\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n            im.close()\n\n        assert not record\n\n    def test_context_manager(self):\n        with pytest.warns(None) as record:\n            with Image.open(\"Tests/images/multipage.tiff\") as im:\n                im.load()\n\n        assert not record\n\n    def test_mac_tiff(self):\n        # Read RGBa images from macOS [@PIL136]\n\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (55, 43)\n            assert im.tile == [(\"raw\", (0, 0, 55, 43), 8, (\"RGBa\", 0, 1))]\n            im.load()\n\n            assert_image_similar_tofile(im, \"Tests/images/pil136.png\", 1)\n\n    def test_wrong_bits_per_sample(self):\n        with Image.open(\"Tests/images/tiff_wrong_bits_per_sample.tiff\") as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (52, 53)\n            assert im.tile == [(\"raw\", (0, 0, 52, 53), 160, (\"RGBA\", 0, 1))]\n            im.load()\n\n    def test_set_legacy_api(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        with pytest.raises(Exception) as e:\n            ifd.legacy_api = None\n        assert str(e.value) == \"Not allowing setting of legacy api\"\n\n    def test_xyres_tiff(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # legacy api\n            assert isinstance(im.tag[X_RESOLUTION][0], tuple)\n            assert isinstance(im.tag[Y_RESOLUTION][0], tuple)\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n\n            assert im.info[\"dpi\"] == (72.0, 72.0)\n\n    def test_xyres_fallback_tiff(self):\n        filename = \"Tests/images/compression.tif\"\n        with Image.open(filename) as im:\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n            with pytest.raises(KeyError):\n                im.tag_v2[RESOLUTION_UNIT]\n\n            # Legacy.\n            assert im.info[\"resolution\"] == (100.0, 100.0)\n            # Fallback \"inch\".\n            assert im.info[\"dpi\"] == (100.0, 100.0)\n\n    def test_int_resolution(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # Try to read a file where X,Y_RESOLUTION are ints\n            im.tag_v2[X_RESOLUTION] = 71\n            im.tag_v2[Y_RESOLUTION] = 71\n            im._setup()\n            assert im.info[\"dpi\"] == (71.0, 71.0)\n\n    @pytest.mark.parametrize(\n        \"resolutionUnit, dpi\",\n        [(None, 72.8), (2, 72.8), (3, 184.912)],\n    )\n    def test_load_float_dpi(self, resolutionUnit, dpi):\n        with Image.open(\n            \"Tests/images/hopper_float_dpi_\" + str(resolutionUnit) + \".tif\"\n        ) as im:\n            assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit\n            assert im.info[\"dpi\"] == (dpi, dpi)\n\n    def test_save_float_dpi(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            dpi = (72.2, 72.2)\n            im.save(outfile, dpi=dpi)\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == dpi\n\n    def test_save_setting_missing_resolution(self):\n        b = BytesIO()\n        with Image.open(\"Tests/images/10ct_32bit_128.tiff\") as im:\n            im.save(b, format=\"tiff\", resolution=123.45)\n        with Image.open(b) as im:\n            assert im.tag_v2[X_RESOLUTION] == 123.45\n            assert im.tag_v2[Y_RESOLUTION] == 123.45\n\n    def test_invalid_file(self):\n        invalid_file = \"Tests/images/flower.jpg\"\n\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n\n        TiffImagePlugin.PREFIXES.append(b\"\\xff\\xd8\\xff\\xe0\")\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n        TiffImagePlugin.PREFIXES.pop()\n\n    def test_bad_exif(self):\n        with Image.open(\"Tests/images/hopper_bad_exif.jpg\") as i:\n            # Should not raise struct.error.\n            pytest.warns(UserWarning, i._getexif)\n\n    def test_save_rgba(self, tmp_path):\n        im = hopper(\"RGBA\")\n        outfile = str(tmp_path / \"temp.tif\")\n        im.save(outfile)\n\n    def test_save_unsupported_mode(self, tmp_path):\n        im = hopper(\"HSV\")\n        outfile = str(tmp_path / \"temp.tif\")\n        with pytest.raises(OSError):\n            im.save(outfile)\n\n    def test_little_endian(self):\n        with Image.open(\"Tests/images/16bit.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (little endian)\n        assert b[0] == ord(b\"\\xe0\")\n        assert b[1] == ord(b\"\\x01\")\n\n    def test_big_endian(self):\n        with Image.open(\"Tests/images/16bit.MM.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16B\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (big endian)\n        assert b[0] == ord(b\"\\x01\")\n        assert b[1] == ord(b\"\\xe0\")\n\n    def test_16bit_s(self):\n        with Image.open(\"Tests/images/16bit.s.tif\") as im:\n            im.load()\n            assert im.mode == \"I\"\n            assert im.getpixel((0, 0)) == 32767\n            assert im.getpixel((0, 1)) == 0\n\n    def test_12bit_rawmode(self):\n        \"\"\"Are we generating the same interpretation\n        of the image as Imagemagick is?\"\"\"\n\n        with Image.open(\"Tests/images/12bit.cropped.tif\") as im:\n            # to make the target --\n            # convert 12bit.cropped.tif -depth 16 tmp.tif\n            # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif\n            # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0,\n            # so we need to unshift so that the integer values are the same.\n\n            assert_image_equal_tofile(im, \"Tests/images/12in16bit.tif\")\n\n    def test_32bit_float(self):\n        # Issue 614, specific 32-bit float format\n        path = \"Tests/images/10ct_32bit_128.tiff\"\n        with Image.open(path) as im:\n            im.load()\n\n            assert im.getpixel((0, 0)) == -0.4526388943195343\n            assert im.getextrema() == (-3.140936851501465, 3.140684127807617)\n\n    def test_unknown_pixel_mode(self):\n        with pytest.raises(OSError):\n            with Image.open(\"Tests/images/hopper_unknown_pixel_mode.tif\"):\n                pass\n\n    def test_n_frames(self):\n        for path, n_frames in [\n            [\"Tests/images/multipage-lastframe.tif\", 1],\n            [\"Tests/images/multipage.tiff\", 3],\n        ]:\n            with Image.open(path) as im:\n                assert im.n_frames == n_frames\n                assert im.is_animated == (n_frames != 1)\n\n    def test_eoferror(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            n_frames = im.n_frames\n\n            # Test seeking past the last frame\n            with pytest.raises(EOFError):\n                im.seek(n_frames)\n            assert im.tell() < n_frames\n\n            # Test that seeking to the last frame does not raise an error\n            im.seek(n_frames - 1)\n\n    def test_multipage(self):\n        # issue #862\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue\n\n            im.seek(0)\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(1)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (255, 0, 0)\n\n            im.seek(0)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(2)\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_multipage_last_frame(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_frame_order(self):\n        # A frame can't progress to itself after reading\n        with Image.open(\"Tests/images/multipage_single_frame_loop.tiff\") as im:\n            assert im.n_frames == 1\n\n        # A frame can't progress to a frame that has already been read\n        with Image.open(\"Tests/images/multipage_multiple_frame_loop.tiff\") as im:\n            assert im.n_frames == 2\n\n        # Frames don't have to be in sequence\n        with Image.open(\"Tests/images/multipage_out_of_order.tiff\") as im:\n            assert im.n_frames == 3\n\n    def test___str__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # Act\n            ret = str(im.ifd)\n\n            # Assert\n            assert isinstance(ret, str)\n\n    def test_dict(self):\n        # Arrange\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # v2 interface\n            v2_tags = {\n                256: 55,\n                257: 43,\n                258: (8, 8, 8, 8),\n                259: 1,\n                262: 2,\n                296: 2,\n                273: (8,),\n                338: (1,),\n                277: 4,\n                279: (9460,),\n                282: 72.0,\n                283: 72.0,\n                284: 1,\n            }\n            assert dict(im.tag_v2) == v2_tags\n\n            # legacy interface\n            legacy_tags = {\n                256: (55,),\n                257: (43,),\n                258: (8, 8, 8, 8),\n                259: (1,),\n                262: (2,),\n                296: (2,),\n                273: (8,),\n                338: (1,),\n                277: (4,),\n                279: (9460,),\n                282: ((720000, 10000),),\n                283: ((720000, 10000),),\n                284: (1,),\n            }\n            assert dict(im.tag) == legacy_tags\n\n    def test__delitem__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            len_before = len(dict(im.ifd))\n            del im.ifd[256]\n            len_after = len(dict(im.ifd))\n            assert len_before == len_after + 1\n\n    def test_load_byte(self):\n        for legacy_api in [False, True]:\n            ifd = TiffImagePlugin.ImageFileDirectory_v2()\n            data = b\"abc\"\n            ret = ifd.load_byte(data, legacy_api)\n            assert ret == b\"abc\"\n\n    def test_load_string(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abc\\0\"\n        ret = ifd.load_string(data, False)\n        assert ret == \"abc\"\n\n    def test_load_float(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdabcd\"\n        ret = ifd.load_float(data, False)\n        assert ret == (1.6777999408082104e22, 1.6777999408082104e22)\n\n    def test_load_double(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdefghabcdefgh\"\n        ret = ifd.load_double(data, False)\n        assert ret == (8.540883223036124e194, 8.540883223036124e194)\n\n    def test_ifd_tag_type(self):\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            assert 0x8825 in im.tag_v2\n\n    def test_exif(self, tmp_path):\n        def check_exif(exif):\n            assert sorted(exif.keys()) == [\n                256,\n                257,\n                258,\n                259,\n                262,\n                271,\n                272,\n                273,\n                277,\n                278,\n                279,\n                282,\n                283,\n                284,\n                296,\n                297,\n                305,\n                339,\n                700,\n                34665,\n                34853,\n                50735,\n            ]\n            assert exif[256] == 640\n            assert exif[271] == \"FLIR\"\n\n            gps = exif.get_ifd(0x8825)\n            assert list(gps.keys()) == [0, 1, 2, 3, 4, 5, 6, 18]\n            assert gps[0] == b\"\\x03\\x02\\x00\\x00\"\n            assert gps[18] == \"WGS-84\"\n\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            exif = im.getexif()\n            check_exif(exif)\n\n            im.save(outfile, exif=exif)\n\n        with Image.open(outfile) as im:\n            exif = im.getexif()\n            check_exif(exif)\n\n    def test_exif_frames(self):\n        # Test that EXIF data can change across frames\n        with Image.open(\"Tests/images/g4-multi.tiff\") as im:\n            assert im.getexif()[273] == (328, 815)\n\n            im.seek(1)\n            assert im.getexif()[273] == (1408, 1907)\n\n    def test_seek(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            im.seek(0)\n            assert im.tell() == 0\n\n    def test_seek_eof(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.tell() == 0\n            with pytest.raises(EOFError):\n                im.seek(-1)\n            with pytest.raises(EOFError):\n                im.seek(1)\n\n    def test__limit_rational_int(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 34\n        ret = _limit_rational(value, 65536)\n        assert ret == (34, 1)\n\n    def test__limit_rational_float(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 22.3\n        ret = _limit_rational(value, 65536)\n        assert ret == (223, 10)\n\n    def test_4bit(self):\n        test_file = \"Tests/images/hopper_gray_4bpp.tif\"\n        original = hopper(\"L\")\n        with Image.open(test_file) as im:\n            assert im.size == (128, 128)\n            assert im.mode == \"L\"\n            assert_image_similar(im, original, 7.3)\n\n    def test_gray_semibyte_per_pixel(self):\n        test_files = (\n            (\n                24.8,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif\",\n                ),\n            ),\n            (\n                7.3,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif\",\n                ),\n            ),\n        )\n        original = hopper(\"L\")\n        for epsilon, group in test_files:\n            with Image.open(group[0]) as im:\n                assert im.size == (128, 128)\n                assert im.mode == \"L\"\n                assert_image_similar(im, original, epsilon)\n                for file in group[1:]:\n                    with Image.open(file) as im2:\n                        assert im2.size == (128, 128)\n                        assert im2.mode == \"L\"\n                        assert_image_equal(im, im2)\n\n    def test_with_underscores(self, tmp_path):\n        kwargs = {\"resolution_unit\": \"inch\", \"x_resolution\": 72, \"y_resolution\": 36}\n        filename = str(tmp_path / \"temp.tif\")\n        hopper(\"RGB\").save(filename, **kwargs)\n        with Image.open(filename) as im:\n\n            # legacy interface\n            assert im.tag[X_RESOLUTION][0][0] == 72\n            assert im.tag[Y_RESOLUTION][0][0] == 36\n\n            # v2 interface\n            assert im.tag_v2[X_RESOLUTION] == 72\n            assert im.tag_v2[Y_RESOLUTION] == 36\n\n    def test_roundtrip_tiff_uint16(self, tmp_path):\n        # Test an image of all '0' values\n        pixel_value = 0x1234\n        infile = \"Tests/images/uint16_1_4660.tif\"\n        with Image.open(infile) as im:\n            assert im.getpixel((0, 0)) == pixel_value\n\n            tmpfile = str(tmp_path / \"temp.tif\")\n            im.save(tmpfile)\n\n            assert_image_equal_tofile(im, tmpfile)\n\n    def test_strip_raw(self):\n        infile = \"Tests/images/tiff_strip_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw(self):\n        # gdal_translate -of GTiff -co INTERLEAVE=BAND \\\n        # tiff_strip_raw.tif tiff_strip_planar_raw.tiff\n        infile = \"Tests/images/tiff_strip_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw_with_overviews(self):\n        # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16\n        infile = \"Tests/images/tiff_strip_planar_raw_with_overviews.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_tiled_planar_raw(self):\n        # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \\\n        # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \\\n        # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff\n        infile = \"Tests/images/tiff_tiled_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_palette(self, tmp_path):\n        def roundtrip(mode):\n            outfile = str(tmp_path / \"temp.tif\")\n\n            im = hopper(mode)\n            im.save(outfile)\n\n            with Image.open(outfile) as reloaded:\n                assert_image_equal(im.convert(\"RGB\"), reloaded.convert(\"RGB\"))\n\n        for mode in [\"P\", \"PA\"]:\n            roundtrip(mode)\n\n    def test_tiff_save_all(self):\n        mp = BytesIO()\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            im.save(mp, format=\"tiff\", save_all=True)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as im:\n            assert im.n_frames == 3\n\n        # Test appending images\n        mp = BytesIO()\n        im = Image.new(\"RGB\", (100, 100), \"#f00\")\n        ims = [Image.new(\"RGB\", (100, 100), color) for color in [\"#0f0\", \"#00f\"]]\n        im.copy().save(mp, format=\"TIFF\", save_all=True, append_images=ims)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            yield from ims\n\n        mp = BytesIO()\n        im.save(mp, format=\"TIFF\", save_all=True, append_images=imGenerator(ims))\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n    def test_saving_icc_profile(self, tmp_path):\n        # Tests saving TIFF with icc_profile set.\n        # At the time of writing this will only work for non-compressed tiffs\n        # as libtiff does not support embedded ICC profiles,\n        # ImageFile._save(..) however does.\n        im = Image.new(\"RGB\", (1, 1))\n        im.info[\"icc_profile\"] = \"Dummy value\"\n\n        # Try save-load round trip to make sure both handle icc_profile.\n        tmpfile = str(tmp_path / \"temp.tif\")\n        im.save(tmpfile, \"TIFF\", compression=\"raw\")\n        with Image.open(tmpfile) as reloaded:\n            assert b\"Dummy value\" == reloaded.info[\"icc_profile\"]\n\n    def test_save_icc_profile(self, tmp_path):\n        im = hopper()\n        assert \"icc_profile\" not in im.info\n\n        outfile = str(tmp_path / \"temp.tif\")\n        icc_profile = b\"Dummy value\"\n        im.save(outfile, icc_profile=icc_profile)\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"icc_profile\"] == icc_profile\n\n    def test_discard_icc_profile(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/icc_profile.png\") as im:\n            assert \"icc_profile\" in im.info\n\n            im.save(outfile, icc_profile=None)\n\n        with Image.open(outfile) as reloaded:\n            assert \"icc_profile\" not in reloaded.info\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/lab.tif\") 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[0][\"format\"] == \"image/tiff\"\n                assert description[3][\"BitsPerSample\"][\"Seq\"][\"li\"] == [\"8\", \"8\", \"8\"]\n\n    def test_close_on_load_exclusive(self, tmp_path):\n        # similar to test_fd_leak, but runs on unixlike os\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        im = Image.open(tmpfile)\n        fp = im.fp\n        assert not fp.closed\n        im.load()\n        assert fp.closed\n\n    def test_close_on_load_nonexclusive(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        with open(tmpfile, \"rb\") as f:\n            im = Image.open(f)\n            fp = im.fp\n            assert not fp.closed\n            im.load()\n            assert not fp.closed\n\n    # Ignore this UserWarning which triggers for four tags:\n    # \"Possibly corrupt EXIF data.  Expecting to read 50404352 bytes but...\"\n    @pytest.mark.filterwarnings(\"ignore:Possibly corrupt EXIF data\")\n    @pytest.mark.skipif(\n        not os.path.exists(\"Tests/images/string_dimension.tiff\"),\n        reason=\"Extra image files not installed\",\n    )\n    def test_string_dimension(self):\n        # Assert that an error is raised if one of the dimensions is a string\n        with Image.open(\"Tests/images/string_dimension.tiff\") as im:\n            with pytest.raises(OSError):\n                im.load()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 25,
                            "end_lineno": 709,
                            "parent": null
                        },
                        {
                            "file_name": "Tests/test_file_tiff.py",
                            "type": "method",
                            "name": "TestFileTiff.test_exif",
                            "content": "class TestFileTiff:\n    def test_sanity(self, tmp_path):\n\n        filename = str(tmp_path / \"temp.tif\")\n\n        hopper(\"RGB\").save(filename)\n\n        with Image.open(filename) as im:\n            im.load()\n        assert im.mode == \"RGB\"\n        assert im.size == (128, 128)\n        assert im.format == \"TIFF\"\n\n        hopper(\"1\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"L\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"P\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"RGB\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"I\").save(filename)\n        with Image.open(filename):\n            pass\n\n    @pytest.mark.skipif(is_pypy(), reason=\"Requires CPython\")\n    def test_unclosed_file(self):\n        def open():\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n\n        pytest.warns(ResourceWarning, open)\n\n    def test_closed_file(self):\n        with pytest.warns(None) as record:\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n            im.close()\n\n        assert not record\n\n    def test_context_manager(self):\n        with pytest.warns(None) as record:\n            with Image.open(\"Tests/images/multipage.tiff\") as im:\n                im.load()\n\n        assert not record\n\n    def test_mac_tiff(self):\n        # Read RGBa images from macOS [@PIL136]\n\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (55, 43)\n            assert im.tile == [(\"raw\", (0, 0, 55, 43), 8, (\"RGBa\", 0, 1))]\n            im.load()\n\n            assert_image_similar_tofile(im, \"Tests/images/pil136.png\", 1)\n\n    def test_wrong_bits_per_sample(self):\n        with Image.open(\"Tests/images/tiff_wrong_bits_per_sample.tiff\") as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (52, 53)\n            assert im.tile == [(\"raw\", (0, 0, 52, 53), 160, (\"RGBA\", 0, 1))]\n            im.load()\n\n    def test_set_legacy_api(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        with pytest.raises(Exception) as e:\n            ifd.legacy_api = None\n        assert str(e.value) == \"Not allowing setting of legacy api\"\n\n    def test_xyres_tiff(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # legacy api\n            assert isinstance(im.tag[X_RESOLUTION][0], tuple)\n            assert isinstance(im.tag[Y_RESOLUTION][0], tuple)\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n\n            assert im.info[\"dpi\"] == (72.0, 72.0)\n\n    def test_xyres_fallback_tiff(self):\n        filename = \"Tests/images/compression.tif\"\n        with Image.open(filename) as im:\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n            with pytest.raises(KeyError):\n                im.tag_v2[RESOLUTION_UNIT]\n\n            # Legacy.\n            assert im.info[\"resolution\"] == (100.0, 100.0)\n            # Fallback \"inch\".\n            assert im.info[\"dpi\"] == (100.0, 100.0)\n\n    def test_int_resolution(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # Try to read a file where X,Y_RESOLUTION are ints\n            im.tag_v2[X_RESOLUTION] = 71\n            im.tag_v2[Y_RESOLUTION] = 71\n            im._setup()\n            assert im.info[\"dpi\"] == (71.0, 71.0)\n\n    @pytest.mark.parametrize(\n        \"resolutionUnit, dpi\",\n        [(None, 72.8), (2, 72.8), (3, 184.912)],\n    )\n    def test_load_float_dpi(self, resolutionUnit, dpi):\n        with Image.open(\n            \"Tests/images/hopper_float_dpi_\" + str(resolutionUnit) + \".tif\"\n        ) as im:\n            assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit\n            assert im.info[\"dpi\"] == (dpi, dpi)\n\n    def test_save_float_dpi(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            dpi = (72.2, 72.2)\n            im.save(outfile, dpi=dpi)\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == dpi\n\n    def test_save_setting_missing_resolution(self):\n        b = BytesIO()\n        with Image.open(\"Tests/images/10ct_32bit_128.tiff\") as im:\n            im.save(b, format=\"tiff\", resolution=123.45)\n        with Image.open(b) as im:\n            assert im.tag_v2[X_RESOLUTION] == 123.45\n            assert im.tag_v2[Y_RESOLUTION] == 123.45\n\n    def test_invalid_file(self):\n        invalid_file = \"Tests/images/flower.jpg\"\n\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n\n        TiffImagePlugin.PREFIXES.append(b\"\\xff\\xd8\\xff\\xe0\")\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n        TiffImagePlugin.PREFIXES.pop()\n\n    def test_bad_exif(self):\n        with Image.open(\"Tests/images/hopper_bad_exif.jpg\") as i:\n            # Should not raise struct.error.\n            pytest.warns(UserWarning, i._getexif)\n\n    def test_save_rgba(self, tmp_path):\n        im = hopper(\"RGBA\")\n        outfile = str(tmp_path / \"temp.tif\")\n        im.save(outfile)\n\n    def test_save_unsupported_mode(self, tmp_path):\n        im = hopper(\"HSV\")\n        outfile = str(tmp_path / \"temp.tif\")\n        with pytest.raises(OSError):\n            im.save(outfile)\n\n    def test_little_endian(self):\n        with Image.open(\"Tests/images/16bit.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (little endian)\n        assert b[0] == ord(b\"\\xe0\")\n        assert b[1] == ord(b\"\\x01\")\n\n    def test_big_endian(self):\n        with Image.open(\"Tests/images/16bit.MM.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16B\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (big endian)\n        assert b[0] == ord(b\"\\x01\")\n        assert b[1] == ord(b\"\\xe0\")\n\n    def test_16bit_s(self):\n        with Image.open(\"Tests/images/16bit.s.tif\") as im:\n            im.load()\n            assert im.mode == \"I\"\n            assert im.getpixel((0, 0)) == 32767\n            assert im.getpixel((0, 1)) == 0\n\n    def test_12bit_rawmode(self):\n        \"\"\"Are we generating the same interpretation\n        of the image as Imagemagick is?\"\"\"\n\n        with Image.open(\"Tests/images/12bit.cropped.tif\") as im:\n            # to make the target --\n            # convert 12bit.cropped.tif -depth 16 tmp.tif\n            # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif\n            # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0,\n            # so we need to unshift so that the integer values are the same.\n\n            assert_image_equal_tofile(im, \"Tests/images/12in16bit.tif\")\n\n    def test_32bit_float(self):\n        # Issue 614, specific 32-bit float format\n        path = \"Tests/images/10ct_32bit_128.tiff\"\n        with Image.open(path) as im:\n            im.load()\n\n            assert im.getpixel((0, 0)) == -0.4526388943195343\n            assert im.getextrema() == (-3.140936851501465, 3.140684127807617)\n\n    def test_unknown_pixel_mode(self):\n        with pytest.raises(OSError):\n            with Image.open(\"Tests/images/hopper_unknown_pixel_mode.tif\"):\n                pass\n\n    def test_n_frames(self):\n        for path, n_frames in [\n            [\"Tests/images/multipage-lastframe.tif\", 1],\n            [\"Tests/images/multipage.tiff\", 3],\n        ]:\n            with Image.open(path) as im:\n                assert im.n_frames == n_frames\n                assert im.is_animated == (n_frames != 1)\n\n    def test_eoferror(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            n_frames = im.n_frames\n\n            # Test seeking past the last frame\n            with pytest.raises(EOFError):\n                im.seek(n_frames)\n            assert im.tell() < n_frames\n\n            # Test that seeking to the last frame does not raise an error\n            im.seek(n_frames - 1)\n\n    def test_multipage(self):\n        # issue #862\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue\n\n            im.seek(0)\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(1)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (255, 0, 0)\n\n            im.seek(0)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(2)\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_multipage_last_frame(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_frame_order(self):\n        # A frame can't progress to itself after reading\n        with Image.open(\"Tests/images/multipage_single_frame_loop.tiff\") as im:\n            assert im.n_frames == 1\n\n        # A frame can't progress to a frame that has already been read\n        with Image.open(\"Tests/images/multipage_multiple_frame_loop.tiff\") as im:\n            assert im.n_frames == 2\n\n        # Frames don't have to be in sequence\n        with Image.open(\"Tests/images/multipage_out_of_order.tiff\") as im:\n            assert im.n_frames == 3\n\n    def test___str__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # Act\n            ret = str(im.ifd)\n\n            # Assert\n            assert isinstance(ret, str)\n\n    def test_dict(self):\n        # Arrange\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # v2 interface\n            v2_tags = {\n                256: 55,\n                257: 43,\n                258: (8, 8, 8, 8),\n                259: 1,\n                262: 2,\n                296: 2,\n                273: (8,),\n                338: (1,),\n                277: 4,\n                279: (9460,),\n                282: 72.0,\n                283: 72.0,\n                284: 1,\n            }\n            assert dict(im.tag_v2) == v2_tags\n\n            # legacy interface\n            legacy_tags = {\n                256: (55,),\n                257: (43,),\n                258: (8, 8, 8, 8),\n                259: (1,),\n                262: (2,),\n                296: (2,),\n                273: (8,),\n                338: (1,),\n                277: (4,),\n                279: (9460,),\n                282: ((720000, 10000),),\n                283: ((720000, 10000),),\n                284: (1,),\n            }\n            assert dict(im.tag) == legacy_tags\n\n    def test__delitem__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            len_before = len(dict(im.ifd))\n            del im.ifd[256]\n            len_after = len(dict(im.ifd))\n            assert len_before == len_after + 1\n\n    def test_load_byte(self):\n        for legacy_api in [False, True]:\n            ifd = TiffImagePlugin.ImageFileDirectory_v2()\n            data = b\"abc\"\n            ret = ifd.load_byte(data, legacy_api)\n            assert ret == b\"abc\"\n\n    def test_load_string(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abc\\0\"\n        ret = ifd.load_string(data, False)\n        assert ret == \"abc\"\n\n    def test_load_float(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdabcd\"\n        ret = ifd.load_float(data, False)\n        assert ret == (1.6777999408082104e22, 1.6777999408082104e22)\n\n    def test_load_double(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdefghabcdefgh\"\n        ret = ifd.load_double(data, False)\n        assert ret == (8.540883223036124e194, 8.540883223036124e194)\n\n    def test_ifd_tag_type(self):\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            assert 0x8825 in im.tag_v2\n\n    def test_exif(self, tmp_path):\n        def check_exif(exif):\n            assert sorted(exif.keys()) == [\n                256,\n                257,\n                258,\n                259,\n                262,\n                271,\n                272,\n                273,\n                277,\n                278,\n                279,\n                282,\n                283,\n                284,\n                296,\n                297,\n                305,\n                339,\n                700,\n                34665,\n                34853,\n                50735,\n            ]\n            assert exif[256] == 640\n            assert exif[271] == \"FLIR\"\n\n            gps = exif.get_ifd(0x8825)\n            assert list(gps.keys()) == [0, 1, 2, 3, 4, 5, 6, 18]\n            assert gps[0] == b\"\\x03\\x02\\x00\\x00\"\n            assert gps[18] == \"WGS-84\"\n\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            exif = im.getexif()\n            check_exif(exif)\n\n            im.save(outfile, exif=exif)\n\n        with Image.open(outfile) as im:\n            exif = im.getexif()\n            check_exif(exif)\n\n    def test_exif_frames(self):\n        # Test that EXIF data can change across frames\n        with Image.open(\"Tests/images/g4-multi.tiff\") as im:\n            assert im.getexif()[273] == (328, 815)\n\n            im.seek(1)\n            assert im.getexif()[273] == (1408, 1907)\n\n    def test_seek(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            im.seek(0)\n            assert im.tell() == 0\n\n    def test_seek_eof(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.tell() == 0\n            with pytest.raises(EOFError):\n                im.seek(-1)\n            with pytest.raises(EOFError):\n                im.seek(1)\n\n    def test__limit_rational_int(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 34\n        ret = _limit_rational(value, 65536)\n        assert ret == (34, 1)\n\n    def test__limit_rational_float(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 22.3\n        ret = _limit_rational(value, 65536)\n        assert ret == (223, 10)\n\n    def test_4bit(self):\n        test_file = \"Tests/images/hopper_gray_4bpp.tif\"\n        original = hopper(\"L\")\n        with Image.open(test_file) as im:\n            assert im.size == (128, 128)\n            assert im.mode == \"L\"\n            assert_image_similar(im, original, 7.3)\n\n    def test_gray_semibyte_per_pixel(self):\n        test_files = (\n            (\n                24.8,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif\",\n                ),\n            ),\n            (\n                7.3,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif\",\n                ),\n            ),\n        )\n        original = hopper(\"L\")\n        for epsilon, group in test_files:\n            with Image.open(group[0]) as im:\n                assert im.size == (128, 128)\n                assert im.mode == \"L\"\n                assert_image_similar(im, original, epsilon)\n                for file in group[1:]:\n                    with Image.open(file) as im2:\n                        assert im2.size == (128, 128)\n                        assert im2.mode == \"L\"\n                        assert_image_equal(im, im2)\n\n    def test_with_underscores(self, tmp_path):\n        kwargs = {\"resolution_unit\": \"inch\", \"x_resolution\": 72, \"y_resolution\": 36}\n        filename = str(tmp_path / \"temp.tif\")\n        hopper(\"RGB\").save(filename, **kwargs)\n        with Image.open(filename) as im:\n\n            # legacy interface\n            assert im.tag[X_RESOLUTION][0][0] == 72\n            assert im.tag[Y_RESOLUTION][0][0] == 36\n\n            # v2 interface\n            assert im.tag_v2[X_RESOLUTION] == 72\n            assert im.tag_v2[Y_RESOLUTION] == 36\n\n    def test_roundtrip_tiff_uint16(self, tmp_path):\n        # Test an image of all '0' values\n        pixel_value = 0x1234\n        infile = \"Tests/images/uint16_1_4660.tif\"\n        with Image.open(infile) as im:\n            assert im.getpixel((0, 0)) == pixel_value\n\n            tmpfile = str(tmp_path / \"temp.tif\")\n            im.save(tmpfile)\n\n            assert_image_equal_tofile(im, tmpfile)\n\n    def test_strip_raw(self):\n        infile = \"Tests/images/tiff_strip_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw(self):\n        # gdal_translate -of GTiff -co INTERLEAVE=BAND \\\n        # tiff_strip_raw.tif tiff_strip_planar_raw.tiff\n        infile = \"Tests/images/tiff_strip_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw_with_overviews(self):\n        # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16\n        infile = \"Tests/images/tiff_strip_planar_raw_with_overviews.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_tiled_planar_raw(self):\n        # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \\\n        # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \\\n        # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff\n        infile = \"Tests/images/tiff_tiled_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_palette(self, tmp_path):\n        def roundtrip(mode):\n            outfile = str(tmp_path / \"temp.tif\")\n\n            im = hopper(mode)\n            im.save(outfile)\n\n            with Image.open(outfile) as reloaded:\n                assert_image_equal(im.convert(\"RGB\"), reloaded.convert(\"RGB\"))\n\n        for mode in [\"P\", \"PA\"]:\n            roundtrip(mode)\n\n    def test_tiff_save_all(self):\n        mp = BytesIO()\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            im.save(mp, format=\"tiff\", save_all=True)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as im:\n            assert im.n_frames == 3\n\n        # Test appending images\n        mp = BytesIO()\n        im = Image.new(\"RGB\", (100, 100), \"#f00\")\n        ims = [Image.new(\"RGB\", (100, 100), color) for color in [\"#0f0\", \"#00f\"]]\n        im.copy().save(mp, format=\"TIFF\", save_all=True, append_images=ims)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            yield from ims\n\n        mp = BytesIO()\n        im.save(mp, format=\"TIFF\", save_all=True, append_images=imGenerator(ims))\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n    def test_saving_icc_profile(self, tmp_path):\n        # Tests saving TIFF with icc_profile set.\n        # At the time of writing this will only work for non-compressed tiffs\n        # as libtiff does not support embedded ICC profiles,\n        # ImageFile._save(..) however does.\n        im = Image.new(\"RGB\", (1, 1))\n        im.info[\"icc_profile\"] = \"Dummy value\"\n\n        # Try save-load round trip to make sure both handle icc_profile.\n        tmpfile = str(tmp_path / \"temp.tif\")\n        im.save(tmpfile, \"TIFF\", compression=\"raw\")\n        with Image.open(tmpfile) as reloaded:\n            assert b\"Dummy value\" == reloaded.info[\"icc_profile\"]\n\n    def test_save_icc_profile(self, tmp_path):\n        im = hopper()\n        assert \"icc_profile\" not in im.info\n\n        outfile = str(tmp_path / \"temp.tif\")\n        icc_profile = b\"Dummy value\"\n        im.save(outfile, icc_profile=icc_profile)\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"icc_profile\"] == icc_profile\n\n    def test_discard_icc_profile(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/icc_profile.png\") as im:\n            assert \"icc_profile\" in im.info\n\n            im.save(outfile, icc_profile=None)\n\n        with Image.open(outfile) as reloaded:\n            assert \"icc_profile\" not in reloaded.info\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/lab.tif\") 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[0][\"format\"] == \"image/tiff\"\n                assert description[3][\"BitsPerSample\"][\"Seq\"][\"li\"] == [\"8\", \"8\", \"8\"]\n\n    def test_close_on_load_exclusive(self, tmp_path):\n        # similar to test_fd_leak, but runs on unixlike os\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        im = Image.open(tmpfile)\n        fp = im.fp\n        assert not fp.closed\n        im.load()\n        assert fp.closed\n\n    def test_close_on_load_nonexclusive(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        with open(tmpfile, \"rb\") as f:\n            im = Image.open(f)\n            fp = im.fp\n            assert not fp.closed\n            im.load()\n            assert not fp.closed\n\n    # Ignore this UserWarning which triggers for four tags:\n    # \"Possibly corrupt EXIF data.  Expecting to read 50404352 bytes but...\"\n    @pytest.mark.filterwarnings(\"ignore:Possibly corrupt EXIF data\")\n    @pytest.mark.skipif(\n        not os.path.exists(\"Tests/images/string_dimension.tiff\"),\n        reason=\"Extra image files not installed\",\n    )\n    def test_string_dimension(self):\n        # Assert that an error is raised if one of the dimensions is a string\n        with Image.open(\"Tests/images/string_dimension.tiff\") as im:\n            with pytest.raises(OSError):\n                im.load()",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 406,
                            "end_lineno": 449,
                            "parent": {
                                "file_name": "Tests/test_file_tiff.py",
                                "type": "class",
                                "name": "TestFileTiff",
                                "content": "class TestFileTiff:\n    def test_sanity(self, tmp_path):\n\n        filename = str(tmp_path / \"temp.tif\")\n\n        hopper(\"RGB\").save(filename)\n\n        with Image.open(filename) as im:\n            im.load()\n        assert im.mode == \"RGB\"\n        assert im.size == (128, 128)\n        assert im.format == \"TIFF\"\n\n        hopper(\"1\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"L\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"P\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"RGB\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"I\").save(filename)\n        with Image.open(filename):\n            pass\n\n    @pytest.mark.skipif(is_pypy(), reason=\"Requires CPython\")\n    def test_unclosed_file(self):\n        def open():\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n\n        pytest.warns(ResourceWarning, open)\n\n    def test_closed_file(self):\n        with pytest.warns(None) as record:\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n            im.close()\n\n        assert not record\n\n    def test_context_manager(self):\n        with pytest.warns(None) as record:\n            with Image.open(\"Tests/images/multipage.tiff\") as im:\n                im.load()\n\n        assert not record\n\n    def test_mac_tiff(self):\n        # Read RGBa images from macOS [@PIL136]\n\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (55, 43)\n            assert im.tile == [(\"raw\", (0, 0, 55, 43), 8, (\"RGBa\", 0, 1))]\n            im.load()\n\n            assert_image_similar_tofile(im, \"Tests/images/pil136.png\", 1)\n\n    def test_wrong_bits_per_sample(self):\n        with Image.open(\"Tests/images/tiff_wrong_bits_per_sample.tiff\") as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (52, 53)\n            assert im.tile == [(\"raw\", (0, 0, 52, 53), 160, (\"RGBA\", 0, 1))]\n            im.load()\n\n    def test_set_legacy_api(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        with pytest.raises(Exception) as e:\n            ifd.legacy_api = None\n        assert str(e.value) == \"Not allowing setting of legacy api\"\n\n    def test_xyres_tiff(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # legacy api\n            assert isinstance(im.tag[X_RESOLUTION][0], tuple)\n            assert isinstance(im.tag[Y_RESOLUTION][0], tuple)\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n\n            assert im.info[\"dpi\"] == (72.0, 72.0)\n\n    def test_xyres_fallback_tiff(self):\n        filename = \"Tests/images/compression.tif\"\n        with Image.open(filename) as im:\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n            with pytest.raises(KeyError):\n                im.tag_v2[RESOLUTION_UNIT]\n\n            # Legacy.\n            assert im.info[\"resolution\"] == (100.0, 100.0)\n            # Fallback \"inch\".\n            assert im.info[\"dpi\"] == (100.0, 100.0)\n\n    def test_int_resolution(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # Try to read a file where X,Y_RESOLUTION are ints\n            im.tag_v2[X_RESOLUTION] = 71\n            im.tag_v2[Y_RESOLUTION] = 71\n            im._setup()\n            assert im.info[\"dpi\"] == (71.0, 71.0)\n\n    @pytest.mark.parametrize(\n        \"resolutionUnit, dpi\",\n        [(None, 72.8), (2, 72.8), (3, 184.912)],\n    )\n    def test_load_float_dpi(self, resolutionUnit, dpi):\n        with Image.open(\n            \"Tests/images/hopper_float_dpi_\" + str(resolutionUnit) + \".tif\"\n        ) as im:\n            assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit\n            assert im.info[\"dpi\"] == (dpi, dpi)\n\n    def test_save_float_dpi(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            dpi = (72.2, 72.2)\n            im.save(outfile, dpi=dpi)\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == dpi\n\n    def test_save_setting_missing_resolution(self):\n        b = BytesIO()\n        with Image.open(\"Tests/images/10ct_32bit_128.tiff\") as im:\n            im.save(b, format=\"tiff\", resolution=123.45)\n        with Image.open(b) as im:\n            assert im.tag_v2[X_RESOLUTION] == 123.45\n            assert im.tag_v2[Y_RESOLUTION] == 123.45\n\n    def test_invalid_file(self):\n        invalid_file = \"Tests/images/flower.jpg\"\n\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n\n        TiffImagePlugin.PREFIXES.append(b\"\\xff\\xd8\\xff\\xe0\")\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n        TiffImagePlugin.PREFIXES.pop()\n\n    def test_bad_exif(self):\n        with Image.open(\"Tests/images/hopper_bad_exif.jpg\") as i:\n            # Should not raise struct.error.\n            pytest.warns(UserWarning, i._getexif)\n\n    def test_save_rgba(self, tmp_path):\n        im = hopper(\"RGBA\")\n        outfile = str(tmp_path / \"temp.tif\")\n        im.save(outfile)\n\n    def test_save_unsupported_mode(self, tmp_path):\n        im = hopper(\"HSV\")\n        outfile = str(tmp_path / \"temp.tif\")\n        with pytest.raises(OSError):\n            im.save(outfile)\n\n    def test_little_endian(self):\n        with Image.open(\"Tests/images/16bit.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (little endian)\n        assert b[0] == ord(b\"\\xe0\")\n        assert b[1] == ord(b\"\\x01\")\n\n    def test_big_endian(self):\n        with Image.open(\"Tests/images/16bit.MM.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16B\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (big endian)\n        assert b[0] == ord(b\"\\x01\")\n        assert b[1] == ord(b\"\\xe0\")\n\n    def test_16bit_s(self):\n        with Image.open(\"Tests/images/16bit.s.tif\") as im:\n            im.load()\n            assert im.mode == \"I\"\n            assert im.getpixel((0, 0)) == 32767\n            assert im.getpixel((0, 1)) == 0\n\n    def test_12bit_rawmode(self):\n        \"\"\"Are we generating the same interpretation\n        of the image as Imagemagick is?\"\"\"\n\n        with Image.open(\"Tests/images/12bit.cropped.tif\") as im:\n            # to make the target --\n            # convert 12bit.cropped.tif -depth 16 tmp.tif\n            # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif\n            # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0,\n            # so we need to unshift so that the integer values are the same.\n\n            assert_image_equal_tofile(im, \"Tests/images/12in16bit.tif\")\n\n    def test_32bit_float(self):\n        # Issue 614, specific 32-bit float format\n        path = \"Tests/images/10ct_32bit_128.tiff\"\n        with Image.open(path) as im:\n            im.load()\n\n            assert im.getpixel((0, 0)) == -0.4526388943195343\n            assert im.getextrema() == (-3.140936851501465, 3.140684127807617)\n\n    def test_unknown_pixel_mode(self):\n        with pytest.raises(OSError):\n            with Image.open(\"Tests/images/hopper_unknown_pixel_mode.tif\"):\n                pass\n\n    def test_n_frames(self):\n        for path, n_frames in [\n            [\"Tests/images/multipage-lastframe.tif\", 1],\n            [\"Tests/images/multipage.tiff\", 3],\n        ]:\n            with Image.open(path) as im:\n                assert im.n_frames == n_frames\n                assert im.is_animated == (n_frames != 1)\n\n    def test_eoferror(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            n_frames = im.n_frames\n\n            # Test seeking past the last frame\n            with pytest.raises(EOFError):\n                im.seek(n_frames)\n            assert im.tell() < n_frames\n\n            # Test that seeking to the last frame does not raise an error\n            im.seek(n_frames - 1)\n\n    def test_multipage(self):\n        # issue #862\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue\n\n            im.seek(0)\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(1)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (255, 0, 0)\n\n            im.seek(0)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(2)\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_multipage_last_frame(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_frame_order(self):\n        # A frame can't progress to itself after reading\n        with Image.open(\"Tests/images/multipage_single_frame_loop.tiff\") as im:\n            assert im.n_frames == 1\n\n        # A frame can't progress to a frame that has already been read\n        with Image.open(\"Tests/images/multipage_multiple_frame_loop.tiff\") as im:\n            assert im.n_frames == 2\n\n        # Frames don't have to be in sequence\n        with Image.open(\"Tests/images/multipage_out_of_order.tiff\") as im:\n            assert im.n_frames == 3\n\n    def test___str__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # Act\n            ret = str(im.ifd)\n\n            # Assert\n            assert isinstance(ret, str)\n\n    def test_dict(self):\n        # Arrange\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # v2 interface\n            v2_tags = {\n                256: 55,\n                257: 43,\n                258: (8, 8, 8, 8),\n                259: 1,\n                262: 2,\n                296: 2,\n                273: (8,),\n                338: (1,),\n                277: 4,\n                279: (9460,),\n                282: 72.0,\n                283: 72.0,\n                284: 1,\n            }\n            assert dict(im.tag_v2) == v2_tags\n\n            # legacy interface\n            legacy_tags = {\n                256: (55,),\n                257: (43,),\n                258: (8, 8, 8, 8),\n                259: (1,),\n                262: (2,),\n                296: (2,),\n                273: (8,),\n                338: (1,),\n                277: (4,),\n                279: (9460,),\n                282: ((720000, 10000),),\n                283: ((720000, 10000),),\n                284: (1,),\n            }\n            assert dict(im.tag) == legacy_tags\n\n    def test__delitem__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            len_before = len(dict(im.ifd))\n            del im.ifd[256]\n            len_after = len(dict(im.ifd))\n            assert len_before == len_after + 1\n\n    def test_load_byte(self):\n        for legacy_api in [False, True]:\n            ifd = TiffImagePlugin.ImageFileDirectory_v2()\n            data = b\"abc\"\n            ret = ifd.load_byte(data, legacy_api)\n            assert ret == b\"abc\"\n\n    def test_load_string(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abc\\0\"\n        ret = ifd.load_string(data, False)\n        assert ret == \"abc\"\n\n    def test_load_float(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdabcd\"\n        ret = ifd.load_float(data, False)\n        assert ret == (1.6777999408082104e22, 1.6777999408082104e22)\n\n    def test_load_double(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdefghabcdefgh\"\n        ret = ifd.load_double(data, False)\n        assert ret == (8.540883223036124e194, 8.540883223036124e194)\n\n    def test_ifd_tag_type(self):\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            assert 0x8825 in im.tag_v2\n\n    def test_exif(self, tmp_path):\n        def check_exif(exif):\n            assert sorted(exif.keys()) == [\n                256,\n                257,\n                258,\n                259,\n                262,\n                271,\n                272,\n                273,\n                277,\n                278,\n                279,\n                282,\n                283,\n                284,\n                296,\n                297,\n                305,\n                339,\n                700,\n                34665,\n                34853,\n                50735,\n            ]\n            assert exif[256] == 640\n            assert exif[271] == \"FLIR\"\n\n            gps = exif.get_ifd(0x8825)\n            assert list(gps.keys()) == [0, 1, 2, 3, 4, 5, 6, 18]\n            assert gps[0] == b\"\\x03\\x02\\x00\\x00\"\n            assert gps[18] == \"WGS-84\"\n\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            exif = im.getexif()\n            check_exif(exif)\n\n            im.save(outfile, exif=exif)\n\n        with Image.open(outfile) as im:\n            exif = im.getexif()\n            check_exif(exif)\n\n    def test_exif_frames(self):\n        # Test that EXIF data can change across frames\n        with Image.open(\"Tests/images/g4-multi.tiff\") as im:\n            assert im.getexif()[273] == (328, 815)\n\n            im.seek(1)\n            assert im.getexif()[273] == (1408, 1907)\n\n    def test_seek(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            im.seek(0)\n            assert im.tell() == 0\n\n    def test_seek_eof(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.tell() == 0\n            with pytest.raises(EOFError):\n                im.seek(-1)\n            with pytest.raises(EOFError):\n                im.seek(1)\n\n    def test__limit_rational_int(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 34\n        ret = _limit_rational(value, 65536)\n        assert ret == (34, 1)\n\n    def test__limit_rational_float(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 22.3\n        ret = _limit_rational(value, 65536)\n        assert ret == (223, 10)\n\n    def test_4bit(self):\n        test_file = \"Tests/images/hopper_gray_4bpp.tif\"\n        original = hopper(\"L\")\n        with Image.open(test_file) as im:\n            assert im.size == (128, 128)\n            assert im.mode == \"L\"\n            assert_image_similar(im, original, 7.3)\n\n    def test_gray_semibyte_per_pixel(self):\n        test_files = (\n            (\n                24.8,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif\",\n                ),\n            ),\n            (\n                7.3,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif\",\n                ),\n            ),\n        )\n        original = hopper(\"L\")\n        for epsilon, group in test_files:\n            with Image.open(group[0]) as im:\n                assert im.size == (128, 128)\n                assert im.mode == \"L\"\n                assert_image_similar(im, original, epsilon)\n                for file in group[1:]:\n                    with Image.open(file) as im2:\n                        assert im2.size == (128, 128)\n                        assert im2.mode == \"L\"\n                        assert_image_equal(im, im2)\n\n    def test_with_underscores(self, tmp_path):\n        kwargs = {\"resolution_unit\": \"inch\", \"x_resolution\": 72, \"y_resolution\": 36}\n        filename = str(tmp_path / \"temp.tif\")\n        hopper(\"RGB\").save(filename, **kwargs)\n        with Image.open(filename) as im:\n\n            # legacy interface\n            assert im.tag[X_RESOLUTION][0][0] == 72\n            assert im.tag[Y_RESOLUTION][0][0] == 36\n\n            # v2 interface\n            assert im.tag_v2[X_RESOLUTION] == 72\n            assert im.tag_v2[Y_RESOLUTION] == 36\n\n    def test_roundtrip_tiff_uint16(self, tmp_path):\n        # Test an image of all '0' values\n        pixel_value = 0x1234\n        infile = \"Tests/images/uint16_1_4660.tif\"\n        with Image.open(infile) as im:\n            assert im.getpixel((0, 0)) == pixel_value\n\n            tmpfile = str(tmp_path / \"temp.tif\")\n            im.save(tmpfile)\n\n            assert_image_equal_tofile(im, tmpfile)\n\n    def test_strip_raw(self):\n        infile = \"Tests/images/tiff_strip_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw(self):\n        # gdal_translate -of GTiff -co INTERLEAVE=BAND \\\n        # tiff_strip_raw.tif tiff_strip_planar_raw.tiff\n        infile = \"Tests/images/tiff_strip_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw_with_overviews(self):\n        # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16\n        infile = \"Tests/images/tiff_strip_planar_raw_with_overviews.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_tiled_planar_raw(self):\n        # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \\\n        # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \\\n        # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff\n        infile = \"Tests/images/tiff_tiled_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_palette(self, tmp_path):\n        def roundtrip(mode):\n            outfile = str(tmp_path / \"temp.tif\")\n\n            im = hopper(mode)\n            im.save(outfile)\n\n            with Image.open(outfile) as reloaded:\n                assert_image_equal(im.convert(\"RGB\"), reloaded.convert(\"RGB\"))\n\n        for mode in [\"P\", \"PA\"]:\n            roundtrip(mode)\n\n    def test_tiff_save_all(self):\n        mp = BytesIO()\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            im.save(mp, format=\"tiff\", save_all=True)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as im:\n            assert im.n_frames == 3\n\n        # Test appending images\n        mp = BytesIO()\n        im = Image.new(\"RGB\", (100, 100), \"#f00\")\n        ims = [Image.new(\"RGB\", (100, 100), color) for color in [\"#0f0\", \"#00f\"]]\n        im.copy().save(mp, format=\"TIFF\", save_all=True, append_images=ims)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            yield from ims\n\n        mp = BytesIO()\n        im.save(mp, format=\"TIFF\", save_all=True, append_images=imGenerator(ims))\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n    def test_saving_icc_profile(self, tmp_path):\n        # Tests saving TIFF with icc_profile set.\n        # At the time of writing this will only work for non-compressed tiffs\n        # as libtiff does not support embedded ICC profiles,\n        # ImageFile._save(..) however does.\n        im = Image.new(\"RGB\", (1, 1))\n        im.info[\"icc_profile\"] = \"Dummy value\"\n\n        # Try save-load round trip to make sure both handle icc_profile.\n        tmpfile = str(tmp_path / \"temp.tif\")\n        im.save(tmpfile, \"TIFF\", compression=\"raw\")\n        with Image.open(tmpfile) as reloaded:\n            assert b\"Dummy value\" == reloaded.info[\"icc_profile\"]\n\n    def test_save_icc_profile(self, tmp_path):\n        im = hopper()\n        assert \"icc_profile\" not in im.info\n\n        outfile = str(tmp_path / \"temp.tif\")\n        icc_profile = b\"Dummy value\"\n        im.save(outfile, icc_profile=icc_profile)\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"icc_profile\"] == icc_profile\n\n    def test_discard_icc_profile(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/icc_profile.png\") as im:\n            assert \"icc_profile\" in im.info\n\n            im.save(outfile, icc_profile=None)\n\n        with Image.open(outfile) as reloaded:\n            assert \"icc_profile\" not in reloaded.info\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/lab.tif\") 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[0][\"format\"] == \"image/tiff\"\n                assert description[3][\"BitsPerSample\"][\"Seq\"][\"li\"] == [\"8\", \"8\", \"8\"]\n\n    def test_close_on_load_exclusive(self, tmp_path):\n        # similar to test_fd_leak, but runs on unixlike os\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        im = Image.open(tmpfile)\n        fp = im.fp\n        assert not fp.closed\n        im.load()\n        assert fp.closed\n\n    def test_close_on_load_nonexclusive(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        with open(tmpfile, \"rb\") as f:\n            im = Image.open(f)\n            fp = im.fp\n            assert not fp.closed\n            im.load()\n            assert not fp.closed\n\n    # Ignore this UserWarning which triggers for four tags:\n    # \"Possibly corrupt EXIF data.  Expecting to read 50404352 bytes but...\"\n    @pytest.mark.filterwarnings(\"ignore:Possibly corrupt EXIF data\")\n    @pytest.mark.skipif(\n        not os.path.exists(\"Tests/images/string_dimension.tiff\"),\n        reason=\"Extra image files not installed\",\n    )\n    def test_string_dimension(self):\n        # Assert that an error is raised if one of the dimensions is a string\n        with Image.open(\"Tests/images/string_dimension.tiff\") as im:\n            with pytest.raises(OSError):\n                im.load()",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 25,
                                "end_lineno": 709,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": ".. _image-file-formats:\n\nImage file formats\n==================\n\nThe Python Imaging Library supports a wide variety of raster file formats.\nOver 30 different file formats can be identified and read by the library.\nWrite support is less extensive, but most common interchange and presentation\nformats are supported.\n\nThe :py:meth:`~PIL.Image.open` function identifies files from their\ncontents, not their names, but the :py:meth:`~PIL.Image.Image.save` method\nlooks at the name to determine which format to use, unless the format is given\nexplicitly.\n\nWhen an image is opened from a file, only that instance of the image is considered to\nhave the format. Copies of the image will contain data loaded from the file, but not\nthe file itself, meaning that it can no longer be considered to be in the original\nformat. So if :py:meth:`~PIL.Image.Image.copy` is called on an image, or another method\ninternally creates a copy of the image, the ``fp`` (file pointer), along with any\nmethods and attributes specific to a format. The :py:attr:`~PIL.Image.Image.format`\nattribute will be ``None``.\n\nFully supported formats\n-----------------------\n\n.. contents::\n\nBMP\n^^^\n\nPillow reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``,\nor ``RGB`` data. 16-colour images are read as ``P`` images. Run-length encoding\nis not supported.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**compression**\n    Set to ``bmp_rle`` if the file is run-length encoded.\n\nDDS\n^^^\n\nDDS is a popular container texture format used in video games and natively supported\nby DirectX. Uncompressed RGB and RGBA can be read, and (since 8.3.0) written. DXT1,\nDXT3 (since 3.4.0) and DXT5 pixel formats can be read, only in ``RGBA`` mode.\n\nDIB\n^^^\n\nPillow reads and writes DIB files. DIB files are similar to BMP files, so see\nabove for more information.\n\n    .. versionadded:: 6.0.0\n\nEPS\n^^^\n\nPillow identifies EPS files containing image data, and can read files that\ncontain embedded raster images (ImageData descriptors). If Ghostscript is\navailable, other EPS files can be read as well. The EPS driver can also write\nEPS images. The EPS driver can read EPS images in ``L``, ``LAB``, ``RGB`` and\n``CMYK`` mode, but Ghostscript may convert the images to ``RGB`` mode rather\nthan leaving them in the original color space. The EPS driver can write images\nin ``L``, ``RGB`` and ``CMYK`` modes.\n\nIf Ghostscript is available, you can call the :py:meth:`~PIL.Image.Image.load`\nmethod with the following parameter to affect how Ghostscript renders the EPS\n\n**scale**\n    Affects the scale of the resultant rasterized image. If the EPS suggests\n    that the image be rendered at 100px x 100px, setting this parameter to\n    2 will make the Ghostscript render a 200px x 200px image instead. The\n    relative position of the bounding box is maintained::\n\n        im = Image.open(...)\n        im.size #(100,100)\n        im.load(scale=2)\n        im.size #(200,200)\n\nGIF\n^^^\n\nPillow reads GIF87a and GIF89a versions of the GIF file format. The library\nwrites run-length encoded files in GIF87a by default, unless GIF89a features\nare used or GIF89a is already in use.\n\nNote that GIF files are always read as grayscale (``L``)\nor palette mode (``P``) images.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**background**\n    Default background color (a palette color index).\n\n**transparency**\n    Transparency color index. This key is omitted if the image is not\n    transparent.\n\n**version**\n    Version (either ``GIF87a`` or ``GIF89a``).\n\n**duration**\n    May not be present. The time to display the current frame\n    of the GIF, in milliseconds.\n\n**loop**\n    May not be present. The number of times the GIF should loop. 0 means that\n    it will loop forever.\n\n**comment**\n    May not be present. A comment about the image.\n\n**extension**\n    May not be present. Contains application specific information.\n\nReading sequences\n~~~~~~~~~~~~~~~~~\n\nThe GIF loader supports the :py:meth:`~PIL.Image.Image.seek` and\n:py:meth:`~PIL.Image.Image.tell` methods. You can combine these methods\nto seek to the next frame (``im.seek(im.tell() + 1)``).\n\n``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the last frame.\n\nSaving\n~~~~~~\n\nWhen calling :py:meth:`~PIL.Image.Image.save` to write a GIF file, the\nfollowing options are available::\n\n    im.save(out, save_all=True, append_images=[im1, im2, ...])\n\n**save_all**\n    If present and true, all frames of the image will be saved. If\n    not, then only the first frame of a multiframe image will be saved.\n\n**append_images**\n    A list of images to append as additional frames. Each of the\n    images in the list can be single or multiframe images.\n    This is currently supported for GIF, PDF, PNG, TIFF, and WebP.\n\n    It is also supported for ICO and ICNS. If images are passed in of relevant\n    sizes, they will be used instead of scaling down the main image.\n\n**include_color_table**\n    Whether or not to include local color table.\n\n**interlace**\n    Whether or not the image is interlaced. By default, it is, unless the image\n    is less than 16 pixels in width or height.\n\n**disposal**\n    Indicates the way in which the graphic is to be treated after being displayed.\n\n    * 0 - No disposal specified.\n    * 1 - Do not dispose.\n    * 2 - Restore to background color.\n    * 3 - Restore to previous content.\n\n     Pass a single integer for a constant disposal, or a list or tuple\n     to set the disposal for each frame separately.\n\n**palette**\n    Use the specified palette for the saved image. The palette should\n    be a bytes or bytearray object containing the palette entries in\n    RGBRGB... form. It should be no more than 768 bytes. Alternately,\n    the palette can be passed in as an\n    :py:class:`PIL.ImagePalette.ImagePalette` object.\n\n**optimize**\n    If present and true, attempt to compress the palette by\n    eliminating unused colors. This is only useful if the palette can\n    be compressed to the next smaller power of 2 elements.\n\nNote that if the image you are saving comes from an existing GIF, it may have\nthe following properties in its :py:attr:`~PIL.Image.Image.info` dictionary.\nFor these options, if you do not pass them in, they will default to\ntheir :py:attr:`~PIL.Image.Image.info` values.\n\n**transparency**\n    Transparency color index.\n\n**duration**\n    The display duration of each frame of the multiframe gif, in\n    milliseconds. Pass a single integer for a constant duration, or a\n    list or tuple to set the duration for each frame separately.\n\n**loop**\n    Integer number of times the GIF should loop. 0 means that it will loop\n    forever. By default, the image will not loop.\n\n**comment**\n    A comment about the image.\n\nReading local images\n~~~~~~~~~~~~~~~~~~~~\n\nThe GIF loader creates an image memory the same size as the GIF file’s *logical\nscreen size*, and pastes the actual pixel data (the *local image*) into this\nimage. If you only want the actual pixel rectangle, you can manipulate the\n:py:attr:`~PIL.Image.Image.size` and :py:attr:`~PIL.ImageFile.ImageFile.tile`\nattributes before loading the file::\n\n    im = Image.open(...)\n\n    if im.tile[0][0] == \"gif\":\n        # only read the first \"local image\" from this GIF file\n        tag, (x0, y0, x1, y1), offset, extra = im.tile[0]\n        im.size = (x1 - x0, y1 - y0)\n        im.tile = [(tag, (0, 0) + im.size, offset, extra)]\n\nICNS\n^^^^\n\nPillow reads and writes macOS ``.icns`` files.  By default, the\nlargest available icon is read, though you can override this by setting the\n:py:attr:`~PIL.Image.Image.size` property before calling\n:py:meth:`~PIL.Image.Image.load`.  The :py:meth:`~PIL.Image.open` method\nsets the following :py:attr:`~PIL.Image.Image.info` property:\n\n.. note::\n\n    Prior to version 8.3.0, Pillow could only write ICNS files on macOS.\n\n**sizes**\n    A list of supported sizes found in this icon file; these are a\n    3-tuple, ``(width, height, scale)``, where ``scale`` is 2 for a retina\n    icon and 1 for a standard icon.  You *are* permitted to use this 3-tuple\n    format for the :py:attr:`~PIL.Image.Image.size` property if you set it\n    before calling :py:meth:`~PIL.Image.Image.load`; after loading, the size\n    will be reset to a 2-tuple containing pixel dimensions (so, e.g. if you\n    ask for ``(512, 512, 2)``, the final value of\n    :py:attr:`~PIL.Image.Image.size` will be ``(1024, 1024)``).\n\nThe :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:\n\n**append_images**\n    A list of images to replace the scaled down versions of the image.\n    The order of the images does not matter, as their use is determined by\n    the size of each image.\n\n    .. versionadded:: 5.1.0\n\nICO\n^^^\n\nICO is used to store icons on Windows. The largest available icon is read.\n\nThe :py:meth:`~PIL.Image.Image.save` method supports the following options:\n\n**sizes**\n    A list of sizes including in this ico file; these are a 2-tuple,\n    ``(width, height)``; Default to ``[(16, 16), (24, 24), (32, 32), (48, 48),\n    (64, 64), (128, 128), (256, 256)]``. Any sizes bigger than the original\n    size or 256 will be ignored.\n\nThe :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:\n\n**append_images**\n    A list of images to replace the scaled down versions of the image.\n    The order of the images does not matter, as their use is determined by\n    the size of each image.\n\n    .. versionadded:: 8.1.0\n\n**bitmap_format**\n    By default, the image data will be saved in PNG format. With a bitmap format of\n    \"bmp\", image data will be saved in BMP format instead.\n\n    .. versionadded:: 8.3.0\n\nIM\n^^\n\nIM is a format used by LabEye and other applications based on the IFUNC image\nprocessing library. The library reads and writes most uncompressed interchange\nversions of this format.\n\nIM is the only format that can store all internal Pillow formats.\n\nJPEG\n^^^^\n\nPillow reads JPEG, JFIF, and Adobe JPEG files containing ``L``, ``RGB``, or\n``CMYK`` data. It writes standard and progressive JFIF files.\n\nUsing the :py:meth:`~PIL.Image.Image.draft` method, you can speed things up by\nconverting ``RGB`` images to ``L``, and resize images to 1/2, 1/4 or 1/8 of\ntheir original size while loading them.\n\nBy default Pillow doesn't allow loading of truncated JPEG files, set\n:data:`.ImageFile.LOAD_TRUNCATED_IMAGES` to override this.\n\nThe :py:meth:`~PIL.Image.open` method may set the following\n:py:attr:`~PIL.Image.Image.info` properties if available:\n\n**jfif**\n    JFIF application marker found. If the file is not a JFIF file, this key is\n    not present.\n\n**jfif_version**\n    A tuple representing the jfif version, (major version, minor version).\n\n**jfif_density**\n    A tuple representing the pixel density of the image, in units specified\n    by jfif_unit.\n\n**jfif_unit**\n    Units for the jfif_density:\n\n    * 0 - No Units\n    * 1 - Pixels per Inch\n    * 2 - Pixels per Centimeter\n\n**dpi**\n    A tuple representing the reported pixel density in pixels per inch, if\n    the file is a jfif file and the units are in inches.\n\n**adobe**\n    Adobe application marker found. If the file is not an Adobe JPEG file, this\n    key is not present.\n\n**adobe_transform**\n    Vendor Specific Tag.\n\n**progression**\n    Indicates that this is a progressive JPEG file.\n\n**icc_profile**\n    The ICC color profile for the image.\n\n**exif**\n    Raw EXIF data from the image.\n\n**comment**\n    A comment about the image.\n\n    .. versionadded:: 7.1.0\n\n\nThe :py:meth:`~PIL.Image.Image.save` method supports the following options:\n\n**quality**\n    The image quality, on a scale from 0 (worst) to 95 (best). The default is\n    75. Values above 95 should be avoided; 100 disables portions of the JPEG\n    compression algorithm, and results in large files with hardly any gain in\n    image quality.\n\n**optimize**\n    If present and true, indicates that the encoder should make an extra pass\n    over the image in order to select optimal encoder settings.\n\n**progressive**\n    If present and true, indicates that this image should be stored as a\n    progressive JPEG file.\n\n**dpi**\n    A tuple of integers representing the pixel density, ``(x,y)``.\n\n**icc_profile**\n    If present and true, the image is stored with the provided ICC profile.\n    If this parameter is not provided, the image will be saved with no profile\n    attached. To preserve the existing profile::\n\n        im.save(filename, 'jpeg', icc_profile=im.info.get('icc_profile'))\n\n**exif**\n    If present, the image will be stored with the provided raw EXIF data.\n\n**subsampling**\n    If present, sets the subsampling for the encoder.\n\n    * ``keep``: Only valid for JPEG files, will retain the original image setting.\n    * ``4:4:4``, ``4:2:2``, ``4:2:0``: Specific sampling values\n    * ``-1``: equivalent to ``keep``\n    * ``0``: equivalent to ``4:4:4``\n    * ``1``: equivalent to ``4:2:2``\n    * ``2``: equivalent to ``4:2:0``\n\n**qtables**\n    If present, sets the qtables for the encoder. This is listed as an\n    advanced option for wizards in the JPEG documentation. Use with\n    caution. ``qtables`` can be one of several types of values:\n\n    *  a string, naming a preset, e.g. ``keep``, ``web_low``, or ``web_high``\n    *  a list, tuple, or dictionary (with integer keys =\n       range(len(keys))) of lists of 64 integers. There must be\n       between 2 and 4 tables.\n\n    .. versionadded:: 2.5.0\n\n\n.. note::\n\n    To enable JPEG support, you need to build and install the IJG JPEG library\n    before building the Python Imaging Library. See the distribution README for\n    details.\n\nJPEG 2000\n^^^^^^^^^\n\n.. versionadded:: 2.4.0\n\nPillow reads and writes JPEG 2000 files containing ``L``, ``LA``, ``RGB`` or\n``RGBA`` data.  It can also read files containing ``YCbCr`` data, which it\nconverts on read into ``RGB`` or ``RGBA`` depending on whether or not there is\nan alpha channel.  Pillow supports JPEG 2000 raw codestreams (``.j2k`` files),\nas well as boxed JPEG 2000 files (``.j2p`` or ``.jpx`` files).  Pillow does\n*not* support files whose components have different sampling frequencies.\n\nWhen loading, if you set the ``mode`` on the image prior to the\n:py:meth:`~PIL.Image.Image.load` method being invoked, you can ask Pillow to\nconvert the image to either ``RGB`` or ``RGBA`` rather than choosing for\nitself.  It is also possible to set ``reduce`` to the number of resolutions to\ndiscard (each one reduces the size of the resulting image by a factor of 2),\nand ``layers`` to specify the number of quality layers to load.\n\nThe :py:meth:`~PIL.Image.Image.save` method supports the following options:\n\n**offset**\n    The image offset, as a tuple of integers, e.g. (16, 16)\n\n**tile_offset**\n    The tile offset, again as a 2-tuple of integers.\n\n**tile_size**\n    The tile size as a 2-tuple.  If not specified, or if set to None, the\n    image will be saved without tiling.\n\n**quality_mode**\n    Either ``\"rates\"`` or ``\"dB\"`` depending on the units you want to use to\n    specify image quality.\n\n**quality_layers**\n    A sequence of numbers, each of which represents either an approximate size\n    reduction (if quality mode is ``\"rates\"``) or a signal to noise ratio value\n    in decibels.  If not specified, defaults to a single layer of full quality.\n\n**num_resolutions**\n    The number of different image resolutions to be stored (which corresponds\n    to the number of Discrete Wavelet Transform decompositions plus one).\n\n**codeblock_size**\n    The code-block size as a 2-tuple.  Minimum size is 4 x 4, maximum is 1024 x\n    1024, with the additional restriction that no code-block may have more\n    than 4096 coefficients (i.e. the product of the two numbers must be no\n    greater than 4096).\n\n**precinct_size**\n    The precinct size as a 2-tuple.  Must be a power of two along both axes,\n    and must be greater than the code-block size.\n\n**irreversible**\n    If ``True``, use the lossy Irreversible Color Transformation\n    followed by DWT 9-7.  Defaults to ``False``, which means to use the\n    Reversible Color Transformation with DWT 5-3.\n\n**progression**\n    Controls the progression order; must be one of ``\"LRCP\"``, ``\"RLCP\"``,\n    ``\"RPCL\"``, ``\"PCRL\"``, ``\"CPRL\"``.  The letters stand for Component,\n    Position, Resolution and Layer respectively and control the order of\n    encoding, the idea being that e.g. an image encoded using LRCP mode can\n    have its quality layers decoded as they arrive at the decoder, while one\n    encoded using RLCP mode will have increasing resolutions decoded as they\n    arrive, and so on.\n\n**cinema_mode**\n    Set the encoder to produce output compliant with the digital cinema\n    specifications.  The options here are ``\"no\"`` (the default),\n    ``\"cinema2k-24\"`` for 24fps 2K, ``\"cinema2k-48\"`` for 48fps 2K, and\n    ``\"cinema4k-24\"`` for 24fps 4K.  Note that for compliant 2K files,\n    *at least one* of your image dimensions must match 2048 x 1080, while\n    for compliant 4K files, *at least one* of the dimensions must match\n    4096 x 2160.\n\n.. note::\n\n   To enable JPEG 2000 support, you need to build and install the OpenJPEG\n   library, version 2.0.0 or higher, before building the Python Imaging\n   Library.\n\n   Windows users can install the OpenJPEG binaries available on the\n   OpenJPEG website, but must add them to their PATH in order to use Pillow (if\n   you fail to do this, you will get errors about not being able to load the\n   ``_imaging`` DLL).\n\nMSP\n^^^\n\nPillow identifies and reads MSP files from Windows 1 and 2. The library writes\nuncompressed (Windows 1) versions of this format.\n\nPCX\n^^^\n\nPillow reads and writes PCX files containing ``1``, ``L``, ``P``, or ``RGB`` data.\n\nPNG\n^^^\n\nPillow identifies, reads, and writes PNG files containing ``1``, ``L``, ``LA``,\n``I``, ``P``, ``RGB`` or ``RGBA`` data. Interlaced files are supported as of\nv1.1.7.\n\nAs of Pillow 6.0, EXIF data can be read from PNG images. However, unlike other\nimage formats, EXIF data is not guaranteed to be present in\n:py:attr:`~PIL.Image.Image.info` until :py:meth:`~PIL.Image.Image.load` has been\ncalled.\n\nBy default Pillow doesn't allow loading of truncated PNG files, set\n:data:`.ImageFile.LOAD_TRUNCATED_IMAGES` to override this.\n\nThe :py:func:`~PIL.Image.open` function sets the following\n:py:attr:`~PIL.Image.Image.info` properties, when appropriate:\n\n**chromaticity**\n    The chromaticity points, as an 8 tuple of floats. (``White Point\n    X``, ``White Point Y``, ``Red X``, ``Red Y``, ``Green X``, ``Green\n    Y``, ``Blue X``, ``Blue Y``)\n\n**gamma**\n    Gamma, given as a floating point number.\n\n**srgb**\n    The sRGB rendering intent as an integer.\n\n      * 0 Perceptual\n      * 1 Relative Colorimetric\n      * 2 Saturation\n      * 3 Absolute Colorimetric\n\n**transparency**\n    For ``P`` images: Either the palette index for full transparent pixels,\n    or a byte string with alpha values for each palette entry.\n\n    For ``1``, ``L``, ``I`` and ``RGB`` images, the color that represents\n    full transparent pixels in this image.\n\n    This key is omitted if the image is not a transparent palette image.\n\n.. _png-text:\n\n``open`` also sets ``Image.text`` to a dictionary of the values of the\n``tEXt``, ``zTXt``, and ``iTXt`` chunks of the PNG image. Individual\ncompressed chunks are limited to a decompressed size of\n:data:`.PngImagePlugin.MAX_TEXT_CHUNK`, by default 1MB, to prevent\ndecompression bombs. Additionally, the total size of all of the text\nchunks is limited to :data:`.PngImagePlugin.MAX_TEXT_MEMORY`, defaulting to\n64MB.\n\nThe :py:meth:`~PIL.Image.Image.save` method supports the following options:\n\n**optimize**\n    If present and true, instructs the PNG writer to make the output file as\n    small as possible. This includes extra processing in order to find optimal\n    encoder settings.\n\n**transparency**\n    For ``P``, ``1``, ``L``, ``I``, and ``RGB`` images, this option controls\n    what color from the image to mark as transparent.\n\n    For ``P`` images, this can be a either the palette index,\n    or a byte string with alpha values for each palette entry.\n\n**dpi**\n    A tuple of two numbers corresponding to the desired dpi in each direction.\n\n**pnginfo**\n    A :py:class:`PIL.PngImagePlugin.PngInfo` instance containing chunks.\n\n**compress_level**\n    ZLIB compression level, a number between 0 and 9: 1 gives best speed,\n    9 gives best compression, 0 gives no compression at all. Default is 6.\n    When ``optimize`` option is True ``compress_level`` has no effect\n    (it is set to 9 regardless of a value passed).\n\n**icc_profile**\n    The ICC Profile to include in the saved file.\n\n**exif**\n    The exif data to include in the saved file.\n\n    .. versionadded:: 6.0.0\n\n**bits (experimental)**\n    For ``P`` images, this option controls how many bits to store. If omitted,\n    the PNG writer uses 8 bits (256 colors).\n\n**dictionary (experimental)**\n    Set the ZLIB encoder dictionary.\n\n.. note::\n\n    To enable PNG support, you need to build and install the ZLIB compression\n    library before building the Python Imaging Library. See the\n    :doc:`installation documentation <../installation>` for details.\n\n.. _apng-sequences:\n\nAPNG sequences\n~~~~~~~~~~~~~~\n\nThe PNG loader includes limited support for reading and writing Animated Portable\nNetwork Graphics (APNG) files.\nWhen an APNG file is loaded, :py:meth:`~PIL.ImageFile.ImageFile.get_format_mimetype`\nwill return ``\"image/apng\"``. The value of the :py:attr:`~PIL.Image.Image.is_animated`\nproperty will be ``True`` when the :py:attr:`~PIL.Image.Image.n_frames` property is\ngreater than 1. For APNG files, the ``n_frames`` property depends on both the animation\nframe count as well as the presence or absence of a default image. See the\n``default_image`` property documentation below for more details.\nThe :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods\nare supported.\n\n``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the last frame.\n\nThese :py:attr:`~PIL.Image.Image.info` properties will be set for APNG frames,\nwhere applicable:\n\n**default_image**\n    Specifies whether or not this APNG file contains a separate default image,\n    which is not a part of the actual APNG animation.\n\n    When an APNG file contains a default image, the initially loaded image (i.e.\n    the result of ``seek(0)``) will be the default image.\n    To account for the presence of the default image, the\n    :py:attr:`~PIL.Image.Image.n_frames` property will be set to ``frame_count + 1``,\n    where ``frame_count`` is the actual APNG animation frame count.\n    To load the first APNG animation frame, ``seek(1)`` must be called.\n\n    * ``True`` - The APNG contains default image, which is not an animation frame.\n    * ``False`` - The APNG does not contain a default image. The ``n_frames`` property\n      will be set to the actual APNG animation frame count.\n      The initially loaded image (i.e. ``seek(0)``) will be the first APNG animation\n      frame.\n\n**loop**\n    The number of times to loop this APNG, 0 indicates infinite looping.\n\n**duration**\n    The time to display this APNG frame (in milliseconds).\n\n.. note::\n\n    The APNG loader returns images the same size as the APNG file's logical screen size.\n    The returned image contains the pixel data for a given frame, after applying\n    any APNG frame disposal and frame blend operations (i.e. it contains what a web\n    browser would render for this frame - the composite of all previous frames and this\n    frame).\n\n    Any APNG file containing sequence errors is treated as an invalid image. The APNG\n    loader will not attempt to repair and reorder files containing sequence errors.\n\n.. _apng-saving:\n\nSaving\n~~~~~~\n\nWhen calling :py:meth:`~PIL.Image.Image.save`, by default only a single frame PNG file\nwill be saved. To save an APNG file (including a single frame APNG), the ``save_all``\nparameter must be set to ``True``. The following parameters can also be set:\n\n**default_image**\n    Boolean value, specifying whether or not the base image is a default image.\n    If ``True``, the base image will be used as the default image, and the first image\n    from the ``append_images`` sequence will be the first APNG animation frame.\n    If ``False``, the base image will be used as the first APNG animation frame.\n    Defaults to ``False``.\n\n**append_images**\n    A list or tuple of images to append as additional frames. Each of the\n    images in the list can be single or multiframe images. The size of each frame\n    should match the size of the base image. Also note that if a frame's mode does\n    not match that of the base image, the frame will be converted to the base image\n    mode.\n\n**loop**\n    Integer number of times to loop this APNG, 0 indicates infinite looping.\n    Defaults to 0.\n\n**duration**\n    Integer (or list or tuple of integers) length of time to display this APNG frame\n    (in milliseconds).\n    Defaults to 0.\n\n**disposal**\n    An integer (or list or tuple of integers) specifying the APNG disposal\n    operation to be used for this frame before rendering the next frame.\n    Defaults to 0.\n\n    * 0 (:py:data:`~PIL.PngImagePlugin.APNG_DISPOSE_OP_NONE`, default) -\n      No disposal is done on this frame before rendering the next frame.\n    * 1 (:py:data:`PIL.PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND`) -\n      This frame's modified region is cleared to fully transparent black before\n      rendering the next frame.\n    * 2 (:py:data:`~PIL.PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS`) -\n      This frame's modified region is reverted to the previous frame's contents before\n      rendering the next frame.\n\n**blend**\n    An integer (or list or tuple of integers) specifying the APNG blend\n    operation to be used for this frame before rendering the next frame.\n    Defaults to 0.\n\n    * 0 (:py:data:`~PIL.PngImagePlugin.APNG_BLEND_OP_SOURCE`) -\n      All color components of this frame, including alpha, overwrite the previous output\n      image contents.\n    * 1 (:py:data:`~PIL.PngImagePlugin.APNG_BLEND_OP_OVER`) -\n      This frame should be alpha composited with the previous output image contents.\n\n.. note::\n\n    The ``duration``, ``disposal`` and ``blend`` parameters can be set to lists or tuples to\n    specify values for each individual frame in the animation. The length of the list or tuple\n    must be identical to the total number of actual frames in the APNG animation.\n    If the APNG contains a default image (i.e. ``default_image`` is set to ``True``),\n    these list or tuple parameters should not include an entry for the default image.\n\n\nPPM\n^^^\n\nPillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L`` or\n``RGB`` data.\n\nSGI\n^^^\n\nPillow reads and writes uncompressed ``L``, ``RGB``, and ``RGBA`` files.\n\n\nSPIDER\n^^^^^^\n\nPillow reads and writes SPIDER image files of 32-bit floating point data\n(\"F;32F\").\n\nPillow also reads SPIDER stack files containing sequences of SPIDER images. The\n:py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods are supported, and\nrandom access is allowed.\n\nThe :py:meth:`~PIL.Image.open` method sets the following attributes:\n\n**format**\n    Set to ``SPIDER``\n\n**istack**\n    Set to 1 if the file is an image stack, else 0.\n\n**n_frames**\n    Set to the number of images in the stack.\n\nA convenience method, :py:meth:`~PIL.SpiderImagePlugin.SpiderImageFile.convert2byte`,\nis provided for converting floating point data to byte data (mode ``L``)::\n\n    im = Image.open('image001.spi').convert2byte()\n\nWriting files in SPIDER format\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe extension of SPIDER files may be any 3 alphanumeric characters. Therefore\nthe output format must be specified explicitly::\n\n    im.save('newimage.spi', format='SPIDER')\n\nFor more information about the SPIDER image processing package, see the\n`SPIDER homepage`_ at `Wadsworth Center`_.\n\n.. _SPIDER homepage: https://spider.wadsworth.org/spider_doc/spider/docs/spider.html\n.. _Wadsworth Center: https://www.wadsworth.org/\n\nTGA\n^^^\n\nPillow reads and writes TGA images containing ``L``, ``LA``, ``P``,\n``RGB``, and ``RGBA`` data. Pillow can read and write both uncompressed and\nrun-length encoded TGAs.\n\nTIFF\n^^^^\n\nPillow reads and writes TIFF files. It can read both striped and tiled\nimages, pixel and plane interleaved multi-band images. If you have\nlibtiff and its headers installed, Pillow can read and write many kinds\nof compressed TIFF files. If not, Pillow will only read and write\nuncompressed files.\n\n.. note::\n\n    Beginning in version 5.0.0, Pillow requires libtiff to read or\n    write compressed files. Prior to that release, Pillow had buggy\n    support for reading Packbits, LZW and JPEG compressed TIFFs\n    without using libtiff.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**compression**\n    Compression mode.\n\n    .. versionadded:: 2.0.0\n\n**dpi**\n    Image resolution as an ``(xdpi, ydpi)`` tuple, where applicable. You can use\n    the :py:attr:`~PIL.TiffImagePlugin.TiffImageFile.tag` attribute to get more\n    detailed information about the image resolution.\n\n    .. versionadded:: 1.1.5\n\n**resolution**\n    Image resolution as an ``(xres, yres)`` tuple, where applicable. This is a\n    measurement in whichever unit is specified by the file.\n\n    .. versionadded:: 1.1.5\n\n\nThe :py:attr:`~PIL.TiffImagePlugin.TiffImageFile.tag_v2` attribute contains a\ndictionary of TIFF metadata. The keys are numerical indexes from\n:py:data:`.TiffTags.TAGS_V2`.  Values are strings or numbers for single\nitems, multiple values are returned in a tuple of values. Rational\nnumbers are returned as a :py:class:`~PIL.TiffImagePlugin.IFDRational`\nobject.\n\n    .. versionadded:: 3.0.0\n\nFor compatibility with legacy code, the\n:py:attr:`~PIL.TiffImagePlugin.TiffImageFile.tag` attribute contains a dictionary\nof decoded TIFF fields as returned prior to version 3.0.0.  Values are\nreturned as either strings or tuples of numeric values. Rational\nnumbers are returned as a tuple of ``(numerator, denominator)``.\n\n    .. deprecated:: 3.0.0\n\nReading Multi-frame TIFF Images\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe TIFF loader supports the :py:meth:`~PIL.Image.Image.seek` and\n:py:meth:`~PIL.Image.Image.tell` methods, taking and returning frame numbers\nwithin the image file. You can combine these methods to seek to the next frame\n(``im.seek(im.tell() + 1)``). Frames are numbered from 0 to ``im.num_frames - 1``,\nand can be accessed in any order.\n\n``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the\nlast frame.\n\nSaving Tiff Images\n~~~~~~~~~~~~~~~~~~\n\nThe :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:\n\n**save_all**\n    If true, Pillow will save all frames of the image to a multiframe tiff document.\n\n    .. versionadded:: 3.4.0\n\n**append_images**\n    A list of images to append as additional frames. Each of the\n    images in the list can be single or multiframe images. Note however, that for\n    correct results, all the appended images should have the same\n    ``encoderinfo`` and ``encoderconfig`` properties.\n\n    .. versionadded:: 4.2.0\n\n**tiffinfo**\n    A :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` object or dict\n    object containing tiff tags and values. The TIFF field type is\n    autodetected for Numeric and string values, any other types\n    require using an :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`\n    object and setting the type in\n    :py:attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype` with\n    the appropriate numerical value from\n    :py:data:`.TiffTags.TYPES`.\n\n    .. versionadded:: 2.3.0\n\n    Metadata values that are of the rational type should be passed in\n    using a :py:class:`~PIL.TiffImagePlugin.IFDRational` object.\n\n    .. versionadded:: 3.1.0\n\n    For compatibility with legacy code, a\n    :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` object may\n    be passed in this field. However, this is deprecated.\n\n    .. versionadded:: 5.4.0\n\n    Previous versions only supported some tags when writing using\n    libtiff. The supported list is found in\n    :py:data:`.TiffTags.LIBTIFF_CORE`.\n\n    .. versionadded:: 6.1.0\n\n    Added support for signed types (e.g. ``TIFF_SIGNED_LONG``) and multiple values.\n    Multiple values for a single tag must be to\n    :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` as a tuple and\n    require a matching type in\n    :py:attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype` tagtype.\n\n**compression**\n    A string containing the desired compression method for the\n    file. (valid only with libtiff installed) Valid compression\n    methods are: :data:`None`, ``\"group3\"``, ``\"group4\"``, ``\"jpeg\"``, ``\"lzma\"``,\n    ``\"packbits\"``, ``\"tiff_adobe_deflate\"``, ``\"tiff_ccitt\"``, ``\"tiff_lzw\"``,\n    ``\"tiff_raw_16\"``, ``\"tiff_sgilog\"``, ``\"tiff_sgilog24\"``, ``\"tiff_thunderscan\"``,\n    ``\"webp\"`, ``\"zstd\"``\n\n**quality**\n    The image quality for JPEG compression, on a scale from 0 (worst) to 100\n    (best). The default is 75.\n\n    .. versionadded:: 6.1.0\n\nThese arguments to set the tiff header fields are an alternative to\nusing the general tags available through tiffinfo.\n\n**description**\n\n**software**\n\n**date_time**\n\n**artist**\n\n**copyright**\n    Strings\n\n**icc_profile**\n    The ICC Profile to include in the saved file.\n\n**resolution_unit**\n    An integer. 1 for no unit, 2 for inches and 3 for centimeters.\n\n**resolution**\n    Either an integer or a float, used for both the x and y resolution.\n\n**x_resolution**\n    Either an integer or a float.\n\n**y_resolution**\n    Either an integer or a float.\n\n**dpi**\n    A tuple of (x_resolution, y_resolution), with inches as the resolution\n    unit. For consistency with other image formats, the x and y resolutions\n    of the dpi will be rounded to the nearest integer.\n\n\nWebP\n^^^^\n\nPillow reads and writes WebP files. The specifics of Pillow's capabilities with\nthis format are currently undocumented.\n\nThe :py:meth:`~PIL.Image.Image.save` method supports the following options:\n\n**lossless**\n    If present and true, instructs the WebP writer to use lossless compression.\n\n**quality**\n    Integer, 1-100, Defaults to 80. For lossy, 0 gives the smallest\n    size and 100 the largest. For lossless, this parameter is the amount\n    of effort put into the compression: 0 is the fastest, but gives larger\n    files compared to the slowest, but best, 100.\n\n**method**\n    Quality/speed trade-off (0=fast, 6=slower-better). Defaults to 4.\n\n**icc_profile**\n    The ICC Profile to include in the saved file. Only supported if\n    the system WebP library was built with webpmux support.\n\n**exif**\n    The exif data to include in the saved file. Only supported if\n    the system WebP library was built with webpmux support.\n\nSaving sequences\n~~~~~~~~~~~~~~~~~\n\n.. note::\n\n    Support for animated WebP files will only be enabled if the system WebP\n    library is v0.5.0 or later. You can check webp animation support at\n    runtime by calling ``features.check(\"webp_anim\")``.\n\nWhen calling :py:meth:`~PIL.Image.Image.save` to write a WebP file, by default\nonly the first frame of a multiframe image will be saved. If the ``save_all``\nargument is present and true, then all frames will be saved, and the following\noptions will also be available.\n\n**append_images**\n    A list of images to append as additional frames. Each of the\n    images in the list can be single or multiframe images.\n\n**duration**\n    The display duration of each frame, in milliseconds. Pass a single\n    integer for a constant duration, or a list or tuple to set the\n    duration for each frame separately.\n\n**loop**\n    Number of times to repeat the animation. Defaults to [0 = infinite].\n\n**background**\n    Background color of the canvas, as an RGBA tuple with values in\n    the range of (0-255).\n\n**minimize_size**\n    If true, minimize the output size (slow). Implicitly disables\n    key-frame insertion.\n\n**kmin, kmax**\n    Minimum and maximum distance between consecutive key frames in\n    the output. The library may insert some key frames as needed\n    to satisfy this criteria. Note that these conditions should\n    hold: kmax > kmin and kmin >= kmax / 2 + 1. Also, if kmax <= 0,\n    then key-frame insertion is disabled; and if kmax == 1, then all\n    frames will be key-frames (kmin value does not matter for these\n    special cases).\n\n**allow_mixed**\n    If true, use mixed compression mode; the encoder heuristically\n    chooses between lossy and lossless for each frame.\n\nXBM\n^^^\n\nPillow reads and writes X bitmap files (mode ``1``).\n\nRead-only formats\n-----------------\n\nBLP\n^^^\n\nBLP is the Blizzard Mipmap Format, a texture format used in World of\nWarcraft. Pillow supports reading ``JPEG`` Compressed or raw ``BLP1``\nimages, and all types of ``BLP2`` images.\n\nCUR\n^^^\n\nCUR is used to store cursors on Windows. The CUR decoder reads the largest\navailable cursor. Animated cursors are not supported.\n\nDCX\n^^^\n\nDCX is a container file format for PCX files, defined by Intel. The DCX format\nis commonly used in fax applications. The DCX decoder can read files containing\n``1``, ``L``, ``P``, or ``RGB`` data.\n\nWhen the file is opened, only the first image is read. You can use\n:py:meth:`~PIL.Image.Image.seek` or :py:mod:`~PIL.ImageSequence` to read other images.\n\nFLI, FLC\n^^^^^^^^\n\nPillow reads Autodesk FLI and FLC animations.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**duration**\n    The delay (in milliseconds) between each frame.\n\nFPX\n^^^\n\nPillow reads Kodak FlashPix files. In the current version, only the highest\nresolution image is read from the file, and the viewing transform is not taken\ninto account.\n\n.. note::\n\n    To enable full FlashPix support, you need to build and install the IJG JPEG\n    library before building the Python Imaging Library. See the distribution\n    README for details.\n\nFTEX\n^^^^\n\n.. versionadded:: 3.2.0\n\nThe FTEX decoder reads textures used for 3D objects in\nIndependence War 2: Edge Of Chaos. The plugin reads a single texture\nper file, in the compressed and uncompressed formats.\n\nGBR\n^^^\n\nThe GBR decoder reads GIMP brush files, version 1 and 2.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**comment**\n    The brush name.\n\n**spacing**\n    The spacing between the brushes, in pixels. Version 2 only.\n\nGD\n^^\n\nPillow reads uncompressed GD2 files. Note that you must use\n:py:func:`PIL.GdImageFile.open` to read such a file.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**transparency**\n    Transparency color index. This key is omitted if the image is not\n    transparent.\n\nIMT\n^^^\n\nPillow reads Image Tools images containing ``L`` data.\n\nIPTC/NAA\n^^^^^^^^\n\nPillow provides limited read support for IPTC/NAA newsphoto files.\n\nMCIDAS\n^^^^^^\n\nPillow identifies and reads 8-bit McIdas area files.\n\nMIC\n^^^\n\nPillow identifies and reads Microsoft Image Composer (MIC) files. When opened,\nthe first sprite in the file is loaded. You can use :py:meth:`~PIL.Image.Image.seek` and\n:py:meth:`~PIL.Image.Image.tell` to read other sprites from the file.\n\nNote that there may be an embedded gamma of 2.2 in MIC files.\n\nMPO\n^^^\n\nPillow identifies and reads Multi Picture Object (MPO) files, loading the primary\nimage when first opened. The :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell`\nmethods may be used to read other pictures from the file. The pictures are\nzero-indexed and random access is supported.\n\nPCD\n^^^\n\nPillow reads PhotoCD files containing ``RGB`` data. This only reads the 768x512\nresolution image from the file. Higher resolutions are encoded in a proprietary\nencoding.\n\nPIXAR\n^^^^^\n\nPillow provides limited support for PIXAR raster files. The library can\nidentify and read “dumped” RGB files.\n\nThe format code is ``PIXAR``.\n\nPSD\n^^^\n\nPillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0.\n\n\nWAL\n^^^\n\n.. versionadded:: 1.1.4\n\nPillow reads Quake2 WAL texture files.\n\nNote that this file format cannot be automatically identified, so you must use\nthe open function in the :py:mod:`~PIL.WalImageFile` module to read files in\nthis format.\n\nBy default, a Quake2 standard palette is attached to the texture. To override\nthe palette, use the putpalette method.\n\nWMF\n^^^\n\nPillow can identify WMF files.\n\nOn Windows, it can read WMF files. By default, it will load the image at 72\ndpi. To load it at another resolution:\n\n.. code-block:: python\n\n    from PIL import Image\n    with Image.open(\"drawing.wmf\") as im:\n        im.load(dpi=144)\n\nTo add other read or write support, use\n:py:func:`PIL.WmfImagePlugin.register_handler` to register a WMF handler.\n\n.. code-block:: python\n\n    from PIL import Image\n    from PIL import WmfImagePlugin\n\n    class WmfHandler:\n        def open(self, im):\n            ...\n        def load(self, im):\n            ...\n            return image\n        def save(self, im, fp, filename):\n            ...\n\n    wmf_handler = WmfHandler()\n\n    WmfImagePlugin.register_handler(wmf_handler)\n\n    im = Image.open(\"sample.wmf\")\n\nXPM\n^^^\n\nPillow reads X pixmap files (mode ``P``) with 256 colors or less.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**transparency**\n    Transparency color index. This key is omitted if the image is not\n    transparent.\n\nWrite-only formats\n------------------\n\nPALM\n^^^^\n\nPillow provides write-only support for PALM pixmap files.\n\nThe format code is ``Palm``, the extension is ``.palm``.\n\nPDF\n^^^\n\nPillow can write PDF (Acrobat) images. Such images are written as binary PDF 1.4\nfiles, using either JPEG or HEX encoding depending on the image mode (and\nwhether JPEG support is available or not).\n\nThe :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:\n\n**save_all**\n    If a multiframe image is used, by default, only the first image will be saved.\n    To save all frames, each frame to a separate page of the PDF, the ``save_all``\n    parameter must be present and set to ``True``.\n\n    .. versionadded:: 3.0.0\n\n**append_images**\n    A list of :py:class:`PIL.Image.Image` objects to append as additional pages. Each\n    of the images in the list can be single or multiframe images. The ``save_all``\n    parameter must be present and set to ``True`` in conjunction with\n    ``append_images``.\n\n    .. versionadded:: 4.2.0\n\n**append**\n    Set to True to append pages to an existing PDF file. If the file doesn't\n    exist, an :py:exc:`OSError` will be raised.\n\n    .. versionadded:: 5.1.0\n\n**resolution**\n    Image resolution in DPI. This, together with the number of pixels in the\n    image, will determine the physical dimensions of the page that will be\n    saved in the PDF.\n\n**title**\n    The document’s title. If not appending to an existing PDF file, this will\n    default to the filename.\n\n    .. versionadded:: 5.1.0\n\n**author**\n    The name of the person who created the document.\n\n    .. versionadded:: 5.1.0\n\n**subject**\n    The subject of the document.\n\n    .. versionadded:: 5.1.0\n\n**keywords**\n    Keywords associated with the document.\n\n    .. versionadded:: 5.1.0\n\n**creator**\n    If the document was converted to PDF from another format, the name of the\n    conforming product that created the original document from which it was\n    converted.\n\n    .. versionadded:: 5.1.0\n\n**producer**\n    If the document was converted to PDF from another format, the name of the\n    conforming product that converted it to PDF.\n\n    .. versionadded:: 5.1.0\n\n**creationDate**\n    The creation date of the document. If not appending to an existing PDF\n    file, this will default to the current time.\n\n    .. versionadded:: 5.3.0\n\n**modDate**\n    The modification date of the document. If not appending to an existing PDF\n    file, this will default to the current time.\n\n    .. versionadded:: 5.3.0\n\nXV Thumbnails\n^^^^^^^^^^^^^\n\nPillow can read XV thumbnail files.\n\nIdentify-only formats\n---------------------\n\nBUFR\n^^^^\n\n.. versionadded:: 1.1.3\n\nPillow provides a stub driver for BUFR files.\n\nTo add read or write support to your application, use\n:py:func:`PIL.BufrStubImagePlugin.register_handler`.\n\nFITS\n^^^^\n\n.. versionadded:: 1.1.5\n\nPillow provides a stub driver for FITS files.\n\nTo add read or write support to your application, use\n:py:func:`PIL.FitsStubImagePlugin.register_handler`.\n\nGRIB\n^^^^\n\n.. versionadded:: 1.1.5\n\nPillow provides a stub driver for GRIB files.\n\nThe driver requires the file to start with a GRIB header. If you have files\nwith embedded GRIB data, or files with multiple GRIB fields, your application\nhas to seek to the header before passing the file handle to Pillow.\n\nTo add read or write support to your application, use\n:py:func:`PIL.GribStubImagePlugin.register_handler`.\n\nHDF5\n^^^^\n\n.. versionadded:: 1.1.5\n\nPillow provides a stub driver for HDF5 files.\n\nTo add read or write support to your application, use\n:py:func:`PIL.Hdf5StubImagePlugin.register_handler`.\n\nMPEG\n^^^^\n\nPillow identifies MPEG files.\n",
            "new_file_content": ".. _image-file-formats:\n\nImage file formats\n==================\n\nThe Python Imaging Library supports a wide variety of raster file formats.\nOver 30 different file formats can be identified and read by the library.\nWrite support is less extensive, but most common interchange and presentation\nformats are supported.\n\nThe :py:meth:`~PIL.Image.open` function identifies files from their\ncontents, not their names, but the :py:meth:`~PIL.Image.Image.save` method\nlooks at the name to determine which format to use, unless the format is given\nexplicitly.\n\nWhen an image is opened from a file, only that instance of the image is considered to\nhave the format. Copies of the image will contain data loaded from the file, but not\nthe file itself, meaning that it can no longer be considered to be in the original\nformat. So if :py:meth:`~PIL.Image.Image.copy` is called on an image, or another method\ninternally creates a copy of the image, the ``fp`` (file pointer), along with any\nmethods and attributes specific to a format. The :py:attr:`~PIL.Image.Image.format`\nattribute will be ``None``.\n\nFully supported formats\n-----------------------\n\n.. contents::\n\nBMP\n^^^\n\nPillow reads and writes Windows and OS/2 BMP files containing ``1``, ``L``, ``P``,\nor ``RGB`` data. 16-colour images are read as ``P`` images. Run-length encoding\nis not supported.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**compression**\n    Set to ``bmp_rle`` if the file is run-length encoded.\n\nDDS\n^^^\n\nDDS is a popular container texture format used in video games and natively supported\nby DirectX. Uncompressed RGB and RGBA can be read, and (since 8.3.0) written. DXT1,\nDXT3 (since 3.4.0) and DXT5 pixel formats can be read, only in ``RGBA`` mode.\n\nDIB\n^^^\n\nPillow reads and writes DIB files. DIB files are similar to BMP files, so see\nabove for more information.\n\n    .. versionadded:: 6.0.0\n\nEPS\n^^^\n\nPillow identifies EPS files containing image data, and can read files that\ncontain embedded raster images (ImageData descriptors). If Ghostscript is\navailable, other EPS files can be read as well. The EPS driver can also write\nEPS images. The EPS driver can read EPS images in ``L``, ``LAB``, ``RGB`` and\n``CMYK`` mode, but Ghostscript may convert the images to ``RGB`` mode rather\nthan leaving them in the original color space. The EPS driver can write images\nin ``L``, ``RGB`` and ``CMYK`` modes.\n\nIf Ghostscript is available, you can call the :py:meth:`~PIL.Image.Image.load`\nmethod with the following parameter to affect how Ghostscript renders the EPS\n\n**scale**\n    Affects the scale of the resultant rasterized image. If the EPS suggests\n    that the image be rendered at 100px x 100px, setting this parameter to\n    2 will make the Ghostscript render a 200px x 200px image instead. The\n    relative position of the bounding box is maintained::\n\n        im = Image.open(...)\n        im.size #(100,100)\n        im.load(scale=2)\n        im.size #(200,200)\n\nGIF\n^^^\n\nPillow reads GIF87a and GIF89a versions of the GIF file format. The library\nwrites run-length encoded files in GIF87a by default, unless GIF89a features\nare used or GIF89a is already in use.\n\nNote that GIF files are always read as grayscale (``L``)\nor palette mode (``P``) images.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**background**\n    Default background color (a palette color index).\n\n**transparency**\n    Transparency color index. This key is omitted if the image is not\n    transparent.\n\n**version**\n    Version (either ``GIF87a`` or ``GIF89a``).\n\n**duration**\n    May not be present. The time to display the current frame\n    of the GIF, in milliseconds.\n\n**loop**\n    May not be present. The number of times the GIF should loop. 0 means that\n    it will loop forever.\n\n**comment**\n    May not be present. A comment about the image.\n\n**extension**\n    May not be present. Contains application specific information.\n\nReading sequences\n~~~~~~~~~~~~~~~~~\n\nThe GIF loader supports the :py:meth:`~PIL.Image.Image.seek` and\n:py:meth:`~PIL.Image.Image.tell` methods. You can combine these methods\nto seek to the next frame (``im.seek(im.tell() + 1)``).\n\n``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the last frame.\n\nSaving\n~~~~~~\n\nWhen calling :py:meth:`~PIL.Image.Image.save` to write a GIF file, the\nfollowing options are available::\n\n    im.save(out, save_all=True, append_images=[im1, im2, ...])\n\n**save_all**\n    If present and true, all frames of the image will be saved. If\n    not, then only the first frame of a multiframe image will be saved.\n\n**append_images**\n    A list of images to append as additional frames. Each of the\n    images in the list can be single or multiframe images.\n    This is currently supported for GIF, PDF, PNG, TIFF, and WebP.\n\n    It is also supported for ICO and ICNS. If images are passed in of relevant\n    sizes, they will be used instead of scaling down the main image.\n\n**include_color_table**\n    Whether or not to include local color table.\n\n**interlace**\n    Whether or not the image is interlaced. By default, it is, unless the image\n    is less than 16 pixels in width or height.\n\n**disposal**\n    Indicates the way in which the graphic is to be treated after being displayed.\n\n    * 0 - No disposal specified.\n    * 1 - Do not dispose.\n    * 2 - Restore to background color.\n    * 3 - Restore to previous content.\n\n     Pass a single integer for a constant disposal, or a list or tuple\n     to set the disposal for each frame separately.\n\n**palette**\n    Use the specified palette for the saved image. The palette should\n    be a bytes or bytearray object containing the palette entries in\n    RGBRGB... form. It should be no more than 768 bytes. Alternately,\n    the palette can be passed in as an\n    :py:class:`PIL.ImagePalette.ImagePalette` object.\n\n**optimize**\n    If present and true, attempt to compress the palette by\n    eliminating unused colors. This is only useful if the palette can\n    be compressed to the next smaller power of 2 elements.\n\nNote that if the image you are saving comes from an existing GIF, it may have\nthe following properties in its :py:attr:`~PIL.Image.Image.info` dictionary.\nFor these options, if you do not pass them in, they will default to\ntheir :py:attr:`~PIL.Image.Image.info` values.\n\n**transparency**\n    Transparency color index.\n\n**duration**\n    The display duration of each frame of the multiframe gif, in\n    milliseconds. Pass a single integer for a constant duration, or a\n    list or tuple to set the duration for each frame separately.\n\n**loop**\n    Integer number of times the GIF should loop. 0 means that it will loop\n    forever. By default, the image will not loop.\n\n**comment**\n    A comment about the image.\n\nReading local images\n~~~~~~~~~~~~~~~~~~~~\n\nThe GIF loader creates an image memory the same size as the GIF file’s *logical\nscreen size*, and pastes the actual pixel data (the *local image*) into this\nimage. If you only want the actual pixel rectangle, you can manipulate the\n:py:attr:`~PIL.Image.Image.size` and :py:attr:`~PIL.ImageFile.ImageFile.tile`\nattributes before loading the file::\n\n    im = Image.open(...)\n\n    if im.tile[0][0] == \"gif\":\n        # only read the first \"local image\" from this GIF file\n        tag, (x0, y0, x1, y1), offset, extra = im.tile[0]\n        im.size = (x1 - x0, y1 - y0)\n        im.tile = [(tag, (0, 0) + im.size, offset, extra)]\n\nICNS\n^^^^\n\nPillow reads and writes macOS ``.icns`` files.  By default, the\nlargest available icon is read, though you can override this by setting the\n:py:attr:`~PIL.Image.Image.size` property before calling\n:py:meth:`~PIL.Image.Image.load`.  The :py:meth:`~PIL.Image.open` method\nsets the following :py:attr:`~PIL.Image.Image.info` property:\n\n.. note::\n\n    Prior to version 8.3.0, Pillow could only write ICNS files on macOS.\n\n**sizes**\n    A list of supported sizes found in this icon file; these are a\n    3-tuple, ``(width, height, scale)``, where ``scale`` is 2 for a retina\n    icon and 1 for a standard icon.  You *are* permitted to use this 3-tuple\n    format for the :py:attr:`~PIL.Image.Image.size` property if you set it\n    before calling :py:meth:`~PIL.Image.Image.load`; after loading, the size\n    will be reset to a 2-tuple containing pixel dimensions (so, e.g. if you\n    ask for ``(512, 512, 2)``, the final value of\n    :py:attr:`~PIL.Image.Image.size` will be ``(1024, 1024)``).\n\nThe :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:\n\n**append_images**\n    A list of images to replace the scaled down versions of the image.\n    The order of the images does not matter, as their use is determined by\n    the size of each image.\n\n    .. versionadded:: 5.1.0\n\nICO\n^^^\n\nICO is used to store icons on Windows. The largest available icon is read.\n\nThe :py:meth:`~PIL.Image.Image.save` method supports the following options:\n\n**sizes**\n    A list of sizes including in this ico file; these are a 2-tuple,\n    ``(width, height)``; Default to ``[(16, 16), (24, 24), (32, 32), (48, 48),\n    (64, 64), (128, 128), (256, 256)]``. Any sizes bigger than the original\n    size or 256 will be ignored.\n\nThe :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:\n\n**append_images**\n    A list of images to replace the scaled down versions of the image.\n    The order of the images does not matter, as their use is determined by\n    the size of each image.\n\n    .. versionadded:: 8.1.0\n\n**bitmap_format**\n    By default, the image data will be saved in PNG format. With a bitmap format of\n    \"bmp\", image data will be saved in BMP format instead.\n\n    .. versionadded:: 8.3.0\n\nIM\n^^\n\nIM is a format used by LabEye and other applications based on the IFUNC image\nprocessing library. The library reads and writes most uncompressed interchange\nversions of this format.\n\nIM is the only format that can store all internal Pillow formats.\n\nJPEG\n^^^^\n\nPillow reads JPEG, JFIF, and Adobe JPEG files containing ``L``, ``RGB``, or\n``CMYK`` data. It writes standard and progressive JFIF files.\n\nUsing the :py:meth:`~PIL.Image.Image.draft` method, you can speed things up by\nconverting ``RGB`` images to ``L``, and resize images to 1/2, 1/4 or 1/8 of\ntheir original size while loading them.\n\nBy default Pillow doesn't allow loading of truncated JPEG files, set\n:data:`.ImageFile.LOAD_TRUNCATED_IMAGES` to override this.\n\nThe :py:meth:`~PIL.Image.open` method may set the following\n:py:attr:`~PIL.Image.Image.info` properties if available:\n\n**jfif**\n    JFIF application marker found. If the file is not a JFIF file, this key is\n    not present.\n\n**jfif_version**\n    A tuple representing the jfif version, (major version, minor version).\n\n**jfif_density**\n    A tuple representing the pixel density of the image, in units specified\n    by jfif_unit.\n\n**jfif_unit**\n    Units for the jfif_density:\n\n    * 0 - No Units\n    * 1 - Pixels per Inch\n    * 2 - Pixels per Centimeter\n\n**dpi**\n    A tuple representing the reported pixel density in pixels per inch, if\n    the file is a jfif file and the units are in inches.\n\n**adobe**\n    Adobe application marker found. If the file is not an Adobe JPEG file, this\n    key is not present.\n\n**adobe_transform**\n    Vendor Specific Tag.\n\n**progression**\n    Indicates that this is a progressive JPEG file.\n\n**icc_profile**\n    The ICC color profile for the image.\n\n**exif**\n    Raw EXIF data from the image.\n\n**comment**\n    A comment about the image.\n\n    .. versionadded:: 7.1.0\n\n\nThe :py:meth:`~PIL.Image.Image.save` method supports the following options:\n\n**quality**\n    The image quality, on a scale from 0 (worst) to 95 (best). The default is\n    75. Values above 95 should be avoided; 100 disables portions of the JPEG\n    compression algorithm, and results in large files with hardly any gain in\n    image quality.\n\n**optimize**\n    If present and true, indicates that the encoder should make an extra pass\n    over the image in order to select optimal encoder settings.\n\n**progressive**\n    If present and true, indicates that this image should be stored as a\n    progressive JPEG file.\n\n**dpi**\n    A tuple of integers representing the pixel density, ``(x,y)``.\n\n**icc_profile**\n    If present and true, the image is stored with the provided ICC profile.\n    If this parameter is not provided, the image will be saved with no profile\n    attached. To preserve the existing profile::\n\n        im.save(filename, 'jpeg', icc_profile=im.info.get('icc_profile'))\n\n**exif**\n    If present, the image will be stored with the provided raw EXIF data.\n\n**subsampling**\n    If present, sets the subsampling for the encoder.\n\n    * ``keep``: Only valid for JPEG files, will retain the original image setting.\n    * ``4:4:4``, ``4:2:2``, ``4:2:0``: Specific sampling values\n    * ``-1``: equivalent to ``keep``\n    * ``0``: equivalent to ``4:4:4``\n    * ``1``: equivalent to ``4:2:2``\n    * ``2``: equivalent to ``4:2:0``\n\n**qtables**\n    If present, sets the qtables for the encoder. This is listed as an\n    advanced option for wizards in the JPEG documentation. Use with\n    caution. ``qtables`` can be one of several types of values:\n\n    *  a string, naming a preset, e.g. ``keep``, ``web_low``, or ``web_high``\n    *  a list, tuple, or dictionary (with integer keys =\n       range(len(keys))) of lists of 64 integers. There must be\n       between 2 and 4 tables.\n\n    .. versionadded:: 2.5.0\n\n\n.. note::\n\n    To enable JPEG support, you need to build and install the IJG JPEG library\n    before building the Python Imaging Library. See the distribution README for\n    details.\n\nJPEG 2000\n^^^^^^^^^\n\n.. versionadded:: 2.4.0\n\nPillow reads and writes JPEG 2000 files containing ``L``, ``LA``, ``RGB`` or\n``RGBA`` data.  It can also read files containing ``YCbCr`` data, which it\nconverts on read into ``RGB`` or ``RGBA`` depending on whether or not there is\nan alpha channel.  Pillow supports JPEG 2000 raw codestreams (``.j2k`` files),\nas well as boxed JPEG 2000 files (``.j2p`` or ``.jpx`` files).  Pillow does\n*not* support files whose components have different sampling frequencies.\n\nWhen loading, if you set the ``mode`` on the image prior to the\n:py:meth:`~PIL.Image.Image.load` method being invoked, you can ask Pillow to\nconvert the image to either ``RGB`` or ``RGBA`` rather than choosing for\nitself.  It is also possible to set ``reduce`` to the number of resolutions to\ndiscard (each one reduces the size of the resulting image by a factor of 2),\nand ``layers`` to specify the number of quality layers to load.\n\nThe :py:meth:`~PIL.Image.Image.save` method supports the following options:\n\n**offset**\n    The image offset, as a tuple of integers, e.g. (16, 16)\n\n**tile_offset**\n    The tile offset, again as a 2-tuple of integers.\n\n**tile_size**\n    The tile size as a 2-tuple.  If not specified, or if set to None, the\n    image will be saved without tiling.\n\n**quality_mode**\n    Either ``\"rates\"`` or ``\"dB\"`` depending on the units you want to use to\n    specify image quality.\n\n**quality_layers**\n    A sequence of numbers, each of which represents either an approximate size\n    reduction (if quality mode is ``\"rates\"``) or a signal to noise ratio value\n    in decibels.  If not specified, defaults to a single layer of full quality.\n\n**num_resolutions**\n    The number of different image resolutions to be stored (which corresponds\n    to the number of Discrete Wavelet Transform decompositions plus one).\n\n**codeblock_size**\n    The code-block size as a 2-tuple.  Minimum size is 4 x 4, maximum is 1024 x\n    1024, with the additional restriction that no code-block may have more\n    than 4096 coefficients (i.e. the product of the two numbers must be no\n    greater than 4096).\n\n**precinct_size**\n    The precinct size as a 2-tuple.  Must be a power of two along both axes,\n    and must be greater than the code-block size.\n\n**irreversible**\n    If ``True``, use the lossy Irreversible Color Transformation\n    followed by DWT 9-7.  Defaults to ``False``, which means to use the\n    Reversible Color Transformation with DWT 5-3.\n\n**progression**\n    Controls the progression order; must be one of ``\"LRCP\"``, ``\"RLCP\"``,\n    ``\"RPCL\"``, ``\"PCRL\"``, ``\"CPRL\"``.  The letters stand for Component,\n    Position, Resolution and Layer respectively and control the order of\n    encoding, the idea being that e.g. an image encoded using LRCP mode can\n    have its quality layers decoded as they arrive at the decoder, while one\n    encoded using RLCP mode will have increasing resolutions decoded as they\n    arrive, and so on.\n\n**cinema_mode**\n    Set the encoder to produce output compliant with the digital cinema\n    specifications.  The options here are ``\"no\"`` (the default),\n    ``\"cinema2k-24\"`` for 24fps 2K, ``\"cinema2k-48\"`` for 48fps 2K, and\n    ``\"cinema4k-24\"`` for 24fps 4K.  Note that for compliant 2K files,\n    *at least one* of your image dimensions must match 2048 x 1080, while\n    for compliant 4K files, *at least one* of the dimensions must match\n    4096 x 2160.\n\n.. note::\n\n   To enable JPEG 2000 support, you need to build and install the OpenJPEG\n   library, version 2.0.0 or higher, before building the Python Imaging\n   Library.\n\n   Windows users can install the OpenJPEG binaries available on the\n   OpenJPEG website, but must add them to their PATH in order to use Pillow (if\n   you fail to do this, you will get errors about not being able to load the\n   ``_imaging`` DLL).\n\nMSP\n^^^\n\nPillow identifies and reads MSP files from Windows 1 and 2. The library writes\nuncompressed (Windows 1) versions of this format.\n\nPCX\n^^^\n\nPillow reads and writes PCX files containing ``1``, ``L``, ``P``, or ``RGB`` data.\n\nPNG\n^^^\n\nPillow identifies, reads, and writes PNG files containing ``1``, ``L``, ``LA``,\n``I``, ``P``, ``RGB`` or ``RGBA`` data. Interlaced files are supported as of\nv1.1.7.\n\nAs of Pillow 6.0, EXIF data can be read from PNG images. However, unlike other\nimage formats, EXIF data is not guaranteed to be present in\n:py:attr:`~PIL.Image.Image.info` until :py:meth:`~PIL.Image.Image.load` has been\ncalled.\n\nBy default Pillow doesn't allow loading of truncated PNG files, set\n:data:`.ImageFile.LOAD_TRUNCATED_IMAGES` to override this.\n\nThe :py:func:`~PIL.Image.open` function sets the following\n:py:attr:`~PIL.Image.Image.info` properties, when appropriate:\n\n**chromaticity**\n    The chromaticity points, as an 8 tuple of floats. (``White Point\n    X``, ``White Point Y``, ``Red X``, ``Red Y``, ``Green X``, ``Green\n    Y``, ``Blue X``, ``Blue Y``)\n\n**gamma**\n    Gamma, given as a floating point number.\n\n**srgb**\n    The sRGB rendering intent as an integer.\n\n      * 0 Perceptual\n      * 1 Relative Colorimetric\n      * 2 Saturation\n      * 3 Absolute Colorimetric\n\n**transparency**\n    For ``P`` images: Either the palette index for full transparent pixels,\n    or a byte string with alpha values for each palette entry.\n\n    For ``1``, ``L``, ``I`` and ``RGB`` images, the color that represents\n    full transparent pixels in this image.\n\n    This key is omitted if the image is not a transparent palette image.\n\n.. _png-text:\n\n``open`` also sets ``Image.text`` to a dictionary of the values of the\n``tEXt``, ``zTXt``, and ``iTXt`` chunks of the PNG image. Individual\ncompressed chunks are limited to a decompressed size of\n:data:`.PngImagePlugin.MAX_TEXT_CHUNK`, by default 1MB, to prevent\ndecompression bombs. Additionally, the total size of all of the text\nchunks is limited to :data:`.PngImagePlugin.MAX_TEXT_MEMORY`, defaulting to\n64MB.\n\nThe :py:meth:`~PIL.Image.Image.save` method supports the following options:\n\n**optimize**\n    If present and true, instructs the PNG writer to make the output file as\n    small as possible. This includes extra processing in order to find optimal\n    encoder settings.\n\n**transparency**\n    For ``P``, ``1``, ``L``, ``I``, and ``RGB`` images, this option controls\n    what color from the image to mark as transparent.\n\n    For ``P`` images, this can be a either the palette index,\n    or a byte string with alpha values for each palette entry.\n\n**dpi**\n    A tuple of two numbers corresponding to the desired dpi in each direction.\n\n**pnginfo**\n    A :py:class:`PIL.PngImagePlugin.PngInfo` instance containing chunks.\n\n**compress_level**\n    ZLIB compression level, a number between 0 and 9: 1 gives best speed,\n    9 gives best compression, 0 gives no compression at all. Default is 6.\n    When ``optimize`` option is True ``compress_level`` has no effect\n    (it is set to 9 regardless of a value passed).\n\n**icc_profile**\n    The ICC Profile to include in the saved file.\n\n**exif**\n    The exif data to include in the saved file.\n\n    .. versionadded:: 6.0.0\n\n**bits (experimental)**\n    For ``P`` images, this option controls how many bits to store. If omitted,\n    the PNG writer uses 8 bits (256 colors).\n\n**dictionary (experimental)**\n    Set the ZLIB encoder dictionary.\n\n.. note::\n\n    To enable PNG support, you need to build and install the ZLIB compression\n    library before building the Python Imaging Library. See the\n    :doc:`installation documentation <../installation>` for details.\n\n.. _apng-sequences:\n\nAPNG sequences\n~~~~~~~~~~~~~~\n\nThe PNG loader includes limited support for reading and writing Animated Portable\nNetwork Graphics (APNG) files.\nWhen an APNG file is loaded, :py:meth:`~PIL.ImageFile.ImageFile.get_format_mimetype`\nwill return ``\"image/apng\"``. The value of the :py:attr:`~PIL.Image.Image.is_animated`\nproperty will be ``True`` when the :py:attr:`~PIL.Image.Image.n_frames` property is\ngreater than 1. For APNG files, the ``n_frames`` property depends on both the animation\nframe count as well as the presence or absence of a default image. See the\n``default_image`` property documentation below for more details.\nThe :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods\nare supported.\n\n``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the last frame.\n\nThese :py:attr:`~PIL.Image.Image.info` properties will be set for APNG frames,\nwhere applicable:\n\n**default_image**\n    Specifies whether or not this APNG file contains a separate default image,\n    which is not a part of the actual APNG animation.\n\n    When an APNG file contains a default image, the initially loaded image (i.e.\n    the result of ``seek(0)``) will be the default image.\n    To account for the presence of the default image, the\n    :py:attr:`~PIL.Image.Image.n_frames` property will be set to ``frame_count + 1``,\n    where ``frame_count`` is the actual APNG animation frame count.\n    To load the first APNG animation frame, ``seek(1)`` must be called.\n\n    * ``True`` - The APNG contains default image, which is not an animation frame.\n    * ``False`` - The APNG does not contain a default image. The ``n_frames`` property\n      will be set to the actual APNG animation frame count.\n      The initially loaded image (i.e. ``seek(0)``) will be the first APNG animation\n      frame.\n\n**loop**\n    The number of times to loop this APNG, 0 indicates infinite looping.\n\n**duration**\n    The time to display this APNG frame (in milliseconds).\n\n.. note::\n\n    The APNG loader returns images the same size as the APNG file's logical screen size.\n    The returned image contains the pixel data for a given frame, after applying\n    any APNG frame disposal and frame blend operations (i.e. it contains what a web\n    browser would render for this frame - the composite of all previous frames and this\n    frame).\n\n    Any APNG file containing sequence errors is treated as an invalid image. The APNG\n    loader will not attempt to repair and reorder files containing sequence errors.\n\n.. _apng-saving:\n\nSaving\n~~~~~~\n\nWhen calling :py:meth:`~PIL.Image.Image.save`, by default only a single frame PNG file\nwill be saved. To save an APNG file (including a single frame APNG), the ``save_all``\nparameter must be set to ``True``. The following parameters can also be set:\n\n**default_image**\n    Boolean value, specifying whether or not the base image is a default image.\n    If ``True``, the base image will be used as the default image, and the first image\n    from the ``append_images`` sequence will be the first APNG animation frame.\n    If ``False``, the base image will be used as the first APNG animation frame.\n    Defaults to ``False``.\n\n**append_images**\n    A list or tuple of images to append as additional frames. Each of the\n    images in the list can be single or multiframe images. The size of each frame\n    should match the size of the base image. Also note that if a frame's mode does\n    not match that of the base image, the frame will be converted to the base image\n    mode.\n\n**loop**\n    Integer number of times to loop this APNG, 0 indicates infinite looping.\n    Defaults to 0.\n\n**duration**\n    Integer (or list or tuple of integers) length of time to display this APNG frame\n    (in milliseconds).\n    Defaults to 0.\n\n**disposal**\n    An integer (or list or tuple of integers) specifying the APNG disposal\n    operation to be used for this frame before rendering the next frame.\n    Defaults to 0.\n\n    * 0 (:py:data:`~PIL.PngImagePlugin.APNG_DISPOSE_OP_NONE`, default) -\n      No disposal is done on this frame before rendering the next frame.\n    * 1 (:py:data:`PIL.PngImagePlugin.APNG_DISPOSE_OP_BACKGROUND`) -\n      This frame's modified region is cleared to fully transparent black before\n      rendering the next frame.\n    * 2 (:py:data:`~PIL.PngImagePlugin.APNG_DISPOSE_OP_PREVIOUS`) -\n      This frame's modified region is reverted to the previous frame's contents before\n      rendering the next frame.\n\n**blend**\n    An integer (or list or tuple of integers) specifying the APNG blend\n    operation to be used for this frame before rendering the next frame.\n    Defaults to 0.\n\n    * 0 (:py:data:`~PIL.PngImagePlugin.APNG_BLEND_OP_SOURCE`) -\n      All color components of this frame, including alpha, overwrite the previous output\n      image contents.\n    * 1 (:py:data:`~PIL.PngImagePlugin.APNG_BLEND_OP_OVER`) -\n      This frame should be alpha composited with the previous output image contents.\n\n.. note::\n\n    The ``duration``, ``disposal`` and ``blend`` parameters can be set to lists or tuples to\n    specify values for each individual frame in the animation. The length of the list or tuple\n    must be identical to the total number of actual frames in the APNG animation.\n    If the APNG contains a default image (i.e. ``default_image`` is set to ``True``),\n    these list or tuple parameters should not include an entry for the default image.\n\n\nPPM\n^^^\n\nPillow reads and writes PBM, PGM, PPM and PNM files containing ``1``, ``L`` or\n``RGB`` data.\n\nSGI\n^^^\n\nPillow reads and writes uncompressed ``L``, ``RGB``, and ``RGBA`` files.\n\n\nSPIDER\n^^^^^^\n\nPillow reads and writes SPIDER image files of 32-bit floating point data\n(\"F;32F\").\n\nPillow also reads SPIDER stack files containing sequences of SPIDER images. The\n:py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` methods are supported, and\nrandom access is allowed.\n\nThe :py:meth:`~PIL.Image.open` method sets the following attributes:\n\n**format**\n    Set to ``SPIDER``\n\n**istack**\n    Set to 1 if the file is an image stack, else 0.\n\n**n_frames**\n    Set to the number of images in the stack.\n\nA convenience method, :py:meth:`~PIL.SpiderImagePlugin.SpiderImageFile.convert2byte`,\nis provided for converting floating point data to byte data (mode ``L``)::\n\n    im = Image.open('image001.spi').convert2byte()\n\nWriting files in SPIDER format\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe extension of SPIDER files may be any 3 alphanumeric characters. Therefore\nthe output format must be specified explicitly::\n\n    im.save('newimage.spi', format='SPIDER')\n\nFor more information about the SPIDER image processing package, see the\n`SPIDER homepage`_ at `Wadsworth Center`_.\n\n.. _SPIDER homepage: https://spider.wadsworth.org/spider_doc/spider/docs/spider.html\n.. _Wadsworth Center: https://www.wadsworth.org/\n\nTGA\n^^^\n\nPillow reads and writes TGA images containing ``L``, ``LA``, ``P``,\n``RGB``, and ``RGBA`` data. Pillow can read and write both uncompressed and\nrun-length encoded TGAs.\n\nTIFF\n^^^^\n\nPillow reads and writes TIFF files. It can read both striped and tiled\nimages, pixel and plane interleaved multi-band images. If you have\nlibtiff and its headers installed, Pillow can read and write many kinds\nof compressed TIFF files. If not, Pillow will only read and write\nuncompressed files.\n\n.. note::\n\n    Beginning in version 5.0.0, Pillow requires libtiff to read or\n    write compressed files. Prior to that release, Pillow had buggy\n    support for reading Packbits, LZW and JPEG compressed TIFFs\n    without using libtiff.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**compression**\n    Compression mode.\n\n    .. versionadded:: 2.0.0\n\n**dpi**\n    Image resolution as an ``(xdpi, ydpi)`` tuple, where applicable. You can use\n    the :py:attr:`~PIL.TiffImagePlugin.TiffImageFile.tag` attribute to get more\n    detailed information about the image resolution.\n\n    .. versionadded:: 1.1.5\n\n**resolution**\n    Image resolution as an ``(xres, yres)`` tuple, where applicable. This is a\n    measurement in whichever unit is specified by the file.\n\n    .. versionadded:: 1.1.5\n\n\nThe :py:attr:`~PIL.TiffImagePlugin.TiffImageFile.tag_v2` attribute contains a\ndictionary of TIFF metadata. The keys are numerical indexes from\n:py:data:`.TiffTags.TAGS_V2`.  Values are strings or numbers for single\nitems, multiple values are returned in a tuple of values. Rational\nnumbers are returned as a :py:class:`~PIL.TiffImagePlugin.IFDRational`\nobject.\n\n    .. versionadded:: 3.0.0\n\nFor compatibility with legacy code, the\n:py:attr:`~PIL.TiffImagePlugin.TiffImageFile.tag` attribute contains a dictionary\nof decoded TIFF fields as returned prior to version 3.0.0.  Values are\nreturned as either strings or tuples of numeric values. Rational\nnumbers are returned as a tuple of ``(numerator, denominator)``.\n\n    .. deprecated:: 3.0.0\n\nReading Multi-frame TIFF Images\n~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n\nThe TIFF loader supports the :py:meth:`~PIL.Image.Image.seek` and\n:py:meth:`~PIL.Image.Image.tell` methods, taking and returning frame numbers\nwithin the image file. You can combine these methods to seek to the next frame\n(``im.seek(im.tell() + 1)``). Frames are numbered from 0 to ``im.num_frames - 1``,\nand can be accessed in any order.\n\n``im.seek()`` raises an :py:exc:`EOFError` if you try to seek after the\nlast frame.\n\nSaving Tiff Images\n~~~~~~~~~~~~~~~~~~\n\nThe :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:\n\n**save_all**\n    If true, Pillow will save all frames of the image to a multiframe tiff document.\n\n    .. versionadded:: 3.4.0\n\n**append_images**\n    A list of images to append as additional frames. Each of the\n    images in the list can be single or multiframe images. Note however, that for\n    correct results, all the appended images should have the same\n    ``encoderinfo`` and ``encoderconfig`` properties.\n\n    .. versionadded:: 4.2.0\n\n**tiffinfo**\n    A :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` object or dict\n    object containing tiff tags and values. The TIFF field type is\n    autodetected for Numeric and string values, any other types\n    require using an :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`\n    object and setting the type in\n    :py:attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype` with\n    the appropriate numerical value from\n    :py:data:`.TiffTags.TYPES`.\n\n    .. versionadded:: 2.3.0\n\n    Metadata values that are of the rational type should be passed in\n    using a :py:class:`~PIL.TiffImagePlugin.IFDRational` object.\n\n    .. versionadded:: 3.1.0\n\n    For compatibility with legacy code, a\n    :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` object may\n    be passed in this field. However, this is deprecated.\n\n    .. versionadded:: 5.4.0\n\n    Previous versions only supported some tags when writing using\n    libtiff. The supported list is found in\n    :py:data:`.TiffTags.LIBTIFF_CORE`.\n\n    .. versionadded:: 6.1.0\n\n    Added support for signed types (e.g. ``TIFF_SIGNED_LONG``) and multiple values.\n    Multiple values for a single tag must be to\n    :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` as a tuple and\n    require a matching type in\n    :py:attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype` tagtype.\n\n**exif**\n    Alternate keyword to \"tiffinfo\", for consistency with other formats.\n\n    .. versionadded:: 8.4.0\n\n**compression**\n    A string containing the desired compression method for the\n    file. (valid only with libtiff installed) Valid compression\n    methods are: :data:`None`, ``\"group3\"``, ``\"group4\"``, ``\"jpeg\"``, ``\"lzma\"``,\n    ``\"packbits\"``, ``\"tiff_adobe_deflate\"``, ``\"tiff_ccitt\"``, ``\"tiff_lzw\"``,\n    ``\"tiff_raw_16\"``, ``\"tiff_sgilog\"``, ``\"tiff_sgilog24\"``, ``\"tiff_thunderscan\"``,\n    ``\"webp\"`, ``\"zstd\"``\n\n**quality**\n    The image quality for JPEG compression, on a scale from 0 (worst) to 100\n    (best). The default is 75.\n\n    .. versionadded:: 6.1.0\n\nThese arguments to set the tiff header fields are an alternative to\nusing the general tags available through tiffinfo.\n\n**description**\n\n**software**\n\n**date_time**\n\n**artist**\n\n**copyright**\n    Strings\n\n**icc_profile**\n    The ICC Profile to include in the saved file.\n\n**resolution_unit**\n    An integer. 1 for no unit, 2 for inches and 3 for centimeters.\n\n**resolution**\n    Either an integer or a float, used for both the x and y resolution.\n\n**x_resolution**\n    Either an integer or a float.\n\n**y_resolution**\n    Either an integer or a float.\n\n**dpi**\n    A tuple of (x_resolution, y_resolution), with inches as the resolution\n    unit. For consistency with other image formats, the x and y resolutions\n    of the dpi will be rounded to the nearest integer.\n\n\nWebP\n^^^^\n\nPillow reads and writes WebP files. The specifics of Pillow's capabilities with\nthis format are currently undocumented.\n\nThe :py:meth:`~PIL.Image.Image.save` method supports the following options:\n\n**lossless**\n    If present and true, instructs the WebP writer to use lossless compression.\n\n**quality**\n    Integer, 1-100, Defaults to 80. For lossy, 0 gives the smallest\n    size and 100 the largest. For lossless, this parameter is the amount\n    of effort put into the compression: 0 is the fastest, but gives larger\n    files compared to the slowest, but best, 100.\n\n**method**\n    Quality/speed trade-off (0=fast, 6=slower-better). Defaults to 4.\n\n**icc_profile**\n    The ICC Profile to include in the saved file. Only supported if\n    the system WebP library was built with webpmux support.\n\n**exif**\n    The exif data to include in the saved file. Only supported if\n    the system WebP library was built with webpmux support.\n\nSaving sequences\n~~~~~~~~~~~~~~~~~\n\n.. note::\n\n    Support for animated WebP files will only be enabled if the system WebP\n    library is v0.5.0 or later. You can check webp animation support at\n    runtime by calling ``features.check(\"webp_anim\")``.\n\nWhen calling :py:meth:`~PIL.Image.Image.save` to write a WebP file, by default\nonly the first frame of a multiframe image will be saved. If the ``save_all``\nargument is present and true, then all frames will be saved, and the following\noptions will also be available.\n\n**append_images**\n    A list of images to append as additional frames. Each of the\n    images in the list can be single or multiframe images.\n\n**duration**\n    The display duration of each frame, in milliseconds. Pass a single\n    integer for a constant duration, or a list or tuple to set the\n    duration for each frame separately.\n\n**loop**\n    Number of times to repeat the animation. Defaults to [0 = infinite].\n\n**background**\n    Background color of the canvas, as an RGBA tuple with values in\n    the range of (0-255).\n\n**minimize_size**\n    If true, minimize the output size (slow). Implicitly disables\n    key-frame insertion.\n\n**kmin, kmax**\n    Minimum and maximum distance between consecutive key frames in\n    the output. The library may insert some key frames as needed\n    to satisfy this criteria. Note that these conditions should\n    hold: kmax > kmin and kmin >= kmax / 2 + 1. Also, if kmax <= 0,\n    then key-frame insertion is disabled; and if kmax == 1, then all\n    frames will be key-frames (kmin value does not matter for these\n    special cases).\n\n**allow_mixed**\n    If true, use mixed compression mode; the encoder heuristically\n    chooses between lossy and lossless for each frame.\n\nXBM\n^^^\n\nPillow reads and writes X bitmap files (mode ``1``).\n\nRead-only formats\n-----------------\n\nBLP\n^^^\n\nBLP is the Blizzard Mipmap Format, a texture format used in World of\nWarcraft. Pillow supports reading ``JPEG`` Compressed or raw ``BLP1``\nimages, and all types of ``BLP2`` images.\n\nCUR\n^^^\n\nCUR is used to store cursors on Windows. The CUR decoder reads the largest\navailable cursor. Animated cursors are not supported.\n\nDCX\n^^^\n\nDCX is a container file format for PCX files, defined by Intel. The DCX format\nis commonly used in fax applications. The DCX decoder can read files containing\n``1``, ``L``, ``P``, or ``RGB`` data.\n\nWhen the file is opened, only the first image is read. You can use\n:py:meth:`~PIL.Image.Image.seek` or :py:mod:`~PIL.ImageSequence` to read other images.\n\nFLI, FLC\n^^^^^^^^\n\nPillow reads Autodesk FLI and FLC animations.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**duration**\n    The delay (in milliseconds) between each frame.\n\nFPX\n^^^\n\nPillow reads Kodak FlashPix files. In the current version, only the highest\nresolution image is read from the file, and the viewing transform is not taken\ninto account.\n\n.. note::\n\n    To enable full FlashPix support, you need to build and install the IJG JPEG\n    library before building the Python Imaging Library. See the distribution\n    README for details.\n\nFTEX\n^^^^\n\n.. versionadded:: 3.2.0\n\nThe FTEX decoder reads textures used for 3D objects in\nIndependence War 2: Edge Of Chaos. The plugin reads a single texture\nper file, in the compressed and uncompressed formats.\n\nGBR\n^^^\n\nThe GBR decoder reads GIMP brush files, version 1 and 2.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**comment**\n    The brush name.\n\n**spacing**\n    The spacing between the brushes, in pixels. Version 2 only.\n\nGD\n^^\n\nPillow reads uncompressed GD2 files. Note that you must use\n:py:func:`PIL.GdImageFile.open` to read such a file.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**transparency**\n    Transparency color index. This key is omitted if the image is not\n    transparent.\n\nIMT\n^^^\n\nPillow reads Image Tools images containing ``L`` data.\n\nIPTC/NAA\n^^^^^^^^\n\nPillow provides limited read support for IPTC/NAA newsphoto files.\n\nMCIDAS\n^^^^^^\n\nPillow identifies and reads 8-bit McIdas area files.\n\nMIC\n^^^\n\nPillow identifies and reads Microsoft Image Composer (MIC) files. When opened,\nthe first sprite in the file is loaded. You can use :py:meth:`~PIL.Image.Image.seek` and\n:py:meth:`~PIL.Image.Image.tell` to read other sprites from the file.\n\nNote that there may be an embedded gamma of 2.2 in MIC files.\n\nMPO\n^^^\n\nPillow identifies and reads Multi Picture Object (MPO) files, loading the primary\nimage when first opened. The :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell`\nmethods may be used to read other pictures from the file. The pictures are\nzero-indexed and random access is supported.\n\nPCD\n^^^\n\nPillow reads PhotoCD files containing ``RGB`` data. This only reads the 768x512\nresolution image from the file. Higher resolutions are encoded in a proprietary\nencoding.\n\nPIXAR\n^^^^^\n\nPillow provides limited support for PIXAR raster files. The library can\nidentify and read “dumped” RGB files.\n\nThe format code is ``PIXAR``.\n\nPSD\n^^^\n\nPillow identifies and reads PSD files written by Adobe Photoshop 2.5 and 3.0.\n\n\nWAL\n^^^\n\n.. versionadded:: 1.1.4\n\nPillow reads Quake2 WAL texture files.\n\nNote that this file format cannot be automatically identified, so you must use\nthe open function in the :py:mod:`~PIL.WalImageFile` module to read files in\nthis format.\n\nBy default, a Quake2 standard palette is attached to the texture. To override\nthe palette, use the putpalette method.\n\nWMF\n^^^\n\nPillow can identify WMF files.\n\nOn Windows, it can read WMF files. By default, it will load the image at 72\ndpi. To load it at another resolution:\n\n.. code-block:: python\n\n    from PIL import Image\n    with Image.open(\"drawing.wmf\") as im:\n        im.load(dpi=144)\n\nTo add other read or write support, use\n:py:func:`PIL.WmfImagePlugin.register_handler` to register a WMF handler.\n\n.. code-block:: python\n\n    from PIL import Image\n    from PIL import WmfImagePlugin\n\n    class WmfHandler:\n        def open(self, im):\n            ...\n        def load(self, im):\n            ...\n            return image\n        def save(self, im, fp, filename):\n            ...\n\n    wmf_handler = WmfHandler()\n\n    WmfImagePlugin.register_handler(wmf_handler)\n\n    im = Image.open(\"sample.wmf\")\n\nXPM\n^^^\n\nPillow reads X pixmap files (mode ``P``) with 256 colors or less.\n\nThe :py:meth:`~PIL.Image.open` method sets the following\n:py:attr:`~PIL.Image.Image.info` properties:\n\n**transparency**\n    Transparency color index. This key is omitted if the image is not\n    transparent.\n\nWrite-only formats\n------------------\n\nPALM\n^^^^\n\nPillow provides write-only support for PALM pixmap files.\n\nThe format code is ``Palm``, the extension is ``.palm``.\n\nPDF\n^^^\n\nPillow can write PDF (Acrobat) images. Such images are written as binary PDF 1.4\nfiles, using either JPEG or HEX encoding depending on the image mode (and\nwhether JPEG support is available or not).\n\nThe :py:meth:`~PIL.Image.Image.save` method can take the following keyword arguments:\n\n**save_all**\n    If a multiframe image is used, by default, only the first image will be saved.\n    To save all frames, each frame to a separate page of the PDF, the ``save_all``\n    parameter must be present and set to ``True``.\n\n    .. versionadded:: 3.0.0\n\n**append_images**\n    A list of :py:class:`PIL.Image.Image` objects to append as additional pages. Each\n    of the images in the list can be single or multiframe images. The ``save_all``\n    parameter must be present and set to ``True`` in conjunction with\n    ``append_images``.\n\n    .. versionadded:: 4.2.0\n\n**append**\n    Set to True to append pages to an existing PDF file. If the file doesn't\n    exist, an :py:exc:`OSError` will be raised.\n\n    .. versionadded:: 5.1.0\n\n**resolution**\n    Image resolution in DPI. This, together with the number of pixels in the\n    image, will determine the physical dimensions of the page that will be\n    saved in the PDF.\n\n**title**\n    The document’s title. If not appending to an existing PDF file, this will\n    default to the filename.\n\n    .. versionadded:: 5.1.0\n\n**author**\n    The name of the person who created the document.\n\n    .. versionadded:: 5.1.0\n\n**subject**\n    The subject of the document.\n\n    .. versionadded:: 5.1.0\n\n**keywords**\n    Keywords associated with the document.\n\n    .. versionadded:: 5.1.0\n\n**creator**\n    If the document was converted to PDF from another format, the name of the\n    conforming product that created the original document from which it was\n    converted.\n\n    .. versionadded:: 5.1.0\n\n**producer**\n    If the document was converted to PDF from another format, the name of the\n    conforming product that converted it to PDF.\n\n    .. versionadded:: 5.1.0\n\n**creationDate**\n    The creation date of the document. If not appending to an existing PDF\n    file, this will default to the current time.\n\n    .. versionadded:: 5.3.0\n\n**modDate**\n    The modification date of the document. If not appending to an existing PDF\n    file, this will default to the current time.\n\n    .. versionadded:: 5.3.0\n\nXV Thumbnails\n^^^^^^^^^^^^^\n\nPillow can read XV thumbnail files.\n\nIdentify-only formats\n---------------------\n\nBUFR\n^^^^\n\n.. versionadded:: 1.1.3\n\nPillow provides a stub driver for BUFR files.\n\nTo add read or write support to your application, use\n:py:func:`PIL.BufrStubImagePlugin.register_handler`.\n\nFITS\n^^^^\n\n.. versionadded:: 1.1.5\n\nPillow provides a stub driver for FITS files.\n\nTo add read or write support to your application, use\n:py:func:`PIL.FitsStubImagePlugin.register_handler`.\n\nGRIB\n^^^^\n\n.. versionadded:: 1.1.5\n\nPillow provides a stub driver for GRIB files.\n\nThe driver requires the file to start with a GRIB header. If you have files\nwith embedded GRIB data, or files with multiple GRIB fields, your application\nhas to seek to the header before passing the file handle to Pillow.\n\nTo add read or write support to your application, use\n:py:func:`PIL.GribStubImagePlugin.register_handler`.\n\nHDF5\n^^^^\n\n.. versionadded:: 1.1.5\n\nPillow provides a stub driver for HDF5 files.\n\nTo add read or write support to your application, use\n:py:func:`PIL.Hdf5StubImagePlugin.register_handler`.\n\nMPEG\n^^^^\n\nPillow identifies MPEG files.\n",
            "header": {
                "file": {
                    "path": "docs/handbook/image-file-formats.rst"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "5d4e83494",
                "new_commit_hash": "1b65ef236",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/docs/handbook/image-file-formats.rst"
            },
            "plus_file": {
                "path": "b/docs/handbook/image-file-formats.rst"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 898,
                            "length": 6
                        },
                        "new_range": {
                            "start": 898,
                            "length": 11
                        },
                        "section": "The :py:meth:`~PIL.Image.Image.save` method can take the following keyword argum"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    require a matching type in",
                                "type": "context"
                            },
                            {
                                "content": "    :py:attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype` tagtype.",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "**exif**",
                                "type": "added"
                            },
                            {
                                "content": "    Alternate keyword to \"tiffinfo\", for consistency with other formats.",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    .. versionadded:: 8.4.0",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "**compression**",
                                "type": "context"
                            },
                            {
                                "content": "    A string containing the desired compression method for the",
                                "type": "context"
                            },
                            {
                                "content": "    file. (valid only with libtiff installed) Valid compression",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        },
        {
            "old_file_content": "#\n# The Python Imaging Library.\n# $Id$\n#\n# TIFF file handling\n#\n# TIFF is a flexible, if somewhat aged, image file format originally\n# defined by Aldus.  Although TIFF supports a wide variety of pixel\n# layouts and compression methods, the name doesn't really stand for\n# \"thousands of incompatible file formats,\" it just feels that way.\n#\n# To read TIFF data from a stream, the stream must be seekable.  For\n# progressive decoding, make sure to use TIFF files where the tag\n# directory is placed first in the file.\n#\n# History:\n# 1995-09-01 fl   Created\n# 1996-05-04 fl   Handle JPEGTABLES tag\n# 1996-05-18 fl   Fixed COLORMAP support\n# 1997-01-05 fl   Fixed PREDICTOR support\n# 1997-08-27 fl   Added support for rational tags (from Perry Stoll)\n# 1998-01-10 fl   Fixed seek/tell (from Jan Blom)\n# 1998-07-15 fl   Use private names for internal variables\n# 1999-06-13 fl   Rewritten for PIL 1.0 (1.0)\n# 2000-10-11 fl   Additional fixes for Python 2.0 (1.1)\n# 2001-04-17 fl   Fixed rewind support (seek to frame 0) (1.2)\n# 2001-05-12 fl   Added write support for more tags (from Greg Couch) (1.3)\n# 2001-12-18 fl   Added workaround for broken Matrox library\n# 2002-01-18 fl   Don't mess up if photometric tag is missing (D. Alan Stewart)\n# 2003-05-19 fl   Check FILLORDER tag\n# 2003-09-26 fl   Added RGBa support\n# 2004-02-24 fl   Added DPI support; fixed rational write support\n# 2005-02-07 fl   Added workaround for broken Corel Draw 10 files\n# 2006-01-09 fl   Added support for float/double tags (from Russell Nelson)\n#\n# Copyright (c) 1997-2006 by Secret Labs AB.  All rights reserved.\n# Copyright (c) 1995-1997 by Fredrik Lundh\n#\n# See the README file for information on usage and redistribution.\n#\nimport io\nimport itertools\nimport logging\nimport os\nimport struct\nimport warnings\nfrom collections.abc import MutableMapping\nfrom fractions import Fraction\nfrom numbers import Number, Rational\n\nfrom . import Image, ImageFile, ImagePalette, TiffTags\nfrom ._binary import o8\nfrom .TiffTags import TYPES\n\nlogger = logging.getLogger(__name__)\n\n# Set these to true to force use of libtiff for reading or writing.\nREAD_LIBTIFF = False\nWRITE_LIBTIFF = False\nIFD_LEGACY_API = True\n\nII = b\"II\"  # little-endian (Intel style)\nMM = b\"MM\"  # big-endian (Motorola style)\n\n#\n# --------------------------------------------------------------------\n# Read TIFF files\n\n# a few tag names, just to make the code below a bit more readable\nIMAGEWIDTH = 256\nIMAGELENGTH = 257\nBITSPERSAMPLE = 258\nCOMPRESSION = 259\nPHOTOMETRIC_INTERPRETATION = 262\nFILLORDER = 266\nIMAGEDESCRIPTION = 270\nSTRIPOFFSETS = 273\nSAMPLESPERPIXEL = 277\nROWSPERSTRIP = 278\nSTRIPBYTECOUNTS = 279\nX_RESOLUTION = 282\nY_RESOLUTION = 283\nPLANAR_CONFIGURATION = 284\nRESOLUTION_UNIT = 296\nTRANSFERFUNCTION = 301\nSOFTWARE = 305\nDATE_TIME = 306\nARTIST = 315\nPREDICTOR = 317\nCOLORMAP = 320\nTILEOFFSETS = 324\nSUBIFD = 330\nEXTRASAMPLES = 338\nSAMPLEFORMAT = 339\nJPEGTABLES = 347\nREFERENCEBLACKWHITE = 532\nCOPYRIGHT = 33432\nIPTC_NAA_CHUNK = 33723  # newsphoto properties\nPHOTOSHOP_CHUNK = 34377  # photoshop properties\nICCPROFILE = 34675\nEXIFIFD = 34665\nXMP = 700\nJPEGQUALITY = 65537  # pseudo-tag by libtiff\n\n# https://github.com/imagej/ImageJA/blob/master/src/main/java/ij/io/TiffDecoder.java\nIMAGEJ_META_DATA_BYTE_COUNTS = 50838\nIMAGEJ_META_DATA = 50839\n\nCOMPRESSION_INFO = {\n    # Compression => pil compression name\n    1: \"raw\",\n    2: \"tiff_ccitt\",\n    3: \"group3\",\n    4: \"group4\",\n    5: \"tiff_lzw\",\n    6: \"tiff_jpeg\",  # obsolete\n    7: \"jpeg\",\n    8: \"tiff_adobe_deflate\",\n    32771: \"tiff_raw_16\",  # 16-bit padding\n    32773: \"packbits\",\n    32809: \"tiff_thunderscan\",\n    32946: \"tiff_deflate\",\n    34676: \"tiff_sgilog\",\n    34677: \"tiff_sgilog24\",\n    34925: \"lzma\",\n    50000: \"zstd\",\n    50001: \"webp\",\n}\n\nCOMPRESSION_INFO_REV = {v: k for k, v in COMPRESSION_INFO.items()}\n\nOPEN_INFO = {\n    # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample,\n    #  ExtraSamples) => mode, rawmode\n    (II, 0, (1,), 1, (1,), ()): (\"1\", \"1;I\"),\n    (MM, 0, (1,), 1, (1,), ()): (\"1\", \"1;I\"),\n    (II, 0, (1,), 2, (1,), ()): (\"1\", \"1;IR\"),\n    (MM, 0, (1,), 2, (1,), ()): (\"1\", \"1;IR\"),\n    (II, 1, (1,), 1, (1,), ()): (\"1\", \"1\"),\n    (MM, 1, (1,), 1, (1,), ()): (\"1\", \"1\"),\n    (II, 1, (1,), 2, (1,), ()): (\"1\", \"1;R\"),\n    (MM, 1, (1,), 2, (1,), ()): (\"1\", \"1;R\"),\n    (II, 0, (1,), 1, (2,), ()): (\"L\", \"L;2I\"),\n    (MM, 0, (1,), 1, (2,), ()): (\"L\", \"L;2I\"),\n    (II, 0, (1,), 2, (2,), ()): (\"L\", \"L;2IR\"),\n    (MM, 0, (1,), 2, (2,), ()): (\"L\", \"L;2IR\"),\n    (II, 1, (1,), 1, (2,), ()): (\"L\", \"L;2\"),\n    (MM, 1, (1,), 1, (2,), ()): (\"L\", \"L;2\"),\n    (II, 1, (1,), 2, (2,), ()): (\"L\", \"L;2R\"),\n    (MM, 1, (1,), 2, (2,), ()): (\"L\", \"L;2R\"),\n    (II, 0, (1,), 1, (4,), ()): (\"L\", \"L;4I\"),\n    (MM, 0, (1,), 1, (4,), ()): (\"L\", \"L;4I\"),\n    (II, 0, (1,), 2, (4,), ()): (\"L\", \"L;4IR\"),\n    (MM, 0, (1,), 2, (4,), ()): (\"L\", \"L;4IR\"),\n    (II, 1, (1,), 1, (4,), ()): (\"L\", \"L;4\"),\n    (MM, 1, (1,), 1, (4,), ()): (\"L\", \"L;4\"),\n    (II, 1, (1,), 2, (4,), ()): (\"L\", \"L;4R\"),\n    (MM, 1, (1,), 2, (4,), ()): (\"L\", \"L;4R\"),\n    (II, 0, (1,), 1, (8,), ()): (\"L\", \"L;I\"),\n    (MM, 0, (1,), 1, (8,), ()): (\"L\", \"L;I\"),\n    (II, 0, (1,), 2, (8,), ()): (\"L\", \"L;IR\"),\n    (MM, 0, (1,), 2, (8,), ()): (\"L\", \"L;IR\"),\n    (II, 1, (1,), 1, (8,), ()): (\"L\", \"L\"),\n    (MM, 1, (1,), 1, (8,), ()): (\"L\", \"L\"),\n    (II, 1, (1,), 2, (8,), ()): (\"L\", \"L;R\"),\n    (MM, 1, (1,), 2, (8,), ()): (\"L\", \"L;R\"),\n    (II, 1, (1,), 1, (12,), ()): (\"I;16\", \"I;12\"),\n    (II, 1, (1,), 1, (16,), ()): (\"I;16\", \"I;16\"),\n    (MM, 1, (1,), 1, (16,), ()): (\"I;16B\", \"I;16B\"),\n    (II, 1, (2,), 1, (16,), ()): (\"I\", \"I;16S\"),\n    (MM, 1, (2,), 1, (16,), ()): (\"I\", \"I;16BS\"),\n    (II, 0, (3,), 1, (32,), ()): (\"F\", \"F;32F\"),\n    (MM, 0, (3,), 1, (32,), ()): (\"F\", \"F;32BF\"),\n    (II, 1, (1,), 1, (32,), ()): (\"I\", \"I;32N\"),\n    (II, 1, (2,), 1, (32,), ()): (\"I\", \"I;32S\"),\n    (MM, 1, (2,), 1, (32,), ()): (\"I\", \"I;32BS\"),\n    (II, 1, (3,), 1, (32,), ()): (\"F\", \"F;32F\"),\n    (MM, 1, (3,), 1, (32,), ()): (\"F\", \"F;32BF\"),\n    (II, 1, (1,), 1, (8, 8), (2,)): (\"LA\", \"LA\"),\n    (MM, 1, (1,), 1, (8, 8), (2,)): (\"LA\", \"LA\"),\n    (II, 2, (1,), 1, (8, 8, 8), ()): (\"RGB\", \"RGB\"),\n    (MM, 2, (1,), 1, (8, 8, 8), ()): (\"RGB\", \"RGB\"),\n    (II, 2, (1,), 2, (8, 8, 8), ()): (\"RGB\", \"RGB;R\"),\n    (MM, 2, (1,), 2, (8, 8, 8), ()): (\"RGB\", \"RGB;R\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8), ()): (\"RGBA\", \"RGBA\"),  # missing ExtraSamples\n    (MM, 2, (1,), 1, (8, 8, 8, 8), ()): (\"RGBA\", \"RGBA\"),  # missing ExtraSamples\n    (II, 2, (1,), 1, (8, 8, 8, 8), (0,)): (\"RGBX\", \"RGBX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8), (0,)): (\"RGBX\", \"RGBX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): (\"RGBX\", \"RGBXX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): (\"RGBX\", \"RGBXX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): (\"RGBX\", \"RGBXXX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): (\"RGBX\", \"RGBXXX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8), (1,)): (\"RGBA\", \"RGBa\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8), (1,)): (\"RGBA\", \"RGBa\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): (\"RGBA\", \"RGBaX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): (\"RGBA\", \"RGBaX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): (\"RGBA\", \"RGBaXX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): (\"RGBA\", \"RGBaXX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8), (2,)): (\"RGBA\", \"RGBA\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8), (2,)): (\"RGBA\", \"RGBA\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): (\"RGBA\", \"RGBAX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): (\"RGBA\", \"RGBAX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): (\"RGBA\", \"RGBAXX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): (\"RGBA\", \"RGBAXX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8), (999,)): (\"RGBA\", \"RGBA\"),  # Corel Draw 10\n    (MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): (\"RGBA\", \"RGBA\"),  # Corel Draw 10\n    (II, 2, (1,), 1, (16, 16, 16), ()): (\"RGB\", \"RGB;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16), ()): (\"RGB\", \"RGB;16B\"),\n    (II, 2, (1,), 1, (16, 16, 16, 16), ()): (\"RGBA\", \"RGBA;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16, 16), ()): (\"RGBA\", \"RGBA;16B\"),\n    (II, 2, (1,), 1, (16, 16, 16, 16), (0,)): (\"RGBX\", \"RGBX;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16, 16), (0,)): (\"RGBX\", \"RGBX;16B\"),\n    (II, 2, (1,), 1, (16, 16, 16, 16), (1,)): (\"RGBA\", \"RGBa;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16, 16), (1,)): (\"RGBA\", \"RGBa;16B\"),\n    (II, 2, (1,), 1, (16, 16, 16, 16), (2,)): (\"RGBA\", \"RGBA;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16, 16), (2,)): (\"RGBA\", \"RGBA;16B\"),\n    (II, 3, (1,), 1, (1,), ()): (\"P\", \"P;1\"),\n    (MM, 3, (1,), 1, (1,), ()): (\"P\", \"P;1\"),\n    (II, 3, (1,), 2, (1,), ()): (\"P\", \"P;1R\"),\n    (MM, 3, (1,), 2, (1,), ()): (\"P\", \"P;1R\"),\n    (II, 3, (1,), 1, (2,), ()): (\"P\", \"P;2\"),\n    (MM, 3, (1,), 1, (2,), ()): (\"P\", \"P;2\"),\n    (II, 3, (1,), 2, (2,), ()): (\"P\", \"P;2R\"),\n    (MM, 3, (1,), 2, (2,), ()): (\"P\", \"P;2R\"),\n    (II, 3, (1,), 1, (4,), ()): (\"P\", \"P;4\"),\n    (MM, 3, (1,), 1, (4,), ()): (\"P\", \"P;4\"),\n    (II, 3, (1,), 2, (4,), ()): (\"P\", \"P;4R\"),\n    (MM, 3, (1,), 2, (4,), ()): (\"P\", \"P;4R\"),\n    (II, 3, (1,), 1, (8,), ()): (\"P\", \"P\"),\n    (MM, 3, (1,), 1, (8,), ()): (\"P\", \"P\"),\n    (II, 3, (1,), 1, (8, 8), (2,)): (\"PA\", \"PA\"),\n    (MM, 3, (1,), 1, (8, 8), (2,)): (\"PA\", \"PA\"),\n    (II, 3, (1,), 2, (8,), ()): (\"P\", \"P;R\"),\n    (MM, 3, (1,), 2, (8,), ()): (\"P\", \"P;R\"),\n    (II, 5, (1,), 1, (8, 8, 8, 8), ()): (\"CMYK\", \"CMYK\"),\n    (MM, 5, (1,), 1, (8, 8, 8, 8), ()): (\"CMYK\", \"CMYK\"),\n    (II, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): (\"CMYK\", \"CMYKX\"),\n    (MM, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): (\"CMYK\", \"CMYKX\"),\n    (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): (\"CMYK\", \"CMYKXX\"),\n    (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): (\"CMYK\", \"CMYKXX\"),\n    (II, 5, (1,), 1, (16, 16, 16, 16), ()): (\"CMYK\", \"CMYK;16L\"),\n    # JPEG compressed images handled by LibTiff and auto-converted to RGBX\n    # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel\n    (II, 6, (1,), 1, (8, 8, 8), ()): (\"RGB\", \"RGBX\"),\n    (MM, 6, (1,), 1, (8, 8, 8), ()): (\"RGB\", \"RGBX\"),\n    (II, 8, (1,), 1, (8, 8, 8), ()): (\"LAB\", \"LAB\"),\n    (MM, 8, (1,), 1, (8, 8, 8), ()): (\"LAB\", \"LAB\"),\n}\n\nPREFIXES = [\n    b\"MM\\x00\\x2A\",  # Valid TIFF header with big-endian byte order\n    b\"II\\x2A\\x00\",  # Valid TIFF header with little-endian byte order\n    b\"MM\\x2A\\x00\",  # Invalid TIFF header, assume big-endian\n    b\"II\\x00\\x2A\",  # Invalid TIFF header, assume little-endian\n]\n\n\ndef _accept(prefix):\n    return prefix[:4] in PREFIXES\n\n\ndef _limit_rational(val, max_val):\n    inv = abs(val) > 1\n    n_d = IFDRational(1 / val if inv else val).limit_rational(max_val)\n    return n_d[::-1] if inv else n_d\n\n\ndef _limit_signed_rational(val, max_val, min_val):\n    frac = Fraction(val)\n    n_d = frac.numerator, frac.denominator\n\n    if min(n_d) < min_val:\n        n_d = _limit_rational(val, abs(min_val))\n\n    if max(n_d) > max_val:\n        val = Fraction(*n_d)\n        n_d = _limit_rational(val, max_val)\n\n    return n_d\n\n\n##\n# Wrapper for TIFF IFDs.\n\n_load_dispatch = {}\n_write_dispatch = {}\n\n\nclass IFDRational(Rational):\n    \"\"\"Implements a rational class where 0/0 is a legal value to match\n    the in the wild use of exif rationals.\n\n    e.g., DigitalZoomRatio - 0.00/0.00  indicates that no digital zoom was used\n    \"\"\"\n\n    \"\"\" If the denominator is 0, store this as a float('nan'), otherwise store\n    as a fractions.Fraction(). Delegate as appropriate\n\n    \"\"\"\n\n    __slots__ = (\"_numerator\", \"_denominator\", \"_val\")\n\n    def __init__(self, value, denominator=1):\n        \"\"\"\n        :param value: either an integer numerator, a\n        float/rational/other number, or an IFDRational\n        :param denominator: Optional integer denominator\n        \"\"\"\n        if isinstance(value, IFDRational):\n            self._numerator = value.numerator\n            self._denominator = value.denominator\n            self._val = value._val\n            return\n\n        if isinstance(value, Fraction):\n            self._numerator = value.numerator\n            self._denominator = value.denominator\n        else:\n            self._numerator = value\n            self._denominator = denominator\n\n        if denominator == 0:\n            self._val = float(\"nan\")\n        elif denominator == 1:\n            self._val = Fraction(value)\n        else:\n            self._val = Fraction(value, denominator)\n\n    @property\n    def numerator(a):\n        return a._numerator\n\n    @property\n    def denominator(a):\n        return a._denominator\n\n    def limit_rational(self, max_denominator):\n        \"\"\"\n\n        :param max_denominator: Integer, the maximum denominator value\n        :returns: Tuple of (numerator, denominator)\n        \"\"\"\n\n        if self.denominator == 0:\n            return (self.numerator, self.denominator)\n\n        f = self._val.limit_denominator(max_denominator)\n        return (f.numerator, f.denominator)\n\n    def __repr__(self):\n        return str(float(self._val))\n\n    def __hash__(self):\n        return self._val.__hash__()\n\n    def __eq__(self, other):\n        val = self._val\n        if isinstance(other, IFDRational):\n            other = other._val\n        if isinstance(other, float):\n            val = float(val)\n        return val == other\n\n    def __getstate__(self):\n        return [self._val, self._numerator, self._denominator]\n\n    def __setstate__(self, state):\n        IFDRational.__init__(self, 0)\n        _val, _numerator, _denominator = state\n        self._val = _val\n        self._numerator = _numerator\n        self._denominator = _denominator\n\n    def _delegate(op):\n        def delegate(self, *args):\n            return getattr(self._val, op)(*args)\n\n        return delegate\n\n    \"\"\" a = ['add','radd', 'sub', 'rsub', 'mul', 'rmul',\n             'truediv', 'rtruediv', 'floordiv', 'rfloordiv',\n             'mod','rmod', 'pow','rpow', 'pos', 'neg',\n             'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'bool',\n             'ceil', 'floor', 'round']\n        print(\"\\n\".join(\"__%s__ = _delegate('__%s__')\" % (s,s) for s in a))\n        \"\"\"\n\n    __add__ = _delegate(\"__add__\")\n    __radd__ = _delegate(\"__radd__\")\n    __sub__ = _delegate(\"__sub__\")\n    __rsub__ = _delegate(\"__rsub__\")\n    __mul__ = _delegate(\"__mul__\")\n    __rmul__ = _delegate(\"__rmul__\")\n    __truediv__ = _delegate(\"__truediv__\")\n    __rtruediv__ = _delegate(\"__rtruediv__\")\n    __floordiv__ = _delegate(\"__floordiv__\")\n    __rfloordiv__ = _delegate(\"__rfloordiv__\")\n    __mod__ = _delegate(\"__mod__\")\n    __rmod__ = _delegate(\"__rmod__\")\n    __pow__ = _delegate(\"__pow__\")\n    __rpow__ = _delegate(\"__rpow__\")\n    __pos__ = _delegate(\"__pos__\")\n    __neg__ = _delegate(\"__neg__\")\n    __abs__ = _delegate(\"__abs__\")\n    __trunc__ = _delegate(\"__trunc__\")\n    __lt__ = _delegate(\"__lt__\")\n    __gt__ = _delegate(\"__gt__\")\n    __le__ = _delegate(\"__le__\")\n    __ge__ = _delegate(\"__ge__\")\n    __bool__ = _delegate(\"__bool__\")\n    __ceil__ = _delegate(\"__ceil__\")\n    __floor__ = _delegate(\"__floor__\")\n    __round__ = _delegate(\"__round__\")\n\n\nclass ImageFileDirectory_v2(MutableMapping):\n    \"\"\"This class represents a TIFF tag directory.  To speed things up, we\n    don't decode tags unless they're asked for.\n\n    Exposes a dictionary interface of the tags in the directory::\n\n        ifd = ImageFileDirectory_v2()\n        ifd[key] = 'Some Data'\n        ifd.tagtype[key] = TiffTags.ASCII\n        print(ifd[key])\n        'Some Data'\n\n    Individual values are returned as the strings or numbers, sequences are\n    returned as tuples of the values.\n\n    The tiff metadata type of each item is stored in a dictionary of\n    tag types in\n    :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types\n    are read from a tiff file, guessed from the type added, or added\n    manually.\n\n    Data Structures:\n\n        * self.tagtype = {}\n\n          * Key: numerical tiff tag number\n          * Value: integer corresponding to the data type from\n                   ~PIL.TiffTags.TYPES`\n\n    .. versionadded:: 3.0.0\n    \"\"\"\n\n    \"\"\"\n    Documentation:\n\n        'internal' data structures:\n        * self._tags_v2 = {} Key: numerical tiff tag number\n                             Value: decoded data, as tuple for multiple values\n        * self._tagdata = {} Key: numerical tiff tag number\n                             Value: undecoded byte string from file\n        * self._tags_v1 = {} Key: numerical tiff tag number\n                             Value: decoded data in the v1 format\n\n    Tags will be found in the private attributes self._tagdata, and in\n    self._tags_v2 once decoded.\n\n    self.legacy_api is a value for internal use, and shouldn't be\n    changed from outside code. In cooperation with the\n    ImageFileDirectory_v1 class, if legacy_api is true, then decoded\n    tags will be populated into both _tags_v1 and _tags_v2. _tags_v2\n    will be used if this IFD is used in the TIFF save routine. Tags\n    should be read from _tags_v1 if legacy_api == true.\n\n    \"\"\"\n\n    def __init__(self, ifh=b\"II\\052\\0\\0\\0\\0\\0\", prefix=None, group=None):\n        \"\"\"Initialize an ImageFileDirectory.\n\n        To construct an ImageFileDirectory from a real file, pass the 8-byte\n        magic header to the constructor.  To only set the endianness, pass it\n        as the 'prefix' keyword argument.\n\n        :param ifh: One of the accepted magic headers (cf. PREFIXES); also sets\n              endianness.\n        :param prefix: Override the endianness of the file.\n        \"\"\"\n        if ifh[:4] not in PREFIXES:\n            raise SyntaxError(f\"not a TIFF file (header {repr(ifh)} not valid)\")\n        self._prefix = prefix if prefix is not None else ifh[:2]\n        if self._prefix == MM:\n            self._endian = \">\"\n        elif self._prefix == II:\n            self._endian = \"<\"\n        else:\n            raise SyntaxError(\"not a TIFF IFD\")\n        self.group = group\n        self.tagtype = {}\n        \"\"\" Dictionary of tag types \"\"\"\n        self.reset()\n        (self.next,) = self._unpack(\"L\", ifh[4:])\n        self._legacy_api = False\n\n    prefix = property(lambda self: self._prefix)\n    offset = property(lambda self: self._offset)\n    legacy_api = property(lambda self: self._legacy_api)\n\n    @legacy_api.setter\n    def legacy_api(self, value):\n        raise Exception(\"Not allowing setting of legacy api\")\n\n    def reset(self):\n        self._tags_v1 = {}  # will remain empty if legacy_api is false\n        self._tags_v2 = {}  # main tag storage\n        self._tagdata = {}\n        self.tagtype = {}  # added 2008-06-05 by Florian Hoech\n        self._next = None\n        self._offset = None\n\n    def __str__(self):\n        return str(dict(self))\n\n    def named(self):\n        \"\"\"\n        :returns: dict of name|key: value\n\n        Returns the complete tag dictionary, with named tags where possible.\n        \"\"\"\n        return {\n            TiffTags.lookup(code, self.group).name: value\n            for code, value in self.items()\n        }\n\n    def __len__(self):\n        return len(set(self._tagdata) | set(self._tags_v2))\n\n    def __getitem__(self, tag):\n        if tag not in self._tags_v2:  # unpack on the fly\n            data = self._tagdata[tag]\n            typ = self.tagtype[tag]\n            size, handler = self._load_dispatch[typ]\n            self[tag] = handler(self, data, self.legacy_api)  # check type\n        val = self._tags_v2[tag]\n        if self.legacy_api and not isinstance(val, (tuple, bytes)):\n            val = (val,)\n        return val\n\n    def __contains__(self, tag):\n        return tag in self._tags_v2 or tag in self._tagdata\n\n    def __setitem__(self, tag, value):\n        self._setitem(tag, value, self.legacy_api)\n\n    def _setitem(self, tag, value, legacy_api):\n        basetypes = (Number, bytes, str)\n\n        info = TiffTags.lookup(tag, self.group)\n        values = [value] if isinstance(value, basetypes) else value\n\n        if tag not in self.tagtype:\n            if info.type:\n                self.tagtype[tag] = info.type\n            else:\n                self.tagtype[tag] = TiffTags.UNDEFINED\n                if all(isinstance(v, IFDRational) for v in values):\n                    self.tagtype[tag] = (\n                        TiffTags.RATIONAL\n                        if all(v >= 0 for v in values)\n                        else TiffTags.SIGNED_RATIONAL\n                    )\n                elif all(isinstance(v, int) for v in values):\n                    if all(0 <= v < 2 ** 16 for v in values):\n                        self.tagtype[tag] = TiffTags.SHORT\n                    elif all(-(2 ** 15) < v < 2 ** 15 for v in values):\n                        self.tagtype[tag] = TiffTags.SIGNED_SHORT\n                    else:\n                        self.tagtype[tag] = (\n                            TiffTags.LONG\n                            if all(v >= 0 for v in values)\n                            else TiffTags.SIGNED_LONG\n                        )\n                elif all(isinstance(v, float) for v in values):\n                    self.tagtype[tag] = TiffTags.DOUBLE\n                elif all(isinstance(v, str) for v in values):\n                    self.tagtype[tag] = TiffTags.ASCII\n                elif all(isinstance(v, bytes) for v in values):\n                    self.tagtype[tag] = TiffTags.BYTE\n\n        if self.tagtype[tag] == TiffTags.UNDEFINED:\n            values = [\n                v.encode(\"ascii\", \"replace\") if isinstance(v, str) else v\n                for v in values\n            ]\n        elif self.tagtype[tag] == TiffTags.RATIONAL:\n            values = [float(v) if isinstance(v, int) else v for v in values]\n\n        is_ifd = self.tagtype[tag] == TiffTags.LONG and isinstance(values, dict)\n        if not is_ifd:\n            values = tuple(info.cvt_enum(value) for value in values)\n\n        dest = self._tags_v1 if legacy_api else self._tags_v2\n\n        # Three branches:\n        # Spec'd length == 1, Actual length 1, store as element\n        # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed.\n        # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple.\n        # Don't mess with the legacy api, since it's frozen.\n        if not is_ifd and (\n            (info.length == 1)\n            or self.tagtype[tag] == TiffTags.BYTE\n            or (info.length is None and len(values) == 1 and not legacy_api)\n        ):\n            # Don't mess with the legacy api, since it's frozen.\n            if legacy_api and self.tagtype[tag] in [\n                TiffTags.RATIONAL,\n                TiffTags.SIGNED_RATIONAL,\n            ]:  # rationals\n                values = (values,)\n            try:\n                (dest[tag],) = values\n            except ValueError:\n                # We've got a builtin tag with 1 expected entry\n                warnings.warn(\n                    f\"Metadata Warning, tag {tag} had too many entries: \"\n                    f\"{len(values)}, expected 1\"\n                )\n                dest[tag] = values[0]\n\n        else:\n            # Spec'd length > 1 or undefined\n            # Unspec'd, and length > 1\n            dest[tag] = values\n\n    def __delitem__(self, tag):\n        self._tags_v2.pop(tag, None)\n        self._tags_v1.pop(tag, None)\n        self._tagdata.pop(tag, None)\n\n    def __iter__(self):\n        return iter(set(self._tagdata) | set(self._tags_v2))\n\n    def _unpack(self, fmt, data):\n        return struct.unpack(self._endian + fmt, data)\n\n    def _pack(self, fmt, *values):\n        return struct.pack(self._endian + fmt, *values)\n\n    def _register_loader(idx, size):\n        def decorator(func):\n            from .TiffTags import TYPES\n\n            if func.__name__.startswith(\"load_\"):\n                TYPES[idx] = func.__name__[5:].replace(\"_\", \" \")\n            _load_dispatch[idx] = size, func  # noqa: F821\n            return func\n\n        return decorator\n\n    def _register_writer(idx):\n        def decorator(func):\n            _write_dispatch[idx] = func  # noqa: F821\n            return func\n\n        return decorator\n\n    def _register_basic(idx_fmt_name):\n        from .TiffTags import TYPES\n\n        idx, fmt, name = idx_fmt_name\n        TYPES[idx] = name\n        size = struct.calcsize(\"=\" + fmt)\n        _load_dispatch[idx] = (  # noqa: F821\n            size,\n            lambda self, data, legacy_api=True: (\n                self._unpack(\"{}{}\".format(len(data) // size, fmt), data)\n            ),\n        )\n        _write_dispatch[idx] = lambda self, *values: (  # noqa: F821\n            b\"\".join(self._pack(fmt, value) for value in values)\n        )\n\n    list(\n        map(\n            _register_basic,\n            [\n                (TiffTags.SHORT, \"H\", \"short\"),\n                (TiffTags.LONG, \"L\", \"long\"),\n                (TiffTags.SIGNED_BYTE, \"b\", \"signed byte\"),\n                (TiffTags.SIGNED_SHORT, \"h\", \"signed short\"),\n                (TiffTags.SIGNED_LONG, \"l\", \"signed long\"),\n                (TiffTags.FLOAT, \"f\", \"float\"),\n                (TiffTags.DOUBLE, \"d\", \"double\"),\n                (TiffTags.IFD, \"L\", \"long\"),\n            ],\n        )\n    )\n\n    @_register_loader(1, 1)  # Basic type, except for the legacy API.\n    def load_byte(self, data, legacy_api=True):\n        return data\n\n    @_register_writer(1)  # Basic type, except for the legacy API.\n    def write_byte(self, data):\n        return data\n\n    @_register_loader(2, 1)\n    def load_string(self, data, legacy_api=True):\n        if data.endswith(b\"\\0\"):\n            data = data[:-1]\n        return data.decode(\"latin-1\", \"replace\")\n\n    @_register_writer(2)\n    def write_string(self, value):\n        # remerge of https://github.com/python-pillow/Pillow/pull/1416\n        return b\"\" + value.encode(\"ascii\", \"replace\") + b\"\\0\"\n\n    @_register_loader(5, 8)\n    def load_rational(self, data, legacy_api=True):\n        vals = self._unpack(\"{}L\".format(len(data) // 4), data)\n\n        def combine(a, b):\n            return (a, b) if legacy_api else IFDRational(a, b)\n\n        return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))\n\n    @_register_writer(5)\n    def write_rational(self, *values):\n        return b\"\".join(\n            self._pack(\"2L\", *_limit_rational(frac, 2 ** 32 - 1)) for frac in values\n        )\n\n    @_register_loader(7, 1)\n    def load_undefined(self, data, legacy_api=True):\n        return data\n\n    @_register_writer(7)\n    def write_undefined(self, value):\n        return value\n\n    @_register_loader(10, 8)\n    def load_signed_rational(self, data, legacy_api=True):\n        vals = self._unpack(\"{}l\".format(len(data) // 4), data)\n\n        def combine(a, b):\n            return (a, b) if legacy_api else IFDRational(a, b)\n\n        return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))\n\n    @_register_writer(10)\n    def write_signed_rational(self, *values):\n        return b\"\".join(\n            self._pack(\"2l\", *_limit_signed_rational(frac, 2 ** 31 - 1, -(2 ** 31)))\n            for frac in values\n        )\n\n    def _ensure_read(self, fp, size):\n        ret = fp.read(size)\n        if len(ret) != size:\n            raise OSError(\n                \"Corrupt EXIF data.  \"\n                f\"Expecting to read {size} bytes but only got {len(ret)}. \"\n            )\n        return ret\n\n    def load(self, fp):\n\n        self.reset()\n        self._offset = fp.tell()\n\n        try:\n            for i in range(self._unpack(\"H\", self._ensure_read(fp, 2))[0]):\n                tag, typ, count, data = self._unpack(\"HHL4s\", self._ensure_read(fp, 12))\n\n                tagname = TiffTags.lookup(tag, self.group).name\n                typname = TYPES.get(typ, \"unknown\")\n                msg = f\"tag: {tagname} ({tag}) - type: {typname} ({typ})\"\n\n                try:\n                    unit_size, handler = self._load_dispatch[typ]\n                except KeyError:\n                    logger.debug(msg + f\" - unsupported type {typ}\")\n                    continue  # ignore unsupported type\n                size = count * unit_size\n                if size > 4:\n                    here = fp.tell()\n                    (offset,) = self._unpack(\"L\", data)\n                    msg += f\" Tag Location: {here} - Data Location: {offset}\"\n                    fp.seek(offset)\n                    data = ImageFile._safe_read(fp, size)\n                    fp.seek(here)\n                else:\n                    data = data[:size]\n\n                if len(data) != size:\n                    warnings.warn(\n                        \"Possibly corrupt EXIF data.  \"\n                        f\"Expecting to read {size} bytes but only got {len(data)}.\"\n                        f\" Skipping tag {tag}\"\n                    )\n                    logger.debug(msg)\n                    continue\n\n                if not data:\n                    logger.debug(msg)\n                    continue\n\n                self._tagdata[tag] = data\n                self.tagtype[tag] = typ\n\n                msg += \" - value: \" + (\n                    \"<table: %d bytes>\" % size if size > 32 else repr(data)\n                )\n                logger.debug(msg)\n\n            (self.next,) = self._unpack(\"L\", self._ensure_read(fp, 4))\n        except OSError as msg:\n            warnings.warn(str(msg))\n            return\n\n    def tobytes(self, offset=0):\n        # FIXME What about tagdata?\n        result = self._pack(\"H\", len(self._tags_v2))\n\n        entries = []\n        offset = offset + len(result) + len(self._tags_v2) * 12 + 4\n        stripoffsets = None\n\n        # pass 1: convert tags to binary format\n        # always write tags in ascending order\n        for tag, value in sorted(self._tags_v2.items()):\n            if tag == STRIPOFFSETS:\n                stripoffsets = len(entries)\n            typ = self.tagtype.get(tag)\n            logger.debug(f\"Tag {tag}, Type: {typ}, Value: {repr(value)}\")\n            is_ifd = typ == TiffTags.LONG and isinstance(value, dict)\n            if is_ifd:\n                if self._endian == \"<\":\n                    ifh = b\"II\\x2A\\x00\\x08\\x00\\x00\\x00\"\n                else:\n                    ifh = b\"MM\\x00\\x2A\\x00\\x00\\x00\\x08\"\n                ifd = ImageFileDirectory_v2(ifh, group=tag)\n                values = self._tags_v2[tag]\n                for ifd_tag, ifd_value in values.items():\n                    ifd[ifd_tag] = ifd_value\n                data = ifd.tobytes(offset)\n            else:\n                values = value if isinstance(value, tuple) else (value,)\n                data = self._write_dispatch[typ](self, *values)\n\n            tagname = TiffTags.lookup(tag, self.group).name\n            typname = \"ifd\" if is_ifd else TYPES.get(typ, \"unknown\")\n            msg = f\"save: {tagname} ({tag}) - type: {typname} ({typ})\"\n            msg += \" - value: \" + (\n                \"<table: %d bytes>\" % len(data) if len(data) >= 16 else str(values)\n            )\n            logger.debug(msg)\n\n            # count is sum of lengths for string and arbitrary data\n            if is_ifd:\n                count = 1\n            elif typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]:\n                count = len(data)\n            else:\n                count = len(values)\n            # figure out if data fits into the entry\n            if len(data) <= 4:\n                entries.append((tag, typ, count, data.ljust(4, b\"\\0\"), b\"\"))\n            else:\n                entries.append((tag, typ, count, self._pack(\"L\", offset), data))\n                offset += (len(data) + 1) // 2 * 2  # pad to word\n\n        # update strip offset data to point beyond auxiliary data\n        if stripoffsets is not None:\n            tag, typ, count, value, data = entries[stripoffsets]\n            if data:\n                raise NotImplementedError(\"multistrip support not yet implemented\")\n            value = self._pack(\"L\", self._unpack(\"L\", value)[0] + offset)\n            entries[stripoffsets] = tag, typ, count, value, data\n\n        # pass 2: write entries to file\n        for tag, typ, count, value, data in entries:\n            logger.debug(f\"{tag} {typ} {count} {repr(value)} {repr(data)}\")\n            result += self._pack(\"HHL4s\", tag, typ, count, value)\n\n        # -- overwrite here for multi-page --\n        result += b\"\\0\\0\\0\\0\"  # end of entries\n\n        # pass 3: write auxiliary data to file\n        for tag, typ, count, value, data in entries:\n            result += data\n            if len(data) & 1:\n                result += b\"\\0\"\n\n        return result\n\n    def save(self, fp):\n\n        if fp.tell() == 0:  # skip TIFF header on subsequent pages\n            # tiff header -- PIL always starts the first IFD at offset 8\n            fp.write(self._prefix + self._pack(\"HL\", 42, 8))\n\n        offset = fp.tell()\n        result = self.tobytes(offset)\n        fp.write(result)\n        return offset + len(result)\n\n\nImageFileDirectory_v2._load_dispatch = _load_dispatch\nImageFileDirectory_v2._write_dispatch = _write_dispatch\nfor idx, name in TYPES.items():\n    name = name.replace(\" \", \"_\")\n    setattr(ImageFileDirectory_v2, \"load_\" + name, _load_dispatch[idx][1])\n    setattr(ImageFileDirectory_v2, \"write_\" + name, _write_dispatch[idx])\ndel _load_dispatch, _write_dispatch, idx, name\n\n\n# Legacy ImageFileDirectory support.\nclass ImageFileDirectory_v1(ImageFileDirectory_v2):\n    \"\"\"This class represents the **legacy** interface to a TIFF tag directory.\n\n    Exposes a dictionary interface of the tags in the directory::\n\n        ifd = ImageFileDirectory_v1()\n        ifd[key] = 'Some Data'\n        ifd.tagtype[key] = TiffTags.ASCII\n        print(ifd[key])\n        ('Some Data',)\n\n    Also contains a dictionary of tag types as read from the tiff image file,\n    :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`.\n\n    Values are returned as a tuple.\n\n    ..  deprecated:: 3.0.0\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._legacy_api = True\n\n    tags = property(lambda self: self._tags_v1)\n    tagdata = property(lambda self: self._tagdata)\n\n    # defined in ImageFileDirectory_v2\n    tagtype: dict\n    \"\"\"Dictionary of tag types\"\"\"\n\n    @classmethod\n    def from_v2(cls, original):\n        \"\"\"Returns an\n        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`\n        instance with the same data as is contained in the original\n        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`\n        instance.\n\n        :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`\n\n        \"\"\"\n\n        ifd = cls(prefix=original.prefix)\n        ifd._tagdata = original._tagdata\n        ifd.tagtype = original.tagtype\n        ifd.next = original.next  # an indicator for multipage tiffs\n        return ifd\n\n    def to_v2(self):\n        \"\"\"Returns an\n        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`\n        instance with the same data as is contained in the original\n        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`\n        instance.\n\n        :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`\n\n        \"\"\"\n\n        ifd = ImageFileDirectory_v2(prefix=self.prefix)\n        ifd._tagdata = dict(self._tagdata)\n        ifd.tagtype = dict(self.tagtype)\n        ifd._tags_v2 = dict(self._tags_v2)\n        return ifd\n\n    def __contains__(self, tag):\n        return tag in self._tags_v1 or tag in self._tagdata\n\n    def __len__(self):\n        return len(set(self._tagdata) | set(self._tags_v1))\n\n    def __iter__(self):\n        return iter(set(self._tagdata) | set(self._tags_v1))\n\n    def __setitem__(self, tag, value):\n        for legacy_api in (False, True):\n            self._setitem(tag, value, legacy_api)\n\n    def __getitem__(self, tag):\n        if tag not in self._tags_v1:  # unpack on the fly\n            data = self._tagdata[tag]\n            typ = self.tagtype[tag]\n            size, handler = self._load_dispatch[typ]\n            for legacy in (False, True):\n                self._setitem(tag, handler(self, data, legacy), legacy)\n        val = self._tags_v1[tag]\n        if not isinstance(val, (tuple, bytes)):\n            val = (val,)\n        return val\n\n\n# undone -- switch this pointer when IFD_LEGACY_API == False\nImageFileDirectory = ImageFileDirectory_v1\n\n\n##\n# Image plugin for TIFF files.\n\n\nclass TiffImageFile(ImageFile.ImageFile):\n\n    format = \"TIFF\"\n    format_description = \"Adobe TIFF\"\n    _close_exclusive_fp_after_loading = False\n\n    def __init__(self, fp=None, filename=None):\n        self.tag_v2 = None\n        \"\"\" Image file directory (tag dictionary) \"\"\"\n\n        self.tag = None\n        \"\"\" Legacy tag entries \"\"\"\n\n        super().__init__(fp, filename)\n\n    def _open(self):\n        \"\"\"Open the first image in a TIFF file\"\"\"\n\n        # Header\n        ifh = self.fp.read(8)\n\n        self.tag_v2 = ImageFileDirectory_v2(ifh)\n\n        # legacy IFD entries will be filled in later\n        self.ifd = None\n\n        # setup frame pointers\n        self.__first = self.__next = self.tag_v2.next\n        self.__frame = -1\n        self.__fp = self.fp\n        self._frame_pos = []\n        self._n_frames = None\n\n        logger.debug(\"*** TiffImageFile._open ***\")\n        logger.debug(f\"- __first: {self.__first}\")\n        logger.debug(f\"- ifh: {repr(ifh)}\")  # Use repr to avoid str(bytes)\n\n        # and load the first frame\n        self._seek(0)\n\n    @property\n    def n_frames(self):\n        if self._n_frames is None:\n            current = self.tell()\n            self._seek(len(self._frame_pos))\n            while self._n_frames is None:\n                self._seek(self.tell() + 1)\n            self.seek(current)\n        return self._n_frames\n\n    def seek(self, frame):\n        \"\"\"Select a given frame as current image\"\"\"\n        if not self._seek_check(frame):\n            return\n        self._seek(frame)\n        # Create a new core image object on second and\n        # subsequent frames in the image. Image may be\n        # different size/mode.\n        Image._decompression_bomb_check(self.size)\n        self.im = Image.core.new(self.mode, self.size)\n\n    def _seek(self, frame):\n        self.fp = self.__fp\n\n        # reset buffered io handle in case fp\n        # was passed to libtiff, invalidating the buffer\n        self.fp.tell()\n\n        while len(self._frame_pos) <= frame:\n            if not self.__next:\n                raise EOFError(\"no more images in TIFF file\")\n            logger.debug(\n                f\"Seeking to frame {frame}, on frame {self.__frame}, \"\n                f\"__next {self.__next}, location: {self.fp.tell()}\"\n            )\n            self.fp.seek(self.__next)\n            self._frame_pos.append(self.__next)\n            logger.debug(\"Loading tags, location: %s\" % self.fp.tell())\n            self.tag_v2.load(self.fp)\n            if self.tag_v2.next in self._frame_pos:\n                # This IFD has already been processed\n                # Declare this to be the end of the image\n                self.__next = 0\n            else:\n                self.__next = self.tag_v2.next\n            if self.__next == 0:\n                self._n_frames = frame + 1\n            if len(self._frame_pos) == 1:\n                self.is_animated = self.__next != 0\n            self.__frame += 1\n        self.fp.seek(self._frame_pos[frame])\n        self.tag_v2.load(self.fp)\n        # fill the legacy tag/ifd entries\n        self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)\n        self.__frame = frame\n        self._setup()\n\n    def tell(self):\n        \"\"\"Return the current frame number\"\"\"\n        return self.__frame\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n        return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {}\n\n    def load(self):\n        if self.tile and self.use_load_libtiff:\n            return self._load_libtiff()\n        return super().load()\n\n    def load_end(self):\n        if self._tile_orientation:\n            method = {\n                2: Image.FLIP_LEFT_RIGHT,\n                3: Image.ROTATE_180,\n                4: Image.FLIP_TOP_BOTTOM,\n                5: Image.TRANSPOSE,\n                6: Image.ROTATE_270,\n                7: Image.TRANSVERSE,\n                8: Image.ROTATE_90,\n            }.get(self._tile_orientation)\n            if method is not None:\n                self.im = self.im.transpose(method)\n                self._size = self.im.size\n\n        # allow closing if we're on the first frame, there's no next\n        # This is the ImageFile.load path only, libtiff specific below.\n        if not self.is_animated:\n            self._close_exclusive_fp_after_loading = True\n\n    def _load_libtiff(self):\n        \"\"\"Overload method triggered when we detect a compressed tiff\n        Calls out to libtiff\"\"\"\n\n        Image.Image.load(self)\n\n        self.load_prepare()\n\n        if not len(self.tile) == 1:\n            raise OSError(\"Not exactly one tile\")\n\n        # (self._compression, (extents tuple),\n        #   0, (rawmode, self._compression, fp))\n        extents = self.tile[0][1]\n        args = list(self.tile[0][3])\n\n        # To be nice on memory footprint, if there's a\n        # file descriptor, use that instead of reading\n        # into a string in python.\n        # libtiff closes the file descriptor, so pass in a dup.\n        try:\n            fp = hasattr(self.fp, \"fileno\") and os.dup(self.fp.fileno())\n            # flush the file descriptor, prevents error on pypy 2.4+\n            # should also eliminate the need for fp.tell\n            # in _seek\n            if hasattr(self.fp, \"flush\"):\n                self.fp.flush()\n        except OSError:\n            # io.BytesIO have a fileno, but returns an OSError if\n            # it doesn't use a file descriptor.\n            fp = False\n\n        if fp:\n            args[2] = fp\n\n        decoder = Image._getdecoder(\n            self.mode, \"libtiff\", tuple(args), self.decoderconfig\n        )\n        try:\n            decoder.setimage(self.im, extents)\n        except ValueError as e:\n            raise OSError(\"Couldn't set the image\") from e\n\n        close_self_fp = self._exclusive_fp and not self.is_animated\n        if hasattr(self.fp, \"getvalue\"):\n            # We've got a stringio like thing passed in. Yay for all in memory.\n            # The decoder needs the entire file in one shot, so there's not\n            # a lot we can do here other than give it the entire file.\n            # unless we could do something like get the address of the\n            # underlying string for stringio.\n            #\n            # Rearranging for supporting byteio items, since they have a fileno\n            # that returns an OSError if there's no underlying fp. Easier to\n            # deal with here by reordering.\n            logger.debug(\"have getvalue. just sending in a string from getvalue\")\n            n, err = decoder.decode(self.fp.getvalue())\n        elif fp:\n            # we've got a actual file on disk, pass in the fp.\n            logger.debug(\"have fileno, calling fileno version of the decoder.\")\n            if not close_self_fp:\n                self.fp.seek(0)\n            # 4 bytes, otherwise the trace might error out\n            n, err = decoder.decode(b\"fpfp\")\n        else:\n            # we have something else.\n            logger.debug(\"don't have fileno or getvalue. just reading\")\n            self.fp.seek(0)\n            # UNDONE -- so much for that buffer size thing.\n            n, err = decoder.decode(self.fp.read())\n\n        self.tile = []\n        self.readonly = 0\n\n        self.load_end()\n\n        # libtiff closed the fp in a, we need to close self.fp, if possible\n        if close_self_fp:\n            self.fp.close()\n            self.fp = None  # might be shared\n\n        if err < 0:\n            raise OSError(err)\n\n        return Image.Image.load(self)\n\n    def _setup(self):\n        \"\"\"Setup this image object based on current tags\"\"\"\n\n        if 0xBC01 in self.tag_v2:\n            raise OSError(\"Windows Media Photo files not yet supported\")\n\n        # extract relevant tags\n        self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)]\n        self._planar_configuration = self.tag_v2.get(PLANAR_CONFIGURATION, 1)\n\n        # photometric is a required tag, but not everyone is reading\n        # the specification\n        photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)\n\n        # old style jpeg compression images most certainly are YCbCr\n        if self._compression == \"tiff_jpeg\":\n            photo = 6\n\n        fillorder = self.tag_v2.get(FILLORDER, 1)\n\n        logger.debug(\"*** Summary ***\")\n        logger.debug(f\"- compression: {self._compression}\")\n        logger.debug(f\"- photometric_interpretation: {photo}\")\n        logger.debug(f\"- planar_configuration: {self._planar_configuration}\")\n        logger.debug(f\"- fill_order: {fillorder}\")\n        logger.debug(f\"- YCbCr subsampling: {self.tag.get(530)}\")\n\n        # size\n        xsize = int(self.tag_v2.get(IMAGEWIDTH))\n        ysize = int(self.tag_v2.get(IMAGELENGTH))\n        self._size = xsize, ysize\n\n        logger.debug(f\"- size: {self.size}\")\n\n        sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,))\n        if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1:\n            # SAMPLEFORMAT is properly per band, so an RGB image will\n            # be (1,1,1).  But, we don't support per band pixel types,\n            # and anything more than one band is a uint8. So, just\n            # take the first element. Revisit this if adding support\n            # for more exotic images.\n            sampleFormat = (1,)\n\n        bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,))\n        extra_tuple = self.tag_v2.get(EXTRASAMPLES, ())\n        if photo in (2, 6, 8):  # RGB, YCbCr, LAB\n            bps_count = 3\n        elif photo == 5:  # CMYK\n            bps_count = 4\n        else:\n            bps_count = 1\n        bps_count += len(extra_tuple)\n        # Some files have only one value in bps_tuple,\n        # while should have more. Fix it\n        if bps_count > len(bps_tuple) and len(bps_tuple) == 1:\n            bps_tuple = bps_tuple * bps_count\n\n        samplesPerPixel = self.tag_v2.get(\n            SAMPLESPERPIXEL,\n            3 if self._compression == \"tiff_jpeg\" and photo in (2, 6) else 1,\n        )\n        if len(bps_tuple) != samplesPerPixel:\n            raise SyntaxError(\"unknown data organization\")\n\n        # mode: check photometric interpretation and bits per pixel\n        key = (\n            self.tag_v2.prefix,\n            photo,\n            sampleFormat,\n            fillorder,\n            bps_tuple,\n            extra_tuple,\n        )\n        logger.debug(f\"format key: {key}\")\n        try:\n            self.mode, rawmode = OPEN_INFO[key]\n        except KeyError as e:\n            logger.debug(\"- unsupported format\")\n            raise SyntaxError(\"unknown pixel mode\") from e\n\n        logger.debug(f\"- raw mode: {rawmode}\")\n        logger.debug(f\"- pil mode: {self.mode}\")\n\n        self.info[\"compression\"] = self._compression\n\n        xres = self.tag_v2.get(X_RESOLUTION, 1)\n        yres = self.tag_v2.get(Y_RESOLUTION, 1)\n\n        if xres and yres:\n            resunit = self.tag_v2.get(RESOLUTION_UNIT)\n            if resunit == 2:  # dots per inch\n                self.info[\"dpi\"] = (xres, yres)\n            elif resunit == 3:  # dots per centimeter. convert to dpi\n                self.info[\"dpi\"] = (xres * 2.54, yres * 2.54)\n            elif resunit is None:  # used to default to 1, but now 2)\n                self.info[\"dpi\"] = (xres, yres)\n                # For backward compatibility,\n                # we also preserve the old behavior\n                self.info[\"resolution\"] = xres, yres\n            else:  # No absolute unit of measurement\n                self.info[\"resolution\"] = xres, yres\n\n        # build tile descriptors\n        x = y = layer = 0\n        self.tile = []\n        self.use_load_libtiff = READ_LIBTIFF or self._compression != \"raw\"\n        if self.use_load_libtiff:\n            # Decoder expects entire file as one tile.\n            # There's a buffer size limit in load (64k)\n            # so large g4 images will fail if we use that\n            # function.\n            #\n            # Setup the one tile for the whole image, then\n            # use the _load_libtiff function.\n\n            # libtiff handles the fillmode for us, so 1;IR should\n            # actually be 1;I. Including the R double reverses the\n            # bits, so stripes of the image are reversed.  See\n            # https://github.com/python-pillow/Pillow/issues/279\n            if fillorder == 2:\n                # Replace fillorder with fillorder=1\n                key = key[:3] + (1,) + key[4:]\n                logger.debug(f\"format key: {key}\")\n                # this should always work, since all the\n                # fillorder==2 modes have a corresponding\n                # fillorder=1 mode\n                self.mode, rawmode = OPEN_INFO[key]\n            # libtiff always returns the bytes in native order.\n            # we're expecting image byte order. So, if the rawmode\n            # contains I;16, we need to convert from native to image\n            # byte order.\n            if rawmode == \"I;16\":\n                rawmode = \"I;16N\"\n            if \";16B\" in rawmode:\n                rawmode = rawmode.replace(\";16B\", \";16N\")\n            if \";16L\" in rawmode:\n                rawmode = rawmode.replace(\";16L\", \";16N\")\n\n            # YCbCr images with new jpeg compression with pixels in one plane\n            # unpacked straight into RGB values\n            if (\n                photo == 6\n                and self._compression == \"jpeg\"\n                and self._planar_configuration == 1\n            ):\n                rawmode = \"RGB\"\n\n            # Offset in the tile tuple is 0, we go from 0,0 to\n            # w,h, and we only do this once -- eds\n            a = (rawmode, self._compression, False, self.tag_v2.offset)\n            self.tile.append((\"libtiff\", (0, 0, xsize, ysize), 0, a))\n\n        elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:\n            # striped image\n            if STRIPOFFSETS in self.tag_v2:\n                offsets = self.tag_v2[STRIPOFFSETS]\n                h = self.tag_v2.get(ROWSPERSTRIP, ysize)\n                w = self.size[0]\n            else:\n                # tiled image\n                offsets = self.tag_v2[TILEOFFSETS]\n                w = self.tag_v2.get(322)\n                h = self.tag_v2.get(323)\n\n            for offset in offsets:\n                if x + w > xsize:\n                    stride = w * sum(bps_tuple) / 8  # bytes per line\n                else:\n                    stride = 0\n\n                tile_rawmode = rawmode\n                if self._planar_configuration == 2:\n                    # each band on it's own layer\n                    tile_rawmode = rawmode[layer]\n                    # adjust stride width accordingly\n                    stride /= bps_count\n\n                a = (tile_rawmode, int(stride), 1)\n                self.tile.append(\n                    (\n                        self._compression,\n                        (x, y, min(x + w, xsize), min(y + h, ysize)),\n                        offset,\n                        a,\n                    )\n                )\n                x = x + w\n                if x >= self.size[0]:\n                    x, y = 0, y + h\n                    if y >= self.size[1]:\n                        x = y = 0\n                        layer += 1\n        else:\n            logger.debug(\"- unsupported data organization\")\n            raise SyntaxError(\"unknown data organization\")\n\n        # Fix up info.\n        if ICCPROFILE in self.tag_v2:\n            self.info[\"icc_profile\"] = self.tag_v2[ICCPROFILE]\n\n        # fixup palette descriptor\n\n        if self.mode in [\"P\", \"PA\"]:\n            palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]\n            self.palette = ImagePalette.raw(\"RGB;L\", b\"\".join(palette))\n\n        self._tile_orientation = self.tag_v2.get(0x0112)\n\n    def _close__fp(self):\n        try:\n            if self.__fp != self.fp:\n                self.__fp.close()\n        except AttributeError:\n            pass\n        finally:\n            self.__fp = None\n\n\n#\n# --------------------------------------------------------------------\n# Write TIFF files\n\n# little endian is default except for image modes with\n# explicit big endian byte-order\n\nSAVE_INFO = {\n    # mode => rawmode, byteorder, photometrics,\n    #           sampleformat, bitspersample, extra\n    \"1\": (\"1\", II, 1, 1, (1,), None),\n    \"L\": (\"L\", II, 1, 1, (8,), None),\n    \"LA\": (\"LA\", II, 1, 1, (8, 8), 2),\n    \"P\": (\"P\", II, 3, 1, (8,), None),\n    \"PA\": (\"PA\", II, 3, 1, (8, 8), 2),\n    \"I\": (\"I;32S\", II, 1, 2, (32,), None),\n    \"I;16\": (\"I;16\", II, 1, 1, (16,), None),\n    \"I;16S\": (\"I;16S\", II, 1, 2, (16,), None),\n    \"F\": (\"F;32F\", II, 1, 3, (32,), None),\n    \"RGB\": (\"RGB\", II, 2, 1, (8, 8, 8), None),\n    \"RGBX\": (\"RGBX\", II, 2, 1, (8, 8, 8, 8), 0),\n    \"RGBA\": (\"RGBA\", II, 2, 1, (8, 8, 8, 8), 2),\n    \"CMYK\": (\"CMYK\", II, 5, 1, (8, 8, 8, 8), None),\n    \"YCbCr\": (\"YCbCr\", II, 6, 1, (8, 8, 8), None),\n    \"LAB\": (\"LAB\", II, 8, 1, (8, 8, 8), None),\n    \"I;32BS\": (\"I;32BS\", MM, 1, 2, (32,), None),\n    \"I;16B\": (\"I;16B\", MM, 1, 1, (16,), None),\n    \"I;16BS\": (\"I;16BS\", MM, 1, 2, (16,), None),\n    \"F;32BF\": (\"F;32BF\", MM, 1, 3, (32,), None),\n}\n\n\ndef _save(im, fp, filename):\n\n    try:\n        rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]\n    except KeyError as e:\n        raise OSError(f\"cannot write mode {im.mode} as TIFF\") from e\n\n    ifd = ImageFileDirectory_v2(prefix=prefix)\n\n    compression = im.encoderinfo.get(\"compression\", im.info.get(\"compression\"))\n    if compression is None:\n        compression = \"raw\"\n    elif compression == \"tiff_jpeg\":\n        # OJPEG is obsolete, so use new-style JPEG compression instead\n        compression = \"jpeg\"\n    elif compression == \"tiff_deflate\":\n        compression = \"tiff_adobe_deflate\"\n\n    libtiff = WRITE_LIBTIFF or compression != \"raw\"\n\n    # required for color libtiff images\n    ifd[PLANAR_CONFIGURATION] = getattr(im, \"_planar_configuration\", 1)\n\n    ifd[IMAGEWIDTH] = im.size[0]\n    ifd[IMAGELENGTH] = im.size[1]\n\n    # write any arbitrary tags passed in as an ImageFileDirectory\n    info = im.encoderinfo.get(\"tiffinfo\", {})\n    logger.debug(\"Tiffinfo Keys: %s\" % list(info))\n    if isinstance(info, ImageFileDirectory_v1):\n        info = info.to_v2()\n    for key in info:\n        ifd[key] = info.get(key)\n        try:\n            ifd.tagtype[key] = info.tagtype[key]\n        except Exception:\n            pass  # might not be an IFD. Might not have populated type\n\n    # additions written by Greg Couch, gregc@cgl.ucsf.edu\n    # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com\n    if hasattr(im, \"tag_v2\"):\n        # preserve tags from original TIFF image file\n        for key in (\n            RESOLUTION_UNIT,\n            X_RESOLUTION,\n            Y_RESOLUTION,\n            IPTC_NAA_CHUNK,\n            PHOTOSHOP_CHUNK,\n            XMP,\n        ):\n            if key in im.tag_v2:\n                ifd[key] = im.tag_v2[key]\n                ifd.tagtype[key] = im.tag_v2.tagtype[key]\n\n    # preserve ICC profile (should also work when saving other formats\n    # which support profiles as TIFF) -- 2008-06-06 Florian Hoech\n    icc = im.encoderinfo.get(\"icc_profile\", im.info.get(\"icc_profile\"))\n    if icc:\n        ifd[ICCPROFILE] = icc\n\n    for key, name in [\n        (IMAGEDESCRIPTION, \"description\"),\n        (X_RESOLUTION, \"resolution\"),\n        (Y_RESOLUTION, \"resolution\"),\n        (X_RESOLUTION, \"x_resolution\"),\n        (Y_RESOLUTION, \"y_resolution\"),\n        (RESOLUTION_UNIT, \"resolution_unit\"),\n        (SOFTWARE, \"software\"),\n        (DATE_TIME, \"date_time\"),\n        (ARTIST, \"artist\"),\n        (COPYRIGHT, \"copyright\"),\n    ]:\n        if name in im.encoderinfo:\n            ifd[key] = im.encoderinfo[name]\n\n    dpi = im.encoderinfo.get(\"dpi\")\n    if dpi:\n        ifd[RESOLUTION_UNIT] = 2\n        ifd[X_RESOLUTION] = dpi[0]\n        ifd[Y_RESOLUTION] = dpi[1]\n\n    if bits != (1,):\n        ifd[BITSPERSAMPLE] = bits\n        if len(bits) != 1:\n            ifd[SAMPLESPERPIXEL] = len(bits)\n    if extra is not None:\n        ifd[EXTRASAMPLES] = extra\n    if format != 1:\n        ifd[SAMPLEFORMAT] = format\n\n    ifd[PHOTOMETRIC_INTERPRETATION] = photo\n\n    if im.mode in [\"P\", \"PA\"]:\n        lut = im.im.getpalette(\"RGB\", \"RGB;L\")\n        ifd[COLORMAP] = tuple(v * 256 for v in lut)\n    # data orientation\n    stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)\n    # aim for 64 KB strips when using libtiff writer\n    if libtiff:\n        rows_per_strip = min((2 ** 16 + stride - 1) // stride, im.size[1])\n    else:\n        rows_per_strip = im.size[1]\n    strip_byte_counts = stride * rows_per_strip\n    strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip\n    ifd[ROWSPERSTRIP] = rows_per_strip\n    if strip_byte_counts >= 2 ** 16:\n        ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG\n    ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (\n        stride * im.size[1] - strip_byte_counts * (strips_per_image - 1),\n    )\n    ifd[STRIPOFFSETS] = tuple(\n        range(0, strip_byte_counts * strips_per_image, strip_byte_counts)\n    )  # this is adjusted by IFD writer\n    # no compression by default:\n    ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)\n\n    if libtiff:\n        if \"quality\" in im.encoderinfo:\n            quality = im.encoderinfo[\"quality\"]\n            if not isinstance(quality, int) or quality < 0 or quality > 100:\n                raise ValueError(\"Invalid quality setting\")\n            if compression != \"jpeg\":\n                raise ValueError(\n                    \"quality setting only supported for 'jpeg' compression\"\n                )\n            ifd[JPEGQUALITY] = quality\n\n        logger.debug(\"Saving using libtiff encoder\")\n        logger.debug(\"Items: %s\" % sorted(ifd.items()))\n        _fp = 0\n        if hasattr(fp, \"fileno\"):\n            try:\n                fp.seek(0)\n                _fp = os.dup(fp.fileno())\n            except io.UnsupportedOperation:\n                pass\n\n        # optional types for non core tags\n        types = {}\n        # SAMPLEFORMAT is determined by the image format and should not be copied\n        # from legacy_ifd.\n        # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library\n        # based on the data in the strip.\n        # The other tags expect arrays with a certain length (fixed or depending on\n        # BITSPERSAMPLE, etc), passing arrays with a different length will result in\n        # segfaults. Block these tags until we add extra validation.\n        # SUBIFD may also cause a segfault.\n        blocklist = [\n            REFERENCEBLACKWHITE,\n            SAMPLEFORMAT,\n            STRIPBYTECOUNTS,\n            STRIPOFFSETS,\n            TRANSFERFUNCTION,\n            SUBIFD,\n        ]\n\n        atts = {}\n        # bits per sample is a single short in the tiff directory, not a list.\n        atts[BITSPERSAMPLE] = bits[0]\n        # Merge the ones that we have with (optional) more bits from\n        # the original file, e.g x,y resolution so that we can\n        # save(load('')) == original file.\n        legacy_ifd = {}\n        if hasattr(im, \"tag\"):\n            legacy_ifd = im.tag.to_v2()\n        for tag, value in itertools.chain(\n            ifd.items(), getattr(im, \"tag_v2\", {}).items(), legacy_ifd.items()\n        ):\n            # Libtiff can only process certain core items without adding\n            # them to the custom dictionary.\n            # Custom items are supported for int, float, unicode, string and byte\n            # values. Other types and tuples require a tagtype.\n            if tag not in TiffTags.LIBTIFF_CORE:\n                if not Image.core.libtiff_support_custom_tags:\n                    continue\n\n                if tag in ifd.tagtype:\n                    types[tag] = ifd.tagtype[tag]\n                elif not (isinstance(value, (int, float, str, bytes))):\n                    continue\n                else:\n                    type = TiffTags.lookup(tag).type\n                    if type:\n                        types[tag] = type\n            if tag not in atts and tag not in blocklist:\n                if isinstance(value, str):\n                    atts[tag] = value.encode(\"ascii\", \"replace\") + b\"\\0\"\n                elif isinstance(value, IFDRational):\n                    atts[tag] = float(value)\n                else:\n                    atts[tag] = value\n\n        logger.debug(\"Converted items: %s\" % sorted(atts.items()))\n\n        # libtiff always expects the bytes in native order.\n        # we're storing image byte order. So, if the rawmode\n        # contains I;16, we need to convert from native to image\n        # byte order.\n        if im.mode in (\"I;16B\", \"I;16\"):\n            rawmode = \"I;16N\"\n\n        # Pass tags as sorted list so that the tags are set in a fixed order.\n        # This is required by libtiff for some tags. For example, the JPEGQUALITY\n        # pseudo tag requires that the COMPRESS tag was already set.\n        tags = list(atts.items())\n        tags.sort()\n        a = (rawmode, compression, _fp, filename, tags, types)\n        e = Image._getencoder(im.mode, \"libtiff\", a, im.encoderconfig)\n        e.setimage(im.im, (0, 0) + im.size)\n        while True:\n            # undone, change to self.decodermaxblock:\n            l, s, d = e.encode(16 * 1024)\n            if not _fp:\n                fp.write(d)\n            if s:\n                break\n        if s < 0:\n            raise OSError(f\"encoder error {s} when writing image file\")\n\n    else:\n        offset = ifd.save(fp)\n\n        ImageFile._save(\n            im, fp, [(\"raw\", (0, 0) + im.size, offset, (rawmode, stride, 1))]\n        )\n\n    # -- helper for multi-page save --\n    if \"_debug_multipage\" in im.encoderinfo:\n        # just to access o32 and o16 (using correct byte order)\n        im._debug_multipage = ifd\n\n\nclass AppendingTiffWriter:\n    fieldSizes = [\n        0,  # None\n        1,  # byte\n        1,  # ascii\n        2,  # short\n        4,  # long\n        8,  # rational\n        1,  # sbyte\n        1,  # undefined\n        2,  # sshort\n        4,  # slong\n        8,  # srational\n        4,  # float\n        8,  # double\n    ]\n\n    #    StripOffsets = 273\n    #    FreeOffsets = 288\n    #    TileOffsets = 324\n    #    JPEGQTables = 519\n    #    JPEGDCTables = 520\n    #    JPEGACTables = 521\n    Tags = {273, 288, 324, 519, 520, 521}\n\n    def __init__(self, fn, new=False):\n        if hasattr(fn, \"read\"):\n            self.f = fn\n            self.close_fp = False\n        else:\n            self.name = fn\n            self.close_fp = True\n            try:\n                self.f = open(fn, \"w+b\" if new else \"r+b\")\n            except OSError:\n                self.f = open(fn, \"w+b\")\n        self.beginning = self.f.tell()\n        self.setup()\n\n    def setup(self):\n        # Reset everything.\n        self.f.seek(self.beginning, os.SEEK_SET)\n\n        self.whereToWriteNewIFDOffset = None\n        self.offsetOfNewPage = 0\n\n        self.IIMM = IIMM = self.f.read(4)\n        if not IIMM:\n            # empty file - first page\n            self.isFirst = True\n            return\n\n        self.isFirst = False\n        if IIMM == b\"II\\x2a\\x00\":\n            self.setEndian(\"<\")\n        elif IIMM == b\"MM\\x00\\x2a\":\n            self.setEndian(\">\")\n        else:\n            raise RuntimeError(\"Invalid TIFF file header\")\n\n        self.skipIFDs()\n        self.goToEnd()\n\n    def finalize(self):\n        if self.isFirst:\n            return\n\n        # fix offsets\n        self.f.seek(self.offsetOfNewPage)\n\n        IIMM = self.f.read(4)\n        if not IIMM:\n            # raise RuntimeError(\"nothing written into new page\")\n            # Make it easy to finish a frame without committing to a new one.\n            return\n\n        if IIMM != self.IIMM:\n            raise RuntimeError(\"IIMM of new page doesn't match IIMM of first page\")\n\n        IFDoffset = self.readLong()\n        IFDoffset += self.offsetOfNewPage\n        self.f.seek(self.whereToWriteNewIFDOffset)\n        self.writeLong(IFDoffset)\n        self.f.seek(IFDoffset)\n        self.fixIFD()\n\n    def newFrame(self):\n        # Call this to finish a frame.\n        self.finalize()\n        self.setup()\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        if self.close_fp:\n            self.close()\n        return False\n\n    def tell(self):\n        return self.f.tell() - self.offsetOfNewPage\n\n    def seek(self, offset, whence=io.SEEK_SET):\n        if whence == os.SEEK_SET:\n            offset += self.offsetOfNewPage\n\n        self.f.seek(offset, whence)\n        return self.tell()\n\n    def goToEnd(self):\n        self.f.seek(0, os.SEEK_END)\n        pos = self.f.tell()\n\n        # pad to 16 byte boundary\n        padBytes = 16 - pos % 16\n        if 0 < padBytes < 16:\n            self.f.write(bytes(padBytes))\n        self.offsetOfNewPage = self.f.tell()\n\n    def setEndian(self, endian):\n        self.endian = endian\n        self.longFmt = self.endian + \"L\"\n        self.shortFmt = self.endian + \"H\"\n        self.tagFormat = self.endian + \"HHL\"\n\n    def skipIFDs(self):\n        while True:\n            IFDoffset = self.readLong()\n            if IFDoffset == 0:\n                self.whereToWriteNewIFDOffset = self.f.tell() - 4\n                break\n\n            self.f.seek(IFDoffset)\n            numTags = self.readShort()\n            self.f.seek(numTags * 12, os.SEEK_CUR)\n\n    def write(self, data):\n        return self.f.write(data)\n\n    def readShort(self):\n        (value,) = struct.unpack(self.shortFmt, self.f.read(2))\n        return value\n\n    def readLong(self):\n        (value,) = struct.unpack(self.longFmt, self.f.read(4))\n        return value\n\n    def rewriteLastShortToLong(self, value):\n        self.f.seek(-2, os.SEEK_CUR)\n        bytesWritten = self.f.write(struct.pack(self.longFmt, value))\n        if bytesWritten is not None and bytesWritten != 4:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 4\")\n\n    def rewriteLastShort(self, value):\n        self.f.seek(-2, os.SEEK_CUR)\n        bytesWritten = self.f.write(struct.pack(self.shortFmt, value))\n        if bytesWritten is not None and bytesWritten != 2:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 2\")\n\n    def rewriteLastLong(self, value):\n        self.f.seek(-4, os.SEEK_CUR)\n        bytesWritten = self.f.write(struct.pack(self.longFmt, value))\n        if bytesWritten is not None and bytesWritten != 4:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 4\")\n\n    def writeShort(self, value):\n        bytesWritten = self.f.write(struct.pack(self.shortFmt, value))\n        if bytesWritten is not None and bytesWritten != 2:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 2\")\n\n    def writeLong(self, value):\n        bytesWritten = self.f.write(struct.pack(self.longFmt, value))\n        if bytesWritten is not None and bytesWritten != 4:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 4\")\n\n    def close(self):\n        self.finalize()\n        self.f.close()\n\n    def fixIFD(self):\n        numTags = self.readShort()\n\n        for i in range(numTags):\n            tag, fieldType, count = struct.unpack(self.tagFormat, self.f.read(8))\n\n            fieldSize = self.fieldSizes[fieldType]\n            totalSize = fieldSize * count\n            isLocal = totalSize <= 4\n            if not isLocal:\n                offset = self.readLong()\n                offset += self.offsetOfNewPage\n                self.rewriteLastLong(offset)\n\n            if tag in self.Tags:\n                curPos = self.f.tell()\n\n                if isLocal:\n                    self.fixOffsets(\n                        count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)\n                    )\n                    self.f.seek(curPos + 4)\n                else:\n                    self.f.seek(offset)\n                    self.fixOffsets(\n                        count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)\n                    )\n                    self.f.seek(curPos)\n\n                offset = curPos = None\n\n            elif isLocal:\n                # skip the locally stored value that is not an offset\n                self.f.seek(4, os.SEEK_CUR)\n\n    def fixOffsets(self, count, isShort=False, isLong=False):\n        if not isShort and not isLong:\n            raise RuntimeError(\"offset is neither short nor long\")\n\n        for i in range(count):\n            offset = self.readShort() if isShort else self.readLong()\n            offset += self.offsetOfNewPage\n            if isShort and offset >= 65536:\n                # offset is now too large - we must convert shorts to longs\n                if count != 1:\n                    raise RuntimeError(\"not implemented\")  # XXX TODO\n\n                # simple case - the offset is just one and therefore it is\n                # local (not referenced with another offset)\n                self.rewriteLastShortToLong(offset)\n                self.f.seek(-10, os.SEEK_CUR)\n                self.writeShort(TiffTags.LONG)  # rewrite the type to LONG\n                self.f.seek(8, os.SEEK_CUR)\n            elif isShort:\n                self.rewriteLastShort(offset)\n            else:\n                self.rewriteLastLong(offset)\n\n\ndef _save_all(im, fp, filename):\n    encoderinfo = im.encoderinfo.copy()\n    encoderconfig = im.encoderconfig\n    append_images = list(encoderinfo.get(\"append_images\", []))\n    if not hasattr(im, \"n_frames\") and not append_images:\n        return _save(im, fp, filename)\n\n    cur_idx = im.tell()\n    try:\n        with AppendingTiffWriter(fp) as tf:\n            for ims in [im] + append_images:\n                ims.encoderinfo = encoderinfo\n                ims.encoderconfig = encoderconfig\n                if not hasattr(ims, \"n_frames\"):\n                    nfr = 1\n                else:\n                    nfr = ims.n_frames\n\n                for idx in range(nfr):\n                    ims.seek(idx)\n                    ims.load()\n                    _save(ims, tf, filename)\n                    tf.newFrame()\n    finally:\n        im.seek(cur_idx)\n\n\n#\n# --------------------------------------------------------------------\n# Register\n\nImage.register_open(TiffImageFile.format, TiffImageFile, _accept)\nImage.register_save(TiffImageFile.format, _save)\nImage.register_save_all(TiffImageFile.format, _save_all)\n\nImage.register_extensions(TiffImageFile.format, [\".tif\", \".tiff\"])\n\nImage.register_mime(TiffImageFile.format, \"image/tiff\")\n",
            "new_file_content": "#\n# The Python Imaging Library.\n# $Id$\n#\n# TIFF file handling\n#\n# TIFF is a flexible, if somewhat aged, image file format originally\n# defined by Aldus.  Although TIFF supports a wide variety of pixel\n# layouts and compression methods, the name doesn't really stand for\n# \"thousands of incompatible file formats,\" it just feels that way.\n#\n# To read TIFF data from a stream, the stream must be seekable.  For\n# progressive decoding, make sure to use TIFF files where the tag\n# directory is placed first in the file.\n#\n# History:\n# 1995-09-01 fl   Created\n# 1996-05-04 fl   Handle JPEGTABLES tag\n# 1996-05-18 fl   Fixed COLORMAP support\n# 1997-01-05 fl   Fixed PREDICTOR support\n# 1997-08-27 fl   Added support for rational tags (from Perry Stoll)\n# 1998-01-10 fl   Fixed seek/tell (from Jan Blom)\n# 1998-07-15 fl   Use private names for internal variables\n# 1999-06-13 fl   Rewritten for PIL 1.0 (1.0)\n# 2000-10-11 fl   Additional fixes for Python 2.0 (1.1)\n# 2001-04-17 fl   Fixed rewind support (seek to frame 0) (1.2)\n# 2001-05-12 fl   Added write support for more tags (from Greg Couch) (1.3)\n# 2001-12-18 fl   Added workaround for broken Matrox library\n# 2002-01-18 fl   Don't mess up if photometric tag is missing (D. Alan Stewart)\n# 2003-05-19 fl   Check FILLORDER tag\n# 2003-09-26 fl   Added RGBa support\n# 2004-02-24 fl   Added DPI support; fixed rational write support\n# 2005-02-07 fl   Added workaround for broken Corel Draw 10 files\n# 2006-01-09 fl   Added support for float/double tags (from Russell Nelson)\n#\n# Copyright (c) 1997-2006 by Secret Labs AB.  All rights reserved.\n# Copyright (c) 1995-1997 by Fredrik Lundh\n#\n# See the README file for information on usage and redistribution.\n#\nimport io\nimport itertools\nimport logging\nimport os\nimport struct\nimport warnings\nfrom collections.abc import MutableMapping\nfrom fractions import Fraction\nfrom numbers import Number, Rational\n\nfrom . import Image, ImageFile, ImagePalette, TiffTags\nfrom ._binary import o8\nfrom .TiffTags import TYPES\n\nlogger = logging.getLogger(__name__)\n\n# Set these to true to force use of libtiff for reading or writing.\nREAD_LIBTIFF = False\nWRITE_LIBTIFF = False\nIFD_LEGACY_API = True\n\nII = b\"II\"  # little-endian (Intel style)\nMM = b\"MM\"  # big-endian (Motorola style)\n\n#\n# --------------------------------------------------------------------\n# Read TIFF files\n\n# a few tag names, just to make the code below a bit more readable\nIMAGEWIDTH = 256\nIMAGELENGTH = 257\nBITSPERSAMPLE = 258\nCOMPRESSION = 259\nPHOTOMETRIC_INTERPRETATION = 262\nFILLORDER = 266\nIMAGEDESCRIPTION = 270\nSTRIPOFFSETS = 273\nSAMPLESPERPIXEL = 277\nROWSPERSTRIP = 278\nSTRIPBYTECOUNTS = 279\nX_RESOLUTION = 282\nY_RESOLUTION = 283\nPLANAR_CONFIGURATION = 284\nRESOLUTION_UNIT = 296\nTRANSFERFUNCTION = 301\nSOFTWARE = 305\nDATE_TIME = 306\nARTIST = 315\nPREDICTOR = 317\nCOLORMAP = 320\nTILEOFFSETS = 324\nSUBIFD = 330\nEXTRASAMPLES = 338\nSAMPLEFORMAT = 339\nJPEGTABLES = 347\nREFERENCEBLACKWHITE = 532\nCOPYRIGHT = 33432\nIPTC_NAA_CHUNK = 33723  # newsphoto properties\nPHOTOSHOP_CHUNK = 34377  # photoshop properties\nICCPROFILE = 34675\nEXIFIFD = 34665\nXMP = 700\nJPEGQUALITY = 65537  # pseudo-tag by libtiff\n\n# https://github.com/imagej/ImageJA/blob/master/src/main/java/ij/io/TiffDecoder.java\nIMAGEJ_META_DATA_BYTE_COUNTS = 50838\nIMAGEJ_META_DATA = 50839\n\nCOMPRESSION_INFO = {\n    # Compression => pil compression name\n    1: \"raw\",\n    2: \"tiff_ccitt\",\n    3: \"group3\",\n    4: \"group4\",\n    5: \"tiff_lzw\",\n    6: \"tiff_jpeg\",  # obsolete\n    7: \"jpeg\",\n    8: \"tiff_adobe_deflate\",\n    32771: \"tiff_raw_16\",  # 16-bit padding\n    32773: \"packbits\",\n    32809: \"tiff_thunderscan\",\n    32946: \"tiff_deflate\",\n    34676: \"tiff_sgilog\",\n    34677: \"tiff_sgilog24\",\n    34925: \"lzma\",\n    50000: \"zstd\",\n    50001: \"webp\",\n}\n\nCOMPRESSION_INFO_REV = {v: k for k, v in COMPRESSION_INFO.items()}\n\nOPEN_INFO = {\n    # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample,\n    #  ExtraSamples) => mode, rawmode\n    (II, 0, (1,), 1, (1,), ()): (\"1\", \"1;I\"),\n    (MM, 0, (1,), 1, (1,), ()): (\"1\", \"1;I\"),\n    (II, 0, (1,), 2, (1,), ()): (\"1\", \"1;IR\"),\n    (MM, 0, (1,), 2, (1,), ()): (\"1\", \"1;IR\"),\n    (II, 1, (1,), 1, (1,), ()): (\"1\", \"1\"),\n    (MM, 1, (1,), 1, (1,), ()): (\"1\", \"1\"),\n    (II, 1, (1,), 2, (1,), ()): (\"1\", \"1;R\"),\n    (MM, 1, (1,), 2, (1,), ()): (\"1\", \"1;R\"),\n    (II, 0, (1,), 1, (2,), ()): (\"L\", \"L;2I\"),\n    (MM, 0, (1,), 1, (2,), ()): (\"L\", \"L;2I\"),\n    (II, 0, (1,), 2, (2,), ()): (\"L\", \"L;2IR\"),\n    (MM, 0, (1,), 2, (2,), ()): (\"L\", \"L;2IR\"),\n    (II, 1, (1,), 1, (2,), ()): (\"L\", \"L;2\"),\n    (MM, 1, (1,), 1, (2,), ()): (\"L\", \"L;2\"),\n    (II, 1, (1,), 2, (2,), ()): (\"L\", \"L;2R\"),\n    (MM, 1, (1,), 2, (2,), ()): (\"L\", \"L;2R\"),\n    (II, 0, (1,), 1, (4,), ()): (\"L\", \"L;4I\"),\n    (MM, 0, (1,), 1, (4,), ()): (\"L\", \"L;4I\"),\n    (II, 0, (1,), 2, (4,), ()): (\"L\", \"L;4IR\"),\n    (MM, 0, (1,), 2, (4,), ()): (\"L\", \"L;4IR\"),\n    (II, 1, (1,), 1, (4,), ()): (\"L\", \"L;4\"),\n    (MM, 1, (1,), 1, (4,), ()): (\"L\", \"L;4\"),\n    (II, 1, (1,), 2, (4,), ()): (\"L\", \"L;4R\"),\n    (MM, 1, (1,), 2, (4,), ()): (\"L\", \"L;4R\"),\n    (II, 0, (1,), 1, (8,), ()): (\"L\", \"L;I\"),\n    (MM, 0, (1,), 1, (8,), ()): (\"L\", \"L;I\"),\n    (II, 0, (1,), 2, (8,), ()): (\"L\", \"L;IR\"),\n    (MM, 0, (1,), 2, (8,), ()): (\"L\", \"L;IR\"),\n    (II, 1, (1,), 1, (8,), ()): (\"L\", \"L\"),\n    (MM, 1, (1,), 1, (8,), ()): (\"L\", \"L\"),\n    (II, 1, (1,), 2, (8,), ()): (\"L\", \"L;R\"),\n    (MM, 1, (1,), 2, (8,), ()): (\"L\", \"L;R\"),\n    (II, 1, (1,), 1, (12,), ()): (\"I;16\", \"I;12\"),\n    (II, 1, (1,), 1, (16,), ()): (\"I;16\", \"I;16\"),\n    (MM, 1, (1,), 1, (16,), ()): (\"I;16B\", \"I;16B\"),\n    (II, 1, (2,), 1, (16,), ()): (\"I\", \"I;16S\"),\n    (MM, 1, (2,), 1, (16,), ()): (\"I\", \"I;16BS\"),\n    (II, 0, (3,), 1, (32,), ()): (\"F\", \"F;32F\"),\n    (MM, 0, (3,), 1, (32,), ()): (\"F\", \"F;32BF\"),\n    (II, 1, (1,), 1, (32,), ()): (\"I\", \"I;32N\"),\n    (II, 1, (2,), 1, (32,), ()): (\"I\", \"I;32S\"),\n    (MM, 1, (2,), 1, (32,), ()): (\"I\", \"I;32BS\"),\n    (II, 1, (3,), 1, (32,), ()): (\"F\", \"F;32F\"),\n    (MM, 1, (3,), 1, (32,), ()): (\"F\", \"F;32BF\"),\n    (II, 1, (1,), 1, (8, 8), (2,)): (\"LA\", \"LA\"),\n    (MM, 1, (1,), 1, (8, 8), (2,)): (\"LA\", \"LA\"),\n    (II, 2, (1,), 1, (8, 8, 8), ()): (\"RGB\", \"RGB\"),\n    (MM, 2, (1,), 1, (8, 8, 8), ()): (\"RGB\", \"RGB\"),\n    (II, 2, (1,), 2, (8, 8, 8), ()): (\"RGB\", \"RGB;R\"),\n    (MM, 2, (1,), 2, (8, 8, 8), ()): (\"RGB\", \"RGB;R\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8), ()): (\"RGBA\", \"RGBA\"),  # missing ExtraSamples\n    (MM, 2, (1,), 1, (8, 8, 8, 8), ()): (\"RGBA\", \"RGBA\"),  # missing ExtraSamples\n    (II, 2, (1,), 1, (8, 8, 8, 8), (0,)): (\"RGBX\", \"RGBX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8), (0,)): (\"RGBX\", \"RGBX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): (\"RGBX\", \"RGBXX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): (\"RGBX\", \"RGBXX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): (\"RGBX\", \"RGBXXX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): (\"RGBX\", \"RGBXXX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8), (1,)): (\"RGBA\", \"RGBa\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8), (1,)): (\"RGBA\", \"RGBa\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): (\"RGBA\", \"RGBaX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): (\"RGBA\", \"RGBaX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): (\"RGBA\", \"RGBaXX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): (\"RGBA\", \"RGBaXX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8), (2,)): (\"RGBA\", \"RGBA\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8), (2,)): (\"RGBA\", \"RGBA\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): (\"RGBA\", \"RGBAX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): (\"RGBA\", \"RGBAX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): (\"RGBA\", \"RGBAXX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): (\"RGBA\", \"RGBAXX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8), (999,)): (\"RGBA\", \"RGBA\"),  # Corel Draw 10\n    (MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): (\"RGBA\", \"RGBA\"),  # Corel Draw 10\n    (II, 2, (1,), 1, (16, 16, 16), ()): (\"RGB\", \"RGB;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16), ()): (\"RGB\", \"RGB;16B\"),\n    (II, 2, (1,), 1, (16, 16, 16, 16), ()): (\"RGBA\", \"RGBA;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16, 16), ()): (\"RGBA\", \"RGBA;16B\"),\n    (II, 2, (1,), 1, (16, 16, 16, 16), (0,)): (\"RGBX\", \"RGBX;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16, 16), (0,)): (\"RGBX\", \"RGBX;16B\"),\n    (II, 2, (1,), 1, (16, 16, 16, 16), (1,)): (\"RGBA\", \"RGBa;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16, 16), (1,)): (\"RGBA\", \"RGBa;16B\"),\n    (II, 2, (1,), 1, (16, 16, 16, 16), (2,)): (\"RGBA\", \"RGBA;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16, 16), (2,)): (\"RGBA\", \"RGBA;16B\"),\n    (II, 3, (1,), 1, (1,), ()): (\"P\", \"P;1\"),\n    (MM, 3, (1,), 1, (1,), ()): (\"P\", \"P;1\"),\n    (II, 3, (1,), 2, (1,), ()): (\"P\", \"P;1R\"),\n    (MM, 3, (1,), 2, (1,), ()): (\"P\", \"P;1R\"),\n    (II, 3, (1,), 1, (2,), ()): (\"P\", \"P;2\"),\n    (MM, 3, (1,), 1, (2,), ()): (\"P\", \"P;2\"),\n    (II, 3, (1,), 2, (2,), ()): (\"P\", \"P;2R\"),\n    (MM, 3, (1,), 2, (2,), ()): (\"P\", \"P;2R\"),\n    (II, 3, (1,), 1, (4,), ()): (\"P\", \"P;4\"),\n    (MM, 3, (1,), 1, (4,), ()): (\"P\", \"P;4\"),\n    (II, 3, (1,), 2, (4,), ()): (\"P\", \"P;4R\"),\n    (MM, 3, (1,), 2, (4,), ()): (\"P\", \"P;4R\"),\n    (II, 3, (1,), 1, (8,), ()): (\"P\", \"P\"),\n    (MM, 3, (1,), 1, (8,), ()): (\"P\", \"P\"),\n    (II, 3, (1,), 1, (8, 8), (2,)): (\"PA\", \"PA\"),\n    (MM, 3, (1,), 1, (8, 8), (2,)): (\"PA\", \"PA\"),\n    (II, 3, (1,), 2, (8,), ()): (\"P\", \"P;R\"),\n    (MM, 3, (1,), 2, (8,), ()): (\"P\", \"P;R\"),\n    (II, 5, (1,), 1, (8, 8, 8, 8), ()): (\"CMYK\", \"CMYK\"),\n    (MM, 5, (1,), 1, (8, 8, 8, 8), ()): (\"CMYK\", \"CMYK\"),\n    (II, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): (\"CMYK\", \"CMYKX\"),\n    (MM, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): (\"CMYK\", \"CMYKX\"),\n    (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): (\"CMYK\", \"CMYKXX\"),\n    (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): (\"CMYK\", \"CMYKXX\"),\n    (II, 5, (1,), 1, (16, 16, 16, 16), ()): (\"CMYK\", \"CMYK;16L\"),\n    # JPEG compressed images handled by LibTiff and auto-converted to RGBX\n    # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel\n    (II, 6, (1,), 1, (8, 8, 8), ()): (\"RGB\", \"RGBX\"),\n    (MM, 6, (1,), 1, (8, 8, 8), ()): (\"RGB\", \"RGBX\"),\n    (II, 8, (1,), 1, (8, 8, 8), ()): (\"LAB\", \"LAB\"),\n    (MM, 8, (1,), 1, (8, 8, 8), ()): (\"LAB\", \"LAB\"),\n}\n\nPREFIXES = [\n    b\"MM\\x00\\x2A\",  # Valid TIFF header with big-endian byte order\n    b\"II\\x2A\\x00\",  # Valid TIFF header with little-endian byte order\n    b\"MM\\x2A\\x00\",  # Invalid TIFF header, assume big-endian\n    b\"II\\x00\\x2A\",  # Invalid TIFF header, assume little-endian\n]\n\n\ndef _accept(prefix):\n    return prefix[:4] in PREFIXES\n\n\ndef _limit_rational(val, max_val):\n    inv = abs(val) > 1\n    n_d = IFDRational(1 / val if inv else val).limit_rational(max_val)\n    return n_d[::-1] if inv else n_d\n\n\ndef _limit_signed_rational(val, max_val, min_val):\n    frac = Fraction(val)\n    n_d = frac.numerator, frac.denominator\n\n    if min(n_d) < min_val:\n        n_d = _limit_rational(val, abs(min_val))\n\n    if max(n_d) > max_val:\n        val = Fraction(*n_d)\n        n_d = _limit_rational(val, max_val)\n\n    return n_d\n\n\n##\n# Wrapper for TIFF IFDs.\n\n_load_dispatch = {}\n_write_dispatch = {}\n\n\nclass IFDRational(Rational):\n    \"\"\"Implements a rational class where 0/0 is a legal value to match\n    the in the wild use of exif rationals.\n\n    e.g., DigitalZoomRatio - 0.00/0.00  indicates that no digital zoom was used\n    \"\"\"\n\n    \"\"\" If the denominator is 0, store this as a float('nan'), otherwise store\n    as a fractions.Fraction(). Delegate as appropriate\n\n    \"\"\"\n\n    __slots__ = (\"_numerator\", \"_denominator\", \"_val\")\n\n    def __init__(self, value, denominator=1):\n        \"\"\"\n        :param value: either an integer numerator, a\n        float/rational/other number, or an IFDRational\n        :param denominator: Optional integer denominator\n        \"\"\"\n        if isinstance(value, IFDRational):\n            self._numerator = value.numerator\n            self._denominator = value.denominator\n            self._val = value._val\n            return\n\n        if isinstance(value, Fraction):\n            self._numerator = value.numerator\n            self._denominator = value.denominator\n        else:\n            self._numerator = value\n            self._denominator = denominator\n\n        if denominator == 0:\n            self._val = float(\"nan\")\n        elif denominator == 1:\n            self._val = Fraction(value)\n        else:\n            self._val = Fraction(value, denominator)\n\n    @property\n    def numerator(a):\n        return a._numerator\n\n    @property\n    def denominator(a):\n        return a._denominator\n\n    def limit_rational(self, max_denominator):\n        \"\"\"\n\n        :param max_denominator: Integer, the maximum denominator value\n        :returns: Tuple of (numerator, denominator)\n        \"\"\"\n\n        if self.denominator == 0:\n            return (self.numerator, self.denominator)\n\n        f = self._val.limit_denominator(max_denominator)\n        return (f.numerator, f.denominator)\n\n    def __repr__(self):\n        return str(float(self._val))\n\n    def __hash__(self):\n        return self._val.__hash__()\n\n    def __eq__(self, other):\n        val = self._val\n        if isinstance(other, IFDRational):\n            other = other._val\n        if isinstance(other, float):\n            val = float(val)\n        return val == other\n\n    def __getstate__(self):\n        return [self._val, self._numerator, self._denominator]\n\n    def __setstate__(self, state):\n        IFDRational.__init__(self, 0)\n        _val, _numerator, _denominator = state\n        self._val = _val\n        self._numerator = _numerator\n        self._denominator = _denominator\n\n    def _delegate(op):\n        def delegate(self, *args):\n            return getattr(self._val, op)(*args)\n\n        return delegate\n\n    \"\"\" a = ['add','radd', 'sub', 'rsub', 'mul', 'rmul',\n             'truediv', 'rtruediv', 'floordiv', 'rfloordiv',\n             'mod','rmod', 'pow','rpow', 'pos', 'neg',\n             'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'bool',\n             'ceil', 'floor', 'round']\n        print(\"\\n\".join(\"__%s__ = _delegate('__%s__')\" % (s,s) for s in a))\n        \"\"\"\n\n    __add__ = _delegate(\"__add__\")\n    __radd__ = _delegate(\"__radd__\")\n    __sub__ = _delegate(\"__sub__\")\n    __rsub__ = _delegate(\"__rsub__\")\n    __mul__ = _delegate(\"__mul__\")\n    __rmul__ = _delegate(\"__rmul__\")\n    __truediv__ = _delegate(\"__truediv__\")\n    __rtruediv__ = _delegate(\"__rtruediv__\")\n    __floordiv__ = _delegate(\"__floordiv__\")\n    __rfloordiv__ = _delegate(\"__rfloordiv__\")\n    __mod__ = _delegate(\"__mod__\")\n    __rmod__ = _delegate(\"__rmod__\")\n    __pow__ = _delegate(\"__pow__\")\n    __rpow__ = _delegate(\"__rpow__\")\n    __pos__ = _delegate(\"__pos__\")\n    __neg__ = _delegate(\"__neg__\")\n    __abs__ = _delegate(\"__abs__\")\n    __trunc__ = _delegate(\"__trunc__\")\n    __lt__ = _delegate(\"__lt__\")\n    __gt__ = _delegate(\"__gt__\")\n    __le__ = _delegate(\"__le__\")\n    __ge__ = _delegate(\"__ge__\")\n    __bool__ = _delegate(\"__bool__\")\n    __ceil__ = _delegate(\"__ceil__\")\n    __floor__ = _delegate(\"__floor__\")\n    __round__ = _delegate(\"__round__\")\n\n\nclass ImageFileDirectory_v2(MutableMapping):\n    \"\"\"This class represents a TIFF tag directory.  To speed things up, we\n    don't decode tags unless they're asked for.\n\n    Exposes a dictionary interface of the tags in the directory::\n\n        ifd = ImageFileDirectory_v2()\n        ifd[key] = 'Some Data'\n        ifd.tagtype[key] = TiffTags.ASCII\n        print(ifd[key])\n        'Some Data'\n\n    Individual values are returned as the strings or numbers, sequences are\n    returned as tuples of the values.\n\n    The tiff metadata type of each item is stored in a dictionary of\n    tag types in\n    :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types\n    are read from a tiff file, guessed from the type added, or added\n    manually.\n\n    Data Structures:\n\n        * self.tagtype = {}\n\n          * Key: numerical tiff tag number\n          * Value: integer corresponding to the data type from\n                   ~PIL.TiffTags.TYPES`\n\n    .. versionadded:: 3.0.0\n    \"\"\"\n\n    \"\"\"\n    Documentation:\n\n        'internal' data structures:\n        * self._tags_v2 = {} Key: numerical tiff tag number\n                             Value: decoded data, as tuple for multiple values\n        * self._tagdata = {} Key: numerical tiff tag number\n                             Value: undecoded byte string from file\n        * self._tags_v1 = {} Key: numerical tiff tag number\n                             Value: decoded data in the v1 format\n\n    Tags will be found in the private attributes self._tagdata, and in\n    self._tags_v2 once decoded.\n\n    self.legacy_api is a value for internal use, and shouldn't be\n    changed from outside code. In cooperation with the\n    ImageFileDirectory_v1 class, if legacy_api is true, then decoded\n    tags will be populated into both _tags_v1 and _tags_v2. _tags_v2\n    will be used if this IFD is used in the TIFF save routine. Tags\n    should be read from _tags_v1 if legacy_api == true.\n\n    \"\"\"\n\n    def __init__(self, ifh=b\"II\\052\\0\\0\\0\\0\\0\", prefix=None, group=None):\n        \"\"\"Initialize an ImageFileDirectory.\n\n        To construct an ImageFileDirectory from a real file, pass the 8-byte\n        magic header to the constructor.  To only set the endianness, pass it\n        as the 'prefix' keyword argument.\n\n        :param ifh: One of the accepted magic headers (cf. PREFIXES); also sets\n              endianness.\n        :param prefix: Override the endianness of the file.\n        \"\"\"\n        if ifh[:4] not in PREFIXES:\n            raise SyntaxError(f\"not a TIFF file (header {repr(ifh)} not valid)\")\n        self._prefix = prefix if prefix is not None else ifh[:2]\n        if self._prefix == MM:\n            self._endian = \">\"\n        elif self._prefix == II:\n            self._endian = \"<\"\n        else:\n            raise SyntaxError(\"not a TIFF IFD\")\n        self.group = group\n        self.tagtype = {}\n        \"\"\" Dictionary of tag types \"\"\"\n        self.reset()\n        (self.next,) = self._unpack(\"L\", ifh[4:])\n        self._legacy_api = False\n\n    prefix = property(lambda self: self._prefix)\n    offset = property(lambda self: self._offset)\n    legacy_api = property(lambda self: self._legacy_api)\n\n    @legacy_api.setter\n    def legacy_api(self, value):\n        raise Exception(\"Not allowing setting of legacy api\")\n\n    def reset(self):\n        self._tags_v1 = {}  # will remain empty if legacy_api is false\n        self._tags_v2 = {}  # main tag storage\n        self._tagdata = {}\n        self.tagtype = {}  # added 2008-06-05 by Florian Hoech\n        self._next = None\n        self._offset = None\n\n    def __str__(self):\n        return str(dict(self))\n\n    def named(self):\n        \"\"\"\n        :returns: dict of name|key: value\n\n        Returns the complete tag dictionary, with named tags where possible.\n        \"\"\"\n        return {\n            TiffTags.lookup(code, self.group).name: value\n            for code, value in self.items()\n        }\n\n    def __len__(self):\n        return len(set(self._tagdata) | set(self._tags_v2))\n\n    def __getitem__(self, tag):\n        if tag not in self._tags_v2:  # unpack on the fly\n            data = self._tagdata[tag]\n            typ = self.tagtype[tag]\n            size, handler = self._load_dispatch[typ]\n            self[tag] = handler(self, data, self.legacy_api)  # check type\n        val = self._tags_v2[tag]\n        if self.legacy_api and not isinstance(val, (tuple, bytes)):\n            val = (val,)\n        return val\n\n    def __contains__(self, tag):\n        return tag in self._tags_v2 or tag in self._tagdata\n\n    def __setitem__(self, tag, value):\n        self._setitem(tag, value, self.legacy_api)\n\n    def _setitem(self, tag, value, legacy_api):\n        basetypes = (Number, bytes, str)\n\n        info = TiffTags.lookup(tag, self.group)\n        values = [value] if isinstance(value, basetypes) else value\n\n        if tag not in self.tagtype:\n            if info.type:\n                self.tagtype[tag] = info.type\n            else:\n                self.tagtype[tag] = TiffTags.UNDEFINED\n                if all(isinstance(v, IFDRational) for v in values):\n                    self.tagtype[tag] = (\n                        TiffTags.RATIONAL\n                        if all(v >= 0 for v in values)\n                        else TiffTags.SIGNED_RATIONAL\n                    )\n                elif all(isinstance(v, int) for v in values):\n                    if all(0 <= v < 2 ** 16 for v in values):\n                        self.tagtype[tag] = TiffTags.SHORT\n                    elif all(-(2 ** 15) < v < 2 ** 15 for v in values):\n                        self.tagtype[tag] = TiffTags.SIGNED_SHORT\n                    else:\n                        self.tagtype[tag] = (\n                            TiffTags.LONG\n                            if all(v >= 0 for v in values)\n                            else TiffTags.SIGNED_LONG\n                        )\n                elif all(isinstance(v, float) for v in values):\n                    self.tagtype[tag] = TiffTags.DOUBLE\n                elif all(isinstance(v, str) for v in values):\n                    self.tagtype[tag] = TiffTags.ASCII\n                elif all(isinstance(v, bytes) for v in values):\n                    self.tagtype[tag] = TiffTags.BYTE\n\n        if self.tagtype[tag] == TiffTags.UNDEFINED:\n            values = [\n                v.encode(\"ascii\", \"replace\") if isinstance(v, str) else v\n                for v in values\n            ]\n        elif self.tagtype[tag] == TiffTags.RATIONAL:\n            values = [float(v) if isinstance(v, int) else v for v in values]\n\n        is_ifd = self.tagtype[tag] == TiffTags.LONG and isinstance(values, dict)\n        if not is_ifd:\n            values = tuple(info.cvt_enum(value) for value in values)\n\n        dest = self._tags_v1 if legacy_api else self._tags_v2\n\n        # Three branches:\n        # Spec'd length == 1, Actual length 1, store as element\n        # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed.\n        # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple.\n        # Don't mess with the legacy api, since it's frozen.\n        if not is_ifd and (\n            (info.length == 1)\n            or self.tagtype[tag] == TiffTags.BYTE\n            or (info.length is None and len(values) == 1 and not legacy_api)\n        ):\n            # Don't mess with the legacy api, since it's frozen.\n            if legacy_api and self.tagtype[tag] in [\n                TiffTags.RATIONAL,\n                TiffTags.SIGNED_RATIONAL,\n            ]:  # rationals\n                values = (values,)\n            try:\n                (dest[tag],) = values\n            except ValueError:\n                # We've got a builtin tag with 1 expected entry\n                warnings.warn(\n                    f\"Metadata Warning, tag {tag} had too many entries: \"\n                    f\"{len(values)}, expected 1\"\n                )\n                dest[tag] = values[0]\n\n        else:\n            # Spec'd length > 1 or undefined\n            # Unspec'd, and length > 1\n            dest[tag] = values\n\n    def __delitem__(self, tag):\n        self._tags_v2.pop(tag, None)\n        self._tags_v1.pop(tag, None)\n        self._tagdata.pop(tag, None)\n\n    def __iter__(self):\n        return iter(set(self._tagdata) | set(self._tags_v2))\n\n    def _unpack(self, fmt, data):\n        return struct.unpack(self._endian + fmt, data)\n\n    def _pack(self, fmt, *values):\n        return struct.pack(self._endian + fmt, *values)\n\n    def _register_loader(idx, size):\n        def decorator(func):\n            from .TiffTags import TYPES\n\n            if func.__name__.startswith(\"load_\"):\n                TYPES[idx] = func.__name__[5:].replace(\"_\", \" \")\n            _load_dispatch[idx] = size, func  # noqa: F821\n            return func\n\n        return decorator\n\n    def _register_writer(idx):\n        def decorator(func):\n            _write_dispatch[idx] = func  # noqa: F821\n            return func\n\n        return decorator\n\n    def _register_basic(idx_fmt_name):\n        from .TiffTags import TYPES\n\n        idx, fmt, name = idx_fmt_name\n        TYPES[idx] = name\n        size = struct.calcsize(\"=\" + fmt)\n        _load_dispatch[idx] = (  # noqa: F821\n            size,\n            lambda self, data, legacy_api=True: (\n                self._unpack(\"{}{}\".format(len(data) // size, fmt), data)\n            ),\n        )\n        _write_dispatch[idx] = lambda self, *values: (  # noqa: F821\n            b\"\".join(self._pack(fmt, value) for value in values)\n        )\n\n    list(\n        map(\n            _register_basic,\n            [\n                (TiffTags.SHORT, \"H\", \"short\"),\n                (TiffTags.LONG, \"L\", \"long\"),\n                (TiffTags.SIGNED_BYTE, \"b\", \"signed byte\"),\n                (TiffTags.SIGNED_SHORT, \"h\", \"signed short\"),\n                (TiffTags.SIGNED_LONG, \"l\", \"signed long\"),\n                (TiffTags.FLOAT, \"f\", \"float\"),\n                (TiffTags.DOUBLE, \"d\", \"double\"),\n                (TiffTags.IFD, \"L\", \"long\"),\n            ],\n        )\n    )\n\n    @_register_loader(1, 1)  # Basic type, except for the legacy API.\n    def load_byte(self, data, legacy_api=True):\n        return data\n\n    @_register_writer(1)  # Basic type, except for the legacy API.\n    def write_byte(self, data):\n        return data\n\n    @_register_loader(2, 1)\n    def load_string(self, data, legacy_api=True):\n        if data.endswith(b\"\\0\"):\n            data = data[:-1]\n        return data.decode(\"latin-1\", \"replace\")\n\n    @_register_writer(2)\n    def write_string(self, value):\n        # remerge of https://github.com/python-pillow/Pillow/pull/1416\n        return b\"\" + value.encode(\"ascii\", \"replace\") + b\"\\0\"\n\n    @_register_loader(5, 8)\n    def load_rational(self, data, legacy_api=True):\n        vals = self._unpack(\"{}L\".format(len(data) // 4), data)\n\n        def combine(a, b):\n            return (a, b) if legacy_api else IFDRational(a, b)\n\n        return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))\n\n    @_register_writer(5)\n    def write_rational(self, *values):\n        return b\"\".join(\n            self._pack(\"2L\", *_limit_rational(frac, 2 ** 32 - 1)) for frac in values\n        )\n\n    @_register_loader(7, 1)\n    def load_undefined(self, data, legacy_api=True):\n        return data\n\n    @_register_writer(7)\n    def write_undefined(self, value):\n        return value\n\n    @_register_loader(10, 8)\n    def load_signed_rational(self, data, legacy_api=True):\n        vals = self._unpack(\"{}l\".format(len(data) // 4), data)\n\n        def combine(a, b):\n            return (a, b) if legacy_api else IFDRational(a, b)\n\n        return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))\n\n    @_register_writer(10)\n    def write_signed_rational(self, *values):\n        return b\"\".join(\n            self._pack(\"2l\", *_limit_signed_rational(frac, 2 ** 31 - 1, -(2 ** 31)))\n            for frac in values\n        )\n\n    def _ensure_read(self, fp, size):\n        ret = fp.read(size)\n        if len(ret) != size:\n            raise OSError(\n                \"Corrupt EXIF data.  \"\n                f\"Expecting to read {size} bytes but only got {len(ret)}. \"\n            )\n        return ret\n\n    def load(self, fp):\n\n        self.reset()\n        self._offset = fp.tell()\n\n        try:\n            for i in range(self._unpack(\"H\", self._ensure_read(fp, 2))[0]):\n                tag, typ, count, data = self._unpack(\"HHL4s\", self._ensure_read(fp, 12))\n\n                tagname = TiffTags.lookup(tag, self.group).name\n                typname = TYPES.get(typ, \"unknown\")\n                msg = f\"tag: {tagname} ({tag}) - type: {typname} ({typ})\"\n\n                try:\n                    unit_size, handler = self._load_dispatch[typ]\n                except KeyError:\n                    logger.debug(msg + f\" - unsupported type {typ}\")\n                    continue  # ignore unsupported type\n                size = count * unit_size\n                if size > 4:\n                    here = fp.tell()\n                    (offset,) = self._unpack(\"L\", data)\n                    msg += f\" Tag Location: {here} - Data Location: {offset}\"\n                    fp.seek(offset)\n                    data = ImageFile._safe_read(fp, size)\n                    fp.seek(here)\n                else:\n                    data = data[:size]\n\n                if len(data) != size:\n                    warnings.warn(\n                        \"Possibly corrupt EXIF data.  \"\n                        f\"Expecting to read {size} bytes but only got {len(data)}.\"\n                        f\" Skipping tag {tag}\"\n                    )\n                    logger.debug(msg)\n                    continue\n\n                if not data:\n                    logger.debug(msg)\n                    continue\n\n                self._tagdata[tag] = data\n                self.tagtype[tag] = typ\n\n                msg += \" - value: \" + (\n                    \"<table: %d bytes>\" % size if size > 32 else repr(data)\n                )\n                logger.debug(msg)\n\n            (self.next,) = self._unpack(\"L\", self._ensure_read(fp, 4))\n        except OSError as msg:\n            warnings.warn(str(msg))\n            return\n\n    def tobytes(self, offset=0):\n        # FIXME What about tagdata?\n        result = self._pack(\"H\", len(self._tags_v2))\n\n        entries = []\n        offset = offset + len(result) + len(self._tags_v2) * 12 + 4\n        stripoffsets = None\n\n        # pass 1: convert tags to binary format\n        # always write tags in ascending order\n        for tag, value in sorted(self._tags_v2.items()):\n            if tag == STRIPOFFSETS:\n                stripoffsets = len(entries)\n            typ = self.tagtype.get(tag)\n            logger.debug(f\"Tag {tag}, Type: {typ}, Value: {repr(value)}\")\n            is_ifd = typ == TiffTags.LONG and isinstance(value, dict)\n            if is_ifd:\n                if self._endian == \"<\":\n                    ifh = b\"II\\x2A\\x00\\x08\\x00\\x00\\x00\"\n                else:\n                    ifh = b\"MM\\x00\\x2A\\x00\\x00\\x00\\x08\"\n                ifd = ImageFileDirectory_v2(ifh, group=tag)\n                values = self._tags_v2[tag]\n                for ifd_tag, ifd_value in values.items():\n                    ifd[ifd_tag] = ifd_value\n                data = ifd.tobytes(offset)\n            else:\n                values = value if isinstance(value, tuple) else (value,)\n                data = self._write_dispatch[typ](self, *values)\n\n            tagname = TiffTags.lookup(tag, self.group).name\n            typname = \"ifd\" if is_ifd else TYPES.get(typ, \"unknown\")\n            msg = f\"save: {tagname} ({tag}) - type: {typname} ({typ})\"\n            msg += \" - value: \" + (\n                \"<table: %d bytes>\" % len(data) if len(data) >= 16 else str(values)\n            )\n            logger.debug(msg)\n\n            # count is sum of lengths for string and arbitrary data\n            if is_ifd:\n                count = 1\n            elif typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]:\n                count = len(data)\n            else:\n                count = len(values)\n            # figure out if data fits into the entry\n            if len(data) <= 4:\n                entries.append((tag, typ, count, data.ljust(4, b\"\\0\"), b\"\"))\n            else:\n                entries.append((tag, typ, count, self._pack(\"L\", offset), data))\n                offset += (len(data) + 1) // 2 * 2  # pad to word\n\n        # update strip offset data to point beyond auxiliary data\n        if stripoffsets is not None:\n            tag, typ, count, value, data = entries[stripoffsets]\n            if data:\n                raise NotImplementedError(\"multistrip support not yet implemented\")\n            value = self._pack(\"L\", self._unpack(\"L\", value)[0] + offset)\n            entries[stripoffsets] = tag, typ, count, value, data\n\n        # pass 2: write entries to file\n        for tag, typ, count, value, data in entries:\n            logger.debug(f\"{tag} {typ} {count} {repr(value)} {repr(data)}\")\n            result += self._pack(\"HHL4s\", tag, typ, count, value)\n\n        # -- overwrite here for multi-page --\n        result += b\"\\0\\0\\0\\0\"  # end of entries\n\n        # pass 3: write auxiliary data to file\n        for tag, typ, count, value, data in entries:\n            result += data\n            if len(data) & 1:\n                result += b\"\\0\"\n\n        return result\n\n    def save(self, fp):\n\n        if fp.tell() == 0:  # skip TIFF header on subsequent pages\n            # tiff header -- PIL always starts the first IFD at offset 8\n            fp.write(self._prefix + self._pack(\"HL\", 42, 8))\n\n        offset = fp.tell()\n        result = self.tobytes(offset)\n        fp.write(result)\n        return offset + len(result)\n\n\nImageFileDirectory_v2._load_dispatch = _load_dispatch\nImageFileDirectory_v2._write_dispatch = _write_dispatch\nfor idx, name in TYPES.items():\n    name = name.replace(\" \", \"_\")\n    setattr(ImageFileDirectory_v2, \"load_\" + name, _load_dispatch[idx][1])\n    setattr(ImageFileDirectory_v2, \"write_\" + name, _write_dispatch[idx])\ndel _load_dispatch, _write_dispatch, idx, name\n\n\n# Legacy ImageFileDirectory support.\nclass ImageFileDirectory_v1(ImageFileDirectory_v2):\n    \"\"\"This class represents the **legacy** interface to a TIFF tag directory.\n\n    Exposes a dictionary interface of the tags in the directory::\n\n        ifd = ImageFileDirectory_v1()\n        ifd[key] = 'Some Data'\n        ifd.tagtype[key] = TiffTags.ASCII\n        print(ifd[key])\n        ('Some Data',)\n\n    Also contains a dictionary of tag types as read from the tiff image file,\n    :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`.\n\n    Values are returned as a tuple.\n\n    ..  deprecated:: 3.0.0\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._legacy_api = True\n\n    tags = property(lambda self: self._tags_v1)\n    tagdata = property(lambda self: self._tagdata)\n\n    # defined in ImageFileDirectory_v2\n    tagtype: dict\n    \"\"\"Dictionary of tag types\"\"\"\n\n    @classmethod\n    def from_v2(cls, original):\n        \"\"\"Returns an\n        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`\n        instance with the same data as is contained in the original\n        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`\n        instance.\n\n        :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`\n\n        \"\"\"\n\n        ifd = cls(prefix=original.prefix)\n        ifd._tagdata = original._tagdata\n        ifd.tagtype = original.tagtype\n        ifd.next = original.next  # an indicator for multipage tiffs\n        return ifd\n\n    def to_v2(self):\n        \"\"\"Returns an\n        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`\n        instance with the same data as is contained in the original\n        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`\n        instance.\n\n        :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`\n\n        \"\"\"\n\n        ifd = ImageFileDirectory_v2(prefix=self.prefix)\n        ifd._tagdata = dict(self._tagdata)\n        ifd.tagtype = dict(self.tagtype)\n        ifd._tags_v2 = dict(self._tags_v2)\n        return ifd\n\n    def __contains__(self, tag):\n        return tag in self._tags_v1 or tag in self._tagdata\n\n    def __len__(self):\n        return len(set(self._tagdata) | set(self._tags_v1))\n\n    def __iter__(self):\n        return iter(set(self._tagdata) | set(self._tags_v1))\n\n    def __setitem__(self, tag, value):\n        for legacy_api in (False, True):\n            self._setitem(tag, value, legacy_api)\n\n    def __getitem__(self, tag):\n        if tag not in self._tags_v1:  # unpack on the fly\n            data = self._tagdata[tag]\n            typ = self.tagtype[tag]\n            size, handler = self._load_dispatch[typ]\n            for legacy in (False, True):\n                self._setitem(tag, handler(self, data, legacy), legacy)\n        val = self._tags_v1[tag]\n        if not isinstance(val, (tuple, bytes)):\n            val = (val,)\n        return val\n\n\n# undone -- switch this pointer when IFD_LEGACY_API == False\nImageFileDirectory = ImageFileDirectory_v1\n\n\n##\n# Image plugin for TIFF files.\n\n\nclass TiffImageFile(ImageFile.ImageFile):\n\n    format = \"TIFF\"\n    format_description = \"Adobe TIFF\"\n    _close_exclusive_fp_after_loading = False\n\n    def __init__(self, fp=None, filename=None):\n        self.tag_v2 = None\n        \"\"\" Image file directory (tag dictionary) \"\"\"\n\n        self.tag = None\n        \"\"\" Legacy tag entries \"\"\"\n\n        super().__init__(fp, filename)\n\n    def _open(self):\n        \"\"\"Open the first image in a TIFF file\"\"\"\n\n        # Header\n        ifh = self.fp.read(8)\n\n        self.tag_v2 = ImageFileDirectory_v2(ifh)\n\n        # legacy IFD entries will be filled in later\n        self.ifd = None\n\n        # setup frame pointers\n        self.__first = self.__next = self.tag_v2.next\n        self.__frame = -1\n        self.__fp = self.fp\n        self._frame_pos = []\n        self._n_frames = None\n\n        logger.debug(\"*** TiffImageFile._open ***\")\n        logger.debug(f\"- __first: {self.__first}\")\n        logger.debug(f\"- ifh: {repr(ifh)}\")  # Use repr to avoid str(bytes)\n\n        # and load the first frame\n        self._seek(0)\n\n    @property\n    def n_frames(self):\n        if self._n_frames is None:\n            current = self.tell()\n            self._seek(len(self._frame_pos))\n            while self._n_frames is None:\n                self._seek(self.tell() + 1)\n            self.seek(current)\n        return self._n_frames\n\n    def seek(self, frame):\n        \"\"\"Select a given frame as current image\"\"\"\n        if not self._seek_check(frame):\n            return\n        self._seek(frame)\n        # Create a new core image object on second and\n        # subsequent frames in the image. Image may be\n        # different size/mode.\n        Image._decompression_bomb_check(self.size)\n        self.im = Image.core.new(self.mode, self.size)\n\n    def _seek(self, frame):\n        self.fp = self.__fp\n\n        # reset buffered io handle in case fp\n        # was passed to libtiff, invalidating the buffer\n        self.fp.tell()\n\n        while len(self._frame_pos) <= frame:\n            if not self.__next:\n                raise EOFError(\"no more images in TIFF file\")\n            logger.debug(\n                f\"Seeking to frame {frame}, on frame {self.__frame}, \"\n                f\"__next {self.__next}, location: {self.fp.tell()}\"\n            )\n            self.fp.seek(self.__next)\n            self._frame_pos.append(self.__next)\n            logger.debug(\"Loading tags, location: %s\" % self.fp.tell())\n            self.tag_v2.load(self.fp)\n            if self.tag_v2.next in self._frame_pos:\n                # This IFD has already been processed\n                # Declare this to be the end of the image\n                self.__next = 0\n            else:\n                self.__next = self.tag_v2.next\n            if self.__next == 0:\n                self._n_frames = frame + 1\n            if len(self._frame_pos) == 1:\n                self.is_animated = self.__next != 0\n            self.__frame += 1\n        self.fp.seek(self._frame_pos[frame])\n        self.tag_v2.load(self.fp)\n        # fill the legacy tag/ifd entries\n        self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)\n        self.__frame = frame\n        self._setup()\n\n    def tell(self):\n        \"\"\"Return the current frame number\"\"\"\n        return self.__frame\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n        return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {}\n\n    def load(self):\n        if self.tile and self.use_load_libtiff:\n            return self._load_libtiff()\n        return super().load()\n\n    def load_end(self):\n        if self._tile_orientation:\n            method = {\n                2: Image.FLIP_LEFT_RIGHT,\n                3: Image.ROTATE_180,\n                4: Image.FLIP_TOP_BOTTOM,\n                5: Image.TRANSPOSE,\n                6: Image.ROTATE_270,\n                7: Image.TRANSVERSE,\n                8: Image.ROTATE_90,\n            }.get(self._tile_orientation)\n            if method is not None:\n                self.im = self.im.transpose(method)\n                self._size = self.im.size\n\n        # allow closing if we're on the first frame, there's no next\n        # This is the ImageFile.load path only, libtiff specific below.\n        if not self.is_animated:\n            self._close_exclusive_fp_after_loading = True\n\n            # reset buffered io handle in case fp\n            # was passed to libtiff, invalidating the buffer\n            self.fp.tell()\n\n            # load IFD data from fp before it is closed\n            exif = self.getexif()\n            for key in TiffTags.TAGS_V2_GROUPS.keys():\n                if key not in exif:\n                    continue\n                exif.get_ifd(key)\n\n    def _load_libtiff(self):\n        \"\"\"Overload method triggered when we detect a compressed tiff\n        Calls out to libtiff\"\"\"\n\n        Image.Image.load(self)\n\n        self.load_prepare()\n\n        if not len(self.tile) == 1:\n            raise OSError(\"Not exactly one tile\")\n\n        # (self._compression, (extents tuple),\n        #   0, (rawmode, self._compression, fp))\n        extents = self.tile[0][1]\n        args = list(self.tile[0][3])\n\n        # To be nice on memory footprint, if there's a\n        # file descriptor, use that instead of reading\n        # into a string in python.\n        # libtiff closes the file descriptor, so pass in a dup.\n        try:\n            fp = hasattr(self.fp, \"fileno\") and os.dup(self.fp.fileno())\n            # flush the file descriptor, prevents error on pypy 2.4+\n            # should also eliminate the need for fp.tell\n            # in _seek\n            if hasattr(self.fp, \"flush\"):\n                self.fp.flush()\n        except OSError:\n            # io.BytesIO have a fileno, but returns an OSError if\n            # it doesn't use a file descriptor.\n            fp = False\n\n        if fp:\n            args[2] = fp\n\n        decoder = Image._getdecoder(\n            self.mode, \"libtiff\", tuple(args), self.decoderconfig\n        )\n        try:\n            decoder.setimage(self.im, extents)\n        except ValueError as e:\n            raise OSError(\"Couldn't set the image\") from e\n\n        close_self_fp = self._exclusive_fp and not self.is_animated\n        if hasattr(self.fp, \"getvalue\"):\n            # We've got a stringio like thing passed in. Yay for all in memory.\n            # The decoder needs the entire file in one shot, so there's not\n            # a lot we can do here other than give it the entire file.\n            # unless we could do something like get the address of the\n            # underlying string for stringio.\n            #\n            # Rearranging for supporting byteio items, since they have a fileno\n            # that returns an OSError if there's no underlying fp. Easier to\n            # deal with here by reordering.\n            logger.debug(\"have getvalue. just sending in a string from getvalue\")\n            n, err = decoder.decode(self.fp.getvalue())\n        elif fp:\n            # we've got a actual file on disk, pass in the fp.\n            logger.debug(\"have fileno, calling fileno version of the decoder.\")\n            if not close_self_fp:\n                self.fp.seek(0)\n            # 4 bytes, otherwise the trace might error out\n            n, err = decoder.decode(b\"fpfp\")\n        else:\n            # we have something else.\n            logger.debug(\"don't have fileno or getvalue. just reading\")\n            self.fp.seek(0)\n            # UNDONE -- so much for that buffer size thing.\n            n, err = decoder.decode(self.fp.read())\n\n        self.tile = []\n        self.readonly = 0\n\n        self.load_end()\n\n        # libtiff closed the fp in a, we need to close self.fp, if possible\n        if close_self_fp:\n            self.fp.close()\n            self.fp = None  # might be shared\n\n        if err < 0:\n            raise OSError(err)\n\n        return Image.Image.load(self)\n\n    def _setup(self):\n        \"\"\"Setup this image object based on current tags\"\"\"\n\n        if 0xBC01 in self.tag_v2:\n            raise OSError(\"Windows Media Photo files not yet supported\")\n\n        # extract relevant tags\n        self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)]\n        self._planar_configuration = self.tag_v2.get(PLANAR_CONFIGURATION, 1)\n\n        # photometric is a required tag, but not everyone is reading\n        # the specification\n        photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)\n\n        # old style jpeg compression images most certainly are YCbCr\n        if self._compression == \"tiff_jpeg\":\n            photo = 6\n\n        fillorder = self.tag_v2.get(FILLORDER, 1)\n\n        logger.debug(\"*** Summary ***\")\n        logger.debug(f\"- compression: {self._compression}\")\n        logger.debug(f\"- photometric_interpretation: {photo}\")\n        logger.debug(f\"- planar_configuration: {self._planar_configuration}\")\n        logger.debug(f\"- fill_order: {fillorder}\")\n        logger.debug(f\"- YCbCr subsampling: {self.tag.get(530)}\")\n\n        # size\n        xsize = int(self.tag_v2.get(IMAGEWIDTH))\n        ysize = int(self.tag_v2.get(IMAGELENGTH))\n        self._size = xsize, ysize\n\n        logger.debug(f\"- size: {self.size}\")\n\n        sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,))\n        if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1:\n            # SAMPLEFORMAT is properly per band, so an RGB image will\n            # be (1,1,1).  But, we don't support per band pixel types,\n            # and anything more than one band is a uint8. So, just\n            # take the first element. Revisit this if adding support\n            # for more exotic images.\n            sampleFormat = (1,)\n\n        bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,))\n        extra_tuple = self.tag_v2.get(EXTRASAMPLES, ())\n        if photo in (2, 6, 8):  # RGB, YCbCr, LAB\n            bps_count = 3\n        elif photo == 5:  # CMYK\n            bps_count = 4\n        else:\n            bps_count = 1\n        bps_count += len(extra_tuple)\n        # Some files have only one value in bps_tuple,\n        # while should have more. Fix it\n        if bps_count > len(bps_tuple) and len(bps_tuple) == 1:\n            bps_tuple = bps_tuple * bps_count\n\n        samplesPerPixel = self.tag_v2.get(\n            SAMPLESPERPIXEL,\n            3 if self._compression == \"tiff_jpeg\" and photo in (2, 6) else 1,\n        )\n        if len(bps_tuple) != samplesPerPixel:\n            raise SyntaxError(\"unknown data organization\")\n\n        # mode: check photometric interpretation and bits per pixel\n        key = (\n            self.tag_v2.prefix,\n            photo,\n            sampleFormat,\n            fillorder,\n            bps_tuple,\n            extra_tuple,\n        )\n        logger.debug(f\"format key: {key}\")\n        try:\n            self.mode, rawmode = OPEN_INFO[key]\n        except KeyError as e:\n            logger.debug(\"- unsupported format\")\n            raise SyntaxError(\"unknown pixel mode\") from e\n\n        logger.debug(f\"- raw mode: {rawmode}\")\n        logger.debug(f\"- pil mode: {self.mode}\")\n\n        self.info[\"compression\"] = self._compression\n\n        xres = self.tag_v2.get(X_RESOLUTION, 1)\n        yres = self.tag_v2.get(Y_RESOLUTION, 1)\n\n        if xres and yres:\n            resunit = self.tag_v2.get(RESOLUTION_UNIT)\n            if resunit == 2:  # dots per inch\n                self.info[\"dpi\"] = (xres, yres)\n            elif resunit == 3:  # dots per centimeter. convert to dpi\n                self.info[\"dpi\"] = (xres * 2.54, yres * 2.54)\n            elif resunit is None:  # used to default to 1, but now 2)\n                self.info[\"dpi\"] = (xres, yres)\n                # For backward compatibility,\n                # we also preserve the old behavior\n                self.info[\"resolution\"] = xres, yres\n            else:  # No absolute unit of measurement\n                self.info[\"resolution\"] = xres, yres\n\n        # build tile descriptors\n        x = y = layer = 0\n        self.tile = []\n        self.use_load_libtiff = READ_LIBTIFF or self._compression != \"raw\"\n        if self.use_load_libtiff:\n            # Decoder expects entire file as one tile.\n            # There's a buffer size limit in load (64k)\n            # so large g4 images will fail if we use that\n            # function.\n            #\n            # Setup the one tile for the whole image, then\n            # use the _load_libtiff function.\n\n            # libtiff handles the fillmode for us, so 1;IR should\n            # actually be 1;I. Including the R double reverses the\n            # bits, so stripes of the image are reversed.  See\n            # https://github.com/python-pillow/Pillow/issues/279\n            if fillorder == 2:\n                # Replace fillorder with fillorder=1\n                key = key[:3] + (1,) + key[4:]\n                logger.debug(f\"format key: {key}\")\n                # this should always work, since all the\n                # fillorder==2 modes have a corresponding\n                # fillorder=1 mode\n                self.mode, rawmode = OPEN_INFO[key]\n            # libtiff always returns the bytes in native order.\n            # we're expecting image byte order. So, if the rawmode\n            # contains I;16, we need to convert from native to image\n            # byte order.\n            if rawmode == \"I;16\":\n                rawmode = \"I;16N\"\n            if \";16B\" in rawmode:\n                rawmode = rawmode.replace(\";16B\", \";16N\")\n            if \";16L\" in rawmode:\n                rawmode = rawmode.replace(\";16L\", \";16N\")\n\n            # YCbCr images with new jpeg compression with pixels in one plane\n            # unpacked straight into RGB values\n            if (\n                photo == 6\n                and self._compression == \"jpeg\"\n                and self._planar_configuration == 1\n            ):\n                rawmode = \"RGB\"\n\n            # Offset in the tile tuple is 0, we go from 0,0 to\n            # w,h, and we only do this once -- eds\n            a = (rawmode, self._compression, False, self.tag_v2.offset)\n            self.tile.append((\"libtiff\", (0, 0, xsize, ysize), 0, a))\n\n        elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:\n            # striped image\n            if STRIPOFFSETS in self.tag_v2:\n                offsets = self.tag_v2[STRIPOFFSETS]\n                h = self.tag_v2.get(ROWSPERSTRIP, ysize)\n                w = self.size[0]\n            else:\n                # tiled image\n                offsets = self.tag_v2[TILEOFFSETS]\n                w = self.tag_v2.get(322)\n                h = self.tag_v2.get(323)\n\n            for offset in offsets:\n                if x + w > xsize:\n                    stride = w * sum(bps_tuple) / 8  # bytes per line\n                else:\n                    stride = 0\n\n                tile_rawmode = rawmode\n                if self._planar_configuration == 2:\n                    # each band on it's own layer\n                    tile_rawmode = rawmode[layer]\n                    # adjust stride width accordingly\n                    stride /= bps_count\n\n                a = (tile_rawmode, int(stride), 1)\n                self.tile.append(\n                    (\n                        self._compression,\n                        (x, y, min(x + w, xsize), min(y + h, ysize)),\n                        offset,\n                        a,\n                    )\n                )\n                x = x + w\n                if x >= self.size[0]:\n                    x, y = 0, y + h\n                    if y >= self.size[1]:\n                        x = y = 0\n                        layer += 1\n        else:\n            logger.debug(\"- unsupported data organization\")\n            raise SyntaxError(\"unknown data organization\")\n\n        # Fix up info.\n        if ICCPROFILE in self.tag_v2:\n            self.info[\"icc_profile\"] = self.tag_v2[ICCPROFILE]\n\n        # fixup palette descriptor\n\n        if self.mode in [\"P\", \"PA\"]:\n            palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]\n            self.palette = ImagePalette.raw(\"RGB;L\", b\"\".join(palette))\n\n        self._tile_orientation = self.tag_v2.get(0x0112)\n\n    def _close__fp(self):\n        try:\n            if self.__fp != self.fp:\n                self.__fp.close()\n        except AttributeError:\n            pass\n        finally:\n            self.__fp = None\n\n\n#\n# --------------------------------------------------------------------\n# Write TIFF files\n\n# little endian is default except for image modes with\n# explicit big endian byte-order\n\nSAVE_INFO = {\n    # mode => rawmode, byteorder, photometrics,\n    #           sampleformat, bitspersample, extra\n    \"1\": (\"1\", II, 1, 1, (1,), None),\n    \"L\": (\"L\", II, 1, 1, (8,), None),\n    \"LA\": (\"LA\", II, 1, 1, (8, 8), 2),\n    \"P\": (\"P\", II, 3, 1, (8,), None),\n    \"PA\": (\"PA\", II, 3, 1, (8, 8), 2),\n    \"I\": (\"I;32S\", II, 1, 2, (32,), None),\n    \"I;16\": (\"I;16\", II, 1, 1, (16,), None),\n    \"I;16S\": (\"I;16S\", II, 1, 2, (16,), None),\n    \"F\": (\"F;32F\", II, 1, 3, (32,), None),\n    \"RGB\": (\"RGB\", II, 2, 1, (8, 8, 8), None),\n    \"RGBX\": (\"RGBX\", II, 2, 1, (8, 8, 8, 8), 0),\n    \"RGBA\": (\"RGBA\", II, 2, 1, (8, 8, 8, 8), 2),\n    \"CMYK\": (\"CMYK\", II, 5, 1, (8, 8, 8, 8), None),\n    \"YCbCr\": (\"YCbCr\", II, 6, 1, (8, 8, 8), None),\n    \"LAB\": (\"LAB\", II, 8, 1, (8, 8, 8), None),\n    \"I;32BS\": (\"I;32BS\", MM, 1, 2, (32,), None),\n    \"I;16B\": (\"I;16B\", MM, 1, 1, (16,), None),\n    \"I;16BS\": (\"I;16BS\", MM, 1, 2, (16,), None),\n    \"F;32BF\": (\"F;32BF\", MM, 1, 3, (32,), None),\n}\n\n\ndef _save(im, fp, filename):\n\n    try:\n        rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]\n    except KeyError as e:\n        raise OSError(f\"cannot write mode {im.mode} as TIFF\") from e\n\n    ifd = ImageFileDirectory_v2(prefix=prefix)\n\n    compression = im.encoderinfo.get(\"compression\", im.info.get(\"compression\"))\n    if compression is None:\n        compression = \"raw\"\n    elif compression == \"tiff_jpeg\":\n        # OJPEG is obsolete, so use new-style JPEG compression instead\n        compression = \"jpeg\"\n    elif compression == \"tiff_deflate\":\n        compression = \"tiff_adobe_deflate\"\n\n    libtiff = WRITE_LIBTIFF or compression != \"raw\"\n\n    # required for color libtiff images\n    ifd[PLANAR_CONFIGURATION] = getattr(im, \"_planar_configuration\", 1)\n\n    ifd[IMAGEWIDTH] = im.size[0]\n    ifd[IMAGELENGTH] = im.size[1]\n\n    # write any arbitrary tags passed in as an ImageFileDirectory\n    info = im.encoderinfo.get(\"tiffinfo\", im.encoderinfo.get(\"exif\", {}))\n    logger.debug(\"Tiffinfo Keys: %s\" % list(info))\n    if isinstance(info, ImageFileDirectory_v1):\n        info = info.to_v2()\n    for key in info:\n        if isinstance(info, Image.Exif) and key in TiffTags.TAGS_V2_GROUPS.keys():\n            ifd[key] = info.get_ifd(key)\n        else:\n            ifd[key] = info.get(key)\n        try:\n            ifd.tagtype[key] = info.tagtype[key]\n        except Exception:\n            pass  # might not be an IFD. Might not have populated type\n\n    # additions written by Greg Couch, gregc@cgl.ucsf.edu\n    # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com\n    if hasattr(im, \"tag_v2\"):\n        # preserve tags from original TIFF image file\n        for key in (\n            RESOLUTION_UNIT,\n            X_RESOLUTION,\n            Y_RESOLUTION,\n            IPTC_NAA_CHUNK,\n            PHOTOSHOP_CHUNK,\n            XMP,\n        ):\n            if key in im.tag_v2:\n                ifd[key] = im.tag_v2[key]\n                ifd.tagtype[key] = im.tag_v2.tagtype[key]\n\n    # preserve ICC profile (should also work when saving other formats\n    # which support profiles as TIFF) -- 2008-06-06 Florian Hoech\n    icc = im.encoderinfo.get(\"icc_profile\", im.info.get(\"icc_profile\"))\n    if icc:\n        ifd[ICCPROFILE] = icc\n\n    for key, name in [\n        (IMAGEDESCRIPTION, \"description\"),\n        (X_RESOLUTION, \"resolution\"),\n        (Y_RESOLUTION, \"resolution\"),\n        (X_RESOLUTION, \"x_resolution\"),\n        (Y_RESOLUTION, \"y_resolution\"),\n        (RESOLUTION_UNIT, \"resolution_unit\"),\n        (SOFTWARE, \"software\"),\n        (DATE_TIME, \"date_time\"),\n        (ARTIST, \"artist\"),\n        (COPYRIGHT, \"copyright\"),\n    ]:\n        if name in im.encoderinfo:\n            ifd[key] = im.encoderinfo[name]\n\n    dpi = im.encoderinfo.get(\"dpi\")\n    if dpi:\n        ifd[RESOLUTION_UNIT] = 2\n        ifd[X_RESOLUTION] = dpi[0]\n        ifd[Y_RESOLUTION] = dpi[1]\n\n    if bits != (1,):\n        ifd[BITSPERSAMPLE] = bits\n        if len(bits) != 1:\n            ifd[SAMPLESPERPIXEL] = len(bits)\n    if extra is not None:\n        ifd[EXTRASAMPLES] = extra\n    if format != 1:\n        ifd[SAMPLEFORMAT] = format\n\n    ifd[PHOTOMETRIC_INTERPRETATION] = photo\n\n    if im.mode in [\"P\", \"PA\"]:\n        lut = im.im.getpalette(\"RGB\", \"RGB;L\")\n        ifd[COLORMAP] = tuple(v * 256 for v in lut)\n    # data orientation\n    stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)\n    # aim for 64 KB strips when using libtiff writer\n    if libtiff:\n        rows_per_strip = min((2 ** 16 + stride - 1) // stride, im.size[1])\n    else:\n        rows_per_strip = im.size[1]\n    strip_byte_counts = stride * rows_per_strip\n    strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip\n    ifd[ROWSPERSTRIP] = rows_per_strip\n    if strip_byte_counts >= 2 ** 16:\n        ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG\n    ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (\n        stride * im.size[1] - strip_byte_counts * (strips_per_image - 1),\n    )\n    ifd[STRIPOFFSETS] = tuple(\n        range(0, strip_byte_counts * strips_per_image, strip_byte_counts)\n    )  # this is adjusted by IFD writer\n    # no compression by default:\n    ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)\n\n    if libtiff:\n        if \"quality\" in im.encoderinfo:\n            quality = im.encoderinfo[\"quality\"]\n            if not isinstance(quality, int) or quality < 0 or quality > 100:\n                raise ValueError(\"Invalid quality setting\")\n            if compression != \"jpeg\":\n                raise ValueError(\n                    \"quality setting only supported for 'jpeg' compression\"\n                )\n            ifd[JPEGQUALITY] = quality\n\n        logger.debug(\"Saving using libtiff encoder\")\n        logger.debug(\"Items: %s\" % sorted(ifd.items()))\n        _fp = 0\n        if hasattr(fp, \"fileno\"):\n            try:\n                fp.seek(0)\n                _fp = os.dup(fp.fileno())\n            except io.UnsupportedOperation:\n                pass\n\n        # optional types for non core tags\n        types = {}\n        # SAMPLEFORMAT is determined by the image format and should not be copied\n        # from legacy_ifd.\n        # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library\n        # based on the data in the strip.\n        # The other tags expect arrays with a certain length (fixed or depending on\n        # BITSPERSAMPLE, etc), passing arrays with a different length will result in\n        # segfaults. Block these tags until we add extra validation.\n        # SUBIFD may also cause a segfault.\n        blocklist = [\n            REFERENCEBLACKWHITE,\n            SAMPLEFORMAT,\n            STRIPBYTECOUNTS,\n            STRIPOFFSETS,\n            TRANSFERFUNCTION,\n            SUBIFD,\n        ]\n\n        atts = {}\n        # bits per sample is a single short in the tiff directory, not a list.\n        atts[BITSPERSAMPLE] = bits[0]\n        # Merge the ones that we have with (optional) more bits from\n        # the original file, e.g x,y resolution so that we can\n        # save(load('')) == original file.\n        legacy_ifd = {}\n        if hasattr(im, \"tag\"):\n            legacy_ifd = im.tag.to_v2()\n        for tag, value in itertools.chain(\n            ifd.items(), getattr(im, \"tag_v2\", {}).items(), legacy_ifd.items()\n        ):\n            # Libtiff can only process certain core items without adding\n            # them to the custom dictionary.\n            # Custom items are supported for int, float, unicode, string and byte\n            # values. Other types and tuples require a tagtype.\n            if tag not in TiffTags.LIBTIFF_CORE:\n                if not Image.core.libtiff_support_custom_tags:\n                    continue\n\n                if tag in ifd.tagtype:\n                    types[tag] = ifd.tagtype[tag]\n                elif not (isinstance(value, (int, float, str, bytes))):\n                    continue\n                else:\n                    type = TiffTags.lookup(tag).type\n                    if type:\n                        types[tag] = type\n            if tag not in atts and tag not in blocklist:\n                if isinstance(value, str):\n                    atts[tag] = value.encode(\"ascii\", \"replace\") + b\"\\0\"\n                elif isinstance(value, IFDRational):\n                    atts[tag] = float(value)\n                else:\n                    atts[tag] = value\n\n        logger.debug(\"Converted items: %s\" % sorted(atts.items()))\n\n        # libtiff always expects the bytes in native order.\n        # we're storing image byte order. So, if the rawmode\n        # contains I;16, we need to convert from native to image\n        # byte order.\n        if im.mode in (\"I;16B\", \"I;16\"):\n            rawmode = \"I;16N\"\n\n        # Pass tags as sorted list so that the tags are set in a fixed order.\n        # This is required by libtiff for some tags. For example, the JPEGQUALITY\n        # pseudo tag requires that the COMPRESS tag was already set.\n        tags = list(atts.items())\n        tags.sort()\n        a = (rawmode, compression, _fp, filename, tags, types)\n        e = Image._getencoder(im.mode, \"libtiff\", a, im.encoderconfig)\n        e.setimage(im.im, (0, 0) + im.size)\n        while True:\n            # undone, change to self.decodermaxblock:\n            l, s, d = e.encode(16 * 1024)\n            if not _fp:\n                fp.write(d)\n            if s:\n                break\n        if s < 0:\n            raise OSError(f\"encoder error {s} when writing image file\")\n\n    else:\n        offset = ifd.save(fp)\n\n        ImageFile._save(\n            im, fp, [(\"raw\", (0, 0) + im.size, offset, (rawmode, stride, 1))]\n        )\n\n    # -- helper for multi-page save --\n    if \"_debug_multipage\" in im.encoderinfo:\n        # just to access o32 and o16 (using correct byte order)\n        im._debug_multipage = ifd\n\n\nclass AppendingTiffWriter:\n    fieldSizes = [\n        0,  # None\n        1,  # byte\n        1,  # ascii\n        2,  # short\n        4,  # long\n        8,  # rational\n        1,  # sbyte\n        1,  # undefined\n        2,  # sshort\n        4,  # slong\n        8,  # srational\n        4,  # float\n        8,  # double\n    ]\n\n    #    StripOffsets = 273\n    #    FreeOffsets = 288\n    #    TileOffsets = 324\n    #    JPEGQTables = 519\n    #    JPEGDCTables = 520\n    #    JPEGACTables = 521\n    Tags = {273, 288, 324, 519, 520, 521}\n\n    def __init__(self, fn, new=False):\n        if hasattr(fn, \"read\"):\n            self.f = fn\n            self.close_fp = False\n        else:\n            self.name = fn\n            self.close_fp = True\n            try:\n                self.f = open(fn, \"w+b\" if new else \"r+b\")\n            except OSError:\n                self.f = open(fn, \"w+b\")\n        self.beginning = self.f.tell()\n        self.setup()\n\n    def setup(self):\n        # Reset everything.\n        self.f.seek(self.beginning, os.SEEK_SET)\n\n        self.whereToWriteNewIFDOffset = None\n        self.offsetOfNewPage = 0\n\n        self.IIMM = IIMM = self.f.read(4)\n        if not IIMM:\n            # empty file - first page\n            self.isFirst = True\n            return\n\n        self.isFirst = False\n        if IIMM == b\"II\\x2a\\x00\":\n            self.setEndian(\"<\")\n        elif IIMM == b\"MM\\x00\\x2a\":\n            self.setEndian(\">\")\n        else:\n            raise RuntimeError(\"Invalid TIFF file header\")\n\n        self.skipIFDs()\n        self.goToEnd()\n\n    def finalize(self):\n        if self.isFirst:\n            return\n\n        # fix offsets\n        self.f.seek(self.offsetOfNewPage)\n\n        IIMM = self.f.read(4)\n        if not IIMM:\n            # raise RuntimeError(\"nothing written into new page\")\n            # Make it easy to finish a frame without committing to a new one.\n            return\n\n        if IIMM != self.IIMM:\n            raise RuntimeError(\"IIMM of new page doesn't match IIMM of first page\")\n\n        IFDoffset = self.readLong()\n        IFDoffset += self.offsetOfNewPage\n        self.f.seek(self.whereToWriteNewIFDOffset)\n        self.writeLong(IFDoffset)\n        self.f.seek(IFDoffset)\n        self.fixIFD()\n\n    def newFrame(self):\n        # Call this to finish a frame.\n        self.finalize()\n        self.setup()\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        if self.close_fp:\n            self.close()\n        return False\n\n    def tell(self):\n        return self.f.tell() - self.offsetOfNewPage\n\n    def seek(self, offset, whence=io.SEEK_SET):\n        if whence == os.SEEK_SET:\n            offset += self.offsetOfNewPage\n\n        self.f.seek(offset, whence)\n        return self.tell()\n\n    def goToEnd(self):\n        self.f.seek(0, os.SEEK_END)\n        pos = self.f.tell()\n\n        # pad to 16 byte boundary\n        padBytes = 16 - pos % 16\n        if 0 < padBytes < 16:\n            self.f.write(bytes(padBytes))\n        self.offsetOfNewPage = self.f.tell()\n\n    def setEndian(self, endian):\n        self.endian = endian\n        self.longFmt = self.endian + \"L\"\n        self.shortFmt = self.endian + \"H\"\n        self.tagFormat = self.endian + \"HHL\"\n\n    def skipIFDs(self):\n        while True:\n            IFDoffset = self.readLong()\n            if IFDoffset == 0:\n                self.whereToWriteNewIFDOffset = self.f.tell() - 4\n                break\n\n            self.f.seek(IFDoffset)\n            numTags = self.readShort()\n            self.f.seek(numTags * 12, os.SEEK_CUR)\n\n    def write(self, data):\n        return self.f.write(data)\n\n    def readShort(self):\n        (value,) = struct.unpack(self.shortFmt, self.f.read(2))\n        return value\n\n    def readLong(self):\n        (value,) = struct.unpack(self.longFmt, self.f.read(4))\n        return value\n\n    def rewriteLastShortToLong(self, value):\n        self.f.seek(-2, os.SEEK_CUR)\n        bytesWritten = self.f.write(struct.pack(self.longFmt, value))\n        if bytesWritten is not None and bytesWritten != 4:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 4\")\n\n    def rewriteLastShort(self, value):\n        self.f.seek(-2, os.SEEK_CUR)\n        bytesWritten = self.f.write(struct.pack(self.shortFmt, value))\n        if bytesWritten is not None and bytesWritten != 2:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 2\")\n\n    def rewriteLastLong(self, value):\n        self.f.seek(-4, os.SEEK_CUR)\n        bytesWritten = self.f.write(struct.pack(self.longFmt, value))\n        if bytesWritten is not None and bytesWritten != 4:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 4\")\n\n    def writeShort(self, value):\n        bytesWritten = self.f.write(struct.pack(self.shortFmt, value))\n        if bytesWritten is not None and bytesWritten != 2:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 2\")\n\n    def writeLong(self, value):\n        bytesWritten = self.f.write(struct.pack(self.longFmt, value))\n        if bytesWritten is not None and bytesWritten != 4:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 4\")\n\n    def close(self):\n        self.finalize()\n        self.f.close()\n\n    def fixIFD(self):\n        numTags = self.readShort()\n\n        for i in range(numTags):\n            tag, fieldType, count = struct.unpack(self.tagFormat, self.f.read(8))\n\n            fieldSize = self.fieldSizes[fieldType]\n            totalSize = fieldSize * count\n            isLocal = totalSize <= 4\n            if not isLocal:\n                offset = self.readLong()\n                offset += self.offsetOfNewPage\n                self.rewriteLastLong(offset)\n\n            if tag in self.Tags:\n                curPos = self.f.tell()\n\n                if isLocal:\n                    self.fixOffsets(\n                        count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)\n                    )\n                    self.f.seek(curPos + 4)\n                else:\n                    self.f.seek(offset)\n                    self.fixOffsets(\n                        count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)\n                    )\n                    self.f.seek(curPos)\n\n                offset = curPos = None\n\n            elif isLocal:\n                # skip the locally stored value that is not an offset\n                self.f.seek(4, os.SEEK_CUR)\n\n    def fixOffsets(self, count, isShort=False, isLong=False):\n        if not isShort and not isLong:\n            raise RuntimeError(\"offset is neither short nor long\")\n\n        for i in range(count):\n            offset = self.readShort() if isShort else self.readLong()\n            offset += self.offsetOfNewPage\n            if isShort and offset >= 65536:\n                # offset is now too large - we must convert shorts to longs\n                if count != 1:\n                    raise RuntimeError(\"not implemented\")  # XXX TODO\n\n                # simple case - the offset is just one and therefore it is\n                # local (not referenced with another offset)\n                self.rewriteLastShortToLong(offset)\n                self.f.seek(-10, os.SEEK_CUR)\n                self.writeShort(TiffTags.LONG)  # rewrite the type to LONG\n                self.f.seek(8, os.SEEK_CUR)\n            elif isShort:\n                self.rewriteLastShort(offset)\n            else:\n                self.rewriteLastLong(offset)\n\n\ndef _save_all(im, fp, filename):\n    encoderinfo = im.encoderinfo.copy()\n    encoderconfig = im.encoderconfig\n    append_images = list(encoderinfo.get(\"append_images\", []))\n    if not hasattr(im, \"n_frames\") and not append_images:\n        return _save(im, fp, filename)\n\n    cur_idx = im.tell()\n    try:\n        with AppendingTiffWriter(fp) as tf:\n            for ims in [im] + append_images:\n                ims.encoderinfo = encoderinfo\n                ims.encoderconfig = encoderconfig\n                if not hasattr(ims, \"n_frames\"):\n                    nfr = 1\n                else:\n                    nfr = ims.n_frames\n\n                for idx in range(nfr):\n                    ims.seek(idx)\n                    ims.load()\n                    _save(ims, tf, filename)\n                    tf.newFrame()\n    finally:\n        im.seek(cur_idx)\n\n\n#\n# --------------------------------------------------------------------\n# Register\n\nImage.register_open(TiffImageFile.format, TiffImageFile, _accept)\nImage.register_save(TiffImageFile.format, _save)\nImage.register_save_all(TiffImageFile.format, _save_all)\n\nImage.register_extensions(TiffImageFile.format, [\".tif\", \".tiff\"])\n\nImage.register_mime(TiffImageFile.format, \"image/tiff\")\n",
            "header": {
                "file": {
                    "path": "src/PIL/TiffImagePlugin.py"
                },
                "misc_line": null
            },
            "index_line": {
                "old_commit_hash": "a5e2bb53d",
                "new_commit_hash": "7a581bd31",
                "mode": "100644"
            },
            "is_binary_file": false,
            "binary_line": null,
            "minus_file": {
                "path": "a/src/PIL/TiffImagePlugin.py"
            },
            "plus_file": {
                "path": "b/src/PIL/TiffImagePlugin.py"
            },
            "hunks": [
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1142,
                            "length": 6
                        },
                        "new_range": {
                            "start": 1142,
                            "length": 17
                        },
                        "section": "class TiffImageFile(ImageFile.ImageFile):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "        if not self.is_animated:",
                                "type": "context"
                            },
                            {
                                "content": "            self._close_exclusive_fp_after_loading = True",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "            # reset buffered io handle in case fp",
                                "type": "added"
                            },
                            {
                                "content": "            # was passed to libtiff, invalidating the buffer",
                                "type": "added"
                            },
                            {
                                "content": "            self.fp.tell()",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "            # load IFD data from fp before it is closed",
                                "type": "added"
                            },
                            {
                                "content": "            exif = self.getexif()",
                                "type": "added"
                            },
                            {
                                "content": "            for key in TiffTags.TAGS_V2_GROUPS.keys():",
                                "type": "added"
                            },
                            {
                                "content": "                if key not in exif:",
                                "type": "added"
                            },
                            {
                                "content": "                    continue",
                                "type": "added"
                            },
                            {
                                "content": "                exif.get_ifd(key)",
                                "type": "added"
                            },
                            {
                                "content": "",
                                "type": "added"
                            },
                            {
                                "content": "    def _load_libtiff(self):",
                                "type": "context"
                            },
                            {
                                "content": "        \"\"\"Overload method triggered when we detect a compressed tiff",
                                "type": "context"
                            },
                            {
                                "content": "        Calls out to libtiff\"\"\"",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/TiffImagePlugin.py",
                            "type": "class",
                            "name": "TiffImageFile",
                            "content": "class TiffImageFile(ImageFile.ImageFile):\n\n    format = \"TIFF\"\n    format_description = \"Adobe TIFF\"\n    _close_exclusive_fp_after_loading = False\n\n    def __init__(self, fp=None, filename=None):\n        self.tag_v2 = None\n        \"\"\" Image file directory (tag dictionary) \"\"\"\n\n        self.tag = None\n        \"\"\" Legacy tag entries \"\"\"\n\n        super().__init__(fp, filename)\n\n    def _open(self):\n        \"\"\"Open the first image in a TIFF file\"\"\"\n\n        # Header\n        ifh = self.fp.read(8)\n\n        self.tag_v2 = ImageFileDirectory_v2(ifh)\n\n        # legacy IFD entries will be filled in later\n        self.ifd = None\n\n        # setup frame pointers\n        self.__first = self.__next = self.tag_v2.next\n        self.__frame = -1\n        self.__fp = self.fp\n        self._frame_pos = []\n        self._n_frames = None\n\n        logger.debug(\"*** TiffImageFile._open ***\")\n        logger.debug(f\"- __first: {self.__first}\")\n        logger.debug(f\"- ifh: {repr(ifh)}\")  # Use repr to avoid str(bytes)\n\n        # and load the first frame\n        self._seek(0)\n\n    @property\n    def n_frames(self):\n        if self._n_frames is None:\n            current = self.tell()\n            self._seek(len(self._frame_pos))\n            while self._n_frames is None:\n                self._seek(self.tell() + 1)\n            self.seek(current)\n        return self._n_frames\n\n    def seek(self, frame):\n        \"\"\"Select a given frame as current image\"\"\"\n        if not self._seek_check(frame):\n            return\n        self._seek(frame)\n        # Create a new core image object on second and\n        # subsequent frames in the image. Image may be\n        # different size/mode.\n        Image._decompression_bomb_check(self.size)\n        self.im = Image.core.new(self.mode, self.size)\n\n    def _seek(self, frame):\n        self.fp = self.__fp\n\n        # reset buffered io handle in case fp\n        # was passed to libtiff, invalidating the buffer\n        self.fp.tell()\n\n        while len(self._frame_pos) <= frame:\n            if not self.__next:\n                raise EOFError(\"no more images in TIFF file\")\n            logger.debug(\n                f\"Seeking to frame {frame}, on frame {self.__frame}, \"\n                f\"__next {self.__next}, location: {self.fp.tell()}\"\n            )\n            self.fp.seek(self.__next)\n            self._frame_pos.append(self.__next)\n            logger.debug(\"Loading tags, location: %s\" % self.fp.tell())\n            self.tag_v2.load(self.fp)\n            if self.tag_v2.next in self._frame_pos:\n                # This IFD has already been processed\n                # Declare this to be the end of the image\n                self.__next = 0\n            else:\n                self.__next = self.tag_v2.next\n            if self.__next == 0:\n                self._n_frames = frame + 1\n            if len(self._frame_pos) == 1:\n                self.is_animated = self.__next != 0\n            self.__frame += 1\n        self.fp.seek(self._frame_pos[frame])\n        self.tag_v2.load(self.fp)\n        # fill the legacy tag/ifd entries\n        self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)\n        self.__frame = frame\n        self._setup()\n\n    def tell(self):\n        \"\"\"Return the current frame number\"\"\"\n        return self.__frame\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n        return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {}\n\n    def load(self):\n        if self.tile and self.use_load_libtiff:\n            return self._load_libtiff()\n        return super().load()\n\n    def load_end(self):\n        if self._tile_orientation:\n            method = {\n                2: Image.FLIP_LEFT_RIGHT,\n                3: Image.ROTATE_180,\n                4: Image.FLIP_TOP_BOTTOM,\n                5: Image.TRANSPOSE,\n                6: Image.ROTATE_270,\n                7: Image.TRANSVERSE,\n                8: Image.ROTATE_90,\n            }.get(self._tile_orientation)\n            if method is not None:\n                self.im = self.im.transpose(method)\n                self._size = self.im.size\n\n        # allow closing if we're on the first frame, there's no next\n        # This is the ImageFile.load path only, libtiff specific below.\n        if not self.is_animated:\n            self._close_exclusive_fp_after_loading = True\n\n            # reset buffered io handle in case fp\n            # was passed to libtiff, invalidating the buffer\n            self.fp.tell()\n\n            # load IFD data from fp before it is closed\n            exif = self.getexif()\n            for key in TiffTags.TAGS_V2_GROUPS.keys():\n                if key not in exif:\n                    continue\n                exif.get_ifd(key)\n\n    def _load_libtiff(self):\n        \"\"\"Overload method triggered when we detect a compressed tiff\n        Calls out to libtiff\"\"\"\n\n        Image.Image.load(self)\n\n        self.load_prepare()\n\n        if not len(self.tile) == 1:\n            raise OSError(\"Not exactly one tile\")\n\n        # (self._compression, (extents tuple),\n        #   0, (rawmode, self._compression, fp))\n        extents = self.tile[0][1]\n        args = list(self.tile[0][3])\n\n        # To be nice on memory footprint, if there's a\n        # file descriptor, use that instead of reading\n        # into a string in python.\n        # libtiff closes the file descriptor, so pass in a dup.\n        try:\n            fp = hasattr(self.fp, \"fileno\") and os.dup(self.fp.fileno())\n            # flush the file descriptor, prevents error on pypy 2.4+\n            # should also eliminate the need for fp.tell\n            # in _seek\n            if hasattr(self.fp, \"flush\"):\n                self.fp.flush()\n        except OSError:\n            # io.BytesIO have a fileno, but returns an OSError if\n            # it doesn't use a file descriptor.\n            fp = False\n\n        if fp:\n            args[2] = fp\n\n        decoder = Image._getdecoder(\n            self.mode, \"libtiff\", tuple(args), self.decoderconfig\n        )\n        try:\n            decoder.setimage(self.im, extents)\n        except ValueError as e:\n            raise OSError(\"Couldn't set the image\") from e\n\n        close_self_fp = self._exclusive_fp and not self.is_animated\n        if hasattr(self.fp, \"getvalue\"):\n            # We've got a stringio like thing passed in. Yay for all in memory.\n            # The decoder needs the entire file in one shot, so there's not\n            # a lot we can do here other than give it the entire file.\n            # unless we could do something like get the address of the\n            # underlying string for stringio.\n            #\n            # Rearranging for supporting byteio items, since they have a fileno\n            # that returns an OSError if there's no underlying fp. Easier to\n            # deal with here by reordering.\n            logger.debug(\"have getvalue. just sending in a string from getvalue\")\n            n, err = decoder.decode(self.fp.getvalue())\n        elif fp:\n            # we've got a actual file on disk, pass in the fp.\n            logger.debug(\"have fileno, calling fileno version of the decoder.\")\n            if not close_self_fp:\n                self.fp.seek(0)\n            # 4 bytes, otherwise the trace might error out\n            n, err = decoder.decode(b\"fpfp\")\n        else:\n            # we have something else.\n            logger.debug(\"don't have fileno or getvalue. just reading\")\n            self.fp.seek(0)\n            # UNDONE -- so much for that buffer size thing.\n            n, err = decoder.decode(self.fp.read())\n\n        self.tile = []\n        self.readonly = 0\n\n        self.load_end()\n\n        # libtiff closed the fp in a, we need to close self.fp, if possible\n        if close_self_fp:\n            self.fp.close()\n            self.fp = None  # might be shared\n\n        if err < 0:\n            raise OSError(err)\n\n        return Image.Image.load(self)\n\n    def _setup(self):\n        \"\"\"Setup this image object based on current tags\"\"\"\n\n        if 0xBC01 in self.tag_v2:\n            raise OSError(\"Windows Media Photo files not yet supported\")\n\n        # extract relevant tags\n        self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)]\n        self._planar_configuration = self.tag_v2.get(PLANAR_CONFIGURATION, 1)\n\n        # photometric is a required tag, but not everyone is reading\n        # the specification\n        photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)\n\n        # old style jpeg compression images most certainly are YCbCr\n        if self._compression == \"tiff_jpeg\":\n            photo = 6\n\n        fillorder = self.tag_v2.get(FILLORDER, 1)\n\n        logger.debug(\"*** Summary ***\")\n        logger.debug(f\"- compression: {self._compression}\")\n        logger.debug(f\"- photometric_interpretation: {photo}\")\n        logger.debug(f\"- planar_configuration: {self._planar_configuration}\")\n        logger.debug(f\"- fill_order: {fillorder}\")\n        logger.debug(f\"- YCbCr subsampling: {self.tag.get(530)}\")\n\n        # size\n        xsize = int(self.tag_v2.get(IMAGEWIDTH))\n        ysize = int(self.tag_v2.get(IMAGELENGTH))\n        self._size = xsize, ysize\n\n        logger.debug(f\"- size: {self.size}\")\n\n        sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,))\n        if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1:\n            # SAMPLEFORMAT is properly per band, so an RGB image will\n            # be (1,1,1).  But, we don't support per band pixel types,\n            # and anything more than one band is a uint8. So, just\n            # take the first element. Revisit this if adding support\n            # for more exotic images.\n            sampleFormat = (1,)\n\n        bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,))\n        extra_tuple = self.tag_v2.get(EXTRASAMPLES, ())\n        if photo in (2, 6, 8):  # RGB, YCbCr, LAB\n            bps_count = 3\n        elif photo == 5:  # CMYK\n            bps_count = 4\n        else:\n            bps_count = 1\n        bps_count += len(extra_tuple)\n        # Some files have only one value in bps_tuple,\n        # while should have more. Fix it\n        if bps_count > len(bps_tuple) and len(bps_tuple) == 1:\n            bps_tuple = bps_tuple * bps_count\n\n        samplesPerPixel = self.tag_v2.get(\n            SAMPLESPERPIXEL,\n            3 if self._compression == \"tiff_jpeg\" and photo in (2, 6) else 1,\n        )\n        if len(bps_tuple) != samplesPerPixel:\n            raise SyntaxError(\"unknown data organization\")\n\n        # mode: check photometric interpretation and bits per pixel\n        key = (\n            self.tag_v2.prefix,\n            photo,\n            sampleFormat,\n            fillorder,\n            bps_tuple,\n            extra_tuple,\n        )\n        logger.debug(f\"format key: {key}\")\n        try:\n            self.mode, rawmode = OPEN_INFO[key]\n        except KeyError as e:\n            logger.debug(\"- unsupported format\")\n            raise SyntaxError(\"unknown pixel mode\") from e\n\n        logger.debug(f\"- raw mode: {rawmode}\")\n        logger.debug(f\"- pil mode: {self.mode}\")\n\n        self.info[\"compression\"] = self._compression\n\n        xres = self.tag_v2.get(X_RESOLUTION, 1)\n        yres = self.tag_v2.get(Y_RESOLUTION, 1)\n\n        if xres and yres:\n            resunit = self.tag_v2.get(RESOLUTION_UNIT)\n            if resunit == 2:  # dots per inch\n                self.info[\"dpi\"] = (xres, yres)\n            elif resunit == 3:  # dots per centimeter. convert to dpi\n                self.info[\"dpi\"] = (xres * 2.54, yres * 2.54)\n            elif resunit is None:  # used to default to 1, but now 2)\n                self.info[\"dpi\"] = (xres, yres)\n                # For backward compatibility,\n                # we also preserve the old behavior\n                self.info[\"resolution\"] = xres, yres\n            else:  # No absolute unit of measurement\n                self.info[\"resolution\"] = xres, yres\n\n        # build tile descriptors\n        x = y = layer = 0\n        self.tile = []\n        self.use_load_libtiff = READ_LIBTIFF or self._compression != \"raw\"\n        if self.use_load_libtiff:\n            # Decoder expects entire file as one tile.\n            # There's a buffer size limit in load (64k)\n            # so large g4 images will fail if we use that\n            # function.\n            #\n            # Setup the one tile for the whole image, then\n            # use the _load_libtiff function.\n\n            # libtiff handles the fillmode for us, so 1;IR should\n            # actually be 1;I. Including the R double reverses the\n            # bits, so stripes of the image are reversed.  See\n            # https://github.com/python-pillow/Pillow/issues/279\n            if fillorder == 2:\n                # Replace fillorder with fillorder=1\n                key = key[:3] + (1,) + key[4:]\n                logger.debug(f\"format key: {key}\")\n                # this should always work, since all the\n                # fillorder==2 modes have a corresponding\n                # fillorder=1 mode\n                self.mode, rawmode = OPEN_INFO[key]\n            # libtiff always returns the bytes in native order.\n            # we're expecting image byte order. So, if the rawmode\n            # contains I;16, we need to convert from native to image\n            # byte order.\n            if rawmode == \"I;16\":\n                rawmode = \"I;16N\"\n            if \";16B\" in rawmode:\n                rawmode = rawmode.replace(\";16B\", \";16N\")\n            if \";16L\" in rawmode:\n                rawmode = rawmode.replace(\";16L\", \";16N\")\n\n            # YCbCr images with new jpeg compression with pixels in one plane\n            # unpacked straight into RGB values\n            if (\n                photo == 6\n                and self._compression == \"jpeg\"\n                and self._planar_configuration == 1\n            ):\n                rawmode = \"RGB\"\n\n            # Offset in the tile tuple is 0, we go from 0,0 to\n            # w,h, and we only do this once -- eds\n            a = (rawmode, self._compression, False, self.tag_v2.offset)\n            self.tile.append((\"libtiff\", (0, 0, xsize, ysize), 0, a))\n\n        elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:\n            # striped image\n            if STRIPOFFSETS in self.tag_v2:\n                offsets = self.tag_v2[STRIPOFFSETS]\n                h = self.tag_v2.get(ROWSPERSTRIP, ysize)\n                w = self.size[0]\n            else:\n                # tiled image\n                offsets = self.tag_v2[TILEOFFSETS]\n                w = self.tag_v2.get(322)\n                h = self.tag_v2.get(323)\n\n            for offset in offsets:\n                if x + w > xsize:\n                    stride = w * sum(bps_tuple) / 8  # bytes per line\n                else:\n                    stride = 0\n\n                tile_rawmode = rawmode\n                if self._planar_configuration == 2:\n                    # each band on it's own layer\n                    tile_rawmode = rawmode[layer]\n                    # adjust stride width accordingly\n                    stride /= bps_count\n\n                a = (tile_rawmode, int(stride), 1)\n                self.tile.append(\n                    (\n                        self._compression,\n                        (x, y, min(x + w, xsize), min(y + h, ysize)),\n                        offset,\n                        a,\n                    )\n                )\n                x = x + w\n                if x >= self.size[0]:\n                    x, y = 0, y + h\n                    if y >= self.size[1]:\n                        x = y = 0\n                        layer += 1\n        else:\n            logger.debug(\"- unsupported data organization\")\n            raise SyntaxError(\"unknown data organization\")\n\n        # Fix up info.\n        if ICCPROFILE in self.tag_v2:\n            self.info[\"icc_profile\"] = self.tag_v2[ICCPROFILE]\n\n        # fixup palette descriptor\n\n        if self.mode in [\"P\", \"PA\"]:\n            palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]\n            self.palette = ImagePalette.raw(\"RGB;L\", b\"\".join(palette))\n\n        self._tile_orientation = self.tag_v2.get(0x0112)\n\n    def _close__fp(self):\n        try:\n            if self.__fp != self.fp:\n                self.__fp.close()\n        except AttributeError:\n            pass\n        finally:\n            self.__fp = None",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 1011,
                            "end_lineno": 1456,
                            "parent": null
                        },
                        {
                            "file_name": "src/PIL/TiffImagePlugin.py",
                            "type": "method",
                            "name": "TiffImageFile.load_end",
                            "content": "class TiffImageFile(ImageFile.ImageFile):\n\n    format = \"TIFF\"\n    format_description = \"Adobe TIFF\"\n    _close_exclusive_fp_after_loading = False\n\n    def __init__(self, fp=None, filename=None):\n        self.tag_v2 = None\n        \"\"\" Image file directory (tag dictionary) \"\"\"\n\n        self.tag = None\n        \"\"\" Legacy tag entries \"\"\"\n\n        super().__init__(fp, filename)\n\n    def _open(self):\n        \"\"\"Open the first image in a TIFF file\"\"\"\n\n        # Header\n        ifh = self.fp.read(8)\n\n        self.tag_v2 = ImageFileDirectory_v2(ifh)\n\n        # legacy IFD entries will be filled in later\n        self.ifd = None\n\n        # setup frame pointers\n        self.__first = self.__next = self.tag_v2.next\n        self.__frame = -1\n        self.__fp = self.fp\n        self._frame_pos = []\n        self._n_frames = None\n\n        logger.debug(\"*** TiffImageFile._open ***\")\n        logger.debug(f\"- __first: {self.__first}\")\n        logger.debug(f\"- ifh: {repr(ifh)}\")  # Use repr to avoid str(bytes)\n\n        # and load the first frame\n        self._seek(0)\n\n    @property\n    def n_frames(self):\n        if self._n_frames is None:\n            current = self.tell()\n            self._seek(len(self._frame_pos))\n            while self._n_frames is None:\n                self._seek(self.tell() + 1)\n            self.seek(current)\n        return self._n_frames\n\n    def seek(self, frame):\n        \"\"\"Select a given frame as current image\"\"\"\n        if not self._seek_check(frame):\n            return\n        self._seek(frame)\n        # Create a new core image object on second and\n        # subsequent frames in the image. Image may be\n        # different size/mode.\n        Image._decompression_bomb_check(self.size)\n        self.im = Image.core.new(self.mode, self.size)\n\n    def _seek(self, frame):\n        self.fp = self.__fp\n\n        # reset buffered io handle in case fp\n        # was passed to libtiff, invalidating the buffer\n        self.fp.tell()\n\n        while len(self._frame_pos) <= frame:\n            if not self.__next:\n                raise EOFError(\"no more images in TIFF file\")\n            logger.debug(\n                f\"Seeking to frame {frame}, on frame {self.__frame}, \"\n                f\"__next {self.__next}, location: {self.fp.tell()}\"\n            )\n            self.fp.seek(self.__next)\n            self._frame_pos.append(self.__next)\n            logger.debug(\"Loading tags, location: %s\" % self.fp.tell())\n            self.tag_v2.load(self.fp)\n            if self.tag_v2.next in self._frame_pos:\n                # This IFD has already been processed\n                # Declare this to be the end of the image\n                self.__next = 0\n            else:\n                self.__next = self.tag_v2.next\n            if self.__next == 0:\n                self._n_frames = frame + 1\n            if len(self._frame_pos) == 1:\n                self.is_animated = self.__next != 0\n            self.__frame += 1\n        self.fp.seek(self._frame_pos[frame])\n        self.tag_v2.load(self.fp)\n        # fill the legacy tag/ifd entries\n        self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)\n        self.__frame = frame\n        self._setup()\n\n    def tell(self):\n        \"\"\"Return the current frame number\"\"\"\n        return self.__frame\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n        return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {}\n\n    def load(self):\n        if self.tile and self.use_load_libtiff:\n            return self._load_libtiff()\n        return super().load()\n\n    def load_end(self):\n        if self._tile_orientation:\n            method = {\n                2: Image.FLIP_LEFT_RIGHT,\n                3: Image.ROTATE_180,\n                4: Image.FLIP_TOP_BOTTOM,\n                5: Image.TRANSPOSE,\n                6: Image.ROTATE_270,\n                7: Image.TRANSVERSE,\n                8: Image.ROTATE_90,\n            }.get(self._tile_orientation)\n            if method is not None:\n                self.im = self.im.transpose(method)\n                self._size = self.im.size\n\n        # allow closing if we're on the first frame, there's no next\n        # This is the ImageFile.load path only, libtiff specific below.\n        if not self.is_animated:\n            self._close_exclusive_fp_after_loading = True\n\n            # reset buffered io handle in case fp\n            # was passed to libtiff, invalidating the buffer\n            self.fp.tell()\n\n            # load IFD data from fp before it is closed\n            exif = self.getexif()\n            for key in TiffTags.TAGS_V2_GROUPS.keys():\n                if key not in exif:\n                    continue\n                exif.get_ifd(key)\n\n    def _load_libtiff(self):\n        \"\"\"Overload method triggered when we detect a compressed tiff\n        Calls out to libtiff\"\"\"\n\n        Image.Image.load(self)\n\n        self.load_prepare()\n\n        if not len(self.tile) == 1:\n            raise OSError(\"Not exactly one tile\")\n\n        # (self._compression, (extents tuple),\n        #   0, (rawmode, self._compression, fp))\n        extents = self.tile[0][1]\n        args = list(self.tile[0][3])\n\n        # To be nice on memory footprint, if there's a\n        # file descriptor, use that instead of reading\n        # into a string in python.\n        # libtiff closes the file descriptor, so pass in a dup.\n        try:\n            fp = hasattr(self.fp, \"fileno\") and os.dup(self.fp.fileno())\n            # flush the file descriptor, prevents error on pypy 2.4+\n            # should also eliminate the need for fp.tell\n            # in _seek\n            if hasattr(self.fp, \"flush\"):\n                self.fp.flush()\n        except OSError:\n            # io.BytesIO have a fileno, but returns an OSError if\n            # it doesn't use a file descriptor.\n            fp = False\n\n        if fp:\n            args[2] = fp\n\n        decoder = Image._getdecoder(\n            self.mode, \"libtiff\", tuple(args), self.decoderconfig\n        )\n        try:\n            decoder.setimage(self.im, extents)\n        except ValueError as e:\n            raise OSError(\"Couldn't set the image\") from e\n\n        close_self_fp = self._exclusive_fp and not self.is_animated\n        if hasattr(self.fp, \"getvalue\"):\n            # We've got a stringio like thing passed in. Yay for all in memory.\n            # The decoder needs the entire file in one shot, so there's not\n            # a lot we can do here other than give it the entire file.\n            # unless we could do something like get the address of the\n            # underlying string for stringio.\n            #\n            # Rearranging for supporting byteio items, since they have a fileno\n            # that returns an OSError if there's no underlying fp. Easier to\n            # deal with here by reordering.\n            logger.debug(\"have getvalue. just sending in a string from getvalue\")\n            n, err = decoder.decode(self.fp.getvalue())\n        elif fp:\n            # we've got a actual file on disk, pass in the fp.\n            logger.debug(\"have fileno, calling fileno version of the decoder.\")\n            if not close_self_fp:\n                self.fp.seek(0)\n            # 4 bytes, otherwise the trace might error out\n            n, err = decoder.decode(b\"fpfp\")\n        else:\n            # we have something else.\n            logger.debug(\"don't have fileno or getvalue. just reading\")\n            self.fp.seek(0)\n            # UNDONE -- so much for that buffer size thing.\n            n, err = decoder.decode(self.fp.read())\n\n        self.tile = []\n        self.readonly = 0\n\n        self.load_end()\n\n        # libtiff closed the fp in a, we need to close self.fp, if possible\n        if close_self_fp:\n            self.fp.close()\n            self.fp = None  # might be shared\n\n        if err < 0:\n            raise OSError(err)\n\n        return Image.Image.load(self)\n\n    def _setup(self):\n        \"\"\"Setup this image object based on current tags\"\"\"\n\n        if 0xBC01 in self.tag_v2:\n            raise OSError(\"Windows Media Photo files not yet supported\")\n\n        # extract relevant tags\n        self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)]\n        self._planar_configuration = self.tag_v2.get(PLANAR_CONFIGURATION, 1)\n\n        # photometric is a required tag, but not everyone is reading\n        # the specification\n        photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)\n\n        # old style jpeg compression images most certainly are YCbCr\n        if self._compression == \"tiff_jpeg\":\n            photo = 6\n\n        fillorder = self.tag_v2.get(FILLORDER, 1)\n\n        logger.debug(\"*** Summary ***\")\n        logger.debug(f\"- compression: {self._compression}\")\n        logger.debug(f\"- photometric_interpretation: {photo}\")\n        logger.debug(f\"- planar_configuration: {self._planar_configuration}\")\n        logger.debug(f\"- fill_order: {fillorder}\")\n        logger.debug(f\"- YCbCr subsampling: {self.tag.get(530)}\")\n\n        # size\n        xsize = int(self.tag_v2.get(IMAGEWIDTH))\n        ysize = int(self.tag_v2.get(IMAGELENGTH))\n        self._size = xsize, ysize\n\n        logger.debug(f\"- size: {self.size}\")\n\n        sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,))\n        if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1:\n            # SAMPLEFORMAT is properly per band, so an RGB image will\n            # be (1,1,1).  But, we don't support per band pixel types,\n            # and anything more than one band is a uint8. So, just\n            # take the first element. Revisit this if adding support\n            # for more exotic images.\n            sampleFormat = (1,)\n\n        bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,))\n        extra_tuple = self.tag_v2.get(EXTRASAMPLES, ())\n        if photo in (2, 6, 8):  # RGB, YCbCr, LAB\n            bps_count = 3\n        elif photo == 5:  # CMYK\n            bps_count = 4\n        else:\n            bps_count = 1\n        bps_count += len(extra_tuple)\n        # Some files have only one value in bps_tuple,\n        # while should have more. Fix it\n        if bps_count > len(bps_tuple) and len(bps_tuple) == 1:\n            bps_tuple = bps_tuple * bps_count\n\n        samplesPerPixel = self.tag_v2.get(\n            SAMPLESPERPIXEL,\n            3 if self._compression == \"tiff_jpeg\" and photo in (2, 6) else 1,\n        )\n        if len(bps_tuple) != samplesPerPixel:\n            raise SyntaxError(\"unknown data organization\")\n\n        # mode: check photometric interpretation and bits per pixel\n        key = (\n            self.tag_v2.prefix,\n            photo,\n            sampleFormat,\n            fillorder,\n            bps_tuple,\n            extra_tuple,\n        )\n        logger.debug(f\"format key: {key}\")\n        try:\n            self.mode, rawmode = OPEN_INFO[key]\n        except KeyError as e:\n            logger.debug(\"- unsupported format\")\n            raise SyntaxError(\"unknown pixel mode\") from e\n\n        logger.debug(f\"- raw mode: {rawmode}\")\n        logger.debug(f\"- pil mode: {self.mode}\")\n\n        self.info[\"compression\"] = self._compression\n\n        xres = self.tag_v2.get(X_RESOLUTION, 1)\n        yres = self.tag_v2.get(Y_RESOLUTION, 1)\n\n        if xres and yres:\n            resunit = self.tag_v2.get(RESOLUTION_UNIT)\n            if resunit == 2:  # dots per inch\n                self.info[\"dpi\"] = (xres, yres)\n            elif resunit == 3:  # dots per centimeter. convert to dpi\n                self.info[\"dpi\"] = (xres * 2.54, yres * 2.54)\n            elif resunit is None:  # used to default to 1, but now 2)\n                self.info[\"dpi\"] = (xres, yres)\n                # For backward compatibility,\n                # we also preserve the old behavior\n                self.info[\"resolution\"] = xres, yres\n            else:  # No absolute unit of measurement\n                self.info[\"resolution\"] = xres, yres\n\n        # build tile descriptors\n        x = y = layer = 0\n        self.tile = []\n        self.use_load_libtiff = READ_LIBTIFF or self._compression != \"raw\"\n        if self.use_load_libtiff:\n            # Decoder expects entire file as one tile.\n            # There's a buffer size limit in load (64k)\n            # so large g4 images will fail if we use that\n            # function.\n            #\n            # Setup the one tile for the whole image, then\n            # use the _load_libtiff function.\n\n            # libtiff handles the fillmode for us, so 1;IR should\n            # actually be 1;I. Including the R double reverses the\n            # bits, so stripes of the image are reversed.  See\n            # https://github.com/python-pillow/Pillow/issues/279\n            if fillorder == 2:\n                # Replace fillorder with fillorder=1\n                key = key[:3] + (1,) + key[4:]\n                logger.debug(f\"format key: {key}\")\n                # this should always work, since all the\n                # fillorder==2 modes have a corresponding\n                # fillorder=1 mode\n                self.mode, rawmode = OPEN_INFO[key]\n            # libtiff always returns the bytes in native order.\n            # we're expecting image byte order. So, if the rawmode\n            # contains I;16, we need to convert from native to image\n            # byte order.\n            if rawmode == \"I;16\":\n                rawmode = \"I;16N\"\n            if \";16B\" in rawmode:\n                rawmode = rawmode.replace(\";16B\", \";16N\")\n            if \";16L\" in rawmode:\n                rawmode = rawmode.replace(\";16L\", \";16N\")\n\n            # YCbCr images with new jpeg compression with pixels in one plane\n            # unpacked straight into RGB values\n            if (\n                photo == 6\n                and self._compression == \"jpeg\"\n                and self._planar_configuration == 1\n            ):\n                rawmode = \"RGB\"\n\n            # Offset in the tile tuple is 0, we go from 0,0 to\n            # w,h, and we only do this once -- eds\n            a = (rawmode, self._compression, False, self.tag_v2.offset)\n            self.tile.append((\"libtiff\", (0, 0, xsize, ysize), 0, a))\n\n        elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:\n            # striped image\n            if STRIPOFFSETS in self.tag_v2:\n                offsets = self.tag_v2[STRIPOFFSETS]\n                h = self.tag_v2.get(ROWSPERSTRIP, ysize)\n                w = self.size[0]\n            else:\n                # tiled image\n                offsets = self.tag_v2[TILEOFFSETS]\n                w = self.tag_v2.get(322)\n                h = self.tag_v2.get(323)\n\n            for offset in offsets:\n                if x + w > xsize:\n                    stride = w * sum(bps_tuple) / 8  # bytes per line\n                else:\n                    stride = 0\n\n                tile_rawmode = rawmode\n                if self._planar_configuration == 2:\n                    # each band on it's own layer\n                    tile_rawmode = rawmode[layer]\n                    # adjust stride width accordingly\n                    stride /= bps_count\n\n                a = (tile_rawmode, int(stride), 1)\n                self.tile.append(\n                    (\n                        self._compression,\n                        (x, y, min(x + w, xsize), min(y + h, ysize)),\n                        offset,\n                        a,\n                    )\n                )\n                x = x + w\n                if x >= self.size[0]:\n                    x, y = 0, y + h\n                    if y >= self.size[1]:\n                        x = y = 0\n                        layer += 1\n        else:\n            logger.debug(\"- unsupported data organization\")\n            raise SyntaxError(\"unknown data organization\")\n\n        # Fix up info.\n        if ICCPROFILE in self.tag_v2:\n            self.info[\"icc_profile\"] = self.tag_v2[ICCPROFILE]\n\n        # fixup palette descriptor\n\n        if self.mode in [\"P\", \"PA\"]:\n            palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]\n            self.palette = ImagePalette.raw(\"RGB;L\", b\"\".join(palette))\n\n        self._tile_orientation = self.tag_v2.get(0x0112)\n\n    def _close__fp(self):\n        try:\n            if self.__fp != self.fp:\n                self.__fp.close()\n        except AttributeError:\n            pass\n        finally:\n            self.__fp = None",
                            "ast_type_str": "ClassDef",
                            "start_lineno": 1125,
                            "end_lineno": 1154,
                            "parent": {
                                "file_name": "src/PIL/TiffImagePlugin.py",
                                "type": "class",
                                "name": "TiffImageFile",
                                "content": "class TiffImageFile(ImageFile.ImageFile):\n\n    format = \"TIFF\"\n    format_description = \"Adobe TIFF\"\n    _close_exclusive_fp_after_loading = False\n\n    def __init__(self, fp=None, filename=None):\n        self.tag_v2 = None\n        \"\"\" Image file directory (tag dictionary) \"\"\"\n\n        self.tag = None\n        \"\"\" Legacy tag entries \"\"\"\n\n        super().__init__(fp, filename)\n\n    def _open(self):\n        \"\"\"Open the first image in a TIFF file\"\"\"\n\n        # Header\n        ifh = self.fp.read(8)\n\n        self.tag_v2 = ImageFileDirectory_v2(ifh)\n\n        # legacy IFD entries will be filled in later\n        self.ifd = None\n\n        # setup frame pointers\n        self.__first = self.__next = self.tag_v2.next\n        self.__frame = -1\n        self.__fp = self.fp\n        self._frame_pos = []\n        self._n_frames = None\n\n        logger.debug(\"*** TiffImageFile._open ***\")\n        logger.debug(f\"- __first: {self.__first}\")\n        logger.debug(f\"- ifh: {repr(ifh)}\")  # Use repr to avoid str(bytes)\n\n        # and load the first frame\n        self._seek(0)\n\n    @property\n    def n_frames(self):\n        if self._n_frames is None:\n            current = self.tell()\n            self._seek(len(self._frame_pos))\n            while self._n_frames is None:\n                self._seek(self.tell() + 1)\n            self.seek(current)\n        return self._n_frames\n\n    def seek(self, frame):\n        \"\"\"Select a given frame as current image\"\"\"\n        if not self._seek_check(frame):\n            return\n        self._seek(frame)\n        # Create a new core image object on second and\n        # subsequent frames in the image. Image may be\n        # different size/mode.\n        Image._decompression_bomb_check(self.size)\n        self.im = Image.core.new(self.mode, self.size)\n\n    def _seek(self, frame):\n        self.fp = self.__fp\n\n        # reset buffered io handle in case fp\n        # was passed to libtiff, invalidating the buffer\n        self.fp.tell()\n\n        while len(self._frame_pos) <= frame:\n            if not self.__next:\n                raise EOFError(\"no more images in TIFF file\")\n            logger.debug(\n                f\"Seeking to frame {frame}, on frame {self.__frame}, \"\n                f\"__next {self.__next}, location: {self.fp.tell()}\"\n            )\n            self.fp.seek(self.__next)\n            self._frame_pos.append(self.__next)\n            logger.debug(\"Loading tags, location: %s\" % self.fp.tell())\n            self.tag_v2.load(self.fp)\n            if self.tag_v2.next in self._frame_pos:\n                # This IFD has already been processed\n                # Declare this to be the end of the image\n                self.__next = 0\n            else:\n                self.__next = self.tag_v2.next\n            if self.__next == 0:\n                self._n_frames = frame + 1\n            if len(self._frame_pos) == 1:\n                self.is_animated = self.__next != 0\n            self.__frame += 1\n        self.fp.seek(self._frame_pos[frame])\n        self.tag_v2.load(self.fp)\n        # fill the legacy tag/ifd entries\n        self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)\n        self.__frame = frame\n        self._setup()\n\n    def tell(self):\n        \"\"\"Return the current frame number\"\"\"\n        return self.__frame\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n        return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {}\n\n    def load(self):\n        if self.tile and self.use_load_libtiff:\n            return self._load_libtiff()\n        return super().load()\n\n    def load_end(self):\n        if self._tile_orientation:\n            method = {\n                2: Image.FLIP_LEFT_RIGHT,\n                3: Image.ROTATE_180,\n                4: Image.FLIP_TOP_BOTTOM,\n                5: Image.TRANSPOSE,\n                6: Image.ROTATE_270,\n                7: Image.TRANSVERSE,\n                8: Image.ROTATE_90,\n            }.get(self._tile_orientation)\n            if method is not None:\n                self.im = self.im.transpose(method)\n                self._size = self.im.size\n\n        # allow closing if we're on the first frame, there's no next\n        # This is the ImageFile.load path only, libtiff specific below.\n        if not self.is_animated:\n            self._close_exclusive_fp_after_loading = True\n\n            # reset buffered io handle in case fp\n            # was passed to libtiff, invalidating the buffer\n            self.fp.tell()\n\n            # load IFD data from fp before it is closed\n            exif = self.getexif()\n            for key in TiffTags.TAGS_V2_GROUPS.keys():\n                if key not in exif:\n                    continue\n                exif.get_ifd(key)\n\n    def _load_libtiff(self):\n        \"\"\"Overload method triggered when we detect a compressed tiff\n        Calls out to libtiff\"\"\"\n\n        Image.Image.load(self)\n\n        self.load_prepare()\n\n        if not len(self.tile) == 1:\n            raise OSError(\"Not exactly one tile\")\n\n        # (self._compression, (extents tuple),\n        #   0, (rawmode, self._compression, fp))\n        extents = self.tile[0][1]\n        args = list(self.tile[0][3])\n\n        # To be nice on memory footprint, if there's a\n        # file descriptor, use that instead of reading\n        # into a string in python.\n        # libtiff closes the file descriptor, so pass in a dup.\n        try:\n            fp = hasattr(self.fp, \"fileno\") and os.dup(self.fp.fileno())\n            # flush the file descriptor, prevents error on pypy 2.4+\n            # should also eliminate the need for fp.tell\n            # in _seek\n            if hasattr(self.fp, \"flush\"):\n                self.fp.flush()\n        except OSError:\n            # io.BytesIO have a fileno, but returns an OSError if\n            # it doesn't use a file descriptor.\n            fp = False\n\n        if fp:\n            args[2] = fp\n\n        decoder = Image._getdecoder(\n            self.mode, \"libtiff\", tuple(args), self.decoderconfig\n        )\n        try:\n            decoder.setimage(self.im, extents)\n        except ValueError as e:\n            raise OSError(\"Couldn't set the image\") from e\n\n        close_self_fp = self._exclusive_fp and not self.is_animated\n        if hasattr(self.fp, \"getvalue\"):\n            # We've got a stringio like thing passed in. Yay for all in memory.\n            # The decoder needs the entire file in one shot, so there's not\n            # a lot we can do here other than give it the entire file.\n            # unless we could do something like get the address of the\n            # underlying string for stringio.\n            #\n            # Rearranging for supporting byteio items, since they have a fileno\n            # that returns an OSError if there's no underlying fp. Easier to\n            # deal with here by reordering.\n            logger.debug(\"have getvalue. just sending in a string from getvalue\")\n            n, err = decoder.decode(self.fp.getvalue())\n        elif fp:\n            # we've got a actual file on disk, pass in the fp.\n            logger.debug(\"have fileno, calling fileno version of the decoder.\")\n            if not close_self_fp:\n                self.fp.seek(0)\n            # 4 bytes, otherwise the trace might error out\n            n, err = decoder.decode(b\"fpfp\")\n        else:\n            # we have something else.\n            logger.debug(\"don't have fileno or getvalue. just reading\")\n            self.fp.seek(0)\n            # UNDONE -- so much for that buffer size thing.\n            n, err = decoder.decode(self.fp.read())\n\n        self.tile = []\n        self.readonly = 0\n\n        self.load_end()\n\n        # libtiff closed the fp in a, we need to close self.fp, if possible\n        if close_self_fp:\n            self.fp.close()\n            self.fp = None  # might be shared\n\n        if err < 0:\n            raise OSError(err)\n\n        return Image.Image.load(self)\n\n    def _setup(self):\n        \"\"\"Setup this image object based on current tags\"\"\"\n\n        if 0xBC01 in self.tag_v2:\n            raise OSError(\"Windows Media Photo files not yet supported\")\n\n        # extract relevant tags\n        self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)]\n        self._planar_configuration = self.tag_v2.get(PLANAR_CONFIGURATION, 1)\n\n        # photometric is a required tag, but not everyone is reading\n        # the specification\n        photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)\n\n        # old style jpeg compression images most certainly are YCbCr\n        if self._compression == \"tiff_jpeg\":\n            photo = 6\n\n        fillorder = self.tag_v2.get(FILLORDER, 1)\n\n        logger.debug(\"*** Summary ***\")\n        logger.debug(f\"- compression: {self._compression}\")\n        logger.debug(f\"- photometric_interpretation: {photo}\")\n        logger.debug(f\"- planar_configuration: {self._planar_configuration}\")\n        logger.debug(f\"- fill_order: {fillorder}\")\n        logger.debug(f\"- YCbCr subsampling: {self.tag.get(530)}\")\n\n        # size\n        xsize = int(self.tag_v2.get(IMAGEWIDTH))\n        ysize = int(self.tag_v2.get(IMAGELENGTH))\n        self._size = xsize, ysize\n\n        logger.debug(f\"- size: {self.size}\")\n\n        sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,))\n        if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1:\n            # SAMPLEFORMAT is properly per band, so an RGB image will\n            # be (1,1,1).  But, we don't support per band pixel types,\n            # and anything more than one band is a uint8. So, just\n            # take the first element. Revisit this if adding support\n            # for more exotic images.\n            sampleFormat = (1,)\n\n        bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,))\n        extra_tuple = self.tag_v2.get(EXTRASAMPLES, ())\n        if photo in (2, 6, 8):  # RGB, YCbCr, LAB\n            bps_count = 3\n        elif photo == 5:  # CMYK\n            bps_count = 4\n        else:\n            bps_count = 1\n        bps_count += len(extra_tuple)\n        # Some files have only one value in bps_tuple,\n        # while should have more. Fix it\n        if bps_count > len(bps_tuple) and len(bps_tuple) == 1:\n            bps_tuple = bps_tuple * bps_count\n\n        samplesPerPixel = self.tag_v2.get(\n            SAMPLESPERPIXEL,\n            3 if self._compression == \"tiff_jpeg\" and photo in (2, 6) else 1,\n        )\n        if len(bps_tuple) != samplesPerPixel:\n            raise SyntaxError(\"unknown data organization\")\n\n        # mode: check photometric interpretation and bits per pixel\n        key = (\n            self.tag_v2.prefix,\n            photo,\n            sampleFormat,\n            fillorder,\n            bps_tuple,\n            extra_tuple,\n        )\n        logger.debug(f\"format key: {key}\")\n        try:\n            self.mode, rawmode = OPEN_INFO[key]\n        except KeyError as e:\n            logger.debug(\"- unsupported format\")\n            raise SyntaxError(\"unknown pixel mode\") from e\n\n        logger.debug(f\"- raw mode: {rawmode}\")\n        logger.debug(f\"- pil mode: {self.mode}\")\n\n        self.info[\"compression\"] = self._compression\n\n        xres = self.tag_v2.get(X_RESOLUTION, 1)\n        yres = self.tag_v2.get(Y_RESOLUTION, 1)\n\n        if xres and yres:\n            resunit = self.tag_v2.get(RESOLUTION_UNIT)\n            if resunit == 2:  # dots per inch\n                self.info[\"dpi\"] = (xres, yres)\n            elif resunit == 3:  # dots per centimeter. convert to dpi\n                self.info[\"dpi\"] = (xres * 2.54, yres * 2.54)\n            elif resunit is None:  # used to default to 1, but now 2)\n                self.info[\"dpi\"] = (xres, yres)\n                # For backward compatibility,\n                # we also preserve the old behavior\n                self.info[\"resolution\"] = xres, yres\n            else:  # No absolute unit of measurement\n                self.info[\"resolution\"] = xres, yres\n\n        # build tile descriptors\n        x = y = layer = 0\n        self.tile = []\n        self.use_load_libtiff = READ_LIBTIFF or self._compression != \"raw\"\n        if self.use_load_libtiff:\n            # Decoder expects entire file as one tile.\n            # There's a buffer size limit in load (64k)\n            # so large g4 images will fail if we use that\n            # function.\n            #\n            # Setup the one tile for the whole image, then\n            # use the _load_libtiff function.\n\n            # libtiff handles the fillmode for us, so 1;IR should\n            # actually be 1;I. Including the R double reverses the\n            # bits, so stripes of the image are reversed.  See\n            # https://github.com/python-pillow/Pillow/issues/279\n            if fillorder == 2:\n                # Replace fillorder with fillorder=1\n                key = key[:3] + (1,) + key[4:]\n                logger.debug(f\"format key: {key}\")\n                # this should always work, since all the\n                # fillorder==2 modes have a corresponding\n                # fillorder=1 mode\n                self.mode, rawmode = OPEN_INFO[key]\n            # libtiff always returns the bytes in native order.\n            # we're expecting image byte order. So, if the rawmode\n            # contains I;16, we need to convert from native to image\n            # byte order.\n            if rawmode == \"I;16\":\n                rawmode = \"I;16N\"\n            if \";16B\" in rawmode:\n                rawmode = rawmode.replace(\";16B\", \";16N\")\n            if \";16L\" in rawmode:\n                rawmode = rawmode.replace(\";16L\", \";16N\")\n\n            # YCbCr images with new jpeg compression with pixels in one plane\n            # unpacked straight into RGB values\n            if (\n                photo == 6\n                and self._compression == \"jpeg\"\n                and self._planar_configuration == 1\n            ):\n                rawmode = \"RGB\"\n\n            # Offset in the tile tuple is 0, we go from 0,0 to\n            # w,h, and we only do this once -- eds\n            a = (rawmode, self._compression, False, self.tag_v2.offset)\n            self.tile.append((\"libtiff\", (0, 0, xsize, ysize), 0, a))\n\n        elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:\n            # striped image\n            if STRIPOFFSETS in self.tag_v2:\n                offsets = self.tag_v2[STRIPOFFSETS]\n                h = self.tag_v2.get(ROWSPERSTRIP, ysize)\n                w = self.size[0]\n            else:\n                # tiled image\n                offsets = self.tag_v2[TILEOFFSETS]\n                w = self.tag_v2.get(322)\n                h = self.tag_v2.get(323)\n\n            for offset in offsets:\n                if x + w > xsize:\n                    stride = w * sum(bps_tuple) / 8  # bytes per line\n                else:\n                    stride = 0\n\n                tile_rawmode = rawmode\n                if self._planar_configuration == 2:\n                    # each band on it's own layer\n                    tile_rawmode = rawmode[layer]\n                    # adjust stride width accordingly\n                    stride /= bps_count\n\n                a = (tile_rawmode, int(stride), 1)\n                self.tile.append(\n                    (\n                        self._compression,\n                        (x, y, min(x + w, xsize), min(y + h, ysize)),\n                        offset,\n                        a,\n                    )\n                )\n                x = x + w\n                if x >= self.size[0]:\n                    x, y = 0, y + h\n                    if y >= self.size[1]:\n                        x = y = 0\n                        layer += 1\n        else:\n            logger.debug(\"- unsupported data organization\")\n            raise SyntaxError(\"unknown data organization\")\n\n        # Fix up info.\n        if ICCPROFILE in self.tag_v2:\n            self.info[\"icc_profile\"] = self.tag_v2[ICCPROFILE]\n\n        # fixup palette descriptor\n\n        if self.mode in [\"P\", \"PA\"]:\n            palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]\n            self.palette = ImagePalette.raw(\"RGB;L\", b\"\".join(palette))\n\n        self._tile_orientation = self.tag_v2.get(0x0112)\n\n    def _close__fp(self):\n        try:\n            if self.__fp != self.fp:\n                self.__fp.close()\n        except AttributeError:\n            pass\n        finally:\n            self.__fp = None",
                                "ast_type_str": "ClassDef",
                                "start_lineno": 1011,
                                "end_lineno": 1456,
                                "parent": null
                            }
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                },
                {
                    "descriptor": {
                        "old_range": {
                            "start": 1504,
                            "length": 12
                        },
                        "new_range": {
                            "start": 1515,
                            "length": 15
                        },
                        "section": "def _save(im, fp, filename):"
                    },
                    "line_group": {
                        "all_lines": [
                            {
                                "content": "    ifd[IMAGELENGTH] = im.size[1]",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            },
                            {
                                "content": "    # write any arbitrary tags passed in as an ImageFileDirectory",
                                "type": "context"
                            },
                            {
                                "content": "    info = im.encoderinfo.get(\"tiffinfo\", {})",
                                "type": "deleted"
                            },
                            {
                                "content": "    info = im.encoderinfo.get(\"tiffinfo\", im.encoderinfo.get(\"exif\", {}))",
                                "type": "added"
                            },
                            {
                                "content": "    logger.debug(\"Tiffinfo Keys: %s\" % list(info))",
                                "type": "context"
                            },
                            {
                                "content": "    if isinstance(info, ImageFileDirectory_v1):",
                                "type": "context"
                            },
                            {
                                "content": "        info = info.to_v2()",
                                "type": "context"
                            },
                            {
                                "content": "    for key in info:",
                                "type": "context"
                            },
                            {
                                "content": "        ifd[key] = info.get(key)",
                                "type": "deleted"
                            },
                            {
                                "content": "        if isinstance(info, Image.Exif) and key in TiffTags.TAGS_V2_GROUPS.keys():",
                                "type": "added"
                            },
                            {
                                "content": "            ifd[key] = info.get_ifd(key)",
                                "type": "added"
                            },
                            {
                                "content": "        else:",
                                "type": "added"
                            },
                            {
                                "content": "            ifd[key] = info.get(key)",
                                "type": "added"
                            },
                            {
                                "content": "        try:",
                                "type": "context"
                            },
                            {
                                "content": "            ifd.tagtype[key] = info.tagtype[key]",
                                "type": "context"
                            },
                            {
                                "content": "        except Exception:",
                                "type": "context"
                            },
                            {
                                "content": "",
                                "type": "context"
                            }
                        ]
                    },
                    "modified_entities": [
                        {
                            "file_name": "src/PIL/TiffImagePlugin.py",
                            "type": "function",
                            "name": "_save",
                            "content": "def _save(im, fp, filename):\n\n    try:\n        rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]\n    except KeyError as e:\n        raise OSError(f\"cannot write mode {im.mode} as TIFF\") from e\n\n    ifd = ImageFileDirectory_v2(prefix=prefix)\n\n    compression = im.encoderinfo.get(\"compression\", im.info.get(\"compression\"))\n    if compression is None:\n        compression = \"raw\"\n    elif compression == \"tiff_jpeg\":\n        # OJPEG is obsolete, so use new-style JPEG compression instead\n        compression = \"jpeg\"\n    elif compression == \"tiff_deflate\":\n        compression = \"tiff_adobe_deflate\"\n\n    libtiff = WRITE_LIBTIFF or compression != \"raw\"\n\n    # required for color libtiff images\n    ifd[PLANAR_CONFIGURATION] = getattr(im, \"_planar_configuration\", 1)\n\n    ifd[IMAGEWIDTH] = im.size[0]\n    ifd[IMAGELENGTH] = im.size[1]\n\n    # write any arbitrary tags passed in as an ImageFileDirectory\n    info = im.encoderinfo.get(\"tiffinfo\", {})\n    logger.debug(\"Tiffinfo Keys: %s\" % list(info))\n    if isinstance(info, ImageFileDirectory_v1):\n        info = info.to_v2()\n    for key in info:\n        ifd[key] = info.get(key)\n        try:\n            ifd.tagtype[key] = info.tagtype[key]\n        except Exception:\n            pass  # might not be an IFD. Might not have populated type\n\n    # additions written by Greg Couch, gregc@cgl.ucsf.edu\n    # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com\n    if hasattr(im, \"tag_v2\"):\n        # preserve tags from original TIFF image file\n        for key in (\n            RESOLUTION_UNIT,\n            X_RESOLUTION,\n            Y_RESOLUTION,\n            IPTC_NAA_CHUNK,\n            PHOTOSHOP_CHUNK,\n            XMP,\n        ):\n            if key in im.tag_v2:\n                ifd[key] = im.tag_v2[key]\n                ifd.tagtype[key] = im.tag_v2.tagtype[key]\n\n    # preserve ICC profile (should also work when saving other formats\n    # which support profiles as TIFF) -- 2008-06-06 Florian Hoech\n    icc = im.encoderinfo.get(\"icc_profile\", im.info.get(\"icc_profile\"))\n    if icc:\n        ifd[ICCPROFILE] = icc\n\n    for key, name in [\n        (IMAGEDESCRIPTION, \"description\"),\n        (X_RESOLUTION, \"resolution\"),\n        (Y_RESOLUTION, \"resolution\"),\n        (X_RESOLUTION, \"x_resolution\"),\n        (Y_RESOLUTION, \"y_resolution\"),\n        (RESOLUTION_UNIT, \"resolution_unit\"),\n        (SOFTWARE, \"software\"),\n        (DATE_TIME, \"date_time\"),\n        (ARTIST, \"artist\"),\n        (COPYRIGHT, \"copyright\"),\n    ]:\n        if name in im.encoderinfo:\n            ifd[key] = im.encoderinfo[name]\n\n    dpi = im.encoderinfo.get(\"dpi\")\n    if dpi:\n        ifd[RESOLUTION_UNIT] = 2\n        ifd[X_RESOLUTION] = dpi[0]\n        ifd[Y_RESOLUTION] = dpi[1]\n\n    if bits != (1,):\n        ifd[BITSPERSAMPLE] = bits\n        if len(bits) != 1:\n            ifd[SAMPLESPERPIXEL] = len(bits)\n    if extra is not None:\n        ifd[EXTRASAMPLES] = extra\n    if format != 1:\n        ifd[SAMPLEFORMAT] = format\n\n    ifd[PHOTOMETRIC_INTERPRETATION] = photo\n\n    if im.mode in [\"P\", \"PA\"]:\n        lut = im.im.getpalette(\"RGB\", \"RGB;L\")\n        ifd[COLORMAP] = tuple(v * 256 for v in lut)\n    # data orientation\n    stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)\n    # aim for 64 KB strips when using libtiff writer\n    if libtiff:\n        rows_per_strip = min((2 ** 16 + stride - 1) // stride, im.size[1])\n    else:\n        rows_per_strip = im.size[1]\n    strip_byte_counts = stride * rows_per_strip\n    strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip\n    ifd[ROWSPERSTRIP] = rows_per_strip\n    if strip_byte_counts >= 2 ** 16:\n        ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG\n    ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (\n        stride * im.size[1] - strip_byte_counts * (strips_per_image - 1),\n    )\n    ifd[STRIPOFFSETS] = tuple(\n        range(0, strip_byte_counts * strips_per_image, strip_byte_counts)\n    )  # this is adjusted by IFD writer\n    # no compression by default:\n    ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)\n\n    if libtiff:\n        if \"quality\" in im.encoderinfo:\n            quality = im.encoderinfo[\"quality\"]\n            if not isinstance(quality, int) or quality < 0 or quality > 100:\n                raise ValueError(\"Invalid quality setting\")\n            if compression != \"jpeg\":\n                raise ValueError(\n                    \"quality setting only supported for 'jpeg' compression\"\n                )\n            ifd[JPEGQUALITY] = quality\n\n        logger.debug(\"Saving using libtiff encoder\")\n        logger.debug(\"Items: %s\" % sorted(ifd.items()))\n        _fp = 0\n        if hasattr(fp, \"fileno\"):\n            try:\n                fp.seek(0)\n                _fp = os.dup(fp.fileno())\n            except io.UnsupportedOperation:\n                pass\n\n        # optional types for non core tags\n        types = {}\n        # SAMPLEFORMAT is determined by the image format and should not be copied\n        # from legacy_ifd.\n        # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library\n        # based on the data in the strip.\n        # The other tags expect arrays with a certain length (fixed or depending on\n        # BITSPERSAMPLE, etc), passing arrays with a different length will result in\n        # segfaults. Block these tags until we add extra validation.\n        # SUBIFD may also cause a segfault.\n        blocklist = [\n            REFERENCEBLACKWHITE,\n            SAMPLEFORMAT,\n            STRIPBYTECOUNTS,\n            STRIPOFFSETS,\n            TRANSFERFUNCTION,\n            SUBIFD,\n        ]\n\n        atts = {}\n        # bits per sample is a single short in the tiff directory, not a list.\n        atts[BITSPERSAMPLE] = bits[0]\n        # Merge the ones that we have with (optional) more bits from\n        # the original file, e.g x,y resolution so that we can\n        # save(load('')) == original file.\n        legacy_ifd = {}\n        if hasattr(im, \"tag\"):\n            legacy_ifd = im.tag.to_v2()\n        for tag, value in itertools.chain(\n            ifd.items(), getattr(im, \"tag_v2\", {}).items(), legacy_ifd.items()\n        ):\n            # Libtiff can only process certain core items without adding\n            # them to the custom dictionary.\n            # Custom items are supported for int, float, unicode, string and byte\n            # values. Other types and tuples require a tagtype.\n            if tag not in TiffTags.LIBTIFF_CORE:\n                if not Image.core.libtiff_support_custom_tags:\n                    continue\n\n                if tag in ifd.tagtype:\n                    types[tag] = ifd.tagtype[tag]\n                elif not (isinstance(value, (int, float, str, bytes))):\n                    continue\n                else:\n                    type = TiffTags.lookup(tag).type\n                    if type:\n                        types[tag] = type\n            if tag not in atts and tag not in blocklist:\n                if isinstance(value, str):\n                    atts[tag] = value.encode(\"ascii\", \"replace\") + b\"\\0\"\n                elif isinstance(value, IFDRational):\n                    atts[tag] = float(value)\n                else:\n                    atts[tag] = value\n\n        logger.debug(\"Converted items: %s\" % sorted(atts.items()))\n\n        # libtiff always expects the bytes in native order.\n        # we're storing image byte order. So, if the rawmode\n        # contains I;16, we need to convert from native to image\n        # byte order.\n        if im.mode in (\"I;16B\", \"I;16\"):\n            rawmode = \"I;16N\"\n\n        # Pass tags as sorted list so that the tags are set in a fixed order.\n        # This is required by libtiff for some tags. For example, the JPEGQUALITY\n        # pseudo tag requires that the COMPRESS tag was already set.\n        tags = list(atts.items())\n        tags.sort()\n        a = (rawmode, compression, _fp, filename, tags, types)\n        e = Image._getencoder(im.mode, \"libtiff\", a, im.encoderconfig)\n        e.setimage(im.im, (0, 0) + im.size)\n        while True:\n            # undone, change to self.decodermaxblock:\n            l, s, d = e.encode(16 * 1024)\n            if not _fp:\n                fp.write(d)\n            if s:\n                break\n        if s < 0:\n            raise OSError(f\"encoder error {s} when writing image file\")\n\n    else:\n        offset = ifd.save(fp)\n\n        ImageFile._save(\n            im, fp, [(\"raw\", (0, 0) + im.size, offset, (rawmode, stride, 1))]\n        )\n\n    # -- helper for multi-page save --\n    if \"_debug_multipage\" in im.encoderinfo:\n        # just to access o32 and o16 (using correct byte order)\n        im._debug_multipage = ifd",
                            "ast_type_str": "FunctionDef",
                            "start_lineno": 1480,
                            "end_lineno": 1709,
                            "parent": null
                        }
                    ],
                    "added_entities": [],
                    "deleted_entities": []
                }
            ]
        }
    ],
    "old_commit_hash": "c0f619384cbccef86185b1cc1b4da9755b1e344b^",
    "new_commit_hash": "c0f619384cbccef86185b1cc1b4da9755b1e344b",
    "commit_message": "Added \"exif\" keyword argument to save Image.Exif instance",
    "commit_date": "2021-07-04T12:33:55+10:00",
    "metadata": {}
}
{
    "repo_name": "pillow",
    "new_commit_hash": "c0f619384cbccef86185b1cc1b4da9755b1e344b",
    "test_file_codes": [
        "import os\nfrom io import BytesIO\n\nimport pytest\n\nfrom PIL import Image, TiffImagePlugin\nfrom PIL.TiffImagePlugin import RESOLUTION_UNIT, X_RESOLUTION, Y_RESOLUTION\n\nfrom .helper import (\n    assert_image_equal,\n    assert_image_equal_tofile,\n    assert_image_similar,\n    assert_image_similar_tofile,\n    hopper,\n    is_pypy,\n    is_win32,\n)\n\ntry:\n    import defusedxml.ElementTree as ElementTree\nexcept ImportError:\n    ElementTree = None\n\n\nclass TestFileTiff:\n    def test_sanity(self, tmp_path):\n\n        filename = str(tmp_path / \"temp.tif\")\n\n        hopper(\"RGB\").save(filename)\n\n        with Image.open(filename) as im:\n            im.load()\n        assert im.mode == \"RGB\"\n        assert im.size == (128, 128)\n        assert im.format == \"TIFF\"\n\n        hopper(\"1\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"L\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"P\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"RGB\").save(filename)\n        with Image.open(filename):\n            pass\n\n        hopper(\"I\").save(filename)\n        with Image.open(filename):\n            pass\n\n    @pytest.mark.skipif(is_pypy(), reason=\"Requires CPython\")\n    def test_unclosed_file(self):\n        def open():\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n\n        pytest.warns(ResourceWarning, open)\n\n    def test_closed_file(self):\n        with pytest.warns(None) as record:\n            im = Image.open(\"Tests/images/multipage.tiff\")\n            im.load()\n            im.close()\n\n        assert not record\n\n    def test_context_manager(self):\n        with pytest.warns(None) as record:\n            with Image.open(\"Tests/images/multipage.tiff\") as im:\n                im.load()\n\n        assert not record\n\n    def test_mac_tiff(self):\n        # Read RGBa images from macOS [@PIL136]\n\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (55, 43)\n            assert im.tile == [(\"raw\", (0, 0, 55, 43), 8, (\"RGBa\", 0, 1))]\n            im.load()\n\n            assert_image_similar_tofile(im, \"Tests/images/pil136.png\", 1)\n\n    def test_wrong_bits_per_sample(self):\n        with Image.open(\"Tests/images/tiff_wrong_bits_per_sample.tiff\") as im:\n            assert im.mode == \"RGBA\"\n            assert im.size == (52, 53)\n            assert im.tile == [(\"raw\", (0, 0, 52, 53), 160, (\"RGBA\", 0, 1))]\n            im.load()\n\n    def test_set_legacy_api(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        with pytest.raises(Exception) as e:\n            ifd.legacy_api = None\n        assert str(e.value) == \"Not allowing setting of legacy api\"\n\n    def test_xyres_tiff(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # legacy api\n            assert isinstance(im.tag[X_RESOLUTION][0], tuple)\n            assert isinstance(im.tag[Y_RESOLUTION][0], tuple)\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n\n            assert im.info[\"dpi\"] == (72.0, 72.0)\n\n    def test_xyres_fallback_tiff(self):\n        filename = \"Tests/images/compression.tif\"\n        with Image.open(filename) as im:\n\n            # v2 api\n            assert isinstance(im.tag_v2[X_RESOLUTION], TiffImagePlugin.IFDRational)\n            assert isinstance(im.tag_v2[Y_RESOLUTION], TiffImagePlugin.IFDRational)\n            with pytest.raises(KeyError):\n                im.tag_v2[RESOLUTION_UNIT]\n\n            # Legacy.\n            assert im.info[\"resolution\"] == (100.0, 100.0)\n            # Fallback \"inch\".\n            assert im.info[\"dpi\"] == (100.0, 100.0)\n\n    def test_int_resolution(self):\n        filename = \"Tests/images/pil168.tif\"\n        with Image.open(filename) as im:\n\n            # Try to read a file where X,Y_RESOLUTION are ints\n            im.tag_v2[X_RESOLUTION] = 71\n            im.tag_v2[Y_RESOLUTION] = 71\n            im._setup()\n            assert im.info[\"dpi\"] == (71.0, 71.0)\n\n    @pytest.mark.parametrize(\n        \"resolutionUnit, dpi\",\n        [(None, 72.8), (2, 72.8), (3, 184.912)],\n    )\n    def test_load_float_dpi(self, resolutionUnit, dpi):\n        with Image.open(\n            \"Tests/images/hopper_float_dpi_\" + str(resolutionUnit) + \".tif\"\n        ) as im:\n            assert im.tag_v2.get(RESOLUTION_UNIT) == resolutionUnit\n            assert im.info[\"dpi\"] == (dpi, dpi)\n\n    def test_save_float_dpi(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/hopper.tif\") as im:\n            dpi = (72.2, 72.2)\n            im.save(outfile, dpi=dpi)\n\n            with Image.open(outfile) as reloaded:\n                assert reloaded.info[\"dpi\"] == dpi\n\n    def test_save_setting_missing_resolution(self):\n        b = BytesIO()\n        with Image.open(\"Tests/images/10ct_32bit_128.tiff\") as im:\n            im.save(b, format=\"tiff\", resolution=123.45)\n        with Image.open(b) as im:\n            assert im.tag_v2[X_RESOLUTION] == 123.45\n            assert im.tag_v2[Y_RESOLUTION] == 123.45\n\n    def test_invalid_file(self):\n        invalid_file = \"Tests/images/flower.jpg\"\n\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n\n        TiffImagePlugin.PREFIXES.append(b\"\\xff\\xd8\\xff\\xe0\")\n        with pytest.raises(SyntaxError):\n            TiffImagePlugin.TiffImageFile(invalid_file)\n        TiffImagePlugin.PREFIXES.pop()\n\n    def test_bad_exif(self):\n        with Image.open(\"Tests/images/hopper_bad_exif.jpg\") as i:\n            # Should not raise struct.error.\n            pytest.warns(UserWarning, i._getexif)\n\n    def test_save_rgba(self, tmp_path):\n        im = hopper(\"RGBA\")\n        outfile = str(tmp_path / \"temp.tif\")\n        im.save(outfile)\n\n    def test_save_unsupported_mode(self, tmp_path):\n        im = hopper(\"HSV\")\n        outfile = str(tmp_path / \"temp.tif\")\n        with pytest.raises(OSError):\n            im.save(outfile)\n\n    def test_little_endian(self):\n        with Image.open(\"Tests/images/16bit.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (little endian)\n        assert b[0] == ord(b\"\\xe0\")\n        assert b[1] == ord(b\"\\x01\")\n\n    def test_big_endian(self):\n        with Image.open(\"Tests/images/16bit.MM.cropped.tif\") as im:\n            assert im.getpixel((0, 0)) == 480\n            assert im.mode == \"I;16B\"\n\n            b = im.tobytes()\n        # Bytes are in image native order (big endian)\n        assert b[0] == ord(b\"\\x01\")\n        assert b[1] == ord(b\"\\xe0\")\n\n    def test_16bit_s(self):\n        with Image.open(\"Tests/images/16bit.s.tif\") as im:\n            im.load()\n            assert im.mode == \"I\"\n            assert im.getpixel((0, 0)) == 32767\n            assert im.getpixel((0, 1)) == 0\n\n    def test_12bit_rawmode(self):\n        \"\"\"Are we generating the same interpretation\n        of the image as Imagemagick is?\"\"\"\n\n        with Image.open(\"Tests/images/12bit.cropped.tif\") as im:\n            # to make the target --\n            # convert 12bit.cropped.tif -depth 16 tmp.tif\n            # convert tmp.tif -evaluate RightShift 4 12in16bit2.tif\n            # imagemagick will auto scale so that a 12bit FFF is 16bit FFF0,\n            # so we need to unshift so that the integer values are the same.\n\n            assert_image_equal_tofile(im, \"Tests/images/12in16bit.tif\")\n\n    def test_32bit_float(self):\n        # Issue 614, specific 32-bit float format\n        path = \"Tests/images/10ct_32bit_128.tiff\"\n        with Image.open(path) as im:\n            im.load()\n\n            assert im.getpixel((0, 0)) == -0.4526388943195343\n            assert im.getextrema() == (-3.140936851501465, 3.140684127807617)\n\n    def test_unknown_pixel_mode(self):\n        with pytest.raises(OSError):\n            with Image.open(\"Tests/images/hopper_unknown_pixel_mode.tif\"):\n                pass\n\n    def test_n_frames(self):\n        for path, n_frames in [\n            [\"Tests/images/multipage-lastframe.tif\", 1],\n            [\"Tests/images/multipage.tiff\", 3],\n        ]:\n            with Image.open(path) as im:\n                assert im.n_frames == n_frames\n                assert im.is_animated == (n_frames != 1)\n\n    def test_eoferror(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            n_frames = im.n_frames\n\n            # Test seeking past the last frame\n            with pytest.raises(EOFError):\n                im.seek(n_frames)\n            assert im.tell() < n_frames\n\n            # Test that seeking to the last frame does not raise an error\n            im.seek(n_frames - 1)\n\n    def test_multipage(self):\n        # issue #862\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            # file is a multipage tiff: 10x10 green, 10x10 red, 20x20 blue\n\n            im.seek(0)\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(1)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (255, 0, 0)\n\n            im.seek(0)\n            im.load()\n            assert im.size == (10, 10)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 128, 0)\n\n            im.seek(2)\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_multipage_last_frame(self):\n        with Image.open(\"Tests/images/multipage-lastframe.tif\") as im:\n            im.load()\n            assert im.size == (20, 20)\n            assert im.convert(\"RGB\").getpixel((0, 0)) == (0, 0, 255)\n\n    def test_frame_order(self):\n        # A frame can't progress to itself after reading\n        with Image.open(\"Tests/images/multipage_single_frame_loop.tiff\") as im:\n            assert im.n_frames == 1\n\n        # A frame can't progress to a frame that has already been read\n        with Image.open(\"Tests/images/multipage_multiple_frame_loop.tiff\") as im:\n            assert im.n_frames == 2\n\n        # Frames don't have to be in sequence\n        with Image.open(\"Tests/images/multipage_out_of_order.tiff\") as im:\n            assert im.n_frames == 3\n\n    def test___str__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # Act\n            ret = str(im.ifd)\n\n            # Assert\n            assert isinstance(ret, str)\n\n    def test_dict(self):\n        # Arrange\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n\n            # v2 interface\n            v2_tags = {\n                256: 55,\n                257: 43,\n                258: (8, 8, 8, 8),\n                259: 1,\n                262: 2,\n                296: 2,\n                273: (8,),\n                338: (1,),\n                277: 4,\n                279: (9460,),\n                282: 72.0,\n                283: 72.0,\n                284: 1,\n            }\n            assert dict(im.tag_v2) == v2_tags\n\n            # legacy interface\n            legacy_tags = {\n                256: (55,),\n                257: (43,),\n                258: (8, 8, 8, 8),\n                259: (1,),\n                262: (2,),\n                296: (2,),\n                273: (8,),\n                338: (1,),\n                277: (4,),\n                279: (9460,),\n                282: ((720000, 10000),),\n                283: ((720000, 10000),),\n                284: (1,),\n            }\n            assert dict(im.tag) == legacy_tags\n\n    def test__delitem__(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            len_before = len(dict(im.ifd))\n            del im.ifd[256]\n            len_after = len(dict(im.ifd))\n            assert len_before == len_after + 1\n\n    def test_load_byte(self):\n        for legacy_api in [False, True]:\n            ifd = TiffImagePlugin.ImageFileDirectory_v2()\n            data = b\"abc\"\n            ret = ifd.load_byte(data, legacy_api)\n            assert ret == b\"abc\"\n\n    def test_load_string(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abc\\0\"\n        ret = ifd.load_string(data, False)\n        assert ret == \"abc\"\n\n    def test_load_float(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdabcd\"\n        ret = ifd.load_float(data, False)\n        assert ret == (1.6777999408082104e22, 1.6777999408082104e22)\n\n    def test_load_double(self):\n        ifd = TiffImagePlugin.ImageFileDirectory_v2()\n        data = b\"abcdefghabcdefgh\"\n        ret = ifd.load_double(data, False)\n        assert ret == (8.540883223036124e194, 8.540883223036124e194)\n\n    def test_ifd_tag_type(self):\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            assert 0x8825 in im.tag_v2\n\n    def test_exif(self, tmp_path):\n        def check_exif(exif):\n            assert sorted(exif.keys()) == [\n                256,\n                257,\n                258,\n                259,\n                262,\n                271,\n                272,\n                273,\n                277,\n                278,\n                279,\n                282,\n                283,\n                284,\n                296,\n                297,\n                305,\n                339,\n                700,\n                34665,\n                34853,\n                50735,\n            ]\n            assert exif[256] == 640\n            assert exif[271] == \"FLIR\"\n\n            gps = exif.get_ifd(0x8825)\n            assert list(gps.keys()) == [0, 1, 2, 3, 4, 5, 6, 18]\n            assert gps[0] == b\"\\x03\\x02\\x00\\x00\"\n            assert gps[18] == \"WGS-84\"\n\n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            exif = im.getexif()\n            check_exif(exif)\n\n            im.save(outfile, exif=exif)\n\n        with Image.open(outfile) as im:\n            exif = im.getexif()\n            check_exif(exif)\n\n    def test_exif_frames(self):\n        # Test that EXIF data can change across frames\n        with Image.open(\"Tests/images/g4-multi.tiff\") as im:\n            assert im.getexif()[273] == (328, 815)\n\n            im.seek(1)\n            assert im.getexif()[273] == (1408, 1907)\n\n    def test_seek(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            im.seek(0)\n            assert im.tell() == 0\n\n    def test_seek_eof(self):\n        filename = \"Tests/images/pil136.tiff\"\n        with Image.open(filename) as im:\n            assert im.tell() == 0\n            with pytest.raises(EOFError):\n                im.seek(-1)\n            with pytest.raises(EOFError):\n                im.seek(1)\n\n    def test__limit_rational_int(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 34\n        ret = _limit_rational(value, 65536)\n        assert ret == (34, 1)\n\n    def test__limit_rational_float(self):\n        from PIL.TiffImagePlugin import _limit_rational\n\n        value = 22.3\n        ret = _limit_rational(value, 65536)\n        assert ret == (223, 10)\n\n    def test_4bit(self):\n        test_file = \"Tests/images/hopper_gray_4bpp.tif\"\n        original = hopper(\"L\")\n        with Image.open(test_file) as im:\n            assert im.size == (128, 128)\n            assert im.mode == \"L\"\n            assert_image_similar(im, original, 7.3)\n\n    def test_gray_semibyte_per_pixel(self):\n        test_files = (\n            (\n                24.8,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper2IR.tif\",\n                ),\n            ),\n            (\n                7.3,  # epsilon\n                (  # group\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4I.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4R.tif\",\n                    \"Tests/images/tiff_gray_2_4_bpp/hopper4IR.tif\",\n                ),\n            ),\n        )\n        original = hopper(\"L\")\n        for epsilon, group in test_files:\n            with Image.open(group[0]) as im:\n                assert im.size == (128, 128)\n                assert im.mode == \"L\"\n                assert_image_similar(im, original, epsilon)\n                for file in group[1:]:\n                    with Image.open(file) as im2:\n                        assert im2.size == (128, 128)\n                        assert im2.mode == \"L\"\n                        assert_image_equal(im, im2)\n\n    def test_with_underscores(self, tmp_path):\n        kwargs = {\"resolution_unit\": \"inch\", \"x_resolution\": 72, \"y_resolution\": 36}\n        filename = str(tmp_path / \"temp.tif\")\n        hopper(\"RGB\").save(filename, **kwargs)\n        with Image.open(filename) as im:\n\n            # legacy interface\n            assert im.tag[X_RESOLUTION][0][0] == 72\n            assert im.tag[Y_RESOLUTION][0][0] == 36\n\n            # v2 interface\n            assert im.tag_v2[X_RESOLUTION] == 72\n            assert im.tag_v2[Y_RESOLUTION] == 36\n\n    def test_roundtrip_tiff_uint16(self, tmp_path):\n        # Test an image of all '0' values\n        pixel_value = 0x1234\n        infile = \"Tests/images/uint16_1_4660.tif\"\n        with Image.open(infile) as im:\n            assert im.getpixel((0, 0)) == pixel_value\n\n            tmpfile = str(tmp_path / \"temp.tif\")\n            im.save(tmpfile)\n\n            assert_image_equal_tofile(im, tmpfile)\n\n    def test_strip_raw(self):\n        infile = \"Tests/images/tiff_strip_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw(self):\n        # gdal_translate -of GTiff -co INTERLEAVE=BAND \\\n        # tiff_strip_raw.tif tiff_strip_planar_raw.tiff\n        infile = \"Tests/images/tiff_strip_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_strip_planar_raw_with_overviews(self):\n        # gdaladdo tiff_strip_planar_raw2.tif 2 4 8 16\n        infile = \"Tests/images/tiff_strip_planar_raw_with_overviews.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_tiled_planar_raw(self):\n        # gdal_translate -of GTiff -co TILED=YES -co BLOCKXSIZE=32 \\\n        # -co BLOCKYSIZE=32 -co INTERLEAVE=BAND \\\n        # tiff_tiled_raw.tif tiff_tiled_planar_raw.tiff\n        infile = \"Tests/images/tiff_tiled_planar_raw.tif\"\n        with Image.open(infile) as im:\n            assert_image_equal_tofile(im, \"Tests/images/tiff_adobe_deflate.png\")\n\n    def test_palette(self, tmp_path):\n        def roundtrip(mode):\n            outfile = str(tmp_path / \"temp.tif\")\n\n            im = hopper(mode)\n            im.save(outfile)\n\n            with Image.open(outfile) as reloaded:\n                assert_image_equal(im.convert(\"RGB\"), reloaded.convert(\"RGB\"))\n\n        for mode in [\"P\", \"PA\"]:\n            roundtrip(mode)\n\n    def test_tiff_save_all(self):\n        mp = BytesIO()\n        with Image.open(\"Tests/images/multipage.tiff\") as im:\n            im.save(mp, format=\"tiff\", save_all=True)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as im:\n            assert im.n_frames == 3\n\n        # Test appending images\n        mp = BytesIO()\n        im = Image.new(\"RGB\", (100, 100), \"#f00\")\n        ims = [Image.new(\"RGB\", (100, 100), color) for color in [\"#0f0\", \"#00f\"]]\n        im.copy().save(mp, format=\"TIFF\", save_all=True, append_images=ims)\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n        # Test appending using a generator\n        def imGenerator(ims):\n            yield from ims\n\n        mp = BytesIO()\n        im.save(mp, format=\"TIFF\", save_all=True, append_images=imGenerator(ims))\n\n        mp.seek(0, os.SEEK_SET)\n        with Image.open(mp) as reread:\n            assert reread.n_frames == 3\n\n    def test_saving_icc_profile(self, tmp_path):\n        # Tests saving TIFF with icc_profile set.\n        # At the time of writing this will only work for non-compressed tiffs\n        # as libtiff does not support embedded ICC profiles,\n        # ImageFile._save(..) however does.\n        im = Image.new(\"RGB\", (1, 1))\n        im.info[\"icc_profile\"] = \"Dummy value\"\n\n        # Try save-load round trip to make sure both handle icc_profile.\n        tmpfile = str(tmp_path / \"temp.tif\")\n        im.save(tmpfile, \"TIFF\", compression=\"raw\")\n        with Image.open(tmpfile) as reloaded:\n            assert b\"Dummy value\" == reloaded.info[\"icc_profile\"]\n\n    def test_save_icc_profile(self, tmp_path):\n        im = hopper()\n        assert \"icc_profile\" not in im.info\n\n        outfile = str(tmp_path / \"temp.tif\")\n        icc_profile = b\"Dummy value\"\n        im.save(outfile, icc_profile=icc_profile)\n\n        with Image.open(outfile) as reloaded:\n            assert reloaded.info[\"icc_profile\"] == icc_profile\n\n    def test_discard_icc_profile(self, tmp_path):\n        outfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/icc_profile.png\") as im:\n            assert \"icc_profile\" in im.info\n\n            im.save(outfile, icc_profile=None)\n\n        with Image.open(outfile) as reloaded:\n            assert \"icc_profile\" not in reloaded.info\n\n    def test_getxmp(self):\n        with Image.open(\"Tests/images/lab.tif\") 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[0][\"format\"] == \"image/tiff\"\n                assert description[3][\"BitsPerSample\"][\"Seq\"][\"li\"] == [\"8\", \"8\", \"8\"]\n\n    def test_close_on_load_exclusive(self, tmp_path):\n        # similar to test_fd_leak, but runs on unixlike os\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        im = Image.open(tmpfile)\n        fp = im.fp\n        assert not fp.closed\n        im.load()\n        assert fp.closed\n\n    def test_close_on_load_nonexclusive(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") as im:\n            im.save(tmpfile)\n\n        with open(tmpfile, \"rb\") as f:\n            im = Image.open(f)\n            fp = im.fp\n            assert not fp.closed\n            im.load()\n            assert not fp.closed\n\n    # Ignore this UserWarning which triggers for four tags:\n    # \"Possibly corrupt EXIF data.  Expecting to read 50404352 bytes but...\"\n    @pytest.mark.filterwarnings(\"ignore:Possibly corrupt EXIF data\")\n    @pytest.mark.skipif(\n        not os.path.exists(\"Tests/images/string_dimension.tiff\"),\n        reason=\"Extra image files not installed\",\n    )\n    def test_string_dimension(self):\n        # Assert that an error is raised if one of the dimensions is a string\n        with Image.open(\"Tests/images/string_dimension.tiff\") as im:\n            with pytest.raises(OSError):\n                im.load()\n\n\n@pytest.mark.skipif(not is_win32(), reason=\"Windows only\")\nclass TestFileTiffW32:\n    def test_fd_leak(self, tmp_path):\n        tmpfile = str(tmp_path / \"temp.tif\")\n\n        # this is an mmaped file.\n        with Image.open(\"Tests/images/uint16_1_4660.tif\") 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\n        # this closes the mmap\n        im.close()\n\n        # this should not fail, as load should have closed the file pointer,\n        # and close should have closed the mmap\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 196ms\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 727ms\nPrepared 1 package in 33.98s\nInstalled 1 package in 1ms\n + pillow==8.4.0.dev0 (from file:///home/gcpuser/buckets/local_repoeval_bucket/repos/pillow_c0f619384cbccef86185b1cc1b4da9755b1e344b)\n",
    "new_commit_res_code": 1,
    "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_c0f619384cbccef86185b1cc1b4da9755b1e344b\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 62 items\n\nr2e_tests/test_1.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31mF\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[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 [ 83%]\n\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[33ms\u001b[0m\u001b[31m                                                               [100%]\u001b[0m\n\n=================================== FAILURES ===================================\n\u001b[31m\u001b[1m________________________ TestFileTiff.test_closed_file _________________________\u001b[0m\n\nself = <r2e_tests.test_1.TestFileTiff object at 0x7e7308656400>\n\n    def test_closed_file(self):\n>       with pytest.warns(None) as record:\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:67: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = WarningsChecker(record=True), expected_warning = None, match_expr = None\n\n    def __init__(\n        self,\n        expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,\n        match_expr: str | Pattern[str] | None = None,\n        *,\n        _ispytest: bool = False,\n    ) -> None:\n        check_ispytest(_ispytest)\n        super().__init__(_ispytest=True)\n    \n        msg = \"exceptions must be derived from Warning, not %s\"\n        if isinstance(expected_warning, tuple):\n            for exc in expected_warning:\n                if not issubclass(exc, Warning):\n                    raise TypeError(msg % type(exc))\n            expected_warning_tup = expected_warning\n        elif isinstance(expected_warning, type) and issubclass(\n            expected_warning, Warning\n        ):\n            expected_warning_tup = (expected_warning,)\n        else:\n>           raise TypeError(msg % type(expected_warning))\n\u001b[1m\u001b[31mE           TypeError: exceptions must be derived from Warning, not <class 'NoneType'>\u001b[0m\n\n\u001b[1m\u001b[31m.venv/lib/python3.9/site-packages/_pytest/recwarn.py\u001b[0m:279: TypeError\n\u001b[31m\u001b[1m______________________ TestFileTiff.test_context_manager _______________________\u001b[0m\n\nself = <r2e_tests.test_1.TestFileTiff object at 0x7e7308656220>\n\n    def test_context_manager(self):\n>       with pytest.warns(None) as record:\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:75: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = WarningsChecker(record=True), expected_warning = None, match_expr = None\n\n    def __init__(\n        self,\n        expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,\n        match_expr: str | Pattern[str] | None = None,\n        *,\n        _ispytest: bool = False,\n    ) -> None:\n        check_ispytest(_ispytest)\n        super().__init__(_ispytest=True)\n    \n        msg = \"exceptions must be derived from Warning, not %s\"\n        if isinstance(expected_warning, tuple):\n            for exc in expected_warning:\n                if not issubclass(exc, Warning):\n                    raise TypeError(msg % type(exc))\n            expected_warning_tup = expected_warning\n        elif isinstance(expected_warning, type) and issubclass(\n            expected_warning, Warning\n        ):\n            expected_warning_tup = (expected_warning,)\n        else:\n>           raise TypeError(msg % type(expected_warning))\n\u001b[1m\u001b[31mE           TypeError: exceptions must be derived from Warning, not <class 'NoneType'>\u001b[0m\n\n\u001b[1m\u001b[31m.venv/lib/python3.9/site-packages/_pytest/recwarn.py\u001b[0m:279: TypeError\n==================================== PASSES ====================================\n\u001b[36m\u001b[1m=========================== short test summary info ============================\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_sanity\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_unclosed_file\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_mac_tiff\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_wrong_bits_per_sample\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_set_legacy_api\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_xyres_tiff\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_xyres_fallback_tiff\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_int_resolution\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_float_dpi[None-72.8]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_float_dpi[2-72.8]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_float_dpi[3-184.912]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_save_float_dpi\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_save_setting_missing_resolution\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_invalid_file\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_bad_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_save_rgba\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_save_unsupported_mode\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_little_endian\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_big_endian\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_16bit_s\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_12bit_rawmode\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_32bit_float\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_unknown_pixel_mode\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_n_frames\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_eoferror\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_multipage\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_multipage_last_frame\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_frame_order\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test___str__\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_dict\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test__delitem__\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_byte\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_string\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_float\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_double\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_ifd_tag_type\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_exif_frames\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_seek\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_seek_eof\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test__limit_rational_int\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test__limit_rational_float\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_4bit\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_gray_semibyte_per_pixel\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_with_underscores\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_roundtrip_tiff_uint16\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_strip_raw\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_strip_planar_raw\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_strip_planar_raw_with_overviews\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_tiled_planar_raw\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_palette\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_tiff_save_all\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_saving_icc_profile\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_save_icc_profile\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_discard_icc_profile\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_getxmp\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_close_on_load_exclusive\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_close_on_load_nonexclusive\u001b[0m\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:700: Extra image files not installed\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:714: Windows only\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_closed_file\u001b[0m - TypeError: exceptions must be derived from Warning, not <class 'NoneType'>\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_context_manager\u001b[0m - TypeError: exceptions must be derived from Warning, not <class 'NoneType'>\n\u001b[31m=================== \u001b[31m\u001b[1m2 failed\u001b[0m, \u001b[32m58 passed\u001b[0m, \u001b[33m2 skipped\u001b[0m\u001b[31m in 0.43s\u001b[0m\u001b[31m ====================\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_c0f619384cbccef86185b1cc1b4da9755b1e344b\nconfigfile: setup.cfg\nplugins: cov-6.0.0\ncollected 62 items\n\nr2e_tests/test_1.py \u001b[32m.\u001b[0m\u001b[32m.\u001b[0m\u001b[31mF\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[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[31m [ 83%]\n\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[33ms\u001b[0m\u001b[31m                                                               [100%]\u001b[0m\n\n=================================== FAILURES ===================================\n\u001b[31m\u001b[1m________________________ TestFileTiff.test_closed_file _________________________\u001b[0m\n\nself = <r2e_tests.test_1.TestFileTiff object at 0x7b1b67cc1af0>\n\n    def test_closed_file(self):\n>       with pytest.warns(None) as record:\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:67: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = WarningsChecker(record=True), expected_warning = None, match_expr = None\n\n    def __init__(\n        self,\n        expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,\n        match_expr: str | Pattern[str] | None = None,\n        *,\n        _ispytest: bool = False,\n    ) -> None:\n        check_ispytest(_ispytest)\n        super().__init__(_ispytest=True)\n    \n        msg = \"exceptions must be derived from Warning, not %s\"\n        if isinstance(expected_warning, tuple):\n            for exc in expected_warning:\n                if not issubclass(exc, Warning):\n                    raise TypeError(msg % type(exc))\n            expected_warning_tup = expected_warning\n        elif isinstance(expected_warning, type) and issubclass(\n            expected_warning, Warning\n        ):\n            expected_warning_tup = (expected_warning,)\n        else:\n>           raise TypeError(msg % type(expected_warning))\n\u001b[1m\u001b[31mE           TypeError: exceptions must be derived from Warning, not <class 'NoneType'>\u001b[0m\n\n\u001b[1m\u001b[31m.venv/lib/python3.9/site-packages/_pytest/recwarn.py\u001b[0m:279: TypeError\n\u001b[31m\u001b[1m______________________ TestFileTiff.test_context_manager _______________________\u001b[0m\n\nself = <r2e_tests.test_1.TestFileTiff object at 0x7b1b67cc1c70>\n\n    def test_context_manager(self):\n>       with pytest.warns(None) as record:\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:75: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nself = WarningsChecker(record=True), expected_warning = None, match_expr = None\n\n    def __init__(\n        self,\n        expected_warning: type[Warning] | tuple[type[Warning], ...] = Warning,\n        match_expr: str | Pattern[str] | None = None,\n        *,\n        _ispytest: bool = False,\n    ) -> None:\n        check_ispytest(_ispytest)\n        super().__init__(_ispytest=True)\n    \n        msg = \"exceptions must be derived from Warning, not %s\"\n        if isinstance(expected_warning, tuple):\n            for exc in expected_warning:\n                if not issubclass(exc, Warning):\n                    raise TypeError(msg % type(exc))\n            expected_warning_tup = expected_warning\n        elif isinstance(expected_warning, type) and issubclass(\n            expected_warning, Warning\n        ):\n            expected_warning_tup = (expected_warning,)\n        else:\n>           raise TypeError(msg % type(expected_warning))\n\u001b[1m\u001b[31mE           TypeError: exceptions must be derived from Warning, not <class 'NoneType'>\u001b[0m\n\n\u001b[1m\u001b[31m.venv/lib/python3.9/site-packages/_pytest/recwarn.py\u001b[0m:279: TypeError\n\u001b[31m\u001b[1m____________________________ TestFileTiff.test_exif ____________________________\u001b[0m\n\nself = <r2e_tests.test_1.TestFileTiff object at 0x7b1b67c77400>\ntmp_path = PosixPath('/tmp/pytest-of-gcpuser/pytest-570/test_exif0')\n\n    def test_exif(self, tmp_path):\n        def check_exif(exif):\n            assert sorted(exif.keys()) == [\n                256,\n                257,\n                258,\n                259,\n                262,\n                271,\n                272,\n                273,\n                277,\n                278,\n                279,\n                282,\n                283,\n                284,\n                296,\n                297,\n                305,\n                339,\n                700,\n                34665,\n                34853,\n                50735,\n            ]\n            assert exif[256] == 640\n            assert exif[271] == \"FLIR\"\n    \n            gps = exif.get_ifd(0x8825)\n            assert list(gps.keys()) == [0, 1, 2, 3, 4, 5, 6, 18]\n            assert gps[0] == b\"\\x03\\x02\\x00\\x00\"\n            assert gps[18] == \"WGS-84\"\n    \n        outfile = str(tmp_path / \"temp.tif\")\n        with Image.open(\"Tests/images/ifd_tag_type.tiff\") as im:\n            exif = im.getexif()\n            check_exif(exif)\n    \n            im.save(outfile, exif=exif)\n    \n        with Image.open(outfile) as im:\n            exif = im.getexif()\n>           check_exif(exif)\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:449: \n_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ \n\nexif = <PIL.Image.Exif object at 0x7b1b67bd35e0>\n\n    def check_exif(exif):\n>       assert sorted(exif.keys()) == [\n            256,\n            257,\n            258,\n            259,\n            262,\n            271,\n            272,\n            273,\n            277,\n            278,\n            279,\n            282,\n            283,\n            284,\n            296,\n            297,\n            305,\n            339,\n            700,\n            34665,\n            34853,\n            50735,\n        ]\n\u001b[1m\u001b[31mE       assert [256, 257, 25...262, 273, ...] == [256, 257, 25...262, 271, ...]\u001b[0m\n\u001b[1m\u001b[31mE         \u001b[0m\n\u001b[1m\u001b[31mE         At index 5 diff: 273 != 271\u001b[0m\n\u001b[1m\u001b[31mE         Right contains 9 more items, first extra item: 284\u001b[0m\n\u001b[1m\u001b[31mE         Use -v to get more diff\u001b[0m\n\n\u001b[1m\u001b[31mr2e_tests/test_1.py\u001b[0m:408: AssertionError\n==================================== PASSES ====================================\n\u001b[36m\u001b[1m=========================== short test summary info ============================\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_sanity\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_unclosed_file\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_mac_tiff\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_wrong_bits_per_sample\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_set_legacy_api\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_xyres_tiff\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_xyres_fallback_tiff\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_int_resolution\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_float_dpi[None-72.8]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_float_dpi[2-72.8]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_float_dpi[3-184.912]\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_save_float_dpi\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_save_setting_missing_resolution\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_invalid_file\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_bad_exif\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_save_rgba\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_save_unsupported_mode\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_little_endian\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_big_endian\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_16bit_s\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_12bit_rawmode\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_32bit_float\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_unknown_pixel_mode\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_n_frames\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_eoferror\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_multipage\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_multipage_last_frame\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_frame_order\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test___str__\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_dict\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test__delitem__\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_byte\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_string\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_float\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_load_double\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_ifd_tag_type\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_exif_frames\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_seek\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_seek_eof\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test__limit_rational_int\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test__limit_rational_float\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_4bit\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_gray_semibyte_per_pixel\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_with_underscores\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_roundtrip_tiff_uint16\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_strip_raw\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_strip_planar_raw\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_strip_planar_raw_with_overviews\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_tiled_planar_raw\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_palette\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_tiff_save_all\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_saving_icc_profile\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_save_icc_profile\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_discard_icc_profile\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_getxmp\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_close_on_load_exclusive\u001b[0m\n\u001b[32mPASSED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_close_on_load_nonexclusive\u001b[0m\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:700: Extra image files not installed\n\u001b[33mSKIPPED\u001b[0m [1] r2e_tests/test_1.py:714: Windows only\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_closed_file\u001b[0m - TypeError: exceptions must be derived from Warning, not <class 'NoneType'>\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_context_manager\u001b[0m - TypeError: exceptions must be derived from Warning, not <class 'NoneType'>\n\u001b[31mFAILED\u001b[0m r2e_tests/test_1.py::\u001b[1mTestFileTiff::test_exif\u001b[0m - assert [256, 257, 25...262, 273, ...] == [256, 257, 25...262, 271, ...]\n\u001b[31m=================== \u001b[31m\u001b[1m3 failed\u001b[0m, \u001b[32m57 passed\u001b[0m, \u001b[33m2 skipped\u001b[0m\u001b[31m in 0.29s\u001b[0m\u001b[31m ====================\u001b[0m\n",
    "old_commit_res_stderr": ""
}
Tests/test_file_tiff.py docs/handbook/image-file-formats.rst src/PIL/TiffImagePlugin.py
We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[ISSUE]
**Title:** EXIF Data Not Preserved Correctly When Saving TIFF Images

**Description:**
When saving a TIFF image that contains EXIF metadata using the `save` method, the EXIF information is not retained as expected. This results in missing or incorrect EXIF tags in the saved image, which can lead to issues in applications that rely on this metadata.

**Bug Reproduction:**

```python
from PIL import Image

# Open an image with EXIF data
with Image.open("Tests/images/ifd_tag_type.tiff") as im:
    exif = im.getexif()
    # Attempt to save the image with EXIF data
    im.save("temp_saved.tiff", "TIFF")

# Re-open the saved image to verify EXIF data
with Image.open("temp_saved.tiff") as im_saved:
    exif_saved = im_saved.getexif()
    print(sorted(exif_saved.keys()))
    # Expected to include tag 271 ("Make"), but it is missing or incorrect
```

**Expected Behavior:**
The saved TIFF image (`temp_saved.tiff`) should retain all original EXIF metadata, including tags like `271` ("Make"). When re-opening the saved image, the EXIF data should match the original, ensuring that all metadata is accurately preserved.

**Actual Behavior:**
After saving, the EXIF data in `temp_saved.tiff` is incomplete or altered. For instance, tag `271` ("Make") is either missing or does not match the original value. This discrepancy causes assertions related to EXIF data validation to fail, indicating that the metadata was not preserved correctly during the save operation.

[/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/TiffImagePlugin.py
#
# The Python Imaging Library.
# $Id$
#
# TIFF file handling
#
# TIFF is a flexible, if somewhat aged, image file format originally
# defined by Aldus.  Although TIFF supports a wide variety of pixel
# layouts and compression methods, the name doesn't really stand for
# "thousands of incompatible file formats," it just feels that way.
#
# To read TIFF data from a stream, the stream must be seekable.  For
# progressive decoding, make sure to use TIFF files where the tag
# directory is placed first in the file.
#
# History:
# 1995-09-01 fl   Created
# 1996-05-04 fl   Handle JPEGTABLES tag
# 1996-05-18 fl   Fixed COLORMAP support
# 1997-01-05 fl   Fixed PREDICTOR support
# 1997-08-27 fl   Added support for rational tags (from Perry Stoll)
# 1998-01-10 fl   Fixed seek/tell (from Jan Blom)
# 1998-07-15 fl   Use private names for internal variables
# 1999-06-13 fl   Rewritten for PIL 1.0 (1.0)
# 2000-10-11 fl   Additional fixes for Python 2.0 (1.1)
# 2001-04-17 fl   Fixed rewind support (seek to frame 0) (1.2)
# 2001-05-12 fl   Added write support for more tags (from Greg Couch) (1.3)
# 2001-12-18 fl   Added workaround for broken Matrox library
# 2002-01-18 fl   Don't mess up if photometric tag is missing (D. Alan Stewart)
# 2003-05-19 fl   Check FILLORDER tag
# 2003-09-26 fl   Added RGBa support
# 2004-02-24 fl   Added DPI support; fixed rational write support
# 2005-02-07 fl   Added workaround for broken Corel Draw 10 files
# 2006-01-09 fl   Added support for float/double tags (from Russell Nelson)
#
# Copyright (c) 1997-2006 by Secret Labs AB.  All rights reserved.
# Copyright (c) 1995-1997 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#
import io
import itertools
import logging
import os
import struct
import warnings
from collections.abc import MutableMapping
from fractions import Fraction
from numbers import Number, Rational

from . import Image, ImageFile, ImagePalette, TiffTags
from ._binary import o8
from .TiffTags import TYPES

logger = logging.getLogger(__name__)

# Set these to true to force use of libtiff for reading or writing.
READ_LIBTIFF = False
WRITE_LIBTIFF = False
IFD_LEGACY_API = True

II = b"II"  # little-endian (Intel style)
MM = b"MM"  # big-endian (Motorola style)

#
# --------------------------------------------------------------------
# Read TIFF files

# a few tag names, just to make the code below a bit more readable
IMAGEWIDTH = 256
IMAGELENGTH = 257
BITSPERSAMPLE = 258
COMPRESSION = 259
PHOTOMETRIC_INTERPRETATION = 262
FILLORDER = 266
IMAGEDESCRIPTION = 270
STRIPOFFSETS = 273
SAMPLESPERPIXEL = 277
ROWSPERSTRIP = 278
STRIPBYTECOUNTS = 279
X_RESOLUTION = 282
Y_RESOLUTION = 283
PLANAR_CONFIGURATION = 284
RESOLUTION_UNIT = 296
TRANSFERFUNCTION = 301
SOFTWARE = 305
DATE_TIME = 306
ARTIST = 315
PREDICTOR = 317
COLORMAP = 320
TILEOFFSETS = 324
SUBIFD = 330
EXTRASAMPLES = 338
SAMPLEFORMAT = 339
JPEGTABLES = 347
REFERENCEBLACKWHITE = 532
COPYRIGHT = 33432
IPTC_NAA_CHUNK = 33723  # newsphoto properties
PHOTOSHOP_CHUNK = 34377  # photoshop properties
ICCPROFILE = 34675
EXIFIFD = 34665
XMP = 700
JPEGQUALITY = 65537  # pseudo-tag by libtiff

# https://github.com/imagej/ImageJA/blob/master/src/main/java/ij/io/TiffDecoder.java
IMAGEJ_META_DATA_BYTE_COUNTS = 50838
IMAGEJ_META_DATA = 50839

COMPRESSION_INFO = {
    # Compression => pil compression name
    1: "raw",
    2: "tiff_ccitt",
    3: "group3",
    4: "group4",
    5: "tiff_lzw",
    6: "tiff_jpeg",  # obsolete
    7: "jpeg",
    8: "tiff_adobe_deflate",
    32771: "tiff_raw_16",  # 16-bit padding
    32773: "packbits",
    32809: "tiff_thunderscan",
    32946: "tiff_deflate",
    34676: "tiff_sgilog",
    34677: "tiff_sgilog24",
    34925: "lzma",
    50000: "zstd",
    50001: "webp",
}

COMPRESSION_INFO_REV = {v: k for k, v in COMPRESSION_INFO.items()}

OPEN_INFO = {
    # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample,
    #  ExtraSamples) => mode, rawmode
    (II, 0, (1,), 1, (1,), ()): ("1", "1;I"),
    (MM, 0, (1,), 1, (1,), ()): ("1", "1;I"),
    (II, 0, (1,), 2, (1,), ()): ("1", "1;IR"),
    (MM, 0, (1,), 2, (1,), ()): ("1", "1;IR"),
    (II, 1, (1,), 1, (1,), ()): ("1", "1"),
    (MM, 1, (1,), 1, (1,), ()): ("1", "1"),
    (II, 1, (1,), 2, (1,), ()): ("1", "1;R"),
    (MM, 1, (1,), 2, (1,), ()): ("1", "1;R"),
    (II, 0, (1,), 1, (2,), ()): ("L", "L;2I"),
    (MM, 0, (1,), 1, (2,), ()): ("L", "L;2I"),
    (II, 0, (1,), 2, (2,), ()): ("L", "L;2IR"),
    (MM, 0, (1,), 2, (2,), ()): ("L", "L;2IR"),
    (II, 1, (1,), 1, (2,), ()): ("L", "L;2"),
    (MM, 1, (1,), 1, (2,), ()): ("L", "L;2"),
    (II, 1, (1,), 2, (2,), ()): ("L", "L;2R"),
    (MM, 1, (1,), 2, (2,), ()): ("L", "L;2R"),
    (II, 0, (1,), 1, (4,), ()): ("L", "L;4I"),
    (MM, 0, (1,), 1, (4,), ()): ("L", "L;4I"),
    (II, 0, (1,), 2, (4,), ()): ("L", "L;4IR"),
    (MM, 0, (1,), 2, (4,), ()): ("L", "L;4IR"),
    (II, 1, (1,), 1, (4,), ()): ("L", "L;4"),
    (MM, 1, (1,), 1, (4,), ()): ("L", "L;4"),
    (II, 1, (1,), 2, (4,), ()): ("L", "L;4R"),
    (MM, 1, (1,), 2, (4,), ()): ("L", "L;4R"),
    (II, 0, (1,), 1, (8,), ()): ("L", "L;I"),
    (MM, 0, (1,), 1, (8,), ()): ("L", "L;I"),
    (II, 0, (1,), 2, (8,), ()): ("L", "L;IR"),
    (MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"),
    (II, 1, (1,), 1, (8,), ()): ("L", "L"),
    (MM, 1, (1,), 1, (8,), ()): ("L", "L"),
    (II, 1, (1,), 2, (8,), ()): ("L", "L;R"),
    (MM, 1, (1,), 2, (8,), ()): ("L", "L;R"),
    (II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"),
    (II, 1, (1,), 1, (16,), ()): ("I;16", "I;16"),
    (MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"),
    (II, 1, (2,), 1, (16,), ()): ("I", "I;16S"),
    (MM, 1, (2,), 1, (16,), ()): ("I", "I;16BS"),
    (II, 0, (3,), 1, (32,), ()): ("F", "F;32F"),
    (MM, 0, (3,), 1, (32,), ()): ("F", "F;32BF"),
    (II, 1, (1,), 1, (32,), ()): ("I", "I;32N"),
    (II, 1, (2,), 1, (32,), ()): ("I", "I;32S"),
    (MM, 1, (2,), 1, (32,), ()): ("I", "I;32BS"),
    (II, 1, (3,), 1, (32,), ()): ("F", "F;32F"),
    (MM, 1, (3,), 1, (32,), ()): ("F", "F;32BF"),
    (II, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"),
    (MM, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"),
    (II, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
    (MM, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
    (II, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
    (MM, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
    (II, 2, (1,), 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"),  # missing ExtraSamples
    (MM, 2, (1,), 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"),  # missing ExtraSamples
    (II, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGBX", "RGBXX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGBX", "RGBXX"),
    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGBX", "RGBXXX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGBX", "RGBXXX"),
    (II, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
    (MM, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"),
    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"),
    (II, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
    (MM, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"),
    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"),
    (II, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"),  # Corel Draw 10
    (MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"),  # Corel Draw 10
    (II, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16L"),
    (MM, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16B"),
    (II, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16L"),
    (MM, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16B"),
    (II, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGBX", "RGBX;16L"),
    (MM, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGBX", "RGBX;16B"),
    (II, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16L"),
    (MM, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16B"),
    (II, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16L"),
    (MM, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16B"),
    (II, 3, (1,), 1, (1,), ()): ("P", "P;1"),
    (MM, 3, (1,), 1, (1,), ()): ("P", "P;1"),
    (II, 3, (1,), 2, (1,), ()): ("P", "P;1R"),
    (MM, 3, (1,), 2, (1,), ()): ("P", "P;1R"),
    (II, 3, (1,), 1, (2,), ()): ("P", "P;2"),
    (MM, 3, (1,), 1, (2,), ()): ("P", "P;2"),
    (II, 3, (1,), 2, (2,), ()): ("P", "P;2R"),
    (MM, 3, (1,), 2, (2,), ()): ("P", "P;2R"),
    (II, 3, (1,), 1, (4,), ()): ("P", "P;4"),
    (MM, 3, (1,), 1, (4,), ()): ("P", "P;4"),
    (II, 3, (1,), 2, (4,), ()): ("P", "P;4R"),
    (MM, 3, (1,), 2, (4,), ()): ("P", "P;4R"),
    (II, 3, (1,), 1, (8,), ()): ("P", "P"),
    (MM, 3, (1,), 1, (8,), ()): ("P", "P"),
    (II, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
    (MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
    (II, 3, (1,), 2, (8,), ()): ("P", "P;R"),
    (MM, 3, (1,), 2, (8,), ()): ("P", "P;R"),
    (II, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
    (MM, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
    (II, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"),
    (MM, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"),
    (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
    (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
    (II, 5, (1,), 1, (16, 16, 16, 16), ()): ("CMYK", "CMYK;16L"),
    # JPEG compressed images handled by LibTiff and auto-converted to RGBX
    # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel
    (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
    (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
    (II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
    (MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
}

PREFIXES = [
    b"MM\x00\x2A",  # Valid TIFF header with big-endian byte order
    b"II\x2A\x00",  # Valid TIFF header with little-endian byte order
    b"MM\x2A\x00",  # Invalid TIFF header, assume big-endian
    b"II\x00\x2A",  # Invalid TIFF header, assume little-endian
]


def _accept(prefix):
    return prefix[:4] in PREFIXES


def _limit_rational(val, max_val):
    inv = abs(val) > 1
    n_d = IFDRational(1 / val if inv else val).limit_rational(max_val)
    return n_d[::-1] if inv else n_d


def _limit_signed_rational(val, max_val, min_val):
    frac = Fraction(val)
    n_d = frac.numerator, frac.denominator

    if min(n_d) < min_val:
        n_d = _limit_rational(val, abs(min_val))

    if max(n_d) > max_val:
        val = Fraction(*n_d)
        n_d = _limit_rational(val, max_val)

    return n_d


##
# Wrapper for TIFF IFDs.

_load_dispatch = {}
_write_dispatch = {}


class IFDRational(Rational):
    """Implements a rational class where 0/0 is a legal value to match
    the in the wild use of exif rationals.

    e.g., DigitalZoomRatio - 0.00/0.00  indicates that no digital zoom was used
    """

    """ If the denominator is 0, store this as a float('nan'), otherwise store
    as a fractions.Fraction(). Delegate as appropriate

    """

    __slots__ = ("_numerator", "_denominator", "_val")

    def __init__(self, value, denominator=1):
        """
        :param value: either an integer numerator, a
        float/rational/other number, or an IFDRational
        :param denominator: Optional integer denominator
        """
        if isinstance(value, IFDRational):
            self._numerator = value.numerator
            self._denominator = value.denominator
            self._val = value._val
            return

        if isinstance(value, Fraction):
            self._numerator = value.numerator
            self._denominator = value.denominator
        else:
            self._numerator = value
            self._denominator = denominator

        if denominator == 0:
            self._val = float("nan")
        elif denominator == 1:
            self._val = Fraction(value)
        else:
            self._val = Fraction(value, denominator)

    @property
    def numerator(a):
        return a._numerator

    @property
    def denominator(a):
        return a._denominator

    def limit_rational(self, max_denominator):
        """

        :param max_denominator: Integer, the maximum denominator value
        :returns: Tuple of (numerator, denominator)
        """

        if self.denominator == 0:
            return (self.numerator, self.denominator)

        f = self._val.limit_denominator(max_denominator)
        return (f.numerator, f.denominator)

    def __repr__(self):
        return str(float(self._val))

    def __hash__(self):
        return self._val.__hash__()

    def __eq__(self, other):
        val = self._val
        if isinstance(other, IFDRational):
            other = other._val
        if isinstance(other, float):
            val = float(val)
        return val == other

    def __getstate__(self):
        return [self._val, self._numerator, self._denominator]

    def __setstate__(self, state):
        IFDRational.__init__(self, 0)
        _val, _numerator, _denominator = state
        self._val = _val
        self._numerator = _numerator
        self._denominator = _denominator

    def _delegate(op):
        def delegate(self, *args):
            return getattr(self._val, op)(*args)

        return delegate

    """ a = ['add','radd', 'sub', 'rsub', 'mul', 'rmul',
             'truediv', 'rtruediv', 'floordiv', 'rfloordiv',
             'mod','rmod', 'pow','rpow', 'pos', 'neg',
             'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'bool',
             'ceil', 'floor', 'round']
        print("\n".join("__%s__ = _delegate('__%s__')" % (s,s) for s in a))
        """

    __add__ = _delegate("__add__")
    __radd__ = _delegate("__radd__")
    __sub__ = _delegate("__sub__")
    __rsub__ = _delegate("__rsub__")
    __mul__ = _delegate("__mul__")
    __rmul__ = _delegate("__rmul__")
    __truediv__ = _delegate("__truediv__")
    __rtruediv__ = _delegate("__rtruediv__")
    __floordiv__ = _delegate("__floordiv__")
    __rfloordiv__ = _delegate("__rfloordiv__")
    __mod__ = _delegate("__mod__")
    __rmod__ = _delegate("__rmod__")
    __pow__ = _delegate("__pow__")
    __rpow__ = _delegate("__rpow__")
    __pos__ = _delegate("__pos__")
    __neg__ = _delegate("__neg__")
    __abs__ = _delegate("__abs__")
    __trunc__ = _delegate("__trunc__")
    __lt__ = _delegate("__lt__")
    __gt__ = _delegate("__gt__")
    __le__ = _delegate("__le__")
    __ge__ = _delegate("__ge__")
    __bool__ = _delegate("__bool__")
    __ceil__ = _delegate("__ceil__")
    __floor__ = _delegate("__floor__")
    __round__ = _delegate("__round__")


class ImageFileDirectory_v2(MutableMapping):
    """This class represents a TIFF tag directory.  To speed things up, we
    don't decode tags unless they're asked for.

    Exposes a dictionary interface of the tags in the directory::

        ifd = ImageFileDirectory_v2()
        ifd[key] = 'Some Data'
        ifd.tagtype[key] = TiffTags.ASCII
        print(ifd[key])
        'Some Data'

    Individual values are returned as the strings or numbers, sequences are
    returned as tuples of the values.

    The tiff metadata type of each item is stored in a dictionary of
    tag types in
    :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types
    are read from a tiff file, guessed from the type added, or added
    manually.

    Data Structures:

        * self.tagtype = {}

          * Key: numerical tiff tag number
          * Value: integer corresponding to the data type from
                   ~PIL.TiffTags.TYPES`

    .. versionadded:: 3.0.0
    """

    """
    Documentation:

        'internal' data structures:
        * self._tags_v2 = {} Key: numerical tiff tag number
                             Value: decoded data, as tuple for multiple values
        * self._tagdata = {} Key: numerical tiff tag number
                             Value: undecoded byte string from file
        * self._tags_v1 = {} Key: numerical tiff tag number
                             Value: decoded data in the v1 format

    Tags will be found in the private attributes self._tagdata, and in
    self._tags_v2 once decoded.

    self.legacy_api is a value for internal use, and shouldn't be
    changed from outside code. In cooperation with the
    ImageFileDirectory_v1 class, if legacy_api is true, then decoded
    tags will be populated into both _tags_v1 and _tags_v2. _tags_v2
    will be used if this IFD is used in the TIFF save routine. Tags
    should be read from _tags_v1 if legacy_api == true.

    """

    def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None):
        """Initialize an ImageFileDirectory.

        To construct an ImageFileDirectory from a real file, pass the 8-byte
        magic header to the constructor.  To only set the endianness, pass it
        as the 'prefix' keyword argument.

        :param ifh: One of the accepted magic headers (cf. PREFIXES); also sets
              endianness.
        :param prefix: Override the endianness of the file.
        """
        if ifh[:4] not in PREFIXES:
            raise SyntaxError(f"not a TIFF file (header {repr(ifh)} not valid)")
        self._prefix = prefix if prefix is not None else ifh[:2]
        if self._prefix == MM:
            self._endian = ">"
        elif self._prefix == II:
            self._endian = "<"
        else:
            raise SyntaxError("not a TIFF IFD")
        self.group = group
        self.tagtype = {}
        """ Dictionary of tag types """
        self.reset()
        (self.next,) = self._unpack("L", ifh[4:])
        self._legacy_api = False

    prefix = property(lambda self: self._prefix)
    offset = property(lambda self: self._offset)
    legacy_api = property(lambda self: self._legacy_api)

    @legacy_api.setter
    def legacy_api(self, value):
        raise Exception("Not allowing setting of legacy api")

    def reset(self):
        self._tags_v1 = {}  # will remain empty if legacy_api is false
        self._tags_v2 = {}  # main tag storage
        self._tagdata = {}
        self.tagtype = {}  # added 2008-06-05 by Florian Hoech
        self._next = None
        self._offset = None

    def __str__(self):
        return str(dict(self))

    def named(self):
        """
        :returns: dict of name|key: value

        Returns the complete tag dictionary, with named tags where possible.
        """
        return {
            TiffTags.lookup(code, self.group).name: value
            for code, value in self.items()
        }

    def __len__(self):
        return len(set(self._tagdata) | set(self._tags_v2))

    def __getitem__(self, tag):
        if tag not in self._tags_v2:  # unpack on the fly
            data = self._tagdata[tag]
            typ = self.tagtype[tag]
            size, handler = self._load_dispatch[typ]
            self[tag] = handler(self, data, self.legacy_api)  # check type
        val = self._tags_v2[tag]
        if self.legacy_api and not isinstance(val, (tuple, bytes)):
            val = (val,)
        return val

    def __contains__(self, tag):
        return tag in self._tags_v2 or tag in self._tagdata

    def __setitem__(self, tag, value):
        self._setitem(tag, value, self.legacy_api)

    def _setitem(self, tag, value, legacy_api):
        basetypes = (Number, bytes, str)

        info = TiffTags.lookup(tag, self.group)
        values = [value] if isinstance(value, basetypes) else value

        if tag not in self.tagtype:
            if info.type:
                self.tagtype[tag] = info.type
            else:
                self.tagtype[tag] = TiffTags.UNDEFINED
                if all(isinstance(v, IFDRational) for v in values):
                    self.tagtype[tag] = (
                        TiffTags.RATIONAL
                        if all(v >= 0 for v in values)
                        else TiffTags.SIGNED_RATIONAL
                    )
                elif all(isinstance(v, int) for v in values):
                    if all(0 <= v < 2 ** 16 for v in values):
                        self.tagtype[tag] = TiffTags.SHORT
                    elif all(-(2 ** 15) < v < 2 ** 15 for v in values):
                        self.tagtype[tag] = TiffTags.SIGNED_SHORT
                    else:
                        self.tagtype[tag] = (
                            TiffTags.LONG
                            if all(v >= 0 for v in values)
                            else TiffTags.SIGNED_LONG
                        )
                elif all(isinstance(v, float) for v in values):
                    self.tagtype[tag] = TiffTags.DOUBLE
                elif all(isinstance(v, str) for v in values):
                    self.tagtype[tag] = TiffTags.ASCII
                elif all(isinstance(v, bytes) for v in values):
                    self.tagtype[tag] = TiffTags.BYTE

        if self.tagtype[tag] == TiffTags.UNDEFINED:
            values = [
                v.encode("ascii", "replace") if isinstance(v, str) else v
                for v in values
            ]
        elif self.tagtype[tag] == TiffTags.RATIONAL:
            values = [float(v) if isinstance(v, int) else v for v in values]

        is_ifd = self.tagtype[tag] == TiffTags.LONG and isinstance(values, dict)
        if not is_ifd:
            values = tuple(info.cvt_enum(value) for value in values)

        dest = self._tags_v1 if legacy_api else self._tags_v2

        # Three branches:
        # Spec'd length == 1, Actual length 1, store as element
        # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed.
        # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple.
        # Don't mess with the legacy api, since it's frozen.
        if not is_ifd and (
            (info.length == 1)
            or self.tagtype[tag] == TiffTags.BYTE
            or (info.length is None and len(values) == 1 and not legacy_api)
        ):
            # Don't mess with the legacy api, since it's frozen.
            if legacy_api and self.tagtype[tag] in [
                TiffTags.RATIONAL,
                TiffTags.SIGNED_RATIONAL,
            ]:  # rationals
                values = (values,)
            try:
                (dest[tag],) = values
            except ValueError:
                # We've got a builtin tag with 1 expected entry
                warnings.warn(
                    f"Metadata Warning, tag {tag} had too many entries: "
                    f"{len(values)}, expected 1"
                )
                dest[tag] = values[0]

        else:
            # Spec'd length > 1 or undefined
            # Unspec'd, and length > 1
            dest[tag] = values

    def __delitem__(self, tag):
        self._tags_v2.pop(tag, None)
        self._tags_v1.pop(tag, None)
        self._tagdata.pop(tag, None)

    def __iter__(self):
        return iter(set(self._tagdata) | set(self._tags_v2))

    def _unpack(self, fmt, data):
        return struct.unpack(self._endian + fmt, data)

    def _pack(self, fmt, *values):
        return struct.pack(self._endian + fmt, *values)

    def _register_loader(idx, size):
        def decorator(func):
            from .TiffTags import TYPES

            if func.__name__.startswith("load_"):
                TYPES[idx] = func.__name__[5:].replace("_", " ")
            _load_dispatch[idx] = size, func  # noqa: F821
            return func

        return decorator

    def _register_writer(idx):
        def decorator(func):
            _write_dispatch[idx] = func  # noqa: F821
            return func

        return decorator

    def _register_basic(idx_fmt_name):
        from .TiffTags import TYPES

        idx, fmt, name = idx_fmt_name
        TYPES[idx] = name
        size = struct.calcsize("=" + fmt)
        _load_dispatch[idx] = (  # noqa: F821
            size,
            lambda self, data, legacy_api=True: (
                self._unpack("{}{}".format(len(data) // size, fmt), data)
            ),
        )
        _write_dispatch[idx] = lambda self, *values: (  # noqa: F821
            b"".join(self._pack(fmt, value) for value in values)
        )

    list(
        map(
            _register_basic,
            [
                (TiffTags.SHORT, "H", "short"),
                (TiffTags.LONG, "L", "long"),
                (TiffTags.SIGNED_BYTE, "b", "signed byte"),
                (TiffTags.SIGNED_SHORT, "h", "signed short"),
                (TiffTags.SIGNED_LONG, "l", "signed long"),
                (TiffTags.FLOAT, "f", "float"),
                (TiffTags.DOUBLE, "d", "double"),
                (TiffTags.IFD, "L", "long"),
            ],
        )
    )

    @_register_loader(1, 1)  # Basic type, except for the legacy API.
    def load_byte(self, data, legacy_api=True):
        return data

    @_register_writer(1)  # Basic type, except for the legacy API.
    def write_byte(self, data):
        return data

    @_register_loader(2, 1)
    def load_string(self, data, legacy_api=True):
        if data.endswith(b"\0"):
            data = data[:-1]
        return data.decode("latin-1", "replace")

    @_register_writer(2)
    def write_string(self, value):
        # remerge of https://github.com/python-pillow/Pillow/pull/1416
        return b"" + value.encode("ascii", "replace") + b"\0"

    @_register_loader(5, 8)
    def load_rational(self, data, legacy_api=True):
        vals = self._unpack("{}L".format(len(data) // 4), data)

        def combine(a, b):
            return (a, b) if legacy_api else IFDRational(a, b)

        return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))

    @_register_writer(5)
    def write_rational(self, *values):
        return b"".join(
            self._pack("2L", *_limit_rational(frac, 2 ** 32 - 1)) for frac in values
        )

    @_register_loader(7, 1)
    def load_undefined(self, data, legacy_api=True):
        return data

    @_register_writer(7)
    def write_undefined(self, value):
        return value

    @_register_loader(10, 8)
    def load_signed_rational(self, data, legacy_api=True):
        vals = self._unpack("{}l".format(len(data) // 4), data)

        def combine(a, b):
            return (a, b) if legacy_api else IFDRational(a, b)

        return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))

    @_register_writer(10)
    def write_signed_rational(self, *values):
        return b"".join(
            self._pack("2l", *_limit_signed_rational(frac, 2 ** 31 - 1, -(2 ** 31)))
            for frac in values
        )

    def _ensure_read(self, fp, size):
        ret = fp.read(size)
        if len(ret) != size:
            raise OSError(
                "Corrupt EXIF data.  "
                f"Expecting to read {size} bytes but only got {len(ret)}. "
            )
        return ret

    def load(self, fp):

        self.reset()
        self._offset = fp.tell()

        try:
            for i in range(self._unpack("H", self._ensure_read(fp, 2))[0]):
                tag, typ, count, data = self._unpack("HHL4s", self._ensure_read(fp, 12))

                tagname = TiffTags.lookup(tag, self.group).name
                typname = TYPES.get(typ, "unknown")
                msg = f"tag: {tagname} ({tag}) - type: {typname} ({typ})"

                try:
                    unit_size, handler = self._load_dispatch[typ]
                except KeyError:
                    logger.debug(msg + f" - unsupported type {typ}")
                    continue  # ignore unsupported type
                size = count * unit_size
                if size > 4:
                    here = fp.tell()
                    (offset,) = self._unpack("L", data)
                    msg += f" Tag Location: {here} - Data Location: {offset}"
                    fp.seek(offset)
                    data = ImageFile._safe_read(fp, size)
                    fp.seek(here)
                else:
                    data = data[:size]

                if len(data) != size:
                    warnings.warn(
                        "Possibly corrupt EXIF data.  "
                        f"Expecting to read {size} bytes but only got {len(data)}."
                        f" Skipping tag {tag}"
                    )
                    logger.debug(msg)
                    continue

                if not data:
                    logger.debug(msg)
                    continue

                self._tagdata[tag] = data
                self.tagtype[tag] = typ

                msg += " - value: " + (
                    "<table: %d bytes>" % size if size > 32 else repr(data)
                )
                logger.debug(msg)

            (self.next,) = self._unpack("L", self._ensure_read(fp, 4))
        except OSError as msg:
            warnings.warn(str(msg))
            return

    def tobytes(self, offset=0):
        # FIXME What about tagdata?
        result = self._pack("H", len(self._tags_v2))

        entries = []
        offset = offset + len(result) + len(self._tags_v2) * 12 + 4
        stripoffsets = None

        # pass 1: convert tags to binary format
        # always write tags in ascending order
        for tag, value in sorted(self._tags_v2.items()):
            if tag == STRIPOFFSETS:
                stripoffsets = len(entries)
            typ = self.tagtype.get(tag)
            logger.debug(f"Tag {tag}, Type: {typ}, Value: {repr(value)}")
            is_ifd = typ == TiffTags.LONG and isinstance(value, dict)
            if is_ifd:
                if self._endian == "<":
                    ifh = b"II\x2A\x00\x08\x00\x00\x00"
                else:
                    ifh = b"MM\x00\x2A\x00\x00\x00\x08"
                ifd = ImageFileDirectory_v2(ifh, group=tag)
                values = self._tags_v2[tag]
                for ifd_tag, ifd_value in values.items():
                    ifd[ifd_tag] = ifd_value
                data = ifd.tobytes(offset)
            else:
                values = value if isinstance(value, tuple) else (value,)
                data = self._write_dispatch[typ](self, *values)

            tagname = TiffTags.lookup(tag, self.group).name
            typname = "ifd" if is_ifd else TYPES.get(typ, "unknown")
            msg = f"save: {tagname} ({tag}) - type: {typname} ({typ})"
            msg += " - value: " + (
                "<table: %d bytes>" % len(data) if len(data) >= 16 else str(values)
            )
            logger.debug(msg)

            # count is sum of lengths for string and arbitrary data
            if is_ifd:
                count = 1
            elif typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]:
                count = len(data)
            else:
                count = len(values)
            # figure out if data fits into the entry
            if len(data) <= 4:
                entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
            else:
                entries.append((tag, typ, count, self._pack("L", offset), data))
                offset += (len(data) + 1) // 2 * 2  # pad to word

        # update strip offset data to point beyond auxiliary data
        if stripoffsets is not None:
            tag, typ, count, value, data = entries[stripoffsets]
            if data:
                raise NotImplementedError("multistrip support not yet implemented")
            value = self._pack("L", self._unpack("L", value)[0] + offset)
            entries[stripoffsets] = tag, typ, count, value, data

        # pass 2: write entries to file
        for tag, typ, count, value, data in entries:
            logger.debug(f"{tag} {typ} {count} {repr(value)} {repr(data)}")
            result += self._pack("HHL4s", tag, typ, count, value)

        # -- overwrite here for multi-page --
        result += b"\0\0\0\0"  # end of entries

        # pass 3: write auxiliary data to file
        for tag, typ, count, value, data in entries:
            result += data
            if len(data) & 1:
                result += b"\0"

        return result

    def save(self, fp):

        if fp.tell() == 0:  # skip TIFF header on subsequent pages
            # tiff header -- PIL always starts the first IFD at offset 8
            fp.write(self._prefix + self._pack("HL", 42, 8))

        offset = fp.tell()
        result = self.tobytes(offset)
        fp.write(result)
        return offset + len(result)


ImageFileDirectory_v2._load_dispatch = _load_dispatch
ImageFileDirectory_v2._write_dispatch = _write_dispatch
for idx, name in TYPES.items():
    name = name.replace(" ", "_")
    setattr(ImageFileDirectory_v2, "load_" + name, _load_dispatch[idx][1])
    setattr(ImageFileDirectory_v2, "write_" + name, _write_dispatch[idx])
del _load_dispatch, _write_dispatch, idx, name


# Legacy ImageFileDirectory support.
class ImageFileDirectory_v1(ImageFileDirectory_v2):
    """This class represents the **legacy** interface to a TIFF tag directory.

    Exposes a dictionary interface of the tags in the directory::

        ifd = ImageFileDirectory_v1()
        ifd[key] = 'Some Data'
        ifd.tagtype[key] = TiffTags.ASCII
        print(ifd[key])
        ('Some Data',)

    Also contains a dictionary of tag types as read from the tiff image file,
    :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`.

    Values are returned as a tuple.

    ..  deprecated:: 3.0.0
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._legacy_api = True

    tags = property(lambda self: self._tags_v1)
    tagdata = property(lambda self: self._tagdata)

    # defined in ImageFileDirectory_v2
    tagtype: dict
    """Dictionary of tag types"""

    @classmethod
    def from_v2(cls, original):
        """Returns an
        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
        instance with the same data as is contained in the original
        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
        instance.

        :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`

        """

        ifd = cls(prefix=original.prefix)
        ifd._tagdata = original._tagdata
        ifd.tagtype = original.tagtype
        ifd.next = original.next  # an indicator for multipage tiffs
        return ifd

    def to_v2(self):
        """Returns an
        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
        instance with the same data as is contained in the original
        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
        instance.

        :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`

        """

        ifd = ImageFileDirectory_v2(prefix=self.prefix)
        ifd._tagdata = dict(self._tagdata)
        ifd.tagtype = dict(self.tagtype)
        ifd._tags_v2 = dict(self._tags_v2)
        return ifd

    def __contains__(self, tag):
        return tag in self._tags_v1 or tag in self._tagdata

    def __len__(self):
        return len(set(self._tagdata) | set(self._tags_v1))

    def __iter__(self):
        return iter(set(self._tagdata) | set(self._tags_v1))

    def __setitem__(self, tag, value):
        for legacy_api in (False, True):
            self._setitem(tag, value, legacy_api)

    def __getitem__(self, tag):
        if tag not in self._tags_v1:  # unpack on the fly
            data = self._tagdata[tag]
            typ = self.tagtype[tag]
            size, handler = self._load_dispatch[typ]
            for legacy in (False, True):
                self._setitem(tag, handler(self, data, legacy), legacy)
        val = self._tags_v1[tag]
        if not isinstance(val, (tuple, bytes)):
            val = (val,)
        return val


# undone -- switch this pointer when IFD_LEGACY_API == False
ImageFileDirectory = ImageFileDirectory_v1


##
# Image plugin for TIFF files.


class TiffImageFile(ImageFile.ImageFile):

    format = "TIFF"
    format_description = "Adobe TIFF"
    _close_exclusive_fp_after_loading = False

    def __init__(self, fp=None, filename=None):
        self.tag_v2 = None
        """ Image file directory (tag dictionary) """

        self.tag = None
        """ Legacy tag entries """

        super().__init__(fp, filename)

    def _open(self):
        """Open the first image in a TIFF file"""

        # Header
        ifh = self.fp.read(8)

        self.tag_v2 = ImageFileDirectory_v2(ifh)

        # legacy IFD entries will be filled in later
        self.ifd = None

        # setup frame pointers
        self.__first = self.__next = self.tag_v2.next
        self.__frame = -1
        self.__fp = self.fp
        self._frame_pos = []
        self._n_frames = None

        logger.debug("*** TiffImageFile._open ***")
        logger.debug(f"- __first: {self.__first}")
        logger.debug(f"- ifh: {repr(ifh)}")  # Use repr to avoid str(bytes)

        # and load the first frame
        self._seek(0)

    @property
    def n_frames(self):
        if self._n_frames is None:
            current = self.tell()
            self._seek(len(self._frame_pos))
            while self._n_frames is None:
                self._seek(self.tell() + 1)
            self.seek(current)
        return self._n_frames

    def seek(self, frame):
        """Select a given frame as current image"""
        if not self._seek_check(frame):
            return
        self._seek(frame)
        # Create a new core image object on second and
        # subsequent frames in the image. Image may be
        # different size/mode.
        Image._decompression_bomb_check(self.size)
        self.im = Image.core.new(self.mode, self.size)

    def _seek(self, frame):
        self.fp = self.__fp

        # reset buffered io handle in case fp
        # was passed to libtiff, invalidating the buffer
        self.fp.tell()

        while len(self._frame_pos) <= frame:
            if not self.__next:
                raise EOFError("no more images in TIFF file")
            logger.debug(
                f"Seeking to frame {frame}, on frame {self.__frame}, "
                f"__next {self.__next}, location: {self.fp.tell()}"
            )
            self.fp.seek(self.__next)
            self._frame_pos.append(self.__next)
            logger.debug("Loading tags, location: %s" % self.fp.tell())
            self.tag_v2.load(self.fp)
            if self.tag_v2.next in self._frame_pos:
                # This IFD has already been processed
                # Declare this to be the end of the image
                self.__next = 0
            else:
                self.__next = self.tag_v2.next
            if self.__next == 0:
                self._n_frames = frame + 1
            if len(self._frame_pos) == 1:
                self.is_animated = self.__next != 0
            self.__frame += 1
        self.fp.seek(self._frame_pos[frame])
        self.tag_v2.load(self.fp)
        # fill the legacy tag/ifd entries
        self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)
        self.__frame = frame
        self._setup()

    def tell(self):
        """Return the current frame number"""
        return self.__frame

    def getxmp(self):
        """
        Returns a dictionary containing the XMP tags.
        Requires defusedxml to be installed.
        :returns: XMP tags in a dictionary.
        """
        return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {}

    def load(self):
        if self.tile and self.use_load_libtiff:
            return self._load_libtiff()
        return super().load()

    def load_end(self):
        if self._tile_orientation:
            method = {
                2: Image.FLIP_LEFT_RIGHT,
                3: Image.ROTATE_180,
                4: Image.FLIP_TOP_BOTTOM,
                5: Image.TRANSPOSE,
                6: Image.ROTATE_270,
                7: Image.TRANSVERSE,
                8: Image.ROTATE_90,
            }.get(self._tile_orientation)
            if method is not None:
                self.im = self.im.transpose(method)
                self._size = self.im.size

        # allow closing if we're on the first frame, there's no next
        # This is the ImageFile.load path only, libtiff specific below.
        if not self.is_animated:
            self._close_exclusive_fp_after_loading = True

    def _load_libtiff(self):
        """Overload method triggered when we detect a compressed tiff
        Calls out to libtiff"""

        Image.Image.load(self)

        self.load_prepare()

        if not len(self.tile) == 1:
            raise OSError("Not exactly one tile")

        # (self._compression, (extents tuple),
        #   0, (rawmode, self._compression, fp))
        extents = self.tile[0][1]
        args = list(self.tile[0][3])

        # To be nice on memory footprint, if there's a
        # file descriptor, use that instead of reading
        # into a string in python.
        # libtiff closes the file descriptor, so pass in a dup.
        try:
            fp = hasattr(self.fp, "fileno") and os.dup(self.fp.fileno())
            # flush the file descriptor, prevents error on pypy 2.4+
            # should also eliminate the need for fp.tell
            # in _seek
            if hasattr(self.fp, "flush"):
                self.fp.flush()
        except OSError:
            # io.BytesIO have a fileno, but returns an OSError if
            # it doesn't use a file descriptor.
            fp = False

        if fp:
            args[2] = fp

        decoder = Image._getdecoder(
            self.mode, "libtiff", tuple(args), self.decoderconfig
        )
        try:
            decoder.setimage(self.im, extents)
        except ValueError as e:
            raise OSError("Couldn't set the image") from e

        close_self_fp = self._exclusive_fp and not self.is_animated
        if hasattr(self.fp, "getvalue"):
            # We've got a stringio like thing passed in. Yay for all in memory.
            # The decoder needs the entire file in one shot, so there's not
            # a lot we can do here other than give it the entire file.
            # unless we could do something like get the address of the
            # underlying string for stringio.
            #
            # Rearranging for supporting byteio items, since they have a fileno
            # that returns an OSError if there's no underlying fp. Easier to
            # deal with here by reordering.
            logger.debug("have getvalue. just sending in a string from getvalue")
            n, err = decoder.decode(self.fp.getvalue())
        elif fp:
            # we've got a actual file on disk, pass in the fp.
            logger.debug("have fileno, calling fileno version of the decoder.")
            if not close_self_fp:
                self.fp.seek(0)
            # 4 bytes, otherwise the trace might error out
            n, err = decoder.decode(b"fpfp")
        else:
            # we have something else.
            logger.debug("don't have fileno or getvalue. just reading")
            self.fp.seek(0)
            # UNDONE -- so much for that buffer size thing.
            n, err = decoder.decode(self.fp.read())

        self.tile = []
        self.readonly = 0

        self.load_end()

        # libtiff closed the fp in a, we need to close self.fp, if possible
        if close_self_fp:
            self.fp.close()
            self.fp = None  # might be shared

        if err < 0:
            raise OSError(err)

        return Image.Image.load(self)

    def _setup(self):
        """Setup this image object based on current tags"""

        if 0xBC01 in self.tag_v2:
            raise OSError("Windows Media Photo files not yet supported")

        # extract relevant tags
        self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)]
        self._planar_configuration = self.tag_v2.get(PLANAR_CONFIGURATION, 1)

        # photometric is a required tag, but not everyone is reading
        # the specification
        photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)

        # old style jpeg compression images most certainly are YCbCr
        if self._compression == "tiff_jpeg":
            photo = 6

        fillorder = self.tag_v2.get(FILLORDER, 1)

        logger.debug("*** Summary ***")
        logger.debug(f"- compression: {self._compression}")
        logger.debug(f"- photometric_interpretation: {photo}")
        logger.debug(f"- planar_configuration: {self._planar_configuration}")
        logger.debug(f"- fill_order: {fillorder}")
        logger.debug(f"- YCbCr subsampling: {self.tag.get(530)}")

        # size
        xsize = int(self.tag_v2.get(IMAGEWIDTH))
        ysize = int(self.tag_v2.get(IMAGELENGTH))
        self._size = xsize, ysize

        logger.debug(f"- size: {self.size}")

        sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,))
        if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1:
            # SAMPLEFORMAT is properly per band, so an RGB image will
            # be (1,1,1).  But, we don't support per band pixel types,
            # and anything more than one band is a uint8. So, just
            # take the first element. Revisit this if adding support
            # for more exotic images.
            sampleFormat = (1,)

        bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,))
        extra_tuple = self.tag_v2.get(EXTRASAMPLES, ())
        if photo in (2, 6, 8):  # RGB, YCbCr, LAB
            bps_count = 3
        elif photo == 5:  # CMYK
            bps_count = 4
        else:
            bps_count = 1
        bps_count += len(extra_tuple)
        # Some files have only one value in bps_tuple,
        # while should have more. Fix it
        if bps_count > len(bps_tuple) and len(bps_tuple) == 1:
            bps_tuple = bps_tuple * bps_count

        samplesPerPixel = self.tag_v2.get(
            SAMPLESPERPIXEL,
            3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1,
        )
        if len(bps_tuple) != samplesPerPixel:
            raise SyntaxError("unknown data organization")

        # mode: check photometric interpretation and bits per pixel
        key = (
            self.tag_v2.prefix,
            photo,
            sampleFormat,
            fillorder,
            bps_tuple,
            extra_tuple,
        )
        logger.debug(f"format key: {key}")
        try:
            self.mode, rawmode = OPEN_INFO[key]
        except KeyError as e:
            logger.debug("- unsupported format")
            raise SyntaxError("unknown pixel mode") from e

        logger.debug(f"- raw mode: {rawmode}")
        logger.debug(f"- pil mode: {self.mode}")

        self.info["compression"] = self._compression

        xres = self.tag_v2.get(X_RESOLUTION, 1)
        yres = self.tag_v2.get(Y_RESOLUTION, 1)

        if xres and yres:
            resunit = self.tag_v2.get(RESOLUTION_UNIT)
            if resunit == 2:  # dots per inch
                self.info["dpi"] = (xres, yres)
            elif resunit == 3:  # dots per centimeter. convert to dpi
                self.info["dpi"] = (xres * 2.54, yres * 2.54)
            elif resunit is None:  # used to default to 1, but now 2)
                self.info["dpi"] = (xres, yres)
                # For backward compatibility,
                # we also preserve the old behavior
                self.info["resolution"] = xres, yres
            else:  # No absolute unit of measurement
                self.info["resolution"] = xres, yres

        # build tile descriptors
        x = y = layer = 0
        self.tile = []
        self.use_load_libtiff = READ_LIBTIFF or self._compression != "raw"
        if self.use_load_libtiff:
            # Decoder expects entire file as one tile.
            # There's a buffer size limit in load (64k)
            # so large g4 images will fail if we use that
            # function.
            #
            # Setup the one tile for the whole image, then
            # use the _load_libtiff function.

            # libtiff handles the fillmode for us, so 1;IR should
            # actually be 1;I. Including the R double reverses the
            # bits, so stripes of the image are reversed.  See
            # https://github.com/python-pillow/Pillow/issues/279
            if fillorder == 2:
                # Replace fillorder with fillorder=1
                key = key[:3] + (1,) + key[4:]
                logger.debug(f"format key: {key}")
                # this should always work, since all the
                # fillorder==2 modes have a corresponding
                # fillorder=1 mode
                self.mode, rawmode = OPEN_INFO[key]
            # libtiff always returns the bytes in native order.
            # we're expecting image byte order. So, if the rawmode
            # contains I;16, we need to convert from native to image
            # byte order.
            if rawmode == "I;16":
                rawmode = "I;16N"
            if ";16B" in rawmode:
                rawmode = rawmode.replace(";16B", ";16N")
            if ";16L" in rawmode:
                rawmode = rawmode.replace(";16L", ";16N")

            # YCbCr images with new jpeg compression with pixels in one plane
            # unpacked straight into RGB values
            if (
                photo == 6
                and self._compression == "jpeg"
                and self._planar_configuration == 1
            ):
                rawmode = "RGB"

            # Offset in the tile tuple is 0, we go from 0,0 to
            # w,h, and we only do this once -- eds
            a = (rawmode, self._compression, False, self.tag_v2.offset)
            self.tile.append(("libtiff", (0, 0, xsize, ysize), 0, a))

        elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:
            # striped image
            if STRIPOFFSETS in self.tag_v2:
                offsets = self.tag_v2[STRIPOFFSETS]
                h = self.tag_v2.get(ROWSPERSTRIP, ysize)
                w = self.size[0]
            else:
                # tiled image
                offsets = self.tag_v2[TILEOFFSETS]
                w = self.tag_v2.get(322)
                h = self.tag_v2.get(323)

            for offset in offsets:
                if x + w > xsize:
                    stride = w * sum(bps_tuple) / 8  # bytes per line
                else:
                    stride = 0

                tile_rawmode = rawmode
                if self._planar_configuration == 2:
                    # each band on it's own layer
                    tile_rawmode = rawmode[layer]
                    # adjust stride width accordingly
                    stride /= bps_count

                a = (tile_rawmode, int(stride), 1)
                self.tile.append(
                    (
                        self._compression,
                        (x, y, min(x + w, xsize), min(y + h, ysize)),
                        offset,
                        a,
                    )
                )
                x = x + w
                if x >= self.size[0]:
                    x, y = 0, y + h
                    if y >= self.size[1]:
                        x = y = 0
                        layer += 1
        else:
            logger.debug("- unsupported data organization")
            raise SyntaxError("unknown data organization")

        # Fix up info.
        if ICCPROFILE in self.tag_v2:
            self.info["icc_profile"] = self.tag_v2[ICCPROFILE]

        # fixup palette descriptor

        if self.mode in ["P", "PA"]:
            palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]
            self.palette = ImagePalette.raw("RGB;L", b"".join(palette))

        self._tile_orientation = self.tag_v2.get(0x0112)

    def _close__fp(self):
        try:
            if self.__fp != self.fp:
                self.__fp.close()
        except AttributeError:
            pass
        finally:
            self.__fp = None


#
# --------------------------------------------------------------------
# Write TIFF files

# little endian is default except for image modes with
# explicit big endian byte-order

SAVE_INFO = {
    # mode => rawmode, byteorder, photometrics,
    #           sampleformat, bitspersample, extra
    "1": ("1", II, 1, 1, (1,), None),
    "L": ("L", II, 1, 1, (8,), None),
    "LA": ("LA", II, 1, 1, (8, 8), 2),
    "P": ("P", II, 3, 1, (8,), None),
    "PA": ("PA", II, 3, 1, (8, 8), 2),
    "I": ("I;32S", II, 1, 2, (32,), None),
    "I;16": ("I;16", II, 1, 1, (16,), None),
    "I;16S": ("I;16S", II, 1, 2, (16,), None),
    "F": ("F;32F", II, 1, 3, (32,), None),
    "RGB": ("RGB", II, 2, 1, (8, 8, 8), None),
    "RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0),
    "RGBA": ("RGBA", II, 2, 1, (8, 8, 8, 8), 2),
    "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
    "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
    "LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
    "I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
    "I;16B": ("I;16B", MM, 1, 1, (16,), None),
    "I;16BS": ("I;16BS", MM, 1, 2, (16,), None),
    "F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
}


def _save(im, fp, filename):

    try:
        rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]
    except KeyError as e:
        raise OSError(f"cannot write mode {im.mode} as TIFF") from e

    ifd = ImageFileDirectory_v2(prefix=prefix)

    compression = im.encoderinfo.get("compression", im.info.get("compression"))
    if compression is None:
        compression = "raw"
    elif compression == "tiff_jpeg":
        # OJPEG is obsolete, so use new-style JPEG compression instead
        compression = "jpeg"
    elif compression == "tiff_deflate":
        compression = "tiff_adobe_deflate"

    libtiff = WRITE_LIBTIFF or compression != "raw"

    # required for color libtiff images
    ifd[PLANAR_CONFIGURATION] = getattr(im, "_planar_configuration", 1)

    ifd[IMAGEWIDTH] = im.size[0]
    ifd[IMAGELENGTH] = im.size[1]

    # write any arbitrary tags passed in as an ImageFileDirectory
    info = im.encoderinfo.get("tiffinfo", {})
    logger.debug("Tiffinfo Keys: %s" % list(info))
    if isinstance(info, ImageFileDirectory_v1):
        info = info.to_v2()
    for key in info:
        ifd[key] = info.get(key)
        try:
            ifd.tagtype[key] = info.tagtype[key]
        except Exception:
            pass  # might not be an IFD. Might not have populated type

    # additions written by Greg Couch, gregc@cgl.ucsf.edu
    # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
    if hasattr(im, "tag_v2"):
        # preserve tags from original TIFF image file
        for key in (
            RESOLUTION_UNIT,
            X_RESOLUTION,
            Y_RESOLUTION,
            IPTC_NAA_CHUNK,
            PHOTOSHOP_CHUNK,
            XMP,
        ):
            if key in im.tag_v2:
                ifd[key] = im.tag_v2[key]
                ifd.tagtype[key] = im.tag_v2.tagtype[key]

    # preserve ICC profile (should also work when saving other formats
    # which support profiles as TIFF) -- 2008-06-06 Florian Hoech
    icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile"))
    if icc:
        ifd[ICCPROFILE] = icc

    for key, name in [
        (IMAGEDESCRIPTION, "description"),
        (X_RESOLUTION, "resolution"),
        (Y_RESOLUTION, "resolution"),
        (X_RESOLUTION, "x_resolution"),
        (Y_RESOLUTION, "y_resolution"),
        (RESOLUTION_UNIT, "resolution_unit"),
        (SOFTWARE, "software"),
        (DATE_TIME, "date_time"),
        (ARTIST, "artist"),
        (COPYRIGHT, "copyright"),
    ]:
        if name in im.encoderinfo:
            ifd[key] = im.encoderinfo[name]

    dpi = im.encoderinfo.get("dpi")
    if dpi:
        ifd[RESOLUTION_UNIT] = 2
        ifd[X_RESOLUTION] = dpi[0]
        ifd[Y_RESOLUTION] = dpi[1]

    if bits != (1,):
        ifd[BITSPERSAMPLE] = bits
        if len(bits) != 1:
            ifd[SAMPLESPERPIXEL] = len(bits)
    if extra is not None:
        ifd[EXTRASAMPLES] = extra
    if format != 1:
        ifd[SAMPLEFORMAT] = format

    ifd[PHOTOMETRIC_INTERPRETATION] = photo

    if im.mode in ["P", "PA"]:
        lut = im.im.getpalette("RGB", "RGB;L")
        ifd[COLORMAP] = tuple(v * 256 for v in lut)
    # data orientation
    stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)
    # aim for 64 KB strips when using libtiff writer
    if libtiff:
        rows_per_strip = min((2 ** 16 + stride - 1) // stride, im.size[1])
    else:
        rows_per_strip = im.size[1]
    strip_byte_counts = stride * rows_per_strip
    strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip
    ifd[ROWSPERSTRIP] = rows_per_strip
    if strip_byte_counts >= 2 ** 16:
        ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
    ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (
        stride * im.size[1] - strip_byte_counts * (strips_per_image - 1),
    )
    ifd[STRIPOFFSETS] = tuple(
        range(0, strip_byte_counts * strips_per_image, strip_byte_counts)
    )  # this is adjusted by IFD writer
    # no compression by default:
    ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)

    if libtiff:
        if "quality" in im.encoderinfo:
            quality = im.encoderinfo["quality"]
            if not isinstance(quality, int) or quality < 0 or quality > 100:
                raise ValueError("Invalid quality setting")
            if compression != "jpeg":
                raise ValueError(
                    "quality setting only supported for 'jpeg' compression"
                )
            ifd[JPEGQUALITY] = quality

        logger.debug("Saving using libtiff encoder")
        logger.debug("Items: %s" % sorted(ifd.items()))
        _fp = 0
        if hasattr(fp, "fileno"):
            try:
                fp.seek(0)
                _fp = os.dup(fp.fileno())
            except io.UnsupportedOperation:
                pass

        # optional types for non core tags
        types = {}
        # SAMPLEFORMAT is determined by the image format and should not be copied
        # from legacy_ifd.
        # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library
        # based on the data in the strip.
        # The other tags expect arrays with a certain length (fixed or depending on
        # BITSPERSAMPLE, etc), passing arrays with a different length will result in
        # segfaults. Block these tags until we add extra validation.
        # SUBIFD may also cause a segfault.
        blocklist = [
            REFERENCEBLACKWHITE,
            SAMPLEFORMAT,
            STRIPBYTECOUNTS,
            STRIPOFFSETS,
            TRANSFERFUNCTION,
            SUBIFD,
        ]

        atts = {}
        # bits per sample is a single short in the tiff directory, not a list.
        atts[BITSPERSAMPLE] = bits[0]
        # Merge the ones that we have with (optional) more bits from
        # the original file, e.g x,y resolution so that we can
        # save(load('')) == original file.
        legacy_ifd = {}
        if hasattr(im, "tag"):
            legacy_ifd = im.tag.to_v2()
        for tag, value in itertools.chain(
            ifd.items(), getattr(im, "tag_v2", {}).items(), legacy_ifd.items()
        ):
            # Libtiff can only process certain core items without adding
            # them to the custom dictionary.
            # Custom items are supported for int, float, unicode, string and byte
            # values. Other types and tuples require a tagtype.
            if tag not in TiffTags.LIBTIFF_CORE:
                if not Image.core.libtiff_support_custom_tags:
                    continue

                if tag in ifd.tagtype:
                    types[tag] = ifd.tagtype[tag]
                elif not (isinstance(value, (int, float, str, bytes))):
                    continue
                else:
                    type = TiffTags.lookup(tag).type
                    if type:
                        types[tag] = type
            if tag not in atts and tag not in blocklist:
                if isinstance(value, str):
                    atts[tag] = value.encode("ascii", "replace") + b"\0"
                elif isinstance(value, IFDRational):
                    atts[tag] = float(value)
                else:
                    atts[tag] = value

        logger.debug("Converted items: %s" % sorted(atts.items()))

        # libtiff always expects the bytes in native order.
        # we're storing image byte order. So, if the rawmode
        # contains I;16, we need to convert from native to image
        # byte order.
        if im.mode in ("I;16B", "I;16"):
            rawmode = "I;16N"

        # Pass tags as sorted list so that the tags are set in a fixed order.
        # This is required by libtiff for some tags. For example, the JPEGQUALITY
        # pseudo tag requires that the COMPRESS tag was already set.
        tags = list(atts.items())
        tags.sort()
        a = (rawmode, compression, _fp, filename, tags, types)
        e = Image._getencoder(im.mode, "libtiff", a, im.encoderconfig)
        e.setimage(im.im, (0, 0) + im.size)
        while True:
            # undone, change to self.decodermaxblock:
            l, s, d = e.encode(16 * 1024)
            if not _fp:
                fp.write(d)
            if s:
                break
        if s < 0:
            raise OSError(f"encoder error {s} when writing image file")

    else:
        offset = ifd.save(fp)

        ImageFile._save(
            im, fp, [("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))]
        )

    # -- helper for multi-page save --
    if "_debug_multipage" in im.encoderinfo:
        # just to access o32 and o16 (using correct byte order)
        im._debug_multipage = ifd


class AppendingTiffWriter:
    fieldSizes = [
        0,  # None
        1,  # byte
        1,  # ascii
        2,  # short
        4,  # long
        8,  # rational
        1,  # sbyte
        1,  # undefined
        2,  # sshort
        4,  # slong
        8,  # srational
        4,  # float
        8,  # double
    ]

    #    StripOffsets = 273
    #    FreeOffsets = 288
    #    TileOffsets = 324
    #    JPEGQTables = 519
    #    JPEGDCTables = 520
    #    JPEGACTables = 521
    Tags = {273, 288, 324, 519, 520, 521}

    def __init__(self, fn, new=False):
        if hasattr(fn, "read"):
            self.f = fn
            self.close_fp = False
        else:
            self.name = fn
            self.close_fp = True
            try:
                self.f = open(fn, "w+b" if new else "r+b")
            except OSError:
                self.f = open(fn, "w+b")
        self.beginning = self.f.tell()
        self.setup()

    def setup(self):
        # Reset everything.
        self.f.seek(self.beginning, os.SEEK_SET)

        self.whereToWriteNewIFDOffset = None
        self.offsetOfNewPage = 0

        self.IIMM = IIMM = self.f.read(4)
        if not IIMM:
            # empty file - first page
            self.isFirst = True
            return

        self.isFirst = False
        if IIMM == b"II\x2a\x00":
            self.setEndian("<")
        elif IIMM == b"MM\x00\x2a":
            self.setEndian(">")
        else:
            raise RuntimeError("Invalid TIFF file header")

        self.skipIFDs()
        self.goToEnd()

    def finalize(self):
        if self.isFirst:
            return

        # fix offsets
        self.f.seek(self.offsetOfNewPage)

        IIMM = self.f.read(4)
        if not IIMM:
            # raise RuntimeError("nothing written into new page")
            # Make it easy to finish a frame without committing to a new one.
            return

        if IIMM != self.IIMM:
            raise RuntimeError("IIMM of new page doesn't match IIMM of first page")

        IFDoffset = self.readLong()
        IFDoffset += self.offsetOfNewPage
        self.f.seek(self.whereToWriteNewIFDOffset)
        self.writeLong(IFDoffset)
        self.f.seek(IFDoffset)
        self.fixIFD()

    def newFrame(self):
        # Call this to finish a frame.
        self.finalize()
        self.setup()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if self.close_fp:
            self.close()
        return False

    def tell(self):
        return self.f.tell() - self.offsetOfNewPage

    def seek(self, offset, whence=io.SEEK_SET):
        if whence == os.SEEK_SET:
            offset += self.offsetOfNewPage

        self.f.seek(offset, whence)
        return self.tell()

    def goToEnd(self):
        self.f.seek(0, os.SEEK_END)
        pos = self.f.tell()

        # pad to 16 byte boundary
        padBytes = 16 - pos % 16
        if 0 < padBytes < 16:
            self.f.write(bytes(padBytes))
        self.offsetOfNewPage = self.f.tell()

    def setEndian(self, endian):
        self.endian = endian
        self.longFmt = self.endian + "L"
        self.shortFmt = self.endian + "H"
        self.tagFormat = self.endian + "HHL"

    def skipIFDs(self):
        while True:
            IFDoffset = self.readLong()
            if IFDoffset == 0:
                self.whereToWriteNewIFDOffset = self.f.tell() - 4
                break

            self.f.seek(IFDoffset)
            numTags = self.readShort()
            self.f.seek(numTags * 12, os.SEEK_CUR)

    def write(self, data):
        return self.f.write(data)

    def readShort(self):
        (value,) = struct.unpack(self.shortFmt, self.f.read(2))
        return value

    def readLong(self):
        (value,) = struct.unpack(self.longFmt, self.f.read(4))
        return value

    def rewriteLastShortToLong(self, value):
        self.f.seek(-2, os.SEEK_CUR)
        bytesWritten = self.f.write(struct.pack(self.longFmt, value))
        if bytesWritten is not None and bytesWritten != 4:
            raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4")

    def rewriteLastShort(self, value):
        self.f.seek(-2, os.SEEK_CUR)
        bytesWritten = self.f.write(struct.pack(self.shortFmt, value))
        if bytesWritten is not None and bytesWritten != 2:
            raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 2")

    def rewriteLastLong(self, value):
        self.f.seek(-4, os.SEEK_CUR)
        bytesWritten = self.f.write(struct.pack(self.longFmt, value))
        if bytesWritten is not None and bytesWritten != 4:
            raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4")

    def writeShort(self, value):
        bytesWritten = self.f.write(struct.pack(self.shortFmt, value))
        if bytesWritten is not None and bytesWritten != 2:
            raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 2")

    def writeLong(self, value):
        bytesWritten = self.f.write(struct.pack(self.longFmt, value))
        if bytesWritten is not None and bytesWritten != 4:
            raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4")

    def close(self):
        self.finalize()
        self.f.close()

    def fixIFD(self):
        numTags = self.readShort()

        for i in range(numTags):
            tag, fieldType, count = struct.unpack(self.tagFormat, self.f.read(8))

            fieldSize = self.fieldSizes[fieldType]
            totalSize = fieldSize * count
            isLocal = totalSize <= 4
            if not isLocal:
                offset = self.readLong()
                offset += self.offsetOfNewPage
                self.rewriteLastLong(offset)

            if tag in self.Tags:
                curPos = self.f.tell()

                if isLocal:
                    self.fixOffsets(
                        count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)
                    )
                    self.f.seek(curPos + 4)
                else:
                    self.f.seek(offset)
                    self.fixOffsets(
                        count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)
                    )
                    self.f.seek(curPos)

                offset = curPos = None

            elif isLocal:
                # skip the locally stored value that is not an offset
                self.f.seek(4, os.SEEK_CUR)

    def fixOffsets(self, count, isShort=False, isLong=False):
        if not isShort and not isLong:
            raise RuntimeError("offset is neither short nor long")

        for i in range(count):
            offset = self.readShort() if isShort else self.readLong()
            offset += self.offsetOfNewPage
            if isShort and offset >= 65536:
                # offset is now too large - we must convert shorts to longs
                if count != 1:
                    raise RuntimeError("not implemented")  # XXX TODO

                # simple case - the offset is just one and therefore it is
                # local (not referenced with another offset)
                self.rewriteLastShortToLong(offset)
                self.f.seek(-10, os.SEEK_CUR)
                self.writeShort(TiffTags.LONG)  # rewrite the type to LONG
                self.f.seek(8, os.SEEK_CUR)
            elif isShort:
                self.rewriteLastShort(offset)
            else:
                self.rewriteLastLong(offset)


def _save_all(im, fp, filename):
    encoderinfo = im.encoderinfo.copy()
    encoderconfig = im.encoderconfig
    append_images = list(encoderinfo.get("append_images", []))
    if not hasattr(im, "n_frames") and not append_images:
        return _save(im, fp, filename)

    cur_idx = im.tell()
    try:
        with AppendingTiffWriter(fp) as tf:
            for ims in [im] + append_images:
                ims.encoderinfo = encoderinfo
                ims.encoderconfig = encoderconfig
                if not hasattr(ims, "n_frames"):
                    nfr = 1
                else:
                    nfr = ims.n_frames

                for idx in range(nfr):
                    ims.seek(idx)
                    ims.load()
                    _save(ims, tf, filename)
                    tf.newFrame()
    finally:
        im.seek(cur_idx)


#
# --------------------------------------------------------------------
# Register

Image.register_open(TiffImageFile.format, TiffImageFile, _accept)
Image.register_save(TiffImageFile.format, _save)
Image.register_save_all(TiffImageFile.format, _save_all)

Image.register_extensions(TiffImageFile.format, [".tif", ".tiff"])

Image.register_mime(TiffImageFile.format, "image/tiff")

```
--- END FILE ---

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

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

Here is an example:

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

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

**Description:**
When saving a TIFF image that contains EXIF metadata using the `save` method, the EXIF information is not retained as expected. This results in missing or incorrect EXIF tags in the saved image, which can lead to issues in applications that rely on this metadata.

**Bug Reproduction:**

```python
from PIL import Image

# Open an image with EXIF data
with Image.open("Tests/images/ifd_tag_type.tiff") as im:
    exif = im.getexif()
    # Attempt to save the image with EXIF data
    im.save("temp_saved.tiff", "TIFF")

# Re-open the saved image to verify EXIF data
with Image.open("temp_saved.tiff") as im_saved:
    exif_saved = im_saved.getexif()
    print(sorted(exif_saved.keys()))
    # Expected to include tag 271 ("Make"), but it is missing or incorrect
```

**Expected Behavior:**
The saved TIFF image (`temp_saved.tiff`) should retain all original EXIF metadata, including tags like `271` ("Make"). When re-opening the saved image, the EXIF data should match the original, ensuring that all metadata is accurately preserved.

**Actual Behavior:**
After saving, the EXIF data in `temp_saved.tiff` is incomplete or altered. For instance, tag `271` ("Make") is either missing or does not match the original value. This discrepancy causes assertions related to EXIF data validation to fail, indicating that the metadata was not preserved correctly during the save operation.

[/ISSUE]
{
    "\u001b[1mTestFileTiff.test_sanity\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_unclosed_file\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_mac_tiff\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_wrong_bits_per_sample\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_set_legacy_api\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_xyres_tiff\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_xyres_fallback_tiff\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_int_resolution\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_load_float_dpi[None-72.8]\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_load_float_dpi[2-72.8]\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_load_float_dpi[3-184.912]\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_save_float_dpi\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_save_setting_missing_resolution\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_invalid_file\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_bad_exif\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_save_rgba\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_save_unsupported_mode\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_little_endian\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_big_endian\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_16bit_s\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_12bit_rawmode\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_32bit_float\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_unknown_pixel_mode\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_n_frames\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_eoferror\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_multipage\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_multipage_last_frame\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_frame_order\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test___str__\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_dict\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test__delitem__\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_load_byte\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_load_string\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_load_float\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_load_double\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_ifd_tag_type\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_exif\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_exif_frames\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_seek\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_seek_eof\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test__limit_rational_int\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test__limit_rational_float\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_4bit\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_gray_semibyte_per_pixel\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_with_underscores\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_roundtrip_tiff_uint16\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_strip_raw\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_strip_planar_raw\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_strip_planar_raw_with_overviews\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_tiled_planar_raw\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_palette\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_tiff_save_all\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_saving_icc_profile\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_save_icc_profile\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_discard_icc_profile\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_getxmp\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_close_on_load_exclusive\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_close_on_load_nonexclusive\u001b[0m": "PASSED",
    "\u001b[1mTestFileTiff.test_closed_file\u001b[0m": "FAILED",
    "\u001b[1mTestFileTiff.test_context_manager\u001b[0m": "FAILED"
}
diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py
index a5e2bb53d..7a581bd31 100644
--- a/src/PIL/TiffImagePlugin.py
+++ b/src/PIL/TiffImagePlugin.py
@@ -1142,6 +1142,17 @@ class TiffImageFile(ImageFile.ImageFile):
         if not self.is_animated:
             self._close_exclusive_fp_after_loading = True
 
+            # reset buffered io handle in case fp
+            # was passed to libtiff, invalidating the buffer
+            self.fp.tell()
+
+            # load IFD data from fp before it is closed
+            exif = self.getexif()
+            for key in TiffTags.TAGS_V2_GROUPS.keys():
+                if key not in exif:
+                    continue
+                exif.get_ifd(key)
+
     def _load_libtiff(self):
         """Overload method triggered when we detect a compressed tiff
         Calls out to libtiff"""
@@ -1504,12 +1515,15 @@ def _save(im, fp, filename):
     ifd[IMAGELENGTH] = im.size[1]
 
     # write any arbitrary tags passed in as an ImageFileDirectory
-    info = im.encoderinfo.get("tiffinfo", {})
+    info = im.encoderinfo.get("tiffinfo", im.encoderinfo.get("exif", {}))
     logger.debug("Tiffinfo Keys: %s" % list(info))
     if isinstance(info, ImageFileDirectory_v1):
         info = info.to_v2()
     for key in info:
-        ifd[key] = info.get(key)
+        if isinstance(info, Image.Exif) and key in TiffTags.TAGS_V2_GROUPS.keys():
+            ifd[key] = info.get_ifd(key)
+        else:
+            ifd[key] = info.get(key)
         try:
             ifd.tagtype[key] = info.tagtype[key]
         except Exception:
 

{"src/PIL/TiffImagePlugin.py": "#\n# The Python Imaging Library.\n# $Id$\n#\n# TIFF file handling\n#\n# TIFF is a flexible, if somewhat aged, image file format originally\n# defined by Aldus.  Although TIFF supports a wide variety of pixel\n# layouts and compression methods, the name doesn't really stand for\n# \"thousands of incompatible file formats,\" it just feels that way.\n#\n# To read TIFF data from a stream, the stream must be seekable.  For\n# progressive decoding, make sure to use TIFF files where the tag\n# directory is placed first in the file.\n#\n# History:\n# 1995-09-01 fl   Created\n# 1996-05-04 fl   Handle JPEGTABLES tag\n# 1996-05-18 fl   Fixed COLORMAP support\n# 1997-01-05 fl   Fixed PREDICTOR support\n# 1997-08-27 fl   Added support for rational tags (from Perry Stoll)\n# 1998-01-10 fl   Fixed seek/tell (from Jan Blom)\n# 1998-07-15 fl   Use private names for internal variables\n# 1999-06-13 fl   Rewritten for PIL 1.0 (1.0)\n# 2000-10-11 fl   Additional fixes for Python 2.0 (1.1)\n# 2001-04-17 fl   Fixed rewind support (seek to frame 0) (1.2)\n# 2001-05-12 fl   Added write support for more tags (from Greg Couch) (1.3)\n# 2001-12-18 fl   Added workaround for broken Matrox library\n# 2002-01-18 fl   Don't mess up if photometric tag is missing (D. Alan Stewart)\n# 2003-05-19 fl   Check FILLORDER tag\n# 2003-09-26 fl   Added RGBa support\n# 2004-02-24 fl   Added DPI support; fixed rational write support\n# 2005-02-07 fl   Added workaround for broken Corel Draw 10 files\n# 2006-01-09 fl   Added support for float/double tags (from Russell Nelson)\n#\n# Copyright (c) 1997-2006 by Secret Labs AB.  All rights reserved.\n# Copyright (c) 1995-1997 by Fredrik Lundh\n#\n# See the README file for information on usage and redistribution.\n#\nimport io\nimport itertools\nimport logging\nimport os\nimport struct\nimport warnings\nfrom collections.abc import MutableMapping\nfrom fractions import Fraction\nfrom numbers import Number, Rational\n\nfrom . import Image, ImageFile, ImagePalette, TiffTags\nfrom ._binary import o8\nfrom .TiffTags import TYPES\n\nlogger = logging.getLogger(__name__)\n\n# Set these to true to force use of libtiff for reading or writing.\nREAD_LIBTIFF = False\nWRITE_LIBTIFF = False\nIFD_LEGACY_API = True\n\nII = b\"II\"  # little-endian (Intel style)\nMM = b\"MM\"  # big-endian (Motorola style)\n\n#\n# --------------------------------------------------------------------\n# Read TIFF files\n\n# a few tag names, just to make the code below a bit more readable\nIMAGEWIDTH = 256\nIMAGELENGTH = 257\nBITSPERSAMPLE = 258\nCOMPRESSION = 259\nPHOTOMETRIC_INTERPRETATION = 262\nFILLORDER = 266\nIMAGEDESCRIPTION = 270\nSTRIPOFFSETS = 273\nSAMPLESPERPIXEL = 277\nROWSPERSTRIP = 278\nSTRIPBYTECOUNTS = 279\nX_RESOLUTION = 282\nY_RESOLUTION = 283\nPLANAR_CONFIGURATION = 284\nRESOLUTION_UNIT = 296\nTRANSFERFUNCTION = 301\nSOFTWARE = 305\nDATE_TIME = 306\nARTIST = 315\nPREDICTOR = 317\nCOLORMAP = 320\nTILEOFFSETS = 324\nSUBIFD = 330\nEXTRASAMPLES = 338\nSAMPLEFORMAT = 339\nJPEGTABLES = 347\nREFERENCEBLACKWHITE = 532\nCOPYRIGHT = 33432\nIPTC_NAA_CHUNK = 33723  # newsphoto properties\nPHOTOSHOP_CHUNK = 34377  # photoshop properties\nICCPROFILE = 34675\nEXIFIFD = 34665\nXMP = 700\nJPEGQUALITY = 65537  # pseudo-tag by libtiff\n\n# https://github.com/imagej/ImageJA/blob/master/src/main/java/ij/io/TiffDecoder.java\nIMAGEJ_META_DATA_BYTE_COUNTS = 50838\nIMAGEJ_META_DATA = 50839\n\nCOMPRESSION_INFO = {\n    # Compression => pil compression name\n    1: \"raw\",\n    2: \"tiff_ccitt\",\n    3: \"group3\",\n    4: \"group4\",\n    5: \"tiff_lzw\",\n    6: \"tiff_jpeg\",  # obsolete\n    7: \"jpeg\",\n    8: \"tiff_adobe_deflate\",\n    32771: \"tiff_raw_16\",  # 16-bit padding\n    32773: \"packbits\",\n    32809: \"tiff_thunderscan\",\n    32946: \"tiff_deflate\",\n    34676: \"tiff_sgilog\",\n    34677: \"tiff_sgilog24\",\n    34925: \"lzma\",\n    50000: \"zstd\",\n    50001: \"webp\",\n}\n\nCOMPRESSION_INFO_REV = {v: k for k, v in COMPRESSION_INFO.items()}\n\nOPEN_INFO = {\n    # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample,\n    #  ExtraSamples) => mode, rawmode\n    (II, 0, (1,), 1, (1,), ()): (\"1\", \"1;I\"),\n    (MM, 0, (1,), 1, (1,), ()): (\"1\", \"1;I\"),\n    (II, 0, (1,), 2, (1,), ()): (\"1\", \"1;IR\"),\n    (MM, 0, (1,), 2, (1,), ()): (\"1\", \"1;IR\"),\n    (II, 1, (1,), 1, (1,), ()): (\"1\", \"1\"),\n    (MM, 1, (1,), 1, (1,), ()): (\"1\", \"1\"),\n    (II, 1, (1,), 2, (1,), ()): (\"1\", \"1;R\"),\n    (MM, 1, (1,), 2, (1,), ()): (\"1\", \"1;R\"),\n    (II, 0, (1,), 1, (2,), ()): (\"L\", \"L;2I\"),\n    (MM, 0, (1,), 1, (2,), ()): (\"L\", \"L;2I\"),\n    (II, 0, (1,), 2, (2,), ()): (\"L\", \"L;2IR\"),\n    (MM, 0, (1,), 2, (2,), ()): (\"L\", \"L;2IR\"),\n    (II, 1, (1,), 1, (2,), ()): (\"L\", \"L;2\"),\n    (MM, 1, (1,), 1, (2,), ()): (\"L\", \"L;2\"),\n    (II, 1, (1,), 2, (2,), ()): (\"L\", \"L;2R\"),\n    (MM, 1, (1,), 2, (2,), ()): (\"L\", \"L;2R\"),\n    (II, 0, (1,), 1, (4,), ()): (\"L\", \"L;4I\"),\n    (MM, 0, (1,), 1, (4,), ()): (\"L\", \"L;4I\"),\n    (II, 0, (1,), 2, (4,), ()): (\"L\", \"L;4IR\"),\n    (MM, 0, (1,), 2, (4,), ()): (\"L\", \"L;4IR\"),\n    (II, 1, (1,), 1, (4,), ()): (\"L\", \"L;4\"),\n    (MM, 1, (1,), 1, (4,), ()): (\"L\", \"L;4\"),\n    (II, 1, (1,), 2, (4,), ()): (\"L\", \"L;4R\"),\n    (MM, 1, (1,), 2, (4,), ()): (\"L\", \"L;4R\"),\n    (II, 0, (1,), 1, (8,), ()): (\"L\", \"L;I\"),\n    (MM, 0, (1,), 1, (8,), ()): (\"L\", \"L;I\"),\n    (II, 0, (1,), 2, (8,), ()): (\"L\", \"L;IR\"),\n    (MM, 0, (1,), 2, (8,), ()): (\"L\", \"L;IR\"),\n    (II, 1, (1,), 1, (8,), ()): (\"L\", \"L\"),\n    (MM, 1, (1,), 1, (8,), ()): (\"L\", \"L\"),\n    (II, 1, (1,), 2, (8,), ()): (\"L\", \"L;R\"),\n    (MM, 1, (1,), 2, (8,), ()): (\"L\", \"L;R\"),\n    (II, 1, (1,), 1, (12,), ()): (\"I;16\", \"I;12\"),\n    (II, 1, (1,), 1, (16,), ()): (\"I;16\", \"I;16\"),\n    (MM, 1, (1,), 1, (16,), ()): (\"I;16B\", \"I;16B\"),\n    (II, 1, (2,), 1, (16,), ()): (\"I\", \"I;16S\"),\n    (MM, 1, (2,), 1, (16,), ()): (\"I\", \"I;16BS\"),\n    (II, 0, (3,), 1, (32,), ()): (\"F\", \"F;32F\"),\n    (MM, 0, (3,), 1, (32,), ()): (\"F\", \"F;32BF\"),\n    (II, 1, (1,), 1, (32,), ()): (\"I\", \"I;32N\"),\n    (II, 1, (2,), 1, (32,), ()): (\"I\", \"I;32S\"),\n    (MM, 1, (2,), 1, (32,), ()): (\"I\", \"I;32BS\"),\n    (II, 1, (3,), 1, (32,), ()): (\"F\", \"F;32F\"),\n    (MM, 1, (3,), 1, (32,), ()): (\"F\", \"F;32BF\"),\n    (II, 1, (1,), 1, (8, 8), (2,)): (\"LA\", \"LA\"),\n    (MM, 1, (1,), 1, (8, 8), (2,)): (\"LA\", \"LA\"),\n    (II, 2, (1,), 1, (8, 8, 8), ()): (\"RGB\", \"RGB\"),\n    (MM, 2, (1,), 1, (8, 8, 8), ()): (\"RGB\", \"RGB\"),\n    (II, 2, (1,), 2, (8, 8, 8), ()): (\"RGB\", \"RGB;R\"),\n    (MM, 2, (1,), 2, (8, 8, 8), ()): (\"RGB\", \"RGB;R\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8), ()): (\"RGBA\", \"RGBA\"),  # missing ExtraSamples\n    (MM, 2, (1,), 1, (8, 8, 8, 8), ()): (\"RGBA\", \"RGBA\"),  # missing ExtraSamples\n    (II, 2, (1,), 1, (8, 8, 8, 8), (0,)): (\"RGBX\", \"RGBX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8), (0,)): (\"RGBX\", \"RGBX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): (\"RGBX\", \"RGBXX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): (\"RGBX\", \"RGBXX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): (\"RGBX\", \"RGBXXX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): (\"RGBX\", \"RGBXXX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8), (1,)): (\"RGBA\", \"RGBa\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8), (1,)): (\"RGBA\", \"RGBa\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): (\"RGBA\", \"RGBaX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): (\"RGBA\", \"RGBaX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): (\"RGBA\", \"RGBaXX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): (\"RGBA\", \"RGBaXX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8), (2,)): (\"RGBA\", \"RGBA\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8), (2,)): (\"RGBA\", \"RGBA\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): (\"RGBA\", \"RGBAX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): (\"RGBA\", \"RGBAX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): (\"RGBA\", \"RGBAXX\"),\n    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): (\"RGBA\", \"RGBAXX\"),\n    (II, 2, (1,), 1, (8, 8, 8, 8), (999,)): (\"RGBA\", \"RGBA\"),  # Corel Draw 10\n    (MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): (\"RGBA\", \"RGBA\"),  # Corel Draw 10\n    (II, 2, (1,), 1, (16, 16, 16), ()): (\"RGB\", \"RGB;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16), ()): (\"RGB\", \"RGB;16B\"),\n    (II, 2, (1,), 1, (16, 16, 16, 16), ()): (\"RGBA\", \"RGBA;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16, 16), ()): (\"RGBA\", \"RGBA;16B\"),\n    (II, 2, (1,), 1, (16, 16, 16, 16), (0,)): (\"RGBX\", \"RGBX;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16, 16), (0,)): (\"RGBX\", \"RGBX;16B\"),\n    (II, 2, (1,), 1, (16, 16, 16, 16), (1,)): (\"RGBA\", \"RGBa;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16, 16), (1,)): (\"RGBA\", \"RGBa;16B\"),\n    (II, 2, (1,), 1, (16, 16, 16, 16), (2,)): (\"RGBA\", \"RGBA;16L\"),\n    (MM, 2, (1,), 1, (16, 16, 16, 16), (2,)): (\"RGBA\", \"RGBA;16B\"),\n    (II, 3, (1,), 1, (1,), ()): (\"P\", \"P;1\"),\n    (MM, 3, (1,), 1, (1,), ()): (\"P\", \"P;1\"),\n    (II, 3, (1,), 2, (1,), ()): (\"P\", \"P;1R\"),\n    (MM, 3, (1,), 2, (1,), ()): (\"P\", \"P;1R\"),\n    (II, 3, (1,), 1, (2,), ()): (\"P\", \"P;2\"),\n    (MM, 3, (1,), 1, (2,), ()): (\"P\", \"P;2\"),\n    (II, 3, (1,), 2, (2,), ()): (\"P\", \"P;2R\"),\n    (MM, 3, (1,), 2, (2,), ()): (\"P\", \"P;2R\"),\n    (II, 3, (1,), 1, (4,), ()): (\"P\", \"P;4\"),\n    (MM, 3, (1,), 1, (4,), ()): (\"P\", \"P;4\"),\n    (II, 3, (1,), 2, (4,), ()): (\"P\", \"P;4R\"),\n    (MM, 3, (1,), 2, (4,), ()): (\"P\", \"P;4R\"),\n    (II, 3, (1,), 1, (8,), ()): (\"P\", \"P\"),\n    (MM, 3, (1,), 1, (8,), ()): (\"P\", \"P\"),\n    (II, 3, (1,), 1, (8, 8), (2,)): (\"PA\", \"PA\"),\n    (MM, 3, (1,), 1, (8, 8), (2,)): (\"PA\", \"PA\"),\n    (II, 3, (1,), 2, (8,), ()): (\"P\", \"P;R\"),\n    (MM, 3, (1,), 2, (8,), ()): (\"P\", \"P;R\"),\n    (II, 5, (1,), 1, (8, 8, 8, 8), ()): (\"CMYK\", \"CMYK\"),\n    (MM, 5, (1,), 1, (8, 8, 8, 8), ()): (\"CMYK\", \"CMYK\"),\n    (II, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): (\"CMYK\", \"CMYKX\"),\n    (MM, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): (\"CMYK\", \"CMYKX\"),\n    (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): (\"CMYK\", \"CMYKXX\"),\n    (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): (\"CMYK\", \"CMYKXX\"),\n    (II, 5, (1,), 1, (16, 16, 16, 16), ()): (\"CMYK\", \"CMYK;16L\"),\n    # JPEG compressed images handled by LibTiff and auto-converted to RGBX\n    # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel\n    (II, 6, (1,), 1, (8, 8, 8), ()): (\"RGB\", \"RGBX\"),\n    (MM, 6, (1,), 1, (8, 8, 8), ()): (\"RGB\", \"RGBX\"),\n    (II, 8, (1,), 1, (8, 8, 8), ()): (\"LAB\", \"LAB\"),\n    (MM, 8, (1,), 1, (8, 8, 8), ()): (\"LAB\", \"LAB\"),\n}\n\nPREFIXES = [\n    b\"MM\\x00\\x2A\",  # Valid TIFF header with big-endian byte order\n    b\"II\\x2A\\x00\",  # Valid TIFF header with little-endian byte order\n    b\"MM\\x2A\\x00\",  # Invalid TIFF header, assume big-endian\n    b\"II\\x00\\x2A\",  # Invalid TIFF header, assume little-endian\n]\n\n\ndef _accept(prefix):\n    return prefix[:4] in PREFIXES\n\n\ndef _limit_rational(val, max_val):\n    inv = abs(val) > 1\n    n_d = IFDRational(1 / val if inv else val).limit_rational(max_val)\n    return n_d[::-1] if inv else n_d\n\n\ndef _limit_signed_rational(val, max_val, min_val):\n    frac = Fraction(val)\n    n_d = frac.numerator, frac.denominator\n\n    if min(n_d) < min_val:\n        n_d = _limit_rational(val, abs(min_val))\n\n    if max(n_d) > max_val:\n        val = Fraction(*n_d)\n        n_d = _limit_rational(val, max_val)\n\n    return n_d\n\n\n##\n# Wrapper for TIFF IFDs.\n\n_load_dispatch = {}\n_write_dispatch = {}\n\n\nclass IFDRational(Rational):\n    \"\"\"Implements a rational class where 0/0 is a legal value to match\n    the in the wild use of exif rationals.\n\n    e.g., DigitalZoomRatio - 0.00/0.00  indicates that no digital zoom was used\n    \"\"\"\n\n    \"\"\" If the denominator is 0, store this as a float('nan'), otherwise store\n    as a fractions.Fraction(). Delegate as appropriate\n\n    \"\"\"\n\n    __slots__ = (\"_numerator\", \"_denominator\", \"_val\")\n\n    def __init__(self, value, denominator=1):\n        \"\"\"\n        :param value: either an integer numerator, a\n        float/rational/other number, or an IFDRational\n        :param denominator: Optional integer denominator\n        \"\"\"\n        if isinstance(value, IFDRational):\n            self._numerator = value.numerator\n            self._denominator = value.denominator\n            self._val = value._val\n            return\n\n        if isinstance(value, Fraction):\n            self._numerator = value.numerator\n            self._denominator = value.denominator\n        else:\n            self._numerator = value\n            self._denominator = denominator\n\n        if denominator == 0:\n            self._val = float(\"nan\")\n        elif denominator == 1:\n            self._val = Fraction(value)\n        else:\n            self._val = Fraction(value, denominator)\n\n    @property\n    def numerator(a):\n        return a._numerator\n\n    @property\n    def denominator(a):\n        return a._denominator\n\n    def limit_rational(self, max_denominator):\n        \"\"\"\n\n        :param max_denominator: Integer, the maximum denominator value\n        :returns: Tuple of (numerator, denominator)\n        \"\"\"\n\n        if self.denominator == 0:\n            return (self.numerator, self.denominator)\n\n        f = self._val.limit_denominator(max_denominator)\n        return (f.numerator, f.denominator)\n\n    def __repr__(self):\n        return str(float(self._val))\n\n    def __hash__(self):\n        return self._val.__hash__()\n\n    def __eq__(self, other):\n        val = self._val\n        if isinstance(other, IFDRational):\n            other = other._val\n        if isinstance(other, float):\n            val = float(val)\n        return val == other\n\n    def __getstate__(self):\n        return [self._val, self._numerator, self._denominator]\n\n    def __setstate__(self, state):\n        IFDRational.__init__(self, 0)\n        _val, _numerator, _denominator = state\n        self._val = _val\n        self._numerator = _numerator\n        self._denominator = _denominator\n\n    def _delegate(op):\n        def delegate(self, *args):\n            return getattr(self._val, op)(*args)\n\n        return delegate\n\n    \"\"\" a = ['add','radd', 'sub', 'rsub', 'mul', 'rmul',\n             'truediv', 'rtruediv', 'floordiv', 'rfloordiv',\n             'mod','rmod', 'pow','rpow', 'pos', 'neg',\n             'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'bool',\n             'ceil', 'floor', 'round']\n        print(\"\\n\".join(\"__%s__ = _delegate('__%s__')\" % (s,s) for s in a))\n        \"\"\"\n\n    __add__ = _delegate(\"__add__\")\n    __radd__ = _delegate(\"__radd__\")\n    __sub__ = _delegate(\"__sub__\")\n    __rsub__ = _delegate(\"__rsub__\")\n    __mul__ = _delegate(\"__mul__\")\n    __rmul__ = _delegate(\"__rmul__\")\n    __truediv__ = _delegate(\"__truediv__\")\n    __rtruediv__ = _delegate(\"__rtruediv__\")\n    __floordiv__ = _delegate(\"__floordiv__\")\n    __rfloordiv__ = _delegate(\"__rfloordiv__\")\n    __mod__ = _delegate(\"__mod__\")\n    __rmod__ = _delegate(\"__rmod__\")\n    __pow__ = _delegate(\"__pow__\")\n    __rpow__ = _delegate(\"__rpow__\")\n    __pos__ = _delegate(\"__pos__\")\n    __neg__ = _delegate(\"__neg__\")\n    __abs__ = _delegate(\"__abs__\")\n    __trunc__ = _delegate(\"__trunc__\")\n    __lt__ = _delegate(\"__lt__\")\n    __gt__ = _delegate(\"__gt__\")\n    __le__ = _delegate(\"__le__\")\n    __ge__ = _delegate(\"__ge__\")\n    __bool__ = _delegate(\"__bool__\")\n    __ceil__ = _delegate(\"__ceil__\")\n    __floor__ = _delegate(\"__floor__\")\n    __round__ = _delegate(\"__round__\")\n\n\nclass ImageFileDirectory_v2(MutableMapping):\n    \"\"\"This class represents a TIFF tag directory.  To speed things up, we\n    don't decode tags unless they're asked for.\n\n    Exposes a dictionary interface of the tags in the directory::\n\n        ifd = ImageFileDirectory_v2()\n        ifd[key] = 'Some Data'\n        ifd.tagtype[key] = TiffTags.ASCII\n        print(ifd[key])\n        'Some Data'\n\n    Individual values are returned as the strings or numbers, sequences are\n    returned as tuples of the values.\n\n    The tiff metadata type of each item is stored in a dictionary of\n    tag types in\n    :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types\n    are read from a tiff file, guessed from the type added, or added\n    manually.\n\n    Data Structures:\n\n        * self.tagtype = {}\n\n          * Key: numerical tiff tag number\n          * Value: integer corresponding to the data type from\n                   ~PIL.TiffTags.TYPES`\n\n    .. versionadded:: 3.0.0\n    \"\"\"\n\n    \"\"\"\n    Documentation:\n\n        'internal' data structures:\n        * self._tags_v2 = {} Key: numerical tiff tag number\n                             Value: decoded data, as tuple for multiple values\n        * self._tagdata = {} Key: numerical tiff tag number\n                             Value: undecoded byte string from file\n        * self._tags_v1 = {} Key: numerical tiff tag number\n                             Value: decoded data in the v1 format\n\n    Tags will be found in the private attributes self._tagdata, and in\n    self._tags_v2 once decoded.\n\n    self.legacy_api is a value for internal use, and shouldn't be\n    changed from outside code. In cooperation with the\n    ImageFileDirectory_v1 class, if legacy_api is true, then decoded\n    tags will be populated into both _tags_v1 and _tags_v2. _tags_v2\n    will be used if this IFD is used in the TIFF save routine. Tags\n    should be read from _tags_v1 if legacy_api == true.\n\n    \"\"\"\n\n    def __init__(self, ifh=b\"II\\052\\0\\0\\0\\0\\0\", prefix=None, group=None):\n        \"\"\"Initialize an ImageFileDirectory.\n\n        To construct an ImageFileDirectory from a real file, pass the 8-byte\n        magic header to the constructor.  To only set the endianness, pass it\n        as the 'prefix' keyword argument.\n\n        :param ifh: One of the accepted magic headers (cf. PREFIXES); also sets\n              endianness.\n        :param prefix: Override the endianness of the file.\n        \"\"\"\n        if ifh[:4] not in PREFIXES:\n            raise SyntaxError(f\"not a TIFF file (header {repr(ifh)} not valid)\")\n        self._prefix = prefix if prefix is not None else ifh[:2]\n        if self._prefix == MM:\n            self._endian = \">\"\n        elif self._prefix == II:\n            self._endian = \"<\"\n        else:\n            raise SyntaxError(\"not a TIFF IFD\")\n        self.group = group\n        self.tagtype = {}\n        \"\"\" Dictionary of tag types \"\"\"\n        self.reset()\n        (self.next,) = self._unpack(\"L\", ifh[4:])\n        self._legacy_api = False\n\n    prefix = property(lambda self: self._prefix)\n    offset = property(lambda self: self._offset)\n    legacy_api = property(lambda self: self._legacy_api)\n\n    @legacy_api.setter\n    def legacy_api(self, value):\n        raise Exception(\"Not allowing setting of legacy api\")\n\n    def reset(self):\n        self._tags_v1 = {}  # will remain empty if legacy_api is false\n        self._tags_v2 = {}  # main tag storage\n        self._tagdata = {}\n        self.tagtype = {}  # added 2008-06-05 by Florian Hoech\n        self._next = None\n        self._offset = None\n\n    def __str__(self):\n        return str(dict(self))\n\n    def named(self):\n        \"\"\"\n        :returns: dict of name|key: value\n\n        Returns the complete tag dictionary, with named tags where possible.\n        \"\"\"\n        return {\n            TiffTags.lookup(code, self.group).name: value\n            for code, value in self.items()\n        }\n\n    def __len__(self):\n        return len(set(self._tagdata) | set(self._tags_v2))\n\n    def __getitem__(self, tag):\n        if tag not in self._tags_v2:  # unpack on the fly\n            data = self._tagdata[tag]\n            typ = self.tagtype[tag]\n            size, handler = self._load_dispatch[typ]\n            self[tag] = handler(self, data, self.legacy_api)  # check type\n        val = self._tags_v2[tag]\n        if self.legacy_api and not isinstance(val, (tuple, bytes)):\n            val = (val,)\n        return val\n\n    def __contains__(self, tag):\n        return tag in self._tags_v2 or tag in self._tagdata\n\n    def __setitem__(self, tag, value):\n        self._setitem(tag, value, self.legacy_api)\n\n    def _setitem(self, tag, value, legacy_api):\n        basetypes = (Number, bytes, str)\n\n        info = TiffTags.lookup(tag, self.group)\n        values = [value] if isinstance(value, basetypes) else value\n\n        if tag not in self.tagtype:\n            if info.type:\n                self.tagtype[tag] = info.type\n            else:\n                self.tagtype[tag] = TiffTags.UNDEFINED\n                if all(isinstance(v, IFDRational) for v in values):\n                    self.tagtype[tag] = (\n                        TiffTags.RATIONAL\n                        if all(v >= 0 for v in values)\n                        else TiffTags.SIGNED_RATIONAL\n                    )\n                elif all(isinstance(v, int) for v in values):\n                    if all(0 <= v < 2 ** 16 for v in values):\n                        self.tagtype[tag] = TiffTags.SHORT\n                    elif all(-(2 ** 15) < v < 2 ** 15 for v in values):\n                        self.tagtype[tag] = TiffTags.SIGNED_SHORT\n                    else:\n                        self.tagtype[tag] = (\n                            TiffTags.LONG\n                            if all(v >= 0 for v in values)\n                            else TiffTags.SIGNED_LONG\n                        )\n                elif all(isinstance(v, float) for v in values):\n                    self.tagtype[tag] = TiffTags.DOUBLE\n                elif all(isinstance(v, str) for v in values):\n                    self.tagtype[tag] = TiffTags.ASCII\n                elif all(isinstance(v, bytes) for v in values):\n                    self.tagtype[tag] = TiffTags.BYTE\n\n        if self.tagtype[tag] == TiffTags.UNDEFINED:\n            values = [\n                v.encode(\"ascii\", \"replace\") if isinstance(v, str) else v\n                for v in values\n            ]\n        elif self.tagtype[tag] == TiffTags.RATIONAL:\n            values = [float(v) if isinstance(v, int) else v for v in values]\n\n        is_ifd = self.tagtype[tag] == TiffTags.LONG and isinstance(values, dict)\n        if not is_ifd:\n            values = tuple(info.cvt_enum(value) for value in values)\n\n        dest = self._tags_v1 if legacy_api else self._tags_v2\n\n        # Three branches:\n        # Spec'd length == 1, Actual length 1, store as element\n        # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed.\n        # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple.\n        # Don't mess with the legacy api, since it's frozen.\n        if not is_ifd and (\n            (info.length == 1)\n            or self.tagtype[tag] == TiffTags.BYTE\n            or (info.length is None and len(values) == 1 and not legacy_api)\n        ):\n            # Don't mess with the legacy api, since it's frozen.\n            if legacy_api and self.tagtype[tag] in [\n                TiffTags.RATIONAL,\n                TiffTags.SIGNED_RATIONAL,\n            ]:  # rationals\n                values = (values,)\n            try:\n                (dest[tag],) = values\n            except ValueError:\n                # We've got a builtin tag with 1 expected entry\n                warnings.warn(\n                    f\"Metadata Warning, tag {tag} had too many entries: \"\n                    f\"{len(values)}, expected 1\"\n                )\n                dest[tag] = values[0]\n\n        else:\n            # Spec'd length > 1 or undefined\n            # Unspec'd, and length > 1\n            dest[tag] = values\n\n    def __delitem__(self, tag):\n        self._tags_v2.pop(tag, None)\n        self._tags_v1.pop(tag, None)\n        self._tagdata.pop(tag, None)\n\n    def __iter__(self):\n        return iter(set(self._tagdata) | set(self._tags_v2))\n\n    def _unpack(self, fmt, data):\n        return struct.unpack(self._endian + fmt, data)\n\n    def _pack(self, fmt, *values):\n        return struct.pack(self._endian + fmt, *values)\n\n    def _register_loader(idx, size):\n        def decorator(func):\n            from .TiffTags import TYPES\n\n            if func.__name__.startswith(\"load_\"):\n                TYPES[idx] = func.__name__[5:].replace(\"_\", \" \")\n            _load_dispatch[idx] = size, func  # noqa: F821\n            return func\n\n        return decorator\n\n    def _register_writer(idx):\n        def decorator(func):\n            _write_dispatch[idx] = func  # noqa: F821\n            return func\n\n        return decorator\n\n    def _register_basic(idx_fmt_name):\n        from .TiffTags import TYPES\n\n        idx, fmt, name = idx_fmt_name\n        TYPES[idx] = name\n        size = struct.calcsize(\"=\" + fmt)\n        _load_dispatch[idx] = (  # noqa: F821\n            size,\n            lambda self, data, legacy_api=True: (\n                self._unpack(\"{}{}\".format(len(data) // size, fmt), data)\n            ),\n        )\n        _write_dispatch[idx] = lambda self, *values: (  # noqa: F821\n            b\"\".join(self._pack(fmt, value) for value in values)\n        )\n\n    list(\n        map(\n            _register_basic,\n            [\n                (TiffTags.SHORT, \"H\", \"short\"),\n                (TiffTags.LONG, \"L\", \"long\"),\n                (TiffTags.SIGNED_BYTE, \"b\", \"signed byte\"),\n                (TiffTags.SIGNED_SHORT, \"h\", \"signed short\"),\n                (TiffTags.SIGNED_LONG, \"l\", \"signed long\"),\n                (TiffTags.FLOAT, \"f\", \"float\"),\n                (TiffTags.DOUBLE, \"d\", \"double\"),\n                (TiffTags.IFD, \"L\", \"long\"),\n            ],\n        )\n    )\n\n    @_register_loader(1, 1)  # Basic type, except for the legacy API.\n    def load_byte(self, data, legacy_api=True):\n        return data\n\n    @_register_writer(1)  # Basic type, except for the legacy API.\n    def write_byte(self, data):\n        return data\n\n    @_register_loader(2, 1)\n    def load_string(self, data, legacy_api=True):\n        if data.endswith(b\"\\0\"):\n            data = data[:-1]\n        return data.decode(\"latin-1\", \"replace\")\n\n    @_register_writer(2)\n    def write_string(self, value):\n        # remerge of https://github.com/python-pillow/Pillow/pull/1416\n        return b\"\" + value.encode(\"ascii\", \"replace\") + b\"\\0\"\n\n    @_register_loader(5, 8)\n    def load_rational(self, data, legacy_api=True):\n        vals = self._unpack(\"{}L\".format(len(data) // 4), data)\n\n        def combine(a, b):\n            return (a, b) if legacy_api else IFDRational(a, b)\n\n        return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))\n\n    @_register_writer(5)\n    def write_rational(self, *values):\n        return b\"\".join(\n            self._pack(\"2L\", *_limit_rational(frac, 2 ** 32 - 1)) for frac in values\n        )\n\n    @_register_loader(7, 1)\n    def load_undefined(self, data, legacy_api=True):\n        return data\n\n    @_register_writer(7)\n    def write_undefined(self, value):\n        return value\n\n    @_register_loader(10, 8)\n    def load_signed_rational(self, data, legacy_api=True):\n        vals = self._unpack(\"{}l\".format(len(data) // 4), data)\n\n        def combine(a, b):\n            return (a, b) if legacy_api else IFDRational(a, b)\n\n        return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))\n\n    @_register_writer(10)\n    def write_signed_rational(self, *values):\n        return b\"\".join(\n            self._pack(\"2l\", *_limit_signed_rational(frac, 2 ** 31 - 1, -(2 ** 31)))\n            for frac in values\n        )\n\n    def _ensure_read(self, fp, size):\n        ret = fp.read(size)\n        if len(ret) != size:\n            raise OSError(\n                \"Corrupt EXIF data.  \"\n                f\"Expecting to read {size} bytes but only got {len(ret)}. \"\n            )\n        return ret\n\n    def load(self, fp):\n\n        self.reset()\n        self._offset = fp.tell()\n\n        try:\n            for i in range(self._unpack(\"H\", self._ensure_read(fp, 2))[0]):\n                tag, typ, count, data = self._unpack(\"HHL4s\", self._ensure_read(fp, 12))\n\n                tagname = TiffTags.lookup(tag, self.group).name\n                typname = TYPES.get(typ, \"unknown\")\n                msg = f\"tag: {tagname} ({tag}) - type: {typname} ({typ})\"\n\n                try:\n                    unit_size, handler = self._load_dispatch[typ]\n                except KeyError:\n                    logger.debug(msg + f\" - unsupported type {typ}\")\n                    continue  # ignore unsupported type\n                size = count * unit_size\n                if size > 4:\n                    here = fp.tell()\n                    (offset,) = self._unpack(\"L\", data)\n                    msg += f\" Tag Location: {here} - Data Location: {offset}\"\n                    fp.seek(offset)\n                    data = ImageFile._safe_read(fp, size)\n                    fp.seek(here)\n                else:\n                    data = data[:size]\n\n                if len(data) != size:\n                    warnings.warn(\n                        \"Possibly corrupt EXIF data.  \"\n                        f\"Expecting to read {size} bytes but only got {len(data)}.\"\n                        f\" Skipping tag {tag}\"\n                    )\n                    logger.debug(msg)\n                    continue\n\n                if not data:\n                    logger.debug(msg)\n                    continue\n\n                self._tagdata[tag] = data\n                self.tagtype[tag] = typ\n\n                msg += \" - value: \" + (\n                    \"<table: %d bytes>\" % size if size > 32 else repr(data)\n                )\n                logger.debug(msg)\n\n            (self.next,) = self._unpack(\"L\", self._ensure_read(fp, 4))\n        except OSError as msg:\n            warnings.warn(str(msg))\n            return\n\n    def tobytes(self, offset=0):\n        # FIXME What about tagdata?\n        result = self._pack(\"H\", len(self._tags_v2))\n\n        entries = []\n        offset = offset + len(result) + len(self._tags_v2) * 12 + 4\n        stripoffsets = None\n\n        # pass 1: convert tags to binary format\n        # always write tags in ascending order\n        for tag, value in sorted(self._tags_v2.items()):\n            if tag == STRIPOFFSETS:\n                stripoffsets = len(entries)\n            typ = self.tagtype.get(tag)\n            logger.debug(f\"Tag {tag}, Type: {typ}, Value: {repr(value)}\")\n            is_ifd = typ == TiffTags.LONG and isinstance(value, dict)\n            if is_ifd:\n                if self._endian == \"<\":\n                    ifh = b\"II\\x2A\\x00\\x08\\x00\\x00\\x00\"\n                else:\n                    ifh = b\"MM\\x00\\x2A\\x00\\x00\\x00\\x08\"\n                ifd = ImageFileDirectory_v2(ifh, group=tag)\n                values = self._tags_v2[tag]\n                for ifd_tag, ifd_value in values.items():\n                    ifd[ifd_tag] = ifd_value\n                data = ifd.tobytes(offset)\n            else:\n                values = value if isinstance(value, tuple) else (value,)\n                data = self._write_dispatch[typ](self, *values)\n\n            tagname = TiffTags.lookup(tag, self.group).name\n            typname = \"ifd\" if is_ifd else TYPES.get(typ, \"unknown\")\n            msg = f\"save: {tagname} ({tag}) - type: {typname} ({typ})\"\n            msg += \" - value: \" + (\n                \"<table: %d bytes>\" % len(data) if len(data) >= 16 else str(values)\n            )\n            logger.debug(msg)\n\n            # count is sum of lengths for string and arbitrary data\n            if is_ifd:\n                count = 1\n            elif typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]:\n                count = len(data)\n            else:\n                count = len(values)\n            # figure out if data fits into the entry\n            if len(data) <= 4:\n                entries.append((tag, typ, count, data.ljust(4, b\"\\0\"), b\"\"))\n            else:\n                entries.append((tag, typ, count, self._pack(\"L\", offset), data))\n                offset += (len(data) + 1) // 2 * 2  # pad to word\n\n        # update strip offset data to point beyond auxiliary data\n        if stripoffsets is not None:\n            tag, typ, count, value, data = entries[stripoffsets]\n            if data:\n                raise NotImplementedError(\"multistrip support not yet implemented\")\n            value = self._pack(\"L\", self._unpack(\"L\", value)[0] + offset)\n            entries[stripoffsets] = tag, typ, count, value, data\n\n        # pass 2: write entries to file\n        for tag, typ, count, value, data in entries:\n            logger.debug(f\"{tag} {typ} {count} {repr(value)} {repr(data)}\")\n            result += self._pack(\"HHL4s\", tag, typ, count, value)\n\n        # -- overwrite here for multi-page --\n        result += b\"\\0\\0\\0\\0\"  # end of entries\n\n        # pass 3: write auxiliary data to file\n        for tag, typ, count, value, data in entries:\n            result += data\n            if len(data) & 1:\n                result += b\"\\0\"\n\n        return result\n\n    def save(self, fp):\n\n        if fp.tell() == 0:  # skip TIFF header on subsequent pages\n            # tiff header -- PIL always starts the first IFD at offset 8\n            fp.write(self._prefix + self._pack(\"HL\", 42, 8))\n\n        offset = fp.tell()\n        result = self.tobytes(offset)\n        fp.write(result)\n        return offset + len(result)\n\n\nImageFileDirectory_v2._load_dispatch = _load_dispatch\nImageFileDirectory_v2._write_dispatch = _write_dispatch\nfor idx, name in TYPES.items():\n    name = name.replace(\" \", \"_\")\n    setattr(ImageFileDirectory_v2, \"load_\" + name, _load_dispatch[idx][1])\n    setattr(ImageFileDirectory_v2, \"write_\" + name, _write_dispatch[idx])\ndel _load_dispatch, _write_dispatch, idx, name\n\n\n# Legacy ImageFileDirectory support.\nclass ImageFileDirectory_v1(ImageFileDirectory_v2):\n    \"\"\"This class represents the **legacy** interface to a TIFF tag directory.\n\n    Exposes a dictionary interface of the tags in the directory::\n\n        ifd = ImageFileDirectory_v1()\n        ifd[key] = 'Some Data'\n        ifd.tagtype[key] = TiffTags.ASCII\n        print(ifd[key])\n        ('Some Data',)\n\n    Also contains a dictionary of tag types as read from the tiff image file,\n    :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`.\n\n    Values are returned as a tuple.\n\n    ..  deprecated:: 3.0.0\n    \"\"\"\n\n    def __init__(self, *args, **kwargs):\n        super().__init__(*args, **kwargs)\n        self._legacy_api = True\n\n    tags = property(lambda self: self._tags_v1)\n    tagdata = property(lambda self: self._tagdata)\n\n    # defined in ImageFileDirectory_v2\n    tagtype: dict\n    \"\"\"Dictionary of tag types\"\"\"\n\n    @classmethod\n    def from_v2(cls, original):\n        \"\"\"Returns an\n        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`\n        instance with the same data as is contained in the original\n        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`\n        instance.\n\n        :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`\n\n        \"\"\"\n\n        ifd = cls(prefix=original.prefix)\n        ifd._tagdata = original._tagdata\n        ifd.tagtype = original.tagtype\n        ifd.next = original.next  # an indicator for multipage tiffs\n        return ifd\n\n    def to_v2(self):\n        \"\"\"Returns an\n        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`\n        instance with the same data as is contained in the original\n        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`\n        instance.\n\n        :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`\n\n        \"\"\"\n\n        ifd = ImageFileDirectory_v2(prefix=self.prefix)\n        ifd._tagdata = dict(self._tagdata)\n        ifd.tagtype = dict(self.tagtype)\n        ifd._tags_v2 = dict(self._tags_v2)\n        return ifd\n\n    def __contains__(self, tag):\n        return tag in self._tags_v1 or tag in self._tagdata\n\n    def __len__(self):\n        return len(set(self._tagdata) | set(self._tags_v1))\n\n    def __iter__(self):\n        return iter(set(self._tagdata) | set(self._tags_v1))\n\n    def __setitem__(self, tag, value):\n        for legacy_api in (False, True):\n            self._setitem(tag, value, legacy_api)\n\n    def __getitem__(self, tag):\n        if tag not in self._tags_v1:  # unpack on the fly\n            data = self._tagdata[tag]\n            typ = self.tagtype[tag]\n            size, handler = self._load_dispatch[typ]\n            for legacy in (False, True):\n                self._setitem(tag, handler(self, data, legacy), legacy)\n        val = self._tags_v1[tag]\n        if not isinstance(val, (tuple, bytes)):\n            val = (val,)\n        return val\n\n\n# undone -- switch this pointer when IFD_LEGACY_API == False\nImageFileDirectory = ImageFileDirectory_v1\n\n\n##\n# Image plugin for TIFF files.\n\n\nclass TiffImageFile(ImageFile.ImageFile):\n\n    format = \"TIFF\"\n    format_description = \"Adobe TIFF\"\n    _close_exclusive_fp_after_loading = False\n\n    def __init__(self, fp=None, filename=None):\n        self.tag_v2 = None\n        \"\"\" Image file directory (tag dictionary) \"\"\"\n\n        self.tag = None\n        \"\"\" Legacy tag entries \"\"\"\n\n        super().__init__(fp, filename)\n\n    def _open(self):\n        \"\"\"Open the first image in a TIFF file\"\"\"\n\n        # Header\n        ifh = self.fp.read(8)\n\n        self.tag_v2 = ImageFileDirectory_v2(ifh)\n\n        # legacy IFD entries will be filled in later\n        self.ifd = None\n\n        # setup frame pointers\n        self.__first = self.__next = self.tag_v2.next\n        self.__frame = -1\n        self.__fp = self.fp\n        self._frame_pos = []\n        self._n_frames = None\n\n        logger.debug(\"*** TiffImageFile._open ***\")\n        logger.debug(f\"- __first: {self.__first}\")\n        logger.debug(f\"- ifh: {repr(ifh)}\")  # Use repr to avoid str(bytes)\n\n        # and load the first frame\n        self._seek(0)\n\n    @property\n    def n_frames(self):\n        if self._n_frames is None:\n            current = self.tell()\n            self._seek(len(self._frame_pos))\n            while self._n_frames is None:\n                self._seek(self.tell() + 1)\n            self.seek(current)\n        return self._n_frames\n\n    def seek(self, frame):\n        \"\"\"Select a given frame as current image\"\"\"\n        if not self._seek_check(frame):\n            return\n        self._seek(frame)\n        # Create a new core image object on second and\n        # subsequent frames in the image. Image may be\n        # different size/mode.\n        Image._decompression_bomb_check(self.size)\n        self.im = Image.core.new(self.mode, self.size)\n\n    def _seek(self, frame):\n        self.fp = self.__fp\n\n        # reset buffered io handle in case fp\n        # was passed to libtiff, invalidating the buffer\n        self.fp.tell()\n\n        while len(self._frame_pos) <= frame:\n            if not self.__next:\n                raise EOFError(\"no more images in TIFF file\")\n            logger.debug(\n                f\"Seeking to frame {frame}, on frame {self.__frame}, \"\n                f\"__next {self.__next}, location: {self.fp.tell()}\"\n            )\n            self.fp.seek(self.__next)\n            self._frame_pos.append(self.__next)\n            logger.debug(\"Loading tags, location: %s\" % self.fp.tell())\n            self.tag_v2.load(self.fp)\n            if self.tag_v2.next in self._frame_pos:\n                # This IFD has already been processed\n                # Declare this to be the end of the image\n                self.__next = 0\n            else:\n                self.__next = self.tag_v2.next\n            if self.__next == 0:\n                self._n_frames = frame + 1\n            if len(self._frame_pos) == 1:\n                self.is_animated = self.__next != 0\n            self.__frame += 1\n        self.fp.seek(self._frame_pos[frame])\n        self.tag_v2.load(self.fp)\n        # fill the legacy tag/ifd entries\n        self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)\n        self.__frame = frame\n        self._setup()\n\n    def tell(self):\n        \"\"\"Return the current frame number\"\"\"\n        return self.__frame\n\n    def getxmp(self):\n        \"\"\"\n        Returns a dictionary containing the XMP tags.\n        Requires defusedxml to be installed.\n        :returns: XMP tags in a dictionary.\n        \"\"\"\n        return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {}\n\n    def load(self):\n        if self.tile and self.use_load_libtiff:\n            return self._load_libtiff()\n        return super().load()\n\n    def load_end(self):\n        if self._tile_orientation:\n            method = {\n                2: Image.FLIP_LEFT_RIGHT,\n                3: Image.ROTATE_180,\n                4: Image.FLIP_TOP_BOTTOM,\n                5: Image.TRANSPOSE,\n                6: Image.ROTATE_270,\n                7: Image.TRANSVERSE,\n                8: Image.ROTATE_90,\n            }.get(self._tile_orientation)\n            if method is not None:\n                self.im = self.im.transpose(method)\n                self._size = self.im.size\n\n        # allow closing if we're on the first frame, there's no next\n        # This is the ImageFile.load path only, libtiff specific below.\n        if not self.is_animated:\n            self._close_exclusive_fp_after_loading = True\n\n    def _load_libtiff(self):\n        \"\"\"Overload method triggered when we detect a compressed tiff\n        Calls out to libtiff\"\"\"\n\n        Image.Image.load(self)\n\n        self.load_prepare()\n\n        if not len(self.tile) == 1:\n            raise OSError(\"Not exactly one tile\")\n\n        # (self._compression, (extents tuple),\n        #   0, (rawmode, self._compression, fp))\n        extents = self.tile[0][1]\n        args = list(self.tile[0][3])\n\n        # To be nice on memory footprint, if there's a\n        # file descriptor, use that instead of reading\n        # into a string in python.\n        # libtiff closes the file descriptor, so pass in a dup.\n        try:\n            fp = hasattr(self.fp, \"fileno\") and os.dup(self.fp.fileno())\n            # flush the file descriptor, prevents error on pypy 2.4+\n            # should also eliminate the need for fp.tell\n            # in _seek\n            if hasattr(self.fp, \"flush\"):\n                self.fp.flush()\n        except OSError:\n            # io.BytesIO have a fileno, but returns an OSError if\n            # it doesn't use a file descriptor.\n            fp = False\n\n        if fp:\n            args[2] = fp\n\n        decoder = Image._getdecoder(\n            self.mode, \"libtiff\", tuple(args), self.decoderconfig\n        )\n        try:\n            decoder.setimage(self.im, extents)\n        except ValueError as e:\n            raise OSError(\"Couldn't set the image\") from e\n\n        close_self_fp = self._exclusive_fp and not self.is_animated\n        if hasattr(self.fp, \"getvalue\"):\n            # We've got a stringio like thing passed in. Yay for all in memory.\n            # The decoder needs the entire file in one shot, so there's not\n            # a lot we can do here other than give it the entire file.\n            # unless we could do something like get the address of the\n            # underlying string for stringio.\n            #\n            # Rearranging for supporting byteio items, since they have a fileno\n            # that returns an OSError if there's no underlying fp. Easier to\n            # deal with here by reordering.\n            logger.debug(\"have getvalue. just sending in a string from getvalue\")\n            n, err = decoder.decode(self.fp.getvalue())\n        elif fp:\n            # we've got a actual file on disk, pass in the fp.\n            logger.debug(\"have fileno, calling fileno version of the decoder.\")\n            if not close_self_fp:\n                self.fp.seek(0)\n            # 4 bytes, otherwise the trace might error out\n            n, err = decoder.decode(b\"fpfp\")\n        else:\n            # we have something else.\n            logger.debug(\"don't have fileno or getvalue. just reading\")\n            self.fp.seek(0)\n            # UNDONE -- so much for that buffer size thing.\n            n, err = decoder.decode(self.fp.read())\n\n        self.tile = []\n        self.readonly = 0\n\n        self.load_end()\n\n        # libtiff closed the fp in a, we need to close self.fp, if possible\n        if close_self_fp:\n            self.fp.close()\n            self.fp = None  # might be shared\n\n        if err < 0:\n            raise OSError(err)\n\n        return Image.Image.load(self)\n\n    def _setup(self):\n        \"\"\"Setup this image object based on current tags\"\"\"\n\n        if 0xBC01 in self.tag_v2:\n            raise OSError(\"Windows Media Photo files not yet supported\")\n\n        # extract relevant tags\n        self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)]\n        self._planar_configuration = self.tag_v2.get(PLANAR_CONFIGURATION, 1)\n\n        # photometric is a required tag, but not everyone is reading\n        # the specification\n        photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)\n\n        # old style jpeg compression images most certainly are YCbCr\n        if self._compression == \"tiff_jpeg\":\n            photo = 6\n\n        fillorder = self.tag_v2.get(FILLORDER, 1)\n\n        logger.debug(\"*** Summary ***\")\n        logger.debug(f\"- compression: {self._compression}\")\n        logger.debug(f\"- photometric_interpretation: {photo}\")\n        logger.debug(f\"- planar_configuration: {self._planar_configuration}\")\n        logger.debug(f\"- fill_order: {fillorder}\")\n        logger.debug(f\"- YCbCr subsampling: {self.tag.get(530)}\")\n\n        # size\n        xsize = int(self.tag_v2.get(IMAGEWIDTH))\n        ysize = int(self.tag_v2.get(IMAGELENGTH))\n        self._size = xsize, ysize\n\n        logger.debug(f\"- size: {self.size}\")\n\n        sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,))\n        if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1:\n            # SAMPLEFORMAT is properly per band, so an RGB image will\n            # be (1,1,1).  But, we don't support per band pixel types,\n            # and anything more than one band is a uint8. So, just\n            # take the first element. Revisit this if adding support\n            # for more exotic images.\n            sampleFormat = (1,)\n\n        bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,))\n        extra_tuple = self.tag_v2.get(EXTRASAMPLES, ())\n        if photo in (2, 6, 8):  # RGB, YCbCr, LAB\n            bps_count = 3\n        elif photo == 5:  # CMYK\n            bps_count = 4\n        else:\n            bps_count = 1\n        bps_count += len(extra_tuple)\n        # Some files have only one value in bps_tuple,\n        # while should have more. Fix it\n        if bps_count > len(bps_tuple) and len(bps_tuple) == 1:\n            bps_tuple = bps_tuple * bps_count\n\n        samplesPerPixel = self.tag_v2.get(\n            SAMPLESPERPIXEL,\n            3 if self._compression == \"tiff_jpeg\" and photo in (2, 6) else 1,\n        )\n        if len(bps_tuple) != samplesPerPixel:\n            raise SyntaxError(\"unknown data organization\")\n\n        # mode: check photometric interpretation and bits per pixel\n        key = (\n            self.tag_v2.prefix,\n            photo,\n            sampleFormat,\n            fillorder,\n            bps_tuple,\n            extra_tuple,\n        )\n        logger.debug(f\"format key: {key}\")\n        try:\n            self.mode, rawmode = OPEN_INFO[key]\n        except KeyError as e:\n            logger.debug(\"- unsupported format\")\n            raise SyntaxError(\"unknown pixel mode\") from e\n\n        logger.debug(f\"- raw mode: {rawmode}\")\n        logger.debug(f\"- pil mode: {self.mode}\")\n\n        self.info[\"compression\"] = self._compression\n\n        xres = self.tag_v2.get(X_RESOLUTION, 1)\n        yres = self.tag_v2.get(Y_RESOLUTION, 1)\n\n        if xres and yres:\n            resunit = self.tag_v2.get(RESOLUTION_UNIT)\n            if resunit == 2:  # dots per inch\n                self.info[\"dpi\"] = (xres, yres)\n            elif resunit == 3:  # dots per centimeter. convert to dpi\n                self.info[\"dpi\"] = (xres * 2.54, yres * 2.54)\n            elif resunit is None:  # used to default to 1, but now 2)\n                self.info[\"dpi\"] = (xres, yres)\n                # For backward compatibility,\n                # we also preserve the old behavior\n                self.info[\"resolution\"] = xres, yres\n            else:  # No absolute unit of measurement\n                self.info[\"resolution\"] = xres, yres\n\n        # build tile descriptors\n        x = y = layer = 0\n        self.tile = []\n        self.use_load_libtiff = READ_LIBTIFF or self._compression != \"raw\"\n        if self.use_load_libtiff:\n            # Decoder expects entire file as one tile.\n            # There's a buffer size limit in load (64k)\n            # so large g4 images will fail if we use that\n            # function.\n            #\n            # Setup the one tile for the whole image, then\n            # use the _load_libtiff function.\n\n            # libtiff handles the fillmode for us, so 1;IR should\n            # actually be 1;I. Including the R double reverses the\n            # bits, so stripes of the image are reversed.  See\n            # https://github.com/python-pillow/Pillow/issues/279\n            if fillorder == 2:\n                # Replace fillorder with fillorder=1\n                key = key[:3] + (1,) + key[4:]\n                logger.debug(f\"format key: {key}\")\n                # this should always work, since all the\n                # fillorder==2 modes have a corresponding\n                # fillorder=1 mode\n                self.mode, rawmode = OPEN_INFO[key]\n            # libtiff always returns the bytes in native order.\n            # we're expecting image byte order. So, if the rawmode\n            # contains I;16, we need to convert from native to image\n            # byte order.\n            if rawmode == \"I;16\":\n                rawmode = \"I;16N\"\n            if \";16B\" in rawmode:\n                rawmode = rawmode.replace(\";16B\", \";16N\")\n            if \";16L\" in rawmode:\n                rawmode = rawmode.replace(\";16L\", \";16N\")\n\n            # YCbCr images with new jpeg compression with pixels in one plane\n            # unpacked straight into RGB values\n            if (\n                photo == 6\n                and self._compression == \"jpeg\"\n                and self._planar_configuration == 1\n            ):\n                rawmode = \"RGB\"\n\n            # Offset in the tile tuple is 0, we go from 0,0 to\n            # w,h, and we only do this once -- eds\n            a = (rawmode, self._compression, False, self.tag_v2.offset)\n            self.tile.append((\"libtiff\", (0, 0, xsize, ysize), 0, a))\n\n        elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:\n            # striped image\n            if STRIPOFFSETS in self.tag_v2:\n                offsets = self.tag_v2[STRIPOFFSETS]\n                h = self.tag_v2.get(ROWSPERSTRIP, ysize)\n                w = self.size[0]\n            else:\n                # tiled image\n                offsets = self.tag_v2[TILEOFFSETS]\n                w = self.tag_v2.get(322)\n                h = self.tag_v2.get(323)\n\n            for offset in offsets:\n                if x + w > xsize:\n                    stride = w * sum(bps_tuple) / 8  # bytes per line\n                else:\n                    stride = 0\n\n                tile_rawmode = rawmode\n                if self._planar_configuration == 2:\n                    # each band on it's own layer\n                    tile_rawmode = rawmode[layer]\n                    # adjust stride width accordingly\n                    stride /= bps_count\n\n                a = (tile_rawmode, int(stride), 1)\n                self.tile.append(\n                    (\n                        self._compression,\n                        (x, y, min(x + w, xsize), min(y + h, ysize)),\n                        offset,\n                        a,\n                    )\n                )\n                x = x + w\n                if x >= self.size[0]:\n                    x, y = 0, y + h\n                    if y >= self.size[1]:\n                        x = y = 0\n                        layer += 1\n        else:\n            logger.debug(\"- unsupported data organization\")\n            raise SyntaxError(\"unknown data organization\")\n\n        # Fix up info.\n        if ICCPROFILE in self.tag_v2:\n            self.info[\"icc_profile\"] = self.tag_v2[ICCPROFILE]\n\n        # fixup palette descriptor\n\n        if self.mode in [\"P\", \"PA\"]:\n            palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]\n            self.palette = ImagePalette.raw(\"RGB;L\", b\"\".join(palette))\n\n        self._tile_orientation = self.tag_v2.get(0x0112)\n\n    def _close__fp(self):\n        try:\n            if self.__fp != self.fp:\n                self.__fp.close()\n        except AttributeError:\n            pass\n        finally:\n            self.__fp = None\n\n\n#\n# --------------------------------------------------------------------\n# Write TIFF files\n\n# little endian is default except for image modes with\n# explicit big endian byte-order\n\nSAVE_INFO = {\n    # mode => rawmode, byteorder, photometrics,\n    #           sampleformat, bitspersample, extra\n    \"1\": (\"1\", II, 1, 1, (1,), None),\n    \"L\": (\"L\", II, 1, 1, (8,), None),\n    \"LA\": (\"LA\", II, 1, 1, (8, 8), 2),\n    \"P\": (\"P\", II, 3, 1, (8,), None),\n    \"PA\": (\"PA\", II, 3, 1, (8, 8), 2),\n    \"I\": (\"I;32S\", II, 1, 2, (32,), None),\n    \"I;16\": (\"I;16\", II, 1, 1, (16,), None),\n    \"I;16S\": (\"I;16S\", II, 1, 2, (16,), None),\n    \"F\": (\"F;32F\", II, 1, 3, (32,), None),\n    \"RGB\": (\"RGB\", II, 2, 1, (8, 8, 8), None),\n    \"RGBX\": (\"RGBX\", II, 2, 1, (8, 8, 8, 8), 0),\n    \"RGBA\": (\"RGBA\", II, 2, 1, (8, 8, 8, 8), 2),\n    \"CMYK\": (\"CMYK\", II, 5, 1, (8, 8, 8, 8), None),\n    \"YCbCr\": (\"YCbCr\", II, 6, 1, (8, 8, 8), None),\n    \"LAB\": (\"LAB\", II, 8, 1, (8, 8, 8), None),\n    \"I;32BS\": (\"I;32BS\", MM, 1, 2, (32,), None),\n    \"I;16B\": (\"I;16B\", MM, 1, 1, (16,), None),\n    \"I;16BS\": (\"I;16BS\", MM, 1, 2, (16,), None),\n    \"F;32BF\": (\"F;32BF\", MM, 1, 3, (32,), None),\n}\n\n\ndef _save(im, fp, filename):\n\n    try:\n        rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]\n    except KeyError as e:\n        raise OSError(f\"cannot write mode {im.mode} as TIFF\") from e\n\n    ifd = ImageFileDirectory_v2(prefix=prefix)\n\n    compression = im.encoderinfo.get(\"compression\", im.info.get(\"compression\"))\n    if compression is None:\n        compression = \"raw\"\n    elif compression == \"tiff_jpeg\":\n        # OJPEG is obsolete, so use new-style JPEG compression instead\n        compression = \"jpeg\"\n    elif compression == \"tiff_deflate\":\n        compression = \"tiff_adobe_deflate\"\n\n    libtiff = WRITE_LIBTIFF or compression != \"raw\"\n\n    # required for color libtiff images\n    ifd[PLANAR_CONFIGURATION] = getattr(im, \"_planar_configuration\", 1)\n\n    ifd[IMAGEWIDTH] = im.size[0]\n    ifd[IMAGELENGTH] = im.size[1]\n\n    # write any arbitrary tags passed in as an ImageFileDirectory\n    info = im.encoderinfo.get(\"tiffinfo\", {})\n    logger.debug(\"Tiffinfo Keys: %s\" % list(info))\n    if isinstance(info, ImageFileDirectory_v1):\n        info = info.to_v2()\n    for key in info:\n        ifd[key] = info.get(key)\n        try:\n            ifd.tagtype[key] = info.tagtype[key]\n        except Exception:\n            pass  # might not be an IFD. Might not have populated type\n\n    # additions written by Greg Couch, gregc@cgl.ucsf.edu\n    # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com\n    if hasattr(im, \"tag_v2\"):\n        # preserve tags from original TIFF image file\n        for key in (\n            RESOLUTION_UNIT,\n            X_RESOLUTION,\n            Y_RESOLUTION,\n            IPTC_NAA_CHUNK,\n            PHOTOSHOP_CHUNK,\n            XMP,\n        ):\n            if key in im.tag_v2:\n                ifd[key] = im.tag_v2[key]\n                ifd.tagtype[key] = im.tag_v2.tagtype[key]\n\n    # preserve ICC profile (should also work when saving other formats\n    # which support profiles as TIFF) -- 2008-06-06 Florian Hoech\n    icc = im.encoderinfo.get(\"icc_profile\", im.info.get(\"icc_profile\"))\n    if icc:\n        ifd[ICCPROFILE] = icc\n\n    for key, name in [\n        (IMAGEDESCRIPTION, \"description\"),\n        (X_RESOLUTION, \"resolution\"),\n        (Y_RESOLUTION, \"resolution\"),\n        (X_RESOLUTION, \"x_resolution\"),\n        (Y_RESOLUTION, \"y_resolution\"),\n        (RESOLUTION_UNIT, \"resolution_unit\"),\n        (SOFTWARE, \"software\"),\n        (DATE_TIME, \"date_time\"),\n        (ARTIST, \"artist\"),\n        (COPYRIGHT, \"copyright\"),\n    ]:\n        if name in im.encoderinfo:\n            ifd[key] = im.encoderinfo[name]\n\n    dpi = im.encoderinfo.get(\"dpi\")\n    if dpi:\n        ifd[RESOLUTION_UNIT] = 2\n        ifd[X_RESOLUTION] = dpi[0]\n        ifd[Y_RESOLUTION] = dpi[1]\n\n    if bits != (1,):\n        ifd[BITSPERSAMPLE] = bits\n        if len(bits) != 1:\n            ifd[SAMPLESPERPIXEL] = len(bits)\n    if extra is not None:\n        ifd[EXTRASAMPLES] = extra\n    if format != 1:\n        ifd[SAMPLEFORMAT] = format\n\n    ifd[PHOTOMETRIC_INTERPRETATION] = photo\n\n    if im.mode in [\"P\", \"PA\"]:\n        lut = im.im.getpalette(\"RGB\", \"RGB;L\")\n        ifd[COLORMAP] = tuple(v * 256 for v in lut)\n    # data orientation\n    stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)\n    # aim for 64 KB strips when using libtiff writer\n    if libtiff:\n        rows_per_strip = min((2 ** 16 + stride - 1) // stride, im.size[1])\n    else:\n        rows_per_strip = im.size[1]\n    strip_byte_counts = stride * rows_per_strip\n    strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip\n    ifd[ROWSPERSTRIP] = rows_per_strip\n    if strip_byte_counts >= 2 ** 16:\n        ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG\n    ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (\n        stride * im.size[1] - strip_byte_counts * (strips_per_image - 1),\n    )\n    ifd[STRIPOFFSETS] = tuple(\n        range(0, strip_byte_counts * strips_per_image, strip_byte_counts)\n    )  # this is adjusted by IFD writer\n    # no compression by default:\n    ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)\n\n    if libtiff:\n        if \"quality\" in im.encoderinfo:\n            quality = im.encoderinfo[\"quality\"]\n            if not isinstance(quality, int) or quality < 0 or quality > 100:\n                raise ValueError(\"Invalid quality setting\")\n            if compression != \"jpeg\":\n                raise ValueError(\n                    \"quality setting only supported for 'jpeg' compression\"\n                )\n            ifd[JPEGQUALITY] = quality\n\n        logger.debug(\"Saving using libtiff encoder\")\n        logger.debug(\"Items: %s\" % sorted(ifd.items()))\n        _fp = 0\n        if hasattr(fp, \"fileno\"):\n            try:\n                fp.seek(0)\n                _fp = os.dup(fp.fileno())\n            except io.UnsupportedOperation:\n                pass\n\n        # optional types for non core tags\n        types = {}\n        # SAMPLEFORMAT is determined by the image format and should not be copied\n        # from legacy_ifd.\n        # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library\n        # based on the data in the strip.\n        # The other tags expect arrays with a certain length (fixed or depending on\n        # BITSPERSAMPLE, etc), passing arrays with a different length will result in\n        # segfaults. Block these tags until we add extra validation.\n        # SUBIFD may also cause a segfault.\n        blocklist = [\n            REFERENCEBLACKWHITE,\n            SAMPLEFORMAT,\n            STRIPBYTECOUNTS,\n            STRIPOFFSETS,\n            TRANSFERFUNCTION,\n            SUBIFD,\n        ]\n\n        atts = {}\n        # bits per sample is a single short in the tiff directory, not a list.\n        atts[BITSPERSAMPLE] = bits[0]\n        # Merge the ones that we have with (optional) more bits from\n        # the original file, e.g x,y resolution so that we can\n        # save(load('')) == original file.\n        legacy_ifd = {}\n        if hasattr(im, \"tag\"):\n            legacy_ifd = im.tag.to_v2()\n        for tag, value in itertools.chain(\n            ifd.items(), getattr(im, \"tag_v2\", {}).items(), legacy_ifd.items()\n        ):\n            # Libtiff can only process certain core items without adding\n            # them to the custom dictionary.\n            # Custom items are supported for int, float, unicode, string and byte\n            # values. Other types and tuples require a tagtype.\n            if tag not in TiffTags.LIBTIFF_CORE:\n                if not Image.core.libtiff_support_custom_tags:\n                    continue\n\n                if tag in ifd.tagtype:\n                    types[tag] = ifd.tagtype[tag]\n                elif not (isinstance(value, (int, float, str, bytes))):\n                    continue\n                else:\n                    type = TiffTags.lookup(tag).type\n                    if type:\n                        types[tag] = type\n            if tag not in atts and tag not in blocklist:\n                if isinstance(value, str):\n                    atts[tag] = value.encode(\"ascii\", \"replace\") + b\"\\0\"\n                elif isinstance(value, IFDRational):\n                    atts[tag] = float(value)\n                else:\n                    atts[tag] = value\n\n        logger.debug(\"Converted items: %s\" % sorted(atts.items()))\n\n        # libtiff always expects the bytes in native order.\n        # we're storing image byte order. So, if the rawmode\n        # contains I;16, we need to convert from native to image\n        # byte order.\n        if im.mode in (\"I;16B\", \"I;16\"):\n            rawmode = \"I;16N\"\n\n        # Pass tags as sorted list so that the tags are set in a fixed order.\n        # This is required by libtiff for some tags. For example, the JPEGQUALITY\n        # pseudo tag requires that the COMPRESS tag was already set.\n        tags = list(atts.items())\n        tags.sort()\n        a = (rawmode, compression, _fp, filename, tags, types)\n        e = Image._getencoder(im.mode, \"libtiff\", a, im.encoderconfig)\n        e.setimage(im.im, (0, 0) + im.size)\n        while True:\n            # undone, change to self.decodermaxblock:\n            l, s, d = e.encode(16 * 1024)\n            if not _fp:\n                fp.write(d)\n            if s:\n                break\n        if s < 0:\n            raise OSError(f\"encoder error {s} when writing image file\")\n\n    else:\n        offset = ifd.save(fp)\n\n        ImageFile._save(\n            im, fp, [(\"raw\", (0, 0) + im.size, offset, (rawmode, stride, 1))]\n        )\n\n    # -- helper for multi-page save --\n    if \"_debug_multipage\" in im.encoderinfo:\n        # just to access o32 and o16 (using correct byte order)\n        im._debug_multipage = ifd\n\n\nclass AppendingTiffWriter:\n    fieldSizes = [\n        0,  # None\n        1,  # byte\n        1,  # ascii\n        2,  # short\n        4,  # long\n        8,  # rational\n        1,  # sbyte\n        1,  # undefined\n        2,  # sshort\n        4,  # slong\n        8,  # srational\n        4,  # float\n        8,  # double\n    ]\n\n    #    StripOffsets = 273\n    #    FreeOffsets = 288\n    #    TileOffsets = 324\n    #    JPEGQTables = 519\n    #    JPEGDCTables = 520\n    #    JPEGACTables = 521\n    Tags = {273, 288, 324, 519, 520, 521}\n\n    def __init__(self, fn, new=False):\n        if hasattr(fn, \"read\"):\n            self.f = fn\n            self.close_fp = False\n        else:\n            self.name = fn\n            self.close_fp = True\n            try:\n                self.f = open(fn, \"w+b\" if new else \"r+b\")\n            except OSError:\n                self.f = open(fn, \"w+b\")\n        self.beginning = self.f.tell()\n        self.setup()\n\n    def setup(self):\n        # Reset everything.\n        self.f.seek(self.beginning, os.SEEK_SET)\n\n        self.whereToWriteNewIFDOffset = None\n        self.offsetOfNewPage = 0\n\n        self.IIMM = IIMM = self.f.read(4)\n        if not IIMM:\n            # empty file - first page\n            self.isFirst = True\n            return\n\n        self.isFirst = False\n        if IIMM == b\"II\\x2a\\x00\":\n            self.setEndian(\"<\")\n        elif IIMM == b\"MM\\x00\\x2a\":\n            self.setEndian(\">\")\n        else:\n            raise RuntimeError(\"Invalid TIFF file header\")\n\n        self.skipIFDs()\n        self.goToEnd()\n\n    def finalize(self):\n        if self.isFirst:\n            return\n\n        # fix offsets\n        self.f.seek(self.offsetOfNewPage)\n\n        IIMM = self.f.read(4)\n        if not IIMM:\n            # raise RuntimeError(\"nothing written into new page\")\n            # Make it easy to finish a frame without committing to a new one.\n            return\n\n        if IIMM != self.IIMM:\n            raise RuntimeError(\"IIMM of new page doesn't match IIMM of first page\")\n\n        IFDoffset = self.readLong()\n        IFDoffset += self.offsetOfNewPage\n        self.f.seek(self.whereToWriteNewIFDOffset)\n        self.writeLong(IFDoffset)\n        self.f.seek(IFDoffset)\n        self.fixIFD()\n\n    def newFrame(self):\n        # Call this to finish a frame.\n        self.finalize()\n        self.setup()\n\n    def __enter__(self):\n        return self\n\n    def __exit__(self, exc_type, exc_value, traceback):\n        if self.close_fp:\n            self.close()\n        return False\n\n    def tell(self):\n        return self.f.tell() - self.offsetOfNewPage\n\n    def seek(self, offset, whence=io.SEEK_SET):\n        if whence == os.SEEK_SET:\n            offset += self.offsetOfNewPage\n\n        self.f.seek(offset, whence)\n        return self.tell()\n\n    def goToEnd(self):\n        self.f.seek(0, os.SEEK_END)\n        pos = self.f.tell()\n\n        # pad to 16 byte boundary\n        padBytes = 16 - pos % 16\n        if 0 < padBytes < 16:\n            self.f.write(bytes(padBytes))\n        self.offsetOfNewPage = self.f.tell()\n\n    def setEndian(self, endian):\n        self.endian = endian\n        self.longFmt = self.endian + \"L\"\n        self.shortFmt = self.endian + \"H\"\n        self.tagFormat = self.endian + \"HHL\"\n\n    def skipIFDs(self):\n        while True:\n            IFDoffset = self.readLong()\n            if IFDoffset == 0:\n                self.whereToWriteNewIFDOffset = self.f.tell() - 4\n                break\n\n            self.f.seek(IFDoffset)\n            numTags = self.readShort()\n            self.f.seek(numTags * 12, os.SEEK_CUR)\n\n    def write(self, data):\n        return self.f.write(data)\n\n    def readShort(self):\n        (value,) = struct.unpack(self.shortFmt, self.f.read(2))\n        return value\n\n    def readLong(self):\n        (value,) = struct.unpack(self.longFmt, self.f.read(4))\n        return value\n\n    def rewriteLastShortToLong(self, value):\n        self.f.seek(-2, os.SEEK_CUR)\n        bytesWritten = self.f.write(struct.pack(self.longFmt, value))\n        if bytesWritten is not None and bytesWritten != 4:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 4\")\n\n    def rewriteLastShort(self, value):\n        self.f.seek(-2, os.SEEK_CUR)\n        bytesWritten = self.f.write(struct.pack(self.shortFmt, value))\n        if bytesWritten is not None and bytesWritten != 2:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 2\")\n\n    def rewriteLastLong(self, value):\n        self.f.seek(-4, os.SEEK_CUR)\n        bytesWritten = self.f.write(struct.pack(self.longFmt, value))\n        if bytesWritten is not None and bytesWritten != 4:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 4\")\n\n    def writeShort(self, value):\n        bytesWritten = self.f.write(struct.pack(self.shortFmt, value))\n        if bytesWritten is not None and bytesWritten != 2:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 2\")\n\n    def writeLong(self, value):\n        bytesWritten = self.f.write(struct.pack(self.longFmt, value))\n        if bytesWritten is not None and bytesWritten != 4:\n            raise RuntimeError(f\"wrote only {bytesWritten} bytes but wanted 4\")\n\n    def close(self):\n        self.finalize()\n        self.f.close()\n\n    def fixIFD(self):\n        numTags = self.readShort()\n\n        for i in range(numTags):\n            tag, fieldType, count = struct.unpack(self.tagFormat, self.f.read(8))\n\n            fieldSize = self.fieldSizes[fieldType]\n            totalSize = fieldSize * count\n            isLocal = totalSize <= 4\n            if not isLocal:\n                offset = self.readLong()\n                offset += self.offsetOfNewPage\n                self.rewriteLastLong(offset)\n\n            if tag in self.Tags:\n                curPos = self.f.tell()\n\n                if isLocal:\n                    self.fixOffsets(\n                        count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)\n                    )\n                    self.f.seek(curPos + 4)\n                else:\n                    self.f.seek(offset)\n                    self.fixOffsets(\n                        count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)\n                    )\n                    self.f.seek(curPos)\n\n                offset = curPos = None\n\n            elif isLocal:\n                # skip the locally stored value that is not an offset\n                self.f.seek(4, os.SEEK_CUR)\n\n    def fixOffsets(self, count, isShort=False, isLong=False):\n        if not isShort and not isLong:\n            raise RuntimeError(\"offset is neither short nor long\")\n\n        for i in range(count):\n            offset = self.readShort() if isShort else self.readLong()\n            offset += self.offsetOfNewPage\n            if isShort and offset >= 65536:\n                # offset is now too large - we must convert shorts to longs\n                if count != 1:\n                    raise RuntimeError(\"not implemented\")  # XXX TODO\n\n                # simple case - the offset is just one and therefore it is\n                # local (not referenced with another offset)\n                self.rewriteLastShortToLong(offset)\n                self.f.seek(-10, os.SEEK_CUR)\n                self.writeShort(TiffTags.LONG)  # rewrite the type to LONG\n                self.f.seek(8, os.SEEK_CUR)\n            elif isShort:\n                self.rewriteLastShort(offset)\n            else:\n                self.rewriteLastLong(offset)\n\n\ndef _save_all(im, fp, filename):\n    encoderinfo = im.encoderinfo.copy()\n    encoderconfig = im.encoderconfig\n    append_images = list(encoderinfo.get(\"append_images\", []))\n    if not hasattr(im, \"n_frames\") and not append_images:\n        return _save(im, fp, filename)\n\n    cur_idx = im.tell()\n    try:\n        with AppendingTiffWriter(fp) as tf:\n            for ims in [im] + append_images:\n                ims.encoderinfo = encoderinfo\n                ims.encoderconfig = encoderconfig\n                if not hasattr(ims, \"n_frames\"):\n                    nfr = 1\n                else:\n                    nfr = ims.n_frames\n\n                for idx in range(nfr):\n                    ims.seek(idx)\n                    ims.load()\n                    _save(ims, tf, filename)\n                    tf.newFrame()\n    finally:\n        im.seek(cur_idx)\n\n\n#\n# --------------------------------------------------------------------\n# Register\n\nImage.register_open(TiffImageFile.format, TiffImageFile, _accept)\nImage.register_save(TiffImageFile.format, _save)\nImage.register_save_all(TiffImageFile.format, _save_all)\n\nImage.register_extensions(TiffImageFile.format, [\".tif\", \".tiff\"])\n\nImage.register_mime(TiffImageFile.format, \"image/tiff\")\n"}
A user will ask you to solve a task. You should first draft your thinking process (inner monologue). Then, generate the solution.

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

**Description:**
When saving a TIFF image that contains EXIF metadata using the `save` method, the EXIF information is not retained as expected. This results in missing or incorrect EXIF tags in the saved image, which can lead to issues in applications that rely on this metadata.

**Bug Reproduction:**

```python
from PIL import Image

# Open an image with EXIF data
with Image.open("Tests/images/ifd_tag_type.tiff") as im:
    exif = im.getexif()
    # Attempt to save the image with EXIF data
    im.save("temp_saved.tiff", "TIFF")

# Re-open the saved image to verify EXIF data
with Image.open("temp_saved.tiff") as im_saved:
    exif_saved = im_saved.getexif()
    print(sorted(exif_saved.keys()))
    # Expected to include tag 271 ("Make"), but it is missing or incorrect
```

**Expected Behavior:**
The saved TIFF image (`temp_saved.tiff`) should retain all original EXIF metadata, including tags like `271` ("Make"). When re-opening the saved image, the EXIF data should match the original, ensuring that all metadata is accurately preserved.

**Actual Behavior:**
After saving, the EXIF data in `temp_saved.tiff` is incomplete or altered. For instance, tag `271` ("Make") is either missing or does not match the original value. This discrepancy causes assertions related to EXIF data validation to fail, indicating that the metadata was not preserved correctly during the save operation.

[/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/TiffImagePlugin.py
#
# The Python Imaging Library.
# $Id$
#
# TIFF file handling
#
# TIFF is a flexible, if somewhat aged, image file format originally
# defined by Aldus.  Although TIFF supports a wide variety of pixel
# layouts and compression methods, the name doesn't really stand for
# "thousands of incompatible file formats," it just feels that way.
#
# To read TIFF data from a stream, the stream must be seekable.  For
# progressive decoding, make sure to use TIFF files where the tag
# directory is placed first in the file.
#
# History:
# 1995-09-01 fl   Created
# 1996-05-04 fl   Handle JPEGTABLES tag
# 1996-05-18 fl   Fixed COLORMAP support
# 1997-01-05 fl   Fixed PREDICTOR support
# 1997-08-27 fl   Added support for rational tags (from Perry Stoll)
# 1998-01-10 fl   Fixed seek/tell (from Jan Blom)
# 1998-07-15 fl   Use private names for internal variables
# 1999-06-13 fl   Rewritten for PIL 1.0 (1.0)
# 2000-10-11 fl   Additional fixes for Python 2.0 (1.1)
# 2001-04-17 fl   Fixed rewind support (seek to frame 0) (1.2)
# 2001-05-12 fl   Added write support for more tags (from Greg Couch) (1.3)
# 2001-12-18 fl   Added workaround for broken Matrox library
# 2002-01-18 fl   Don't mess up if photometric tag is missing (D. Alan Stewart)
# 2003-05-19 fl   Check FILLORDER tag
# 2003-09-26 fl   Added RGBa support
# 2004-02-24 fl   Added DPI support; fixed rational write support
# 2005-02-07 fl   Added workaround for broken Corel Draw 10 files
# 2006-01-09 fl   Added support for float/double tags (from Russell Nelson)
#
# Copyright (c) 1997-2006 by Secret Labs AB.  All rights reserved.
# Copyright (c) 1995-1997 by Fredrik Lundh
#
# See the README file for information on usage and redistribution.
#
import io
import itertools
import logging
import os
import struct
import warnings
from collections.abc import MutableMapping
from fractions import Fraction
from numbers import Number, Rational

from . import Image, ImageFile, ImagePalette, TiffTags
from ._binary import o8
from .TiffTags import TYPES

logger = logging.getLogger(__name__)

# Set these to true to force use of libtiff for reading or writing.
READ_LIBTIFF = False
WRITE_LIBTIFF = False
IFD_LEGACY_API = True

II = b"II"  # little-endian (Intel style)
MM = b"MM"  # big-endian (Motorola style)

#
# --------------------------------------------------------------------
# Read TIFF files

# a few tag names, just to make the code below a bit more readable
IMAGEWIDTH = 256
IMAGELENGTH = 257
BITSPERSAMPLE = 258
COMPRESSION = 259
PHOTOMETRIC_INTERPRETATION = 262
FILLORDER = 266
IMAGEDESCRIPTION = 270
STRIPOFFSETS = 273
SAMPLESPERPIXEL = 277
ROWSPERSTRIP = 278
STRIPBYTECOUNTS = 279
X_RESOLUTION = 282
Y_RESOLUTION = 283
PLANAR_CONFIGURATION = 284
RESOLUTION_UNIT = 296
TRANSFERFUNCTION = 301
SOFTWARE = 305
DATE_TIME = 306
ARTIST = 315
PREDICTOR = 317
COLORMAP = 320
TILEOFFSETS = 324
SUBIFD = 330
EXTRASAMPLES = 338
SAMPLEFORMAT = 339
JPEGTABLES = 347
REFERENCEBLACKWHITE = 532
COPYRIGHT = 33432
IPTC_NAA_CHUNK = 33723  # newsphoto properties
PHOTOSHOP_CHUNK = 34377  # photoshop properties
ICCPROFILE = 34675
EXIFIFD = 34665
XMP = 700
JPEGQUALITY = 65537  # pseudo-tag by libtiff

# https://github.com/imagej/ImageJA/blob/master/src/main/java/ij/io/TiffDecoder.java
IMAGEJ_META_DATA_BYTE_COUNTS = 50838
IMAGEJ_META_DATA = 50839

COMPRESSION_INFO = {
    # Compression => pil compression name
    1: "raw",
    2: "tiff_ccitt",
    3: "group3",
    4: "group4",
    5: "tiff_lzw",
    6: "tiff_jpeg",  # obsolete
    7: "jpeg",
    8: "tiff_adobe_deflate",
    32771: "tiff_raw_16",  # 16-bit padding
    32773: "packbits",
    32809: "tiff_thunderscan",
    32946: "tiff_deflate",
    34676: "tiff_sgilog",
    34677: "tiff_sgilog24",
    34925: "lzma",
    50000: "zstd",
    50001: "webp",
}

COMPRESSION_INFO_REV = {v: k for k, v in COMPRESSION_INFO.items()}

OPEN_INFO = {
    # (ByteOrder, PhotoInterpretation, SampleFormat, FillOrder, BitsPerSample,
    #  ExtraSamples) => mode, rawmode
    (II, 0, (1,), 1, (1,), ()): ("1", "1;I"),
    (MM, 0, (1,), 1, (1,), ()): ("1", "1;I"),
    (II, 0, (1,), 2, (1,), ()): ("1", "1;IR"),
    (MM, 0, (1,), 2, (1,), ()): ("1", "1;IR"),
    (II, 1, (1,), 1, (1,), ()): ("1", "1"),
    (MM, 1, (1,), 1, (1,), ()): ("1", "1"),
    (II, 1, (1,), 2, (1,), ()): ("1", "1;R"),
    (MM, 1, (1,), 2, (1,), ()): ("1", "1;R"),
    (II, 0, (1,), 1, (2,), ()): ("L", "L;2I"),
    (MM, 0, (1,), 1, (2,), ()): ("L", "L;2I"),
    (II, 0, (1,), 2, (2,), ()): ("L", "L;2IR"),
    (MM, 0, (1,), 2, (2,), ()): ("L", "L;2IR"),
    (II, 1, (1,), 1, (2,), ()): ("L", "L;2"),
    (MM, 1, (1,), 1, (2,), ()): ("L", "L;2"),
    (II, 1, (1,), 2, (2,), ()): ("L", "L;2R"),
    (MM, 1, (1,), 2, (2,), ()): ("L", "L;2R"),
    (II, 0, (1,), 1, (4,), ()): ("L", "L;4I"),
    (MM, 0, (1,), 1, (4,), ()): ("L", "L;4I"),
    (II, 0, (1,), 2, (4,), ()): ("L", "L;4IR"),
    (MM, 0, (1,), 2, (4,), ()): ("L", "L;4IR"),
    (II, 1, (1,), 1, (4,), ()): ("L", "L;4"),
    (MM, 1, (1,), 1, (4,), ()): ("L", "L;4"),
    (II, 1, (1,), 2, (4,), ()): ("L", "L;4R"),
    (MM, 1, (1,), 2, (4,), ()): ("L", "L;4R"),
    (II, 0, (1,), 1, (8,), ()): ("L", "L;I"),
    (MM, 0, (1,), 1, (8,), ()): ("L", "L;I"),
    (II, 0, (1,), 2, (8,), ()): ("L", "L;IR"),
    (MM, 0, (1,), 2, (8,), ()): ("L", "L;IR"),
    (II, 1, (1,), 1, (8,), ()): ("L", "L"),
    (MM, 1, (1,), 1, (8,), ()): ("L", "L"),
    (II, 1, (1,), 2, (8,), ()): ("L", "L;R"),
    (MM, 1, (1,), 2, (8,), ()): ("L", "L;R"),
    (II, 1, (1,), 1, (12,), ()): ("I;16", "I;12"),
    (II, 1, (1,), 1, (16,), ()): ("I;16", "I;16"),
    (MM, 1, (1,), 1, (16,), ()): ("I;16B", "I;16B"),
    (II, 1, (2,), 1, (16,), ()): ("I", "I;16S"),
    (MM, 1, (2,), 1, (16,), ()): ("I", "I;16BS"),
    (II, 0, (3,), 1, (32,), ()): ("F", "F;32F"),
    (MM, 0, (3,), 1, (32,), ()): ("F", "F;32BF"),
    (II, 1, (1,), 1, (32,), ()): ("I", "I;32N"),
    (II, 1, (2,), 1, (32,), ()): ("I", "I;32S"),
    (MM, 1, (2,), 1, (32,), ()): ("I", "I;32BS"),
    (II, 1, (3,), 1, (32,), ()): ("F", "F;32F"),
    (MM, 1, (3,), 1, (32,), ()): ("F", "F;32BF"),
    (II, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"),
    (MM, 1, (1,), 1, (8, 8), (2,)): ("LA", "LA"),
    (II, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
    (MM, 2, (1,), 1, (8, 8, 8), ()): ("RGB", "RGB"),
    (II, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
    (MM, 2, (1,), 2, (8, 8, 8), ()): ("RGB", "RGB;R"),
    (II, 2, (1,), 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"),  # missing ExtraSamples
    (MM, 2, (1,), 1, (8, 8, 8, 8), ()): ("RGBA", "RGBA"),  # missing ExtraSamples
    (II, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8), (0,)): ("RGBX", "RGBX"),
    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGBX", "RGBXX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (0, 0)): ("RGBX", "RGBXX"),
    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGBX", "RGBXXX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0, 0)): ("RGBX", "RGBXXX"),
    (II, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
    (MM, 2, (1,), 1, (8, 8, 8, 8), (1,)): ("RGBA", "RGBa"),
    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (1, 0)): ("RGBA", "RGBaX"),
    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (1, 0, 0)): ("RGBA", "RGBaXX"),
    (II, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
    (MM, 2, (1,), 1, (8, 8, 8, 8), (2,)): ("RGBA", "RGBA"),
    (II, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8, 8), (2, 0)): ("RGBA", "RGBAX"),
    (II, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"),
    (MM, 2, (1,), 1, (8, 8, 8, 8, 8, 8), (2, 0, 0)): ("RGBA", "RGBAXX"),
    (II, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"),  # Corel Draw 10
    (MM, 2, (1,), 1, (8, 8, 8, 8), (999,)): ("RGBA", "RGBA"),  # Corel Draw 10
    (II, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16L"),
    (MM, 2, (1,), 1, (16, 16, 16), ()): ("RGB", "RGB;16B"),
    (II, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16L"),
    (MM, 2, (1,), 1, (16, 16, 16, 16), ()): ("RGBA", "RGBA;16B"),
    (II, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGBX", "RGBX;16L"),
    (MM, 2, (1,), 1, (16, 16, 16, 16), (0,)): ("RGBX", "RGBX;16B"),
    (II, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16L"),
    (MM, 2, (1,), 1, (16, 16, 16, 16), (1,)): ("RGBA", "RGBa;16B"),
    (II, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16L"),
    (MM, 2, (1,), 1, (16, 16, 16, 16), (2,)): ("RGBA", "RGBA;16B"),
    (II, 3, (1,), 1, (1,), ()): ("P", "P;1"),
    (MM, 3, (1,), 1, (1,), ()): ("P", "P;1"),
    (II, 3, (1,), 2, (1,), ()): ("P", "P;1R"),
    (MM, 3, (1,), 2, (1,), ()): ("P", "P;1R"),
    (II, 3, (1,), 1, (2,), ()): ("P", "P;2"),
    (MM, 3, (1,), 1, (2,), ()): ("P", "P;2"),
    (II, 3, (1,), 2, (2,), ()): ("P", "P;2R"),
    (MM, 3, (1,), 2, (2,), ()): ("P", "P;2R"),
    (II, 3, (1,), 1, (4,), ()): ("P", "P;4"),
    (MM, 3, (1,), 1, (4,), ()): ("P", "P;4"),
    (II, 3, (1,), 2, (4,), ()): ("P", "P;4R"),
    (MM, 3, (1,), 2, (4,), ()): ("P", "P;4R"),
    (II, 3, (1,), 1, (8,), ()): ("P", "P"),
    (MM, 3, (1,), 1, (8,), ()): ("P", "P"),
    (II, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
    (MM, 3, (1,), 1, (8, 8), (2,)): ("PA", "PA"),
    (II, 3, (1,), 2, (8,), ()): ("P", "P;R"),
    (MM, 3, (1,), 2, (8,), ()): ("P", "P;R"),
    (II, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
    (MM, 5, (1,), 1, (8, 8, 8, 8), ()): ("CMYK", "CMYK"),
    (II, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"),
    (MM, 5, (1,), 1, (8, 8, 8, 8, 8), (0,)): ("CMYK", "CMYKX"),
    (II, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
    (MM, 5, (1,), 1, (8, 8, 8, 8, 8, 8), (0, 0)): ("CMYK", "CMYKXX"),
    (II, 5, (1,), 1, (16, 16, 16, 16), ()): ("CMYK", "CMYK;16L"),
    # JPEG compressed images handled by LibTiff and auto-converted to RGBX
    # Minimal Baseline TIFF requires YCbCr images to have 3 SamplesPerPixel
    (II, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
    (MM, 6, (1,), 1, (8, 8, 8), ()): ("RGB", "RGBX"),
    (II, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
    (MM, 8, (1,), 1, (8, 8, 8), ()): ("LAB", "LAB"),
}

PREFIXES = [
    b"MM\x00\x2A",  # Valid TIFF header with big-endian byte order
    b"II\x2A\x00",  # Valid TIFF header with little-endian byte order
    b"MM\x2A\x00",  # Invalid TIFF header, assume big-endian
    b"II\x00\x2A",  # Invalid TIFF header, assume little-endian
]


def _accept(prefix):
    return prefix[:4] in PREFIXES


def _limit_rational(val, max_val):
    inv = abs(val) > 1
    n_d = IFDRational(1 / val if inv else val).limit_rational(max_val)
    return n_d[::-1] if inv else n_d


def _limit_signed_rational(val, max_val, min_val):
    frac = Fraction(val)
    n_d = frac.numerator, frac.denominator

    if min(n_d) < min_val:
        n_d = _limit_rational(val, abs(min_val))

    if max(n_d) > max_val:
        val = Fraction(*n_d)
        n_d = _limit_rational(val, max_val)

    return n_d


##
# Wrapper for TIFF IFDs.

_load_dispatch = {}
_write_dispatch = {}


class IFDRational(Rational):
    """Implements a rational class where 0/0 is a legal value to match
    the in the wild use of exif rationals.

    e.g., DigitalZoomRatio - 0.00/0.00  indicates that no digital zoom was used
    """

    """ If the denominator is 0, store this as a float('nan'), otherwise store
    as a fractions.Fraction(). Delegate as appropriate

    """

    __slots__ = ("_numerator", "_denominator", "_val")

    def __init__(self, value, denominator=1):
        """
        :param value: either an integer numerator, a
        float/rational/other number, or an IFDRational
        :param denominator: Optional integer denominator
        """
        if isinstance(value, IFDRational):
            self._numerator = value.numerator
            self._denominator = value.denominator
            self._val = value._val
            return

        if isinstance(value, Fraction):
            self._numerator = value.numerator
            self._denominator = value.denominator
        else:
            self._numerator = value
            self._denominator = denominator

        if denominator == 0:
            self._val = float("nan")
        elif denominator == 1:
            self._val = Fraction(value)
        else:
            self._val = Fraction(value, denominator)

    @property
    def numerator(a):
        return a._numerator

    @property
    def denominator(a):
        return a._denominator

    def limit_rational(self, max_denominator):
        """

        :param max_denominator: Integer, the maximum denominator value
        :returns: Tuple of (numerator, denominator)
        """

        if self.denominator == 0:
            return (self.numerator, self.denominator)

        f = self._val.limit_denominator(max_denominator)
        return (f.numerator, f.denominator)

    def __repr__(self):
        return str(float(self._val))

    def __hash__(self):
        return self._val.__hash__()

    def __eq__(self, other):
        val = self._val
        if isinstance(other, IFDRational):
            other = other._val
        if isinstance(other, float):
            val = float(val)
        return val == other

    def __getstate__(self):
        return [self._val, self._numerator, self._denominator]

    def __setstate__(self, state):
        IFDRational.__init__(self, 0)
        _val, _numerator, _denominator = state
        self._val = _val
        self._numerator = _numerator
        self._denominator = _denominator

    def _delegate(op):
        def delegate(self, *args):
            return getattr(self._val, op)(*args)

        return delegate

    """ a = ['add','radd', 'sub', 'rsub', 'mul', 'rmul',
             'truediv', 'rtruediv', 'floordiv', 'rfloordiv',
             'mod','rmod', 'pow','rpow', 'pos', 'neg',
             'abs', 'trunc', 'lt', 'gt', 'le', 'ge', 'bool',
             'ceil', 'floor', 'round']
        print("\n".join("__%s__ = _delegate('__%s__')" % (s,s) for s in a))
        """

    __add__ = _delegate("__add__")
    __radd__ = _delegate("__radd__")
    __sub__ = _delegate("__sub__")
    __rsub__ = _delegate("__rsub__")
    __mul__ = _delegate("__mul__")
    __rmul__ = _delegate("__rmul__")
    __truediv__ = _delegate("__truediv__")
    __rtruediv__ = _delegate("__rtruediv__")
    __floordiv__ = _delegate("__floordiv__")
    __rfloordiv__ = _delegate("__rfloordiv__")
    __mod__ = _delegate("__mod__")
    __rmod__ = _delegate("__rmod__")
    __pow__ = _delegate("__pow__")
    __rpow__ = _delegate("__rpow__")
    __pos__ = _delegate("__pos__")
    __neg__ = _delegate("__neg__")
    __abs__ = _delegate("__abs__")
    __trunc__ = _delegate("__trunc__")
    __lt__ = _delegate("__lt__")
    __gt__ = _delegate("__gt__")
    __le__ = _delegate("__le__")
    __ge__ = _delegate("__ge__")
    __bool__ = _delegate("__bool__")
    __ceil__ = _delegate("__ceil__")
    __floor__ = _delegate("__floor__")
    __round__ = _delegate("__round__")


class ImageFileDirectory_v2(MutableMapping):
    """This class represents a TIFF tag directory.  To speed things up, we
    don't decode tags unless they're asked for.

    Exposes a dictionary interface of the tags in the directory::

        ifd = ImageFileDirectory_v2()
        ifd[key] = 'Some Data'
        ifd.tagtype[key] = TiffTags.ASCII
        print(ifd[key])
        'Some Data'

    Individual values are returned as the strings or numbers, sequences are
    returned as tuples of the values.

    The tiff metadata type of each item is stored in a dictionary of
    tag types in
    :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v2.tagtype`. The types
    are read from a tiff file, guessed from the type added, or added
    manually.

    Data Structures:

        * self.tagtype = {}

          * Key: numerical tiff tag number
          * Value: integer corresponding to the data type from
                   ~PIL.TiffTags.TYPES`

    .. versionadded:: 3.0.0
    """

    """
    Documentation:

        'internal' data structures:
        * self._tags_v2 = {} Key: numerical tiff tag number
                             Value: decoded data, as tuple for multiple values
        * self._tagdata = {} Key: numerical tiff tag number
                             Value: undecoded byte string from file
        * self._tags_v1 = {} Key: numerical tiff tag number
                             Value: decoded data in the v1 format

    Tags will be found in the private attributes self._tagdata, and in
    self._tags_v2 once decoded.

    self.legacy_api is a value for internal use, and shouldn't be
    changed from outside code. In cooperation with the
    ImageFileDirectory_v1 class, if legacy_api is true, then decoded
    tags will be populated into both _tags_v1 and _tags_v2. _tags_v2
    will be used if this IFD is used in the TIFF save routine. Tags
    should be read from _tags_v1 if legacy_api == true.

    """

    def __init__(self, ifh=b"II\052\0\0\0\0\0", prefix=None, group=None):
        """Initialize an ImageFileDirectory.

        To construct an ImageFileDirectory from a real file, pass the 8-byte
        magic header to the constructor.  To only set the endianness, pass it
        as the 'prefix' keyword argument.

        :param ifh: One of the accepted magic headers (cf. PREFIXES); also sets
              endianness.
        :param prefix: Override the endianness of the file.
        """
        if ifh[:4] not in PREFIXES:
            raise SyntaxError(f"not a TIFF file (header {repr(ifh)} not valid)")
        self._prefix = prefix if prefix is not None else ifh[:2]
        if self._prefix == MM:
            self._endian = ">"
        elif self._prefix == II:
            self._endian = "<"
        else:
            raise SyntaxError("not a TIFF IFD")
        self.group = group
        self.tagtype = {}
        """ Dictionary of tag types """
        self.reset()
        (self.next,) = self._unpack("L", ifh[4:])
        self._legacy_api = False

    prefix = property(lambda self: self._prefix)
    offset = property(lambda self: self._offset)
    legacy_api = property(lambda self: self._legacy_api)

    @legacy_api.setter
    def legacy_api(self, value):
        raise Exception("Not allowing setting of legacy api")

    def reset(self):
        self._tags_v1 = {}  # will remain empty if legacy_api is false
        self._tags_v2 = {}  # main tag storage
        self._tagdata = {}
        self.tagtype = {}  # added 2008-06-05 by Florian Hoech
        self._next = None
        self._offset = None

    def __str__(self):
        return str(dict(self))

    def named(self):
        """
        :returns: dict of name|key: value

        Returns the complete tag dictionary, with named tags where possible.
        """
        return {
            TiffTags.lookup(code, self.group).name: value
            for code, value in self.items()
        }

    def __len__(self):
        return len(set(self._tagdata) | set(self._tags_v2))

    def __getitem__(self, tag):
        if tag not in self._tags_v2:  # unpack on the fly
            data = self._tagdata[tag]
            typ = self.tagtype[tag]
            size, handler = self._load_dispatch[typ]
            self[tag] = handler(self, data, self.legacy_api)  # check type
        val = self._tags_v2[tag]
        if self.legacy_api and not isinstance(val, (tuple, bytes)):
            val = (val,)
        return val

    def __contains__(self, tag):
        return tag in self._tags_v2 or tag in self._tagdata

    def __setitem__(self, tag, value):
        self._setitem(tag, value, self.legacy_api)

    def _setitem(self, tag, value, legacy_api):
        basetypes = (Number, bytes, str)

        info = TiffTags.lookup(tag, self.group)
        values = [value] if isinstance(value, basetypes) else value

        if tag not in self.tagtype:
            if info.type:
                self.tagtype[tag] = info.type
            else:
                self.tagtype[tag] = TiffTags.UNDEFINED
                if all(isinstance(v, IFDRational) for v in values):
                    self.tagtype[tag] = (
                        TiffTags.RATIONAL
                        if all(v >= 0 for v in values)
                        else TiffTags.SIGNED_RATIONAL
                    )
                elif all(isinstance(v, int) for v in values):
                    if all(0 <= v < 2 ** 16 for v in values):
                        self.tagtype[tag] = TiffTags.SHORT
                    elif all(-(2 ** 15) < v < 2 ** 15 for v in values):
                        self.tagtype[tag] = TiffTags.SIGNED_SHORT
                    else:
                        self.tagtype[tag] = (
                            TiffTags.LONG
                            if all(v >= 0 for v in values)
                            else TiffTags.SIGNED_LONG
                        )
                elif all(isinstance(v, float) for v in values):
                    self.tagtype[tag] = TiffTags.DOUBLE
                elif all(isinstance(v, str) for v in values):
                    self.tagtype[tag] = TiffTags.ASCII
                elif all(isinstance(v, bytes) for v in values):
                    self.tagtype[tag] = TiffTags.BYTE

        if self.tagtype[tag] == TiffTags.UNDEFINED:
            values = [
                v.encode("ascii", "replace") if isinstance(v, str) else v
                for v in values
            ]
        elif self.tagtype[tag] == TiffTags.RATIONAL:
            values = [float(v) if isinstance(v, int) else v for v in values]

        is_ifd = self.tagtype[tag] == TiffTags.LONG and isinstance(values, dict)
        if not is_ifd:
            values = tuple(info.cvt_enum(value) for value in values)

        dest = self._tags_v1 if legacy_api else self._tags_v2

        # Three branches:
        # Spec'd length == 1, Actual length 1, store as element
        # Spec'd length == 1, Actual > 1, Warn and truncate. Formerly barfed.
        # No Spec, Actual length 1, Formerly (<4.2) returned a 1 element tuple.
        # Don't mess with the legacy api, since it's frozen.
        if not is_ifd and (
            (info.length == 1)
            or self.tagtype[tag] == TiffTags.BYTE
            or (info.length is None and len(values) == 1 and not legacy_api)
        ):
            # Don't mess with the legacy api, since it's frozen.
            if legacy_api and self.tagtype[tag] in [
                TiffTags.RATIONAL,
                TiffTags.SIGNED_RATIONAL,
            ]:  # rationals
                values = (values,)
            try:
                (dest[tag],) = values
            except ValueError:
                # We've got a builtin tag with 1 expected entry
                warnings.warn(
                    f"Metadata Warning, tag {tag} had too many entries: "
                    f"{len(values)}, expected 1"
                )
                dest[tag] = values[0]

        else:
            # Spec'd length > 1 or undefined
            # Unspec'd, and length > 1
            dest[tag] = values

    def __delitem__(self, tag):
        self._tags_v2.pop(tag, None)
        self._tags_v1.pop(tag, None)
        self._tagdata.pop(tag, None)

    def __iter__(self):
        return iter(set(self._tagdata) | set(self._tags_v2))

    def _unpack(self, fmt, data):
        return struct.unpack(self._endian + fmt, data)

    def _pack(self, fmt, *values):
        return struct.pack(self._endian + fmt, *values)

    def _register_loader(idx, size):
        def decorator(func):
            from .TiffTags import TYPES

            if func.__name__.startswith("load_"):
                TYPES[idx] = func.__name__[5:].replace("_", " ")
            _load_dispatch[idx] = size, func  # noqa: F821
            return func

        return decorator

    def _register_writer(idx):
        def decorator(func):
            _write_dispatch[idx] = func  # noqa: F821
            return func

        return decorator

    def _register_basic(idx_fmt_name):
        from .TiffTags import TYPES

        idx, fmt, name = idx_fmt_name
        TYPES[idx] = name
        size = struct.calcsize("=" + fmt)
        _load_dispatch[idx] = (  # noqa: F821
            size,
            lambda self, data, legacy_api=True: (
                self._unpack("{}{}".format(len(data) // size, fmt), data)
            ),
        )
        _write_dispatch[idx] = lambda self, *values: (  # noqa: F821
            b"".join(self._pack(fmt, value) for value in values)
        )

    list(
        map(
            _register_basic,
            [
                (TiffTags.SHORT, "H", "short"),
                (TiffTags.LONG, "L", "long"),
                (TiffTags.SIGNED_BYTE, "b", "signed byte"),
                (TiffTags.SIGNED_SHORT, "h", "signed short"),
                (TiffTags.SIGNED_LONG, "l", "signed long"),
                (TiffTags.FLOAT, "f", "float"),
                (TiffTags.DOUBLE, "d", "double"),
                (TiffTags.IFD, "L", "long"),
            ],
        )
    )

    @_register_loader(1, 1)  # Basic type, except for the legacy API.
    def load_byte(self, data, legacy_api=True):
        return data

    @_register_writer(1)  # Basic type, except for the legacy API.
    def write_byte(self, data):
        return data

    @_register_loader(2, 1)
    def load_string(self, data, legacy_api=True):
        if data.endswith(b"\0"):
            data = data[:-1]
        return data.decode("latin-1", "replace")

    @_register_writer(2)
    def write_string(self, value):
        # remerge of https://github.com/python-pillow/Pillow/pull/1416
        return b"" + value.encode("ascii", "replace") + b"\0"

    @_register_loader(5, 8)
    def load_rational(self, data, legacy_api=True):
        vals = self._unpack("{}L".format(len(data) // 4), data)

        def combine(a, b):
            return (a, b) if legacy_api else IFDRational(a, b)

        return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))

    @_register_writer(5)
    def write_rational(self, *values):
        return b"".join(
            self._pack("2L", *_limit_rational(frac, 2 ** 32 - 1)) for frac in values
        )

    @_register_loader(7, 1)
    def load_undefined(self, data, legacy_api=True):
        return data

    @_register_writer(7)
    def write_undefined(self, value):
        return value

    @_register_loader(10, 8)
    def load_signed_rational(self, data, legacy_api=True):
        vals = self._unpack("{}l".format(len(data) // 4), data)

        def combine(a, b):
            return (a, b) if legacy_api else IFDRational(a, b)

        return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2]))

    @_register_writer(10)
    def write_signed_rational(self, *values):
        return b"".join(
            self._pack("2l", *_limit_signed_rational(frac, 2 ** 31 - 1, -(2 ** 31)))
            for frac in values
        )

    def _ensure_read(self, fp, size):
        ret = fp.read(size)
        if len(ret) != size:
            raise OSError(
                "Corrupt EXIF data.  "
                f"Expecting to read {size} bytes but only got {len(ret)}. "
            )
        return ret

    def load(self, fp):

        self.reset()
        self._offset = fp.tell()

        try:
            for i in range(self._unpack("H", self._ensure_read(fp, 2))[0]):
                tag, typ, count, data = self._unpack("HHL4s", self._ensure_read(fp, 12))

                tagname = TiffTags.lookup(tag, self.group).name
                typname = TYPES.get(typ, "unknown")
                msg = f"tag: {tagname} ({tag}) - type: {typname} ({typ})"

                try:
                    unit_size, handler = self._load_dispatch[typ]
                except KeyError:
                    logger.debug(msg + f" - unsupported type {typ}")
                    continue  # ignore unsupported type
                size = count * unit_size
                if size > 4:
                    here = fp.tell()
                    (offset,) = self._unpack("L", data)
                    msg += f" Tag Location: {here} - Data Location: {offset}"
                    fp.seek(offset)
                    data = ImageFile._safe_read(fp, size)
                    fp.seek(here)
                else:
                    data = data[:size]

                if len(data) != size:
                    warnings.warn(
                        "Possibly corrupt EXIF data.  "
                        f"Expecting to read {size} bytes but only got {len(data)}."
                        f" Skipping tag {tag}"
                    )
                    logger.debug(msg)
                    continue

                if not data:
                    logger.debug(msg)
                    continue

                self._tagdata[tag] = data
                self.tagtype[tag] = typ

                msg += " - value: " + (
                    "<table: %d bytes>" % size if size > 32 else repr(data)
                )
                logger.debug(msg)

            (self.next,) = self._unpack("L", self._ensure_read(fp, 4))
        except OSError as msg:
            warnings.warn(str(msg))
            return

    def tobytes(self, offset=0):
        # FIXME What about tagdata?
        result = self._pack("H", len(self._tags_v2))

        entries = []
        offset = offset + len(result) + len(self._tags_v2) * 12 + 4
        stripoffsets = None

        # pass 1: convert tags to binary format
        # always write tags in ascending order
        for tag, value in sorted(self._tags_v2.items()):
            if tag == STRIPOFFSETS:
                stripoffsets = len(entries)
            typ = self.tagtype.get(tag)
            logger.debug(f"Tag {tag}, Type: {typ}, Value: {repr(value)}")
            is_ifd = typ == TiffTags.LONG and isinstance(value, dict)
            if is_ifd:
                if self._endian == "<":
                    ifh = b"II\x2A\x00\x08\x00\x00\x00"
                else:
                    ifh = b"MM\x00\x2A\x00\x00\x00\x08"
                ifd = ImageFileDirectory_v2(ifh, group=tag)
                values = self._tags_v2[tag]
                for ifd_tag, ifd_value in values.items():
                    ifd[ifd_tag] = ifd_value
                data = ifd.tobytes(offset)
            else:
                values = value if isinstance(value, tuple) else (value,)
                data = self._write_dispatch[typ](self, *values)

            tagname = TiffTags.lookup(tag, self.group).name
            typname = "ifd" if is_ifd else TYPES.get(typ, "unknown")
            msg = f"save: {tagname} ({tag}) - type: {typname} ({typ})"
            msg += " - value: " + (
                "<table: %d bytes>" % len(data) if len(data) >= 16 else str(values)
            )
            logger.debug(msg)

            # count is sum of lengths for string and arbitrary data
            if is_ifd:
                count = 1
            elif typ in [TiffTags.BYTE, TiffTags.ASCII, TiffTags.UNDEFINED]:
                count = len(data)
            else:
                count = len(values)
            # figure out if data fits into the entry
            if len(data) <= 4:
                entries.append((tag, typ, count, data.ljust(4, b"\0"), b""))
            else:
                entries.append((tag, typ, count, self._pack("L", offset), data))
                offset += (len(data) + 1) // 2 * 2  # pad to word

        # update strip offset data to point beyond auxiliary data
        if stripoffsets is not None:
            tag, typ, count, value, data = entries[stripoffsets]
            if data:
                raise NotImplementedError("multistrip support not yet implemented")
            value = self._pack("L", self._unpack("L", value)[0] + offset)
            entries[stripoffsets] = tag, typ, count, value, data

        # pass 2: write entries to file
        for tag, typ, count, value, data in entries:
            logger.debug(f"{tag} {typ} {count} {repr(value)} {repr(data)}")
            result += self._pack("HHL4s", tag, typ, count, value)

        # -- overwrite here for multi-page --
        result += b"\0\0\0\0"  # end of entries

        # pass 3: write auxiliary data to file
        for tag, typ, count, value, data in entries:
            result += data
            if len(data) & 1:
                result += b"\0"

        return result

    def save(self, fp):

        if fp.tell() == 0:  # skip TIFF header on subsequent pages
            # tiff header -- PIL always starts the first IFD at offset 8
            fp.write(self._prefix + self._pack("HL", 42, 8))

        offset = fp.tell()
        result = self.tobytes(offset)
        fp.write(result)
        return offset + len(result)


ImageFileDirectory_v2._load_dispatch = _load_dispatch
ImageFileDirectory_v2._write_dispatch = _write_dispatch
for idx, name in TYPES.items():
    name = name.replace(" ", "_")
    setattr(ImageFileDirectory_v2, "load_" + name, _load_dispatch[idx][1])
    setattr(ImageFileDirectory_v2, "write_" + name, _write_dispatch[idx])
del _load_dispatch, _write_dispatch, idx, name


# Legacy ImageFileDirectory support.
class ImageFileDirectory_v1(ImageFileDirectory_v2):
    """This class represents the **legacy** interface to a TIFF tag directory.

    Exposes a dictionary interface of the tags in the directory::

        ifd = ImageFileDirectory_v1()
        ifd[key] = 'Some Data'
        ifd.tagtype[key] = TiffTags.ASCII
        print(ifd[key])
        ('Some Data',)

    Also contains a dictionary of tag types as read from the tiff image file,
    :attr:`~PIL.TiffImagePlugin.ImageFileDirectory_v1.tagtype`.

    Values are returned as a tuple.

    ..  deprecated:: 3.0.0
    """

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self._legacy_api = True

    tags = property(lambda self: self._tags_v1)
    tagdata = property(lambda self: self._tagdata)

    # defined in ImageFileDirectory_v2
    tagtype: dict
    """Dictionary of tag types"""

    @classmethod
    def from_v2(cls, original):
        """Returns an
        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
        instance with the same data as is contained in the original
        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
        instance.

        :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`

        """

        ifd = cls(prefix=original.prefix)
        ifd._tagdata = original._tagdata
        ifd.tagtype = original.tagtype
        ifd.next = original.next  # an indicator for multipage tiffs
        return ifd

    def to_v2(self):
        """Returns an
        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`
        instance with the same data as is contained in the original
        :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1`
        instance.

        :returns: :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2`

        """

        ifd = ImageFileDirectory_v2(prefix=self.prefix)
        ifd._tagdata = dict(self._tagdata)
        ifd.tagtype = dict(self.tagtype)
        ifd._tags_v2 = dict(self._tags_v2)
        return ifd

    def __contains__(self, tag):
        return tag in self._tags_v1 or tag in self._tagdata

    def __len__(self):
        return len(set(self._tagdata) | set(self._tags_v1))

    def __iter__(self):
        return iter(set(self._tagdata) | set(self._tags_v1))

    def __setitem__(self, tag, value):
        for legacy_api in (False, True):
            self._setitem(tag, value, legacy_api)

    def __getitem__(self, tag):
        if tag not in self._tags_v1:  # unpack on the fly
            data = self._tagdata[tag]
            typ = self.tagtype[tag]
            size, handler = self._load_dispatch[typ]
            for legacy in (False, True):
                self._setitem(tag, handler(self, data, legacy), legacy)
        val = self._tags_v1[tag]
        if not isinstance(val, (tuple, bytes)):
            val = (val,)
        return val


# undone -- switch this pointer when IFD_LEGACY_API == False
ImageFileDirectory = ImageFileDirectory_v1


##
# Image plugin for TIFF files.


class TiffImageFile(ImageFile.ImageFile):

    format = "TIFF"
    format_description = "Adobe TIFF"
    _close_exclusive_fp_after_loading = False

    def __init__(self, fp=None, filename=None):
        self.tag_v2 = None
        """ Image file directory (tag dictionary) """

        self.tag = None
        """ Legacy tag entries """

        super().__init__(fp, filename)

    def _open(self):
        """Open the first image in a TIFF file"""

        # Header
        ifh = self.fp.read(8)

        self.tag_v2 = ImageFileDirectory_v2(ifh)

        # legacy IFD entries will be filled in later
        self.ifd = None

        # setup frame pointers
        self.__first = self.__next = self.tag_v2.next
        self.__frame = -1
        self.__fp = self.fp
        self._frame_pos = []
        self._n_frames = None

        logger.debug("*** TiffImageFile._open ***")
        logger.debug(f"- __first: {self.__first}")
        logger.debug(f"- ifh: {repr(ifh)}")  # Use repr to avoid str(bytes)

        # and load the first frame
        self._seek(0)

    @property
    def n_frames(self):
        if self._n_frames is None:
            current = self.tell()
            self._seek(len(self._frame_pos))
            while self._n_frames is None:
                self._seek(self.tell() + 1)
            self.seek(current)
        return self._n_frames

    def seek(self, frame):
        """Select a given frame as current image"""
        if not self._seek_check(frame):
            return
        self._seek(frame)
        # Create a new core image object on second and
        # subsequent frames in the image. Image may be
        # different size/mode.
        Image._decompression_bomb_check(self.size)
        self.im = Image.core.new(self.mode, self.size)

    def _seek(self, frame):
        self.fp = self.__fp

        # reset buffered io handle in case fp
        # was passed to libtiff, invalidating the buffer
        self.fp.tell()

        while len(self._frame_pos) <= frame:
            if not self.__next:
                raise EOFError("no more images in TIFF file")
            logger.debug(
                f"Seeking to frame {frame}, on frame {self.__frame}, "
                f"__next {self.__next}, location: {self.fp.tell()}"
            )
            self.fp.seek(self.__next)
            self._frame_pos.append(self.__next)
            logger.debug("Loading tags, location: %s" % self.fp.tell())
            self.tag_v2.load(self.fp)
            if self.tag_v2.next in self._frame_pos:
                # This IFD has already been processed
                # Declare this to be the end of the image
                self.__next = 0
            else:
                self.__next = self.tag_v2.next
            if self.__next == 0:
                self._n_frames = frame + 1
            if len(self._frame_pos) == 1:
                self.is_animated = self.__next != 0
            self.__frame += 1
        self.fp.seek(self._frame_pos[frame])
        self.tag_v2.load(self.fp)
        # fill the legacy tag/ifd entries
        self.tag = self.ifd = ImageFileDirectory_v1.from_v2(self.tag_v2)
        self.__frame = frame
        self._setup()

    def tell(self):
        """Return the current frame number"""
        return self.__frame

    def getxmp(self):
        """
        Returns a dictionary containing the XMP tags.
        Requires defusedxml to be installed.
        :returns: XMP tags in a dictionary.
        """
        return self._getxmp(self.tag_v2[700]) if 700 in self.tag_v2 else {}

    def load(self):
        if self.tile and self.use_load_libtiff:
            return self._load_libtiff()
        return super().load()

    def load_end(self):
        if self._tile_orientation:
            method = {
                2: Image.FLIP_LEFT_RIGHT,
                3: Image.ROTATE_180,
                4: Image.FLIP_TOP_BOTTOM,
                5: Image.TRANSPOSE,
                6: Image.ROTATE_270,
                7: Image.TRANSVERSE,
                8: Image.ROTATE_90,
            }.get(self._tile_orientation)
            if method is not None:
                self.im = self.im.transpose(method)
                self._size = self.im.size

        # allow closing if we're on the first frame, there's no next
        # This is the ImageFile.load path only, libtiff specific below.
        if not self.is_animated:
            self._close_exclusive_fp_after_loading = True

    def _load_libtiff(self):
        """Overload method triggered when we detect a compressed tiff
        Calls out to libtiff"""

        Image.Image.load(self)

        self.load_prepare()

        if not len(self.tile) == 1:
            raise OSError("Not exactly one tile")

        # (self._compression, (extents tuple),
        #   0, (rawmode, self._compression, fp))
        extents = self.tile[0][1]
        args = list(self.tile[0][3])

        # To be nice on memory footprint, if there's a
        # file descriptor, use that instead of reading
        # into a string in python.
        # libtiff closes the file descriptor, so pass in a dup.
        try:
            fp = hasattr(self.fp, "fileno") and os.dup(self.fp.fileno())
            # flush the file descriptor, prevents error on pypy 2.4+
            # should also eliminate the need for fp.tell
            # in _seek
            if hasattr(self.fp, "flush"):
                self.fp.flush()
        except OSError:
            # io.BytesIO have a fileno, but returns an OSError if
            # it doesn't use a file descriptor.
            fp = False

        if fp:
            args[2] = fp

        decoder = Image._getdecoder(
            self.mode, "libtiff", tuple(args), self.decoderconfig
        )
        try:
            decoder.setimage(self.im, extents)
        except ValueError as e:
            raise OSError("Couldn't set the image") from e

        close_self_fp = self._exclusive_fp and not self.is_animated
        if hasattr(self.fp, "getvalue"):
            # We've got a stringio like thing passed in. Yay for all in memory.
            # The decoder needs the entire file in one shot, so there's not
            # a lot we can do here other than give it the entire file.
            # unless we could do something like get the address of the
            # underlying string for stringio.
            #
            # Rearranging for supporting byteio items, since they have a fileno
            # that returns an OSError if there's no underlying fp. Easier to
            # deal with here by reordering.
            logger.debug("have getvalue. just sending in a string from getvalue")
            n, err = decoder.decode(self.fp.getvalue())
        elif fp:
            # we've got a actual file on disk, pass in the fp.
            logger.debug("have fileno, calling fileno version of the decoder.")
            if not close_self_fp:
                self.fp.seek(0)
            # 4 bytes, otherwise the trace might error out
            n, err = decoder.decode(b"fpfp")
        else:
            # we have something else.
            logger.debug("don't have fileno or getvalue. just reading")
            self.fp.seek(0)
            # UNDONE -- so much for that buffer size thing.
            n, err = decoder.decode(self.fp.read())

        self.tile = []
        self.readonly = 0

        self.load_end()

        # libtiff closed the fp in a, we need to close self.fp, if possible
        if close_self_fp:
            self.fp.close()
            self.fp = None  # might be shared

        if err < 0:
            raise OSError(err)

        return Image.Image.load(self)

    def _setup(self):
        """Setup this image object based on current tags"""

        if 0xBC01 in self.tag_v2:
            raise OSError("Windows Media Photo files not yet supported")

        # extract relevant tags
        self._compression = COMPRESSION_INFO[self.tag_v2.get(COMPRESSION, 1)]
        self._planar_configuration = self.tag_v2.get(PLANAR_CONFIGURATION, 1)

        # photometric is a required tag, but not everyone is reading
        # the specification
        photo = self.tag_v2.get(PHOTOMETRIC_INTERPRETATION, 0)

        # old style jpeg compression images most certainly are YCbCr
        if self._compression == "tiff_jpeg":
            photo = 6

        fillorder = self.tag_v2.get(FILLORDER, 1)

        logger.debug("*** Summary ***")
        logger.debug(f"- compression: {self._compression}")
        logger.debug(f"- photometric_interpretation: {photo}")
        logger.debug(f"- planar_configuration: {self._planar_configuration}")
        logger.debug(f"- fill_order: {fillorder}")
        logger.debug(f"- YCbCr subsampling: {self.tag.get(530)}")

        # size
        xsize = int(self.tag_v2.get(IMAGEWIDTH))
        ysize = int(self.tag_v2.get(IMAGELENGTH))
        self._size = xsize, ysize

        logger.debug(f"- size: {self.size}")

        sampleFormat = self.tag_v2.get(SAMPLEFORMAT, (1,))
        if len(sampleFormat) > 1 and max(sampleFormat) == min(sampleFormat) == 1:
            # SAMPLEFORMAT is properly per band, so an RGB image will
            # be (1,1,1).  But, we don't support per band pixel types,
            # and anything more than one band is a uint8. So, just
            # take the first element. Revisit this if adding support
            # for more exotic images.
            sampleFormat = (1,)

        bps_tuple = self.tag_v2.get(BITSPERSAMPLE, (1,))
        extra_tuple = self.tag_v2.get(EXTRASAMPLES, ())
        if photo in (2, 6, 8):  # RGB, YCbCr, LAB
            bps_count = 3
        elif photo == 5:  # CMYK
            bps_count = 4
        else:
            bps_count = 1
        bps_count += len(extra_tuple)
        # Some files have only one value in bps_tuple,
        # while should have more. Fix it
        if bps_count > len(bps_tuple) and len(bps_tuple) == 1:
            bps_tuple = bps_tuple * bps_count

        samplesPerPixel = self.tag_v2.get(
            SAMPLESPERPIXEL,
            3 if self._compression == "tiff_jpeg" and photo in (2, 6) else 1,
        )
        if len(bps_tuple) != samplesPerPixel:
            raise SyntaxError("unknown data organization")

        # mode: check photometric interpretation and bits per pixel
        key = (
            self.tag_v2.prefix,
            photo,
            sampleFormat,
            fillorder,
            bps_tuple,
            extra_tuple,
        )
        logger.debug(f"format key: {key}")
        try:
            self.mode, rawmode = OPEN_INFO[key]
        except KeyError as e:
            logger.debug("- unsupported format")
            raise SyntaxError("unknown pixel mode") from e

        logger.debug(f"- raw mode: {rawmode}")
        logger.debug(f"- pil mode: {self.mode}")

        self.info["compression"] = self._compression

        xres = self.tag_v2.get(X_RESOLUTION, 1)
        yres = self.tag_v2.get(Y_RESOLUTION, 1)

        if xres and yres:
            resunit = self.tag_v2.get(RESOLUTION_UNIT)
            if resunit == 2:  # dots per inch
                self.info["dpi"] = (xres, yres)
            elif resunit == 3:  # dots per centimeter. convert to dpi
                self.info["dpi"] = (xres * 2.54, yres * 2.54)
            elif resunit is None:  # used to default to 1, but now 2)
                self.info["dpi"] = (xres, yres)
                # For backward compatibility,
                # we also preserve the old behavior
                self.info["resolution"] = xres, yres
            else:  # No absolute unit of measurement
                self.info["resolution"] = xres, yres

        # build tile descriptors
        x = y = layer = 0
        self.tile = []
        self.use_load_libtiff = READ_LIBTIFF or self._compression != "raw"
        if self.use_load_libtiff:
            # Decoder expects entire file as one tile.
            # There's a buffer size limit in load (64k)
            # so large g4 images will fail if we use that
            # function.
            #
            # Setup the one tile for the whole image, then
            # use the _load_libtiff function.

            # libtiff handles the fillmode for us, so 1;IR should
            # actually be 1;I. Including the R double reverses the
            # bits, so stripes of the image are reversed.  See
            # https://github.com/python-pillow/Pillow/issues/279
            if fillorder == 2:
                # Replace fillorder with fillorder=1
                key = key[:3] + (1,) + key[4:]
                logger.debug(f"format key: {key}")
                # this should always work, since all the
                # fillorder==2 modes have a corresponding
                # fillorder=1 mode
                self.mode, rawmode = OPEN_INFO[key]
            # libtiff always returns the bytes in native order.
            # we're expecting image byte order. So, if the rawmode
            # contains I;16, we need to convert from native to image
            # byte order.
            if rawmode == "I;16":
                rawmode = "I;16N"
            if ";16B" in rawmode:
                rawmode = rawmode.replace(";16B", ";16N")
            if ";16L" in rawmode:
                rawmode = rawmode.replace(";16L", ";16N")

            # YCbCr images with new jpeg compression with pixels in one plane
            # unpacked straight into RGB values
            if (
                photo == 6
                and self._compression == "jpeg"
                and self._planar_configuration == 1
            ):
                rawmode = "RGB"

            # Offset in the tile tuple is 0, we go from 0,0 to
            # w,h, and we only do this once -- eds
            a = (rawmode, self._compression, False, self.tag_v2.offset)
            self.tile.append(("libtiff", (0, 0, xsize, ysize), 0, a))

        elif STRIPOFFSETS in self.tag_v2 or TILEOFFSETS in self.tag_v2:
            # striped image
            if STRIPOFFSETS in self.tag_v2:
                offsets = self.tag_v2[STRIPOFFSETS]
                h = self.tag_v2.get(ROWSPERSTRIP, ysize)
                w = self.size[0]
            else:
                # tiled image
                offsets = self.tag_v2[TILEOFFSETS]
                w = self.tag_v2.get(322)
                h = self.tag_v2.get(323)

            for offset in offsets:
                if x + w > xsize:
                    stride = w * sum(bps_tuple) / 8  # bytes per line
                else:
                    stride = 0

                tile_rawmode = rawmode
                if self._planar_configuration == 2:
                    # each band on it's own layer
                    tile_rawmode = rawmode[layer]
                    # adjust stride width accordingly
                    stride /= bps_count

                a = (tile_rawmode, int(stride), 1)
                self.tile.append(
                    (
                        self._compression,
                        (x, y, min(x + w, xsize), min(y + h, ysize)),
                        offset,
                        a,
                    )
                )
                x = x + w
                if x >= self.size[0]:
                    x, y = 0, y + h
                    if y >= self.size[1]:
                        x = y = 0
                        layer += 1
        else:
            logger.debug("- unsupported data organization")
            raise SyntaxError("unknown data organization")

        # Fix up info.
        if ICCPROFILE in self.tag_v2:
            self.info["icc_profile"] = self.tag_v2[ICCPROFILE]

        # fixup palette descriptor

        if self.mode in ["P", "PA"]:
            palette = [o8(b // 256) for b in self.tag_v2[COLORMAP]]
            self.palette = ImagePalette.raw("RGB;L", b"".join(palette))

        self._tile_orientation = self.tag_v2.get(0x0112)

    def _close__fp(self):
        try:
            if self.__fp != self.fp:
                self.__fp.close()
        except AttributeError:
            pass
        finally:
            self.__fp = None


#
# --------------------------------------------------------------------
# Write TIFF files

# little endian is default except for image modes with
# explicit big endian byte-order

SAVE_INFO = {
    # mode => rawmode, byteorder, photometrics,
    #           sampleformat, bitspersample, extra
    "1": ("1", II, 1, 1, (1,), None),
    "L": ("L", II, 1, 1, (8,), None),
    "LA": ("LA", II, 1, 1, (8, 8), 2),
    "P": ("P", II, 3, 1, (8,), None),
    "PA": ("PA", II, 3, 1, (8, 8), 2),
    "I": ("I;32S", II, 1, 2, (32,), None),
    "I;16": ("I;16", II, 1, 1, (16,), None),
    "I;16S": ("I;16S", II, 1, 2, (16,), None),
    "F": ("F;32F", II, 1, 3, (32,), None),
    "RGB": ("RGB", II, 2, 1, (8, 8, 8), None),
    "RGBX": ("RGBX", II, 2, 1, (8, 8, 8, 8), 0),
    "RGBA": ("RGBA", II, 2, 1, (8, 8, 8, 8), 2),
    "CMYK": ("CMYK", II, 5, 1, (8, 8, 8, 8), None),
    "YCbCr": ("YCbCr", II, 6, 1, (8, 8, 8), None),
    "LAB": ("LAB", II, 8, 1, (8, 8, 8), None),
    "I;32BS": ("I;32BS", MM, 1, 2, (32,), None),
    "I;16B": ("I;16B", MM, 1, 1, (16,), None),
    "I;16BS": ("I;16BS", MM, 1, 2, (16,), None),
    "F;32BF": ("F;32BF", MM, 1, 3, (32,), None),
}


def _save(im, fp, filename):

    try:
        rawmode, prefix, photo, format, bits, extra = SAVE_INFO[im.mode]
    except KeyError as e:
        raise OSError(f"cannot write mode {im.mode} as TIFF") from e

    ifd = ImageFileDirectory_v2(prefix=prefix)

    compression = im.encoderinfo.get("compression", im.info.get("compression"))
    if compression is None:
        compression = "raw"
    elif compression == "tiff_jpeg":
        # OJPEG is obsolete, so use new-style JPEG compression instead
        compression = "jpeg"
    elif compression == "tiff_deflate":
        compression = "tiff_adobe_deflate"

    libtiff = WRITE_LIBTIFF or compression != "raw"

    # required for color libtiff images
    ifd[PLANAR_CONFIGURATION] = getattr(im, "_planar_configuration", 1)

    ifd[IMAGEWIDTH] = im.size[0]
    ifd[IMAGELENGTH] = im.size[1]

    # write any arbitrary tags passed in as an ImageFileDirectory
    info = im.encoderinfo.get("tiffinfo", {})
    logger.debug("Tiffinfo Keys: %s" % list(info))
    if isinstance(info, ImageFileDirectory_v1):
        info = info.to_v2()
    for key in info:
        ifd[key] = info.get(key)
        try:
            ifd.tagtype[key] = info.tagtype[key]
        except Exception:
            pass  # might not be an IFD. Might not have populated type

    # additions written by Greg Couch, gregc@cgl.ucsf.edu
    # inspired by image-sig posting from Kevin Cazabon, kcazabon@home.com
    if hasattr(im, "tag_v2"):
        # preserve tags from original TIFF image file
        for key in (
            RESOLUTION_UNIT,
            X_RESOLUTION,
            Y_RESOLUTION,
            IPTC_NAA_CHUNK,
            PHOTOSHOP_CHUNK,
            XMP,
        ):
            if key in im.tag_v2:
                ifd[key] = im.tag_v2[key]
                ifd.tagtype[key] = im.tag_v2.tagtype[key]

    # preserve ICC profile (should also work when saving other formats
    # which support profiles as TIFF) -- 2008-06-06 Florian Hoech
    icc = im.encoderinfo.get("icc_profile", im.info.get("icc_profile"))
    if icc:
        ifd[ICCPROFILE] = icc

    for key, name in [
        (IMAGEDESCRIPTION, "description"),
        (X_RESOLUTION, "resolution"),
        (Y_RESOLUTION, "resolution"),
        (X_RESOLUTION, "x_resolution"),
        (Y_RESOLUTION, "y_resolution"),
        (RESOLUTION_UNIT, "resolution_unit"),
        (SOFTWARE, "software"),
        (DATE_TIME, "date_time"),
        (ARTIST, "artist"),
        (COPYRIGHT, "copyright"),
    ]:
        if name in im.encoderinfo:
            ifd[key] = im.encoderinfo[name]

    dpi = im.encoderinfo.get("dpi")
    if dpi:
        ifd[RESOLUTION_UNIT] = 2
        ifd[X_RESOLUTION] = dpi[0]
        ifd[Y_RESOLUTION] = dpi[1]

    if bits != (1,):
        ifd[BITSPERSAMPLE] = bits
        if len(bits) != 1:
            ifd[SAMPLESPERPIXEL] = len(bits)
    if extra is not None:
        ifd[EXTRASAMPLES] = extra
    if format != 1:
        ifd[SAMPLEFORMAT] = format

    ifd[PHOTOMETRIC_INTERPRETATION] = photo

    if im.mode in ["P", "PA"]:
        lut = im.im.getpalette("RGB", "RGB;L")
        ifd[COLORMAP] = tuple(v * 256 for v in lut)
    # data orientation
    stride = len(bits) * ((im.size[0] * bits[0] + 7) // 8)
    # aim for 64 KB strips when using libtiff writer
    if libtiff:
        rows_per_strip = min((2 ** 16 + stride - 1) // stride, im.size[1])
    else:
        rows_per_strip = im.size[1]
    strip_byte_counts = stride * rows_per_strip
    strips_per_image = (im.size[1] + rows_per_strip - 1) // rows_per_strip
    ifd[ROWSPERSTRIP] = rows_per_strip
    if strip_byte_counts >= 2 ** 16:
        ifd.tagtype[STRIPBYTECOUNTS] = TiffTags.LONG
    ifd[STRIPBYTECOUNTS] = (strip_byte_counts,) * (strips_per_image - 1) + (
        stride * im.size[1] - strip_byte_counts * (strips_per_image - 1),
    )
    ifd[STRIPOFFSETS] = tuple(
        range(0, strip_byte_counts * strips_per_image, strip_byte_counts)
    )  # this is adjusted by IFD writer
    # no compression by default:
    ifd[COMPRESSION] = COMPRESSION_INFO_REV.get(compression, 1)

    if libtiff:
        if "quality" in im.encoderinfo:
            quality = im.encoderinfo["quality"]
            if not isinstance(quality, int) or quality < 0 or quality > 100:
                raise ValueError("Invalid quality setting")
            if compression != "jpeg":
                raise ValueError(
                    "quality setting only supported for 'jpeg' compression"
                )
            ifd[JPEGQUALITY] = quality

        logger.debug("Saving using libtiff encoder")
        logger.debug("Items: %s" % sorted(ifd.items()))
        _fp = 0
        if hasattr(fp, "fileno"):
            try:
                fp.seek(0)
                _fp = os.dup(fp.fileno())
            except io.UnsupportedOperation:
                pass

        # optional types for non core tags
        types = {}
        # SAMPLEFORMAT is determined by the image format and should not be copied
        # from legacy_ifd.
        # STRIPOFFSETS and STRIPBYTECOUNTS are added by the library
        # based on the data in the strip.
        # The other tags expect arrays with a certain length (fixed or depending on
        # BITSPERSAMPLE, etc), passing arrays with a different length will result in
        # segfaults. Block these tags until we add extra validation.
        # SUBIFD may also cause a segfault.
        blocklist = [
            REFERENCEBLACKWHITE,
            SAMPLEFORMAT,
            STRIPBYTECOUNTS,
            STRIPOFFSETS,
            TRANSFERFUNCTION,
            SUBIFD,
        ]

        atts = {}
        # bits per sample is a single short in the tiff directory, not a list.
        atts[BITSPERSAMPLE] = bits[0]
        # Merge the ones that we have with (optional) more bits from
        # the original file, e.g x,y resolution so that we can
        # save(load('')) == original file.
        legacy_ifd = {}
        if hasattr(im, "tag"):
            legacy_ifd = im.tag.to_v2()
        for tag, value in itertools.chain(
            ifd.items(), getattr(im, "tag_v2", {}).items(), legacy_ifd.items()
        ):
            # Libtiff can only process certain core items without adding
            # them to the custom dictionary.
            # Custom items are supported for int, float, unicode, string and byte
            # values. Other types and tuples require a tagtype.
            if tag not in TiffTags.LIBTIFF_CORE:
                if not Image.core.libtiff_support_custom_tags:
                    continue

                if tag in ifd.tagtype:
                    types[tag] = ifd.tagtype[tag]
                elif not (isinstance(value, (int, float, str, bytes))):
                    continue
                else:
                    type = TiffTags.lookup(tag).type
                    if type:
                        types[tag] = type
            if tag not in atts and tag not in blocklist:
                if isinstance(value, str):
                    atts[tag] = value.encode("ascii", "replace") + b"\0"
                elif isinstance(value, IFDRational):
                    atts[tag] = float(value)
                else:
                    atts[tag] = value

        logger.debug("Converted items: %s" % sorted(atts.items()))

        # libtiff always expects the bytes in native order.
        # we're storing image byte order. So, if the rawmode
        # contains I;16, we need to convert from native to image
        # byte order.
        if im.mode in ("I;16B", "I;16"):
            rawmode = "I;16N"

        # Pass tags as sorted list so that the tags are set in a fixed order.
        # This is required by libtiff for some tags. For example, the JPEGQUALITY
        # pseudo tag requires that the COMPRESS tag was already set.
        tags = list(atts.items())
        tags.sort()
        a = (rawmode, compression, _fp, filename, tags, types)
        e = Image._getencoder(im.mode, "libtiff", a, im.encoderconfig)
        e.setimage(im.im, (0, 0) + im.size)
        while True:
            # undone, change to self.decodermaxblock:
            l, s, d = e.encode(16 * 1024)
            if not _fp:
                fp.write(d)
            if s:
                break
        if s < 0:
            raise OSError(f"encoder error {s} when writing image file")

    else:
        offset = ifd.save(fp)

        ImageFile._save(
            im, fp, [("raw", (0, 0) + im.size, offset, (rawmode, stride, 1))]
        )

    # -- helper for multi-page save --
    if "_debug_multipage" in im.encoderinfo:
        # just to access o32 and o16 (using correct byte order)
        im._debug_multipage = ifd


class AppendingTiffWriter:
    fieldSizes = [
        0,  # None
        1,  # byte
        1,  # ascii
        2,  # short
        4,  # long
        8,  # rational
        1,  # sbyte
        1,  # undefined
        2,  # sshort
        4,  # slong
        8,  # srational
        4,  # float
        8,  # double
    ]

    #    StripOffsets = 273
    #    FreeOffsets = 288
    #    TileOffsets = 324
    #    JPEGQTables = 519
    #    JPEGDCTables = 520
    #    JPEGACTables = 521
    Tags = {273, 288, 324, 519, 520, 521}

    def __init__(self, fn, new=False):
        if hasattr(fn, "read"):
            self.f = fn
            self.close_fp = False
        else:
            self.name = fn
            self.close_fp = True
            try:
                self.f = open(fn, "w+b" if new else "r+b")
            except OSError:
                self.f = open(fn, "w+b")
        self.beginning = self.f.tell()
        self.setup()

    def setup(self):
        # Reset everything.
        self.f.seek(self.beginning, os.SEEK_SET)

        self.whereToWriteNewIFDOffset = None
        self.offsetOfNewPage = 0

        self.IIMM = IIMM = self.f.read(4)
        if not IIMM:
            # empty file - first page
            self.isFirst = True
            return

        self.isFirst = False
        if IIMM == b"II\x2a\x00":
            self.setEndian("<")
        elif IIMM == b"MM\x00\x2a":
            self.setEndian(">")
        else:
            raise RuntimeError("Invalid TIFF file header")

        self.skipIFDs()
        self.goToEnd()

    def finalize(self):
        if self.isFirst:
            return

        # fix offsets
        self.f.seek(self.offsetOfNewPage)

        IIMM = self.f.read(4)
        if not IIMM:
            # raise RuntimeError("nothing written into new page")
            # Make it easy to finish a frame without committing to a new one.
            return

        if IIMM != self.IIMM:
            raise RuntimeError("IIMM of new page doesn't match IIMM of first page")

        IFDoffset = self.readLong()
        IFDoffset += self.offsetOfNewPage
        self.f.seek(self.whereToWriteNewIFDOffset)
        self.writeLong(IFDoffset)
        self.f.seek(IFDoffset)
        self.fixIFD()

    def newFrame(self):
        # Call this to finish a frame.
        self.finalize()
        self.setup()

    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        if self.close_fp:
            self.close()
        return False

    def tell(self):
        return self.f.tell() - self.offsetOfNewPage

    def seek(self, offset, whence=io.SEEK_SET):
        if whence == os.SEEK_SET:
            offset += self.offsetOfNewPage

        self.f.seek(offset, whence)
        return self.tell()

    def goToEnd(self):
        self.f.seek(0, os.SEEK_END)
        pos = self.f.tell()

        # pad to 16 byte boundary
        padBytes = 16 - pos % 16
        if 0 < padBytes < 16:
            self.f.write(bytes(padBytes))
        self.offsetOfNewPage = self.f.tell()

    def setEndian(self, endian):
        self.endian = endian
        self.longFmt = self.endian + "L"
        self.shortFmt = self.endian + "H"
        self.tagFormat = self.endian + "HHL"

    def skipIFDs(self):
        while True:
            IFDoffset = self.readLong()
            if IFDoffset == 0:
                self.whereToWriteNewIFDOffset = self.f.tell() - 4
                break

            self.f.seek(IFDoffset)
            numTags = self.readShort()
            self.f.seek(numTags * 12, os.SEEK_CUR)

    def write(self, data):
        return self.f.write(data)

    def readShort(self):
        (value,) = struct.unpack(self.shortFmt, self.f.read(2))
        return value

    def readLong(self):
        (value,) = struct.unpack(self.longFmt, self.f.read(4))
        return value

    def rewriteLastShortToLong(self, value):
        self.f.seek(-2, os.SEEK_CUR)
        bytesWritten = self.f.write(struct.pack(self.longFmt, value))
        if bytesWritten is not None and bytesWritten != 4:
            raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4")

    def rewriteLastShort(self, value):
        self.f.seek(-2, os.SEEK_CUR)
        bytesWritten = self.f.write(struct.pack(self.shortFmt, value))
        if bytesWritten is not None and bytesWritten != 2:
            raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 2")

    def rewriteLastLong(self, value):
        self.f.seek(-4, os.SEEK_CUR)
        bytesWritten = self.f.write(struct.pack(self.longFmt, value))
        if bytesWritten is not None and bytesWritten != 4:
            raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4")

    def writeShort(self, value):
        bytesWritten = self.f.write(struct.pack(self.shortFmt, value))
        if bytesWritten is not None and bytesWritten != 2:
            raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 2")

    def writeLong(self, value):
        bytesWritten = self.f.write(struct.pack(self.longFmt, value))
        if bytesWritten is not None and bytesWritten != 4:
            raise RuntimeError(f"wrote only {bytesWritten} bytes but wanted 4")

    def close(self):
        self.finalize()
        self.f.close()

    def fixIFD(self):
        numTags = self.readShort()

        for i in range(numTags):
            tag, fieldType, count = struct.unpack(self.tagFormat, self.f.read(8))

            fieldSize = self.fieldSizes[fieldType]
            totalSize = fieldSize * count
            isLocal = totalSize <= 4
            if not isLocal:
                offset = self.readLong()
                offset += self.offsetOfNewPage
                self.rewriteLastLong(offset)

            if tag in self.Tags:
                curPos = self.f.tell()

                if isLocal:
                    self.fixOffsets(
                        count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)
                    )
                    self.f.seek(curPos + 4)
                else:
                    self.f.seek(offset)
                    self.fixOffsets(
                        count, isShort=(fieldSize == 2), isLong=(fieldSize == 4)
                    )
                    self.f.seek(curPos)

                offset = curPos = None

            elif isLocal:
                # skip the locally stored value that is not an offset
                self.f.seek(4, os.SEEK_CUR)

    def fixOffsets(self, count, isShort=False, isLong=False):
        if not isShort and not isLong:
            raise RuntimeError("offset is neither short nor long")

        for i in range(count):
            offset = self.readShort() if isShort else self.readLong()
            offset += self.offsetOfNewPage
            if isShort and offset >= 65536:
                # offset is now too large - we must convert shorts to longs
                if count != 1:
                    raise RuntimeError("not implemented")  # XXX TODO

                # simple case - the offset is just one and therefore it is
                # local (not referenced with another offset)
                self.rewriteLastShortToLong(offset)
                self.f.seek(-10, os.SEEK_CUR)
                self.writeShort(TiffTags.LONG)  # rewrite the type to LONG
                self.f.seek(8, os.SEEK_CUR)
            elif isShort:
                self.rewriteLastShort(offset)
            else:
                self.rewriteLastLong(offset)


def _save_all(im, fp, filename):
    encoderinfo = im.encoderinfo.copy()
    encoderconfig = im.encoderconfig
    append_images = list(encoderinfo.get("append_images", []))
    if not hasattr(im, "n_frames") and not append_images:
        return _save(im, fp, filename)

    cur_idx = im.tell()
    try:
        with AppendingTiffWriter(fp) as tf:
            for ims in [im] + append_images:
                ims.encoderinfo = encoderinfo
                ims.encoderconfig = encoderconfig
                if not hasattr(ims, "n_frames"):
                    nfr = 1
                else:
                    nfr = ims.n_frames

                for idx in range(nfr):
                    ims.seek(idx)
                    ims.load()
                    _save(ims, tf, filename)
                    tf.newFrame()
    finally:
        im.seek(cur_idx)


#
# --------------------------------------------------------------------
# Register

Image.register_open(TiffImageFile.format, TiffImageFile, _accept)
Image.register_save(TiffImageFile.format, _save)
Image.register_save_all(TiffImageFile.format, _save_all)

Image.register_extensions(TiffImageFile.format, [".tif", ".tiff"])

Image.register_mime(TiffImageFile.format, "image/tiff")

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