mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 09:57:43 +03:00 
			
		
		
		
	Merge branch 'main' into iptc
This commit is contained in:
		
						commit
						dcfce9487e
					
				| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
repos:
 | 
					repos:
 | 
				
			||||||
  - repo: https://github.com/psf/black
 | 
					  - repo: https://github.com/psf/black
 | 
				
			||||||
    rev: 23.3.0
 | 
					    rev: 23.7.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: black
 | 
					      - id: black
 | 
				
			||||||
        args: [--target-version=py38]
 | 
					        args: [--target-version=py38]
 | 
				
			||||||
| 
						 | 
					@ -23,13 +23,13 @@ repos:
 | 
				
			||||||
      - id: yesqa
 | 
					      - id: yesqa
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/Lucas-C/pre-commit-hooks
 | 
					  - repo: https://github.com/Lucas-C/pre-commit-hooks
 | 
				
			||||||
    rev: v1.5.1
 | 
					    rev: v1.5.3
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: remove-tabs
 | 
					      - id: remove-tabs
 | 
				
			||||||
        exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
 | 
					        exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/PyCQA/flake8
 | 
					  - repo: https://github.com/PyCQA/flake8
 | 
				
			||||||
    rev: 6.0.0
 | 
					    rev: 6.1.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: flake8
 | 
					      - id: flake8
 | 
				
			||||||
        additional_dependencies:
 | 
					        additional_dependencies:
 | 
				
			||||||
| 
						 | 
					@ -55,7 +55,7 @@ repos:
 | 
				
			||||||
      - id: sphinx-lint
 | 
					      - id: sphinx-lint
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - repo: https://github.com/tox-dev/pyproject-fmt
 | 
					  - repo: https://github.com/tox-dev/pyproject-fmt
 | 
				
			||||||
    rev: 0.12.1
 | 
					    rev: 0.13.0
 | 
				
			||||||
    hooks:
 | 
					    hooks:
 | 
				
			||||||
      - id: pyproject-fmt
 | 
					      - id: pyproject-fmt
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										15
									
								
								CHANGES.rst
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								CHANGES.rst
									
									
									
									
									
								
							| 
						 | 
					@ -5,6 +5,21 @@ Changelog (Pillow)
 | 
				
			||||||
10.1.0 (unreleased)
 | 
					10.1.0 (unreleased)
 | 
				
			||||||
-------------------
 | 
					-------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Read WebP duration after opening #7311
 | 
				
			||||||
 | 
					  [k128, radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Allow "loop=None" when saving GIF images #7329
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Fixed transparency when saving P mode images to PDF #7323
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Added saving LA images as PDFs #7299
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- Set SMaskInData to 1 for PDFs with alpha #7316, #7317
 | 
				
			||||||
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
- Changed Image mode property to be read-only by default #7307
 | 
					- Changed Image mode property to be read-only by default #7307
 | 
				
			||||||
  [radarhere]
 | 
					  [radarhere]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -875,6 +875,14 @@ def test_identical_frames_to_single_frame(duration, tmp_path):
 | 
				
			||||||
        assert reread.info["duration"] == 8500
 | 
					        assert reread.info["duration"] == 8500
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_loop_none(tmp_path):
 | 
				
			||||||
 | 
					    out = str(tmp_path / "temp.gif")
 | 
				
			||||||
 | 
					    im = Image.new("L", (100, 100), "#000")
 | 
				
			||||||
 | 
					    im.save(out, loop=None)
 | 
				
			||||||
 | 
					    with Image.open(out) as reread:
 | 
				
			||||||
 | 
					        assert "loop" not in reread.info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_number_of_loops(tmp_path):
 | 
					def test_number_of_loops(tmp_path):
 | 
				
			||||||
    number_of_loops = 2
 | 
					    number_of_loops = 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -48,6 +48,22 @@ def test_save_alpha(tmp_path, mode):
 | 
				
			||||||
    helper_save_as_pdf(tmp_path, mode)
 | 
					    helper_save_as_pdf(tmp_path, mode)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def test_p_alpha(tmp_path):
 | 
				
			||||||
 | 
					    # Arrange
 | 
				
			||||||
 | 
					    outfile = str(tmp_path / "temp.pdf")
 | 
				
			||||||
 | 
					    with Image.open("Tests/images/pil123p.png") as im:
 | 
				
			||||||
 | 
					        assert im.mode == "P"
 | 
				
			||||||
 | 
					        assert isinstance(im.info["transparency"], bytes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        # Act
 | 
				
			||||||
 | 
					        im.save(outfile)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Assert
 | 
				
			||||||
 | 
					    with open(outfile, "rb") as fp:
 | 
				
			||||||
 | 
					        contents = fp.read()
 | 
				
			||||||
 | 
					    assert b"\n/SMask " in contents
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def test_monochrome(tmp_path):
 | 
					def test_monochrome(tmp_path):
 | 
				
			||||||
    # Arrange
 | 
					    # Arrange
 | 
				
			||||||
    mode = "1"
 | 
					    mode = "1"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -79,7 +79,7 @@ class TestFilePng:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def test_sanity(self, tmp_path):
 | 
					    def test_sanity(self, tmp_path):
 | 
				
			||||||
        # internal version number
 | 
					        # internal version number
 | 
				
			||||||
        assert re.search(r"\d+\.\d+\.\d+(\.\d+)?$", features.version_codec("zlib"))
 | 
					        assert re.search(r"\d+(\.\d+){1,3}$", features.version_codec("zlib"))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        test_file = str(tmp_path / "temp.png")
 | 
					        test_file = str(tmp_path / "temp.png")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -233,5 +233,4 @@ class TestFileWebp:
 | 
				
			||||||
            im.save(out_webp, save_all=True)
 | 
					            im.save(out_webp, save_all=True)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        with Image.open(out_webp) as reloaded:
 | 
					        with Image.open(out_webp) as reloaded:
 | 
				
			||||||
            reloaded.load()
 | 
					 | 
				
			||||||
            assert reloaded.info["duration"] == 1000
 | 
					            assert reloaded.info["duration"] == 1000
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -661,15 +661,15 @@ class TestImage:
 | 
				
			||||||
        blank_p.palette = None
 | 
					        blank_p.palette = None
 | 
				
			||||||
        blank_pa.palette = None
 | 
					        blank_pa.palette = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        def _make_new(base_image, im, palette_result=None):
 | 
					        def _make_new(base_image, image, palette_result=None):
 | 
				
			||||||
            new_im = base_image._new(im)
 | 
					            new_image = base_image._new(image.im)
 | 
				
			||||||
            assert new_im.mode == im.mode
 | 
					            assert new_image.mode == image.mode
 | 
				
			||||||
            assert new_im.size == im.size
 | 
					            assert new_image.size == image.size
 | 
				
			||||||
            assert new_im.info == base_image.info
 | 
					            assert new_image.info == base_image.info
 | 
				
			||||||
            if palette_result is not None:
 | 
					            if palette_result is not None:
 | 
				
			||||||
                assert new_im.palette.tobytes() == palette_result.tobytes()
 | 
					                assert new_image.palette.tobytes() == palette_result.tobytes()
 | 
				
			||||||
            else:
 | 
					            else:
 | 
				
			||||||
                assert new_im.palette is None
 | 
					                assert new_image.palette is None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        _make_new(im, im_p, ImagePalette.ImagePalette(list(range(256)) * 3))
 | 
					        _make_new(im, im_p, ImagePalette.ImagePalette(list(range(256)) * 3))
 | 
				
			||||||
        _make_new(im_p, im, None)
 | 
					        _make_new(im_p, im, None)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -253,7 +253,7 @@ their :py:attr:`~PIL.Image.Image.info` values.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**loop**
 | 
					**loop**
 | 
				
			||||||
    Integer number of times the GIF should loop. 0 means that it will loop
 | 
					    Integer number of times the GIF should loop. 0 means that it will loop
 | 
				
			||||||
    forever. By default, the image will not loop.
 | 
					    forever. If omitted or ``None``, the image will not loop.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**comment**
 | 
					**comment**
 | 
				
			||||||
    A comment about the image.
 | 
					    A comment about the image.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -83,10 +83,9 @@ Install Pillow with :command:`pip`::
 | 
				
			||||||
.. tab:: Windows
 | 
					.. tab:: Windows
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    We provide Pillow binaries for Windows compiled for the matrix of
 | 
					    We provide Pillow binaries for Windows compiled for the matrix of
 | 
				
			||||||
    supported Pythons in both 32 and 64-bit versions in the wheel format.
 | 
					    supported Pythons in 64-bit versions in the wheel format. These binaries include
 | 
				
			||||||
    These binaries include support for all optional libraries except
 | 
					    support for all optional libraries except libimagequant and libxcb. Raqm support
 | 
				
			||||||
    libimagequant and libxcb. Raqm support requires
 | 
					    requires FriBiDi to be installed separately::
 | 
				
			||||||
    FriBiDi to be installed separately::
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        python3 -m pip install --upgrade pip
 | 
					        python3 -m pip install --upgrade pip
 | 
				
			||||||
        python3 -m pip install --upgrade Pillow
 | 
					        python3 -m pip install --upgrade Pillow
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -206,4 +206,4 @@ Support reading signed 8-bit TIFF images
 | 
				
			||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
					^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
 | 
				
			||||||
 | 
					
 | 
				
			||||||
TIFF images with signed integer data, 8 bits per sample and a photometric
 | 
					TIFF images with signed integer data, 8 bits per sample and a photometric
 | 
				
			||||||
interpretaton of BlackIsZero can now be read.
 | 
					interpretation of BlackIsZero can now be read.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -912,7 +912,7 @@ def _get_global_header(im, info):
 | 
				
			||||||
        info
 | 
					        info
 | 
				
			||||||
        and (
 | 
					        and (
 | 
				
			||||||
            "transparency" in info
 | 
					            "transparency" in info
 | 
				
			||||||
            or "loop" in info
 | 
					            or info.get("loop") is not None
 | 
				
			||||||
            or info.get("duration")
 | 
					            or info.get("duration")
 | 
				
			||||||
            or info.get("comment")
 | 
					            or info.get("comment")
 | 
				
			||||||
        )
 | 
					        )
 | 
				
			||||||
| 
						 | 
					@ -937,7 +937,7 @@ def _get_global_header(im, info):
 | 
				
			||||||
        # Global Color Table
 | 
					        # Global Color Table
 | 
				
			||||||
        _get_header_palette(palette_bytes),
 | 
					        _get_header_palette(palette_bytes),
 | 
				
			||||||
    ]
 | 
					    ]
 | 
				
			||||||
    if "loop" in info:
 | 
					    if info.get("loop") is not None:
 | 
				
			||||||
        header.append(
 | 
					        header.append(
 | 
				
			||||||
            b"!"
 | 
					            b"!"
 | 
				
			||||||
            + o8(255)  # extension intro
 | 
					            + o8(255)  # extension intro
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -247,7 +247,7 @@ def eval(expression, _dict={}, **kw):
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def scan(code):
 | 
					    def scan(code):
 | 
				
			||||||
        for const in code.co_consts:
 | 
					        for const in code.co_consts:
 | 
				
			||||||
            if type(const) == type(compiled_code):
 | 
					            if type(const) is type(compiled_code):
 | 
				
			||||||
                scan(const)
 | 
					                scan(const)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        for name in code.co_names:
 | 
					        for name in code.co_names:
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,6 +46,132 @@ def _save_all(im, fp, filename):
 | 
				
			||||||
# (Internal) Image save plugin for the PDF format.
 | 
					# (Internal) Image save plugin for the PDF format.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def _write_image(im, filename, existing_pdf, image_refs):
 | 
				
			||||||
 | 
					    # FIXME: Should replace ASCIIHexDecode with RunLengthDecode
 | 
				
			||||||
 | 
					    # (packbits) or LZWDecode (tiff/lzw compression).  Note that
 | 
				
			||||||
 | 
					    # PDF 1.2 also supports Flatedecode (zip compression).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    params = None
 | 
				
			||||||
 | 
					    decode = None
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # Get image characteristics
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    width, height = im.size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    dict_obj = {"BitsPerComponent": 8}
 | 
				
			||||||
 | 
					    if im.mode == "1":
 | 
				
			||||||
 | 
					        if features.check("libtiff"):
 | 
				
			||||||
 | 
					            filter = "CCITTFaxDecode"
 | 
				
			||||||
 | 
					            dict_obj["BitsPerComponent"] = 1
 | 
				
			||||||
 | 
					            params = PdfParser.PdfArray(
 | 
				
			||||||
 | 
					                [
 | 
				
			||||||
 | 
					                    PdfParser.PdfDict(
 | 
				
			||||||
 | 
					                        {
 | 
				
			||||||
 | 
					                            "K": -1,
 | 
				
			||||||
 | 
					                            "BlackIs1": True,
 | 
				
			||||||
 | 
					                            "Columns": width,
 | 
				
			||||||
 | 
					                            "Rows": height,
 | 
				
			||||||
 | 
					                        }
 | 
				
			||||||
 | 
					                    )
 | 
				
			||||||
 | 
					                ]
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					        else:
 | 
				
			||||||
 | 
					            filter = "DCTDecode"
 | 
				
			||||||
 | 
					        dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray")
 | 
				
			||||||
 | 
					        procset = "ImageB"  # grayscale
 | 
				
			||||||
 | 
					    elif im.mode == "L":
 | 
				
			||||||
 | 
					        filter = "DCTDecode"
 | 
				
			||||||
 | 
					        # params = f"<< /Predictor 15 /Columns {width-2} >>"
 | 
				
			||||||
 | 
					        dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray")
 | 
				
			||||||
 | 
					        procset = "ImageB"  # grayscale
 | 
				
			||||||
 | 
					    elif im.mode == "LA":
 | 
				
			||||||
 | 
					        filter = "JPXDecode"
 | 
				
			||||||
 | 
					        # params = f"<< /Predictor 15 /Columns {width-2} >>"
 | 
				
			||||||
 | 
					        procset = "ImageB"  # grayscale
 | 
				
			||||||
 | 
					        dict_obj["SMaskInData"] = 1
 | 
				
			||||||
 | 
					    elif im.mode == "P":
 | 
				
			||||||
 | 
					        filter = "ASCIIHexDecode"
 | 
				
			||||||
 | 
					        palette = im.getpalette()
 | 
				
			||||||
 | 
					        dict_obj["ColorSpace"] = [
 | 
				
			||||||
 | 
					            PdfParser.PdfName("Indexed"),
 | 
				
			||||||
 | 
					            PdfParser.PdfName("DeviceRGB"),
 | 
				
			||||||
 | 
					            255,
 | 
				
			||||||
 | 
					            PdfParser.PdfBinary(palette),
 | 
				
			||||||
 | 
					        ]
 | 
				
			||||||
 | 
					        procset = "ImageI"  # indexed color
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if "transparency" in im.info:
 | 
				
			||||||
 | 
					            smask = im.convert("LA").getchannel("A")
 | 
				
			||||||
 | 
					            smask.encoderinfo = {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            image_ref = _write_image(smask, filename, existing_pdf, image_refs)[0]
 | 
				
			||||||
 | 
					            dict_obj["SMask"] = image_ref
 | 
				
			||||||
 | 
					    elif im.mode == "RGB":
 | 
				
			||||||
 | 
					        filter = "DCTDecode"
 | 
				
			||||||
 | 
					        dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceRGB")
 | 
				
			||||||
 | 
					        procset = "ImageC"  # color images
 | 
				
			||||||
 | 
					    elif im.mode == "RGBA":
 | 
				
			||||||
 | 
					        filter = "JPXDecode"
 | 
				
			||||||
 | 
					        procset = "ImageC"  # color images
 | 
				
			||||||
 | 
					        dict_obj["SMaskInData"] = 1
 | 
				
			||||||
 | 
					    elif im.mode == "CMYK":
 | 
				
			||||||
 | 
					        filter = "DCTDecode"
 | 
				
			||||||
 | 
					        dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceCMYK")
 | 
				
			||||||
 | 
					        procset = "ImageC"  # color images
 | 
				
			||||||
 | 
					        decode = [1, 0, 1, 0, 1, 0, 1, 0]
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        msg = f"cannot save mode {im.mode}"
 | 
				
			||||||
 | 
					        raise ValueError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    #
 | 
				
			||||||
 | 
					    # image
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    op = io.BytesIO()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if filter == "ASCIIHexDecode":
 | 
				
			||||||
 | 
					        ImageFile._save(im, op, [("hex", (0, 0) + im.size, 0, im.mode)])
 | 
				
			||||||
 | 
					    elif filter == "CCITTFaxDecode":
 | 
				
			||||||
 | 
					        im.save(
 | 
				
			||||||
 | 
					            op,
 | 
				
			||||||
 | 
					            "TIFF",
 | 
				
			||||||
 | 
					            compression="group4",
 | 
				
			||||||
 | 
					            # use a single strip
 | 
				
			||||||
 | 
					            strip_size=math.ceil(width / 8) * height,
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					    elif filter == "DCTDecode":
 | 
				
			||||||
 | 
					        Image.SAVE["JPEG"](im, op, filename)
 | 
				
			||||||
 | 
					    elif filter == "JPXDecode":
 | 
				
			||||||
 | 
					        del dict_obj["BitsPerComponent"]
 | 
				
			||||||
 | 
					        Image.SAVE["JPEG2000"](im, op, filename)
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        msg = f"unsupported PDF filter ({filter})"
 | 
				
			||||||
 | 
					        raise ValueError(msg)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    stream = op.getvalue()
 | 
				
			||||||
 | 
					    if filter == "CCITTFaxDecode":
 | 
				
			||||||
 | 
					        stream = stream[8:]
 | 
				
			||||||
 | 
					        filter = PdfParser.PdfArray([PdfParser.PdfName(filter)])
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        filter = PdfParser.PdfName(filter)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    image_ref = image_refs.pop(0)
 | 
				
			||||||
 | 
					    existing_pdf.write_obj(
 | 
				
			||||||
 | 
					        image_ref,
 | 
				
			||||||
 | 
					        stream=stream,
 | 
				
			||||||
 | 
					        Type=PdfParser.PdfName("XObject"),
 | 
				
			||||||
 | 
					        Subtype=PdfParser.PdfName("Image"),
 | 
				
			||||||
 | 
					        Width=width,  # * 72.0 / x_resolution,
 | 
				
			||||||
 | 
					        Height=height,  # * 72.0 / y_resolution,
 | 
				
			||||||
 | 
					        Filter=filter,
 | 
				
			||||||
 | 
					        Decode=decode,
 | 
				
			||||||
 | 
					        DecodeParms=params,
 | 
				
			||||||
 | 
					        **dict_obj,
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return image_ref, procset
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def _save(im, fp, filename, save_all=False):
 | 
					def _save(im, fp, filename, save_all=False):
 | 
				
			||||||
    is_appending = im.encoderinfo.get("append", False)
 | 
					    is_appending = im.encoderinfo.get("append", False)
 | 
				
			||||||
    if is_appending:
 | 
					    if is_appending:
 | 
				
			||||||
| 
						 | 
					@ -109,6 +235,9 @@ def _save(im, fp, filename, save_all=False):
 | 
				
			||||||
        number_of_pages += im_number_of_pages
 | 
					        number_of_pages += im_number_of_pages
 | 
				
			||||||
        for i in range(im_number_of_pages):
 | 
					        for i in range(im_number_of_pages):
 | 
				
			||||||
            image_refs.append(existing_pdf.next_object_id(0))
 | 
					            image_refs.append(existing_pdf.next_object_id(0))
 | 
				
			||||||
 | 
					            if im.mode == "P" and "transparency" in im.info:
 | 
				
			||||||
 | 
					                image_refs.append(existing_pdf.next_object_id(0))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            page_refs.append(existing_pdf.next_object_id(0))
 | 
					            page_refs.append(existing_pdf.next_object_id(0))
 | 
				
			||||||
            contents_refs.append(existing_pdf.next_object_id(0))
 | 
					            contents_refs.append(existing_pdf.next_object_id(0))
 | 
				
			||||||
            existing_pdf.pages.append(page_refs[-1])
 | 
					            existing_pdf.pages.append(page_refs[-1])
 | 
				
			||||||
| 
						 | 
					@ -121,123 +250,7 @@ def _save(im, fp, filename, save_all=False):
 | 
				
			||||||
    for im_sequence in ims:
 | 
					    for im_sequence in ims:
 | 
				
			||||||
        im_pages = ImageSequence.Iterator(im_sequence) if save_all else [im_sequence]
 | 
					        im_pages = ImageSequence.Iterator(im_sequence) if save_all else [im_sequence]
 | 
				
			||||||
        for im in im_pages:
 | 
					        for im in im_pages:
 | 
				
			||||||
            # FIXME: Should replace ASCIIHexDecode with RunLengthDecode
 | 
					            image_ref, procset = _write_image(im, filename, existing_pdf, image_refs)
 | 
				
			||||||
            # (packbits) or LZWDecode (tiff/lzw compression).  Note that
 | 
					 | 
				
			||||||
            # PDF 1.2 also supports Flatedecode (zip compression).
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            params = None
 | 
					 | 
				
			||||||
            decode = None
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            #
 | 
					 | 
				
			||||||
            # Get image characteristics
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            width, height = im.size
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            dict_obj = {"BitsPerComponent": 8}
 | 
					 | 
				
			||||||
            if im.mode == "1":
 | 
					 | 
				
			||||||
                if features.check("libtiff"):
 | 
					 | 
				
			||||||
                    filter = "CCITTFaxDecode"
 | 
					 | 
				
			||||||
                    dict_obj["BitsPerComponent"] = 1
 | 
					 | 
				
			||||||
                    params = PdfParser.PdfArray(
 | 
					 | 
				
			||||||
                        [
 | 
					 | 
				
			||||||
                            PdfParser.PdfDict(
 | 
					 | 
				
			||||||
                                {
 | 
					 | 
				
			||||||
                                    "K": -1,
 | 
					 | 
				
			||||||
                                    "BlackIs1": True,
 | 
					 | 
				
			||||||
                                    "Columns": width,
 | 
					 | 
				
			||||||
                                    "Rows": height,
 | 
					 | 
				
			||||||
                                }
 | 
					 | 
				
			||||||
                            )
 | 
					 | 
				
			||||||
                        ]
 | 
					 | 
				
			||||||
                    )
 | 
					 | 
				
			||||||
                else:
 | 
					 | 
				
			||||||
                    filter = "DCTDecode"
 | 
					 | 
				
			||||||
                dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray")
 | 
					 | 
				
			||||||
                procset = "ImageB"  # grayscale
 | 
					 | 
				
			||||||
            elif im.mode == "L":
 | 
					 | 
				
			||||||
                filter = "DCTDecode"
 | 
					 | 
				
			||||||
                # params = f"<< /Predictor 15 /Columns {width-2} >>"
 | 
					 | 
				
			||||||
                dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceGray")
 | 
					 | 
				
			||||||
                procset = "ImageB"  # grayscale
 | 
					 | 
				
			||||||
            elif im.mode == "LA":
 | 
					 | 
				
			||||||
                filter = "JPXDecode"
 | 
					 | 
				
			||||||
                # params = f"<< /Predictor 15 /Columns {width-2} >>"
 | 
					 | 
				
			||||||
                procset = "ImageB"  # grayscale
 | 
					 | 
				
			||||||
                dict_obj["SMaskInData"] = 1
 | 
					 | 
				
			||||||
            elif im.mode == "P":
 | 
					 | 
				
			||||||
                filter = "ASCIIHexDecode"
 | 
					 | 
				
			||||||
                palette = im.getpalette()
 | 
					 | 
				
			||||||
                dict_obj["ColorSpace"] = [
 | 
					 | 
				
			||||||
                    PdfParser.PdfName("Indexed"),
 | 
					 | 
				
			||||||
                    PdfParser.PdfName("DeviceRGB"),
 | 
					 | 
				
			||||||
                    255,
 | 
					 | 
				
			||||||
                    PdfParser.PdfBinary(palette),
 | 
					 | 
				
			||||||
                ]
 | 
					 | 
				
			||||||
                procset = "ImageI"  # indexed color
 | 
					 | 
				
			||||||
            elif im.mode == "RGB":
 | 
					 | 
				
			||||||
                filter = "DCTDecode"
 | 
					 | 
				
			||||||
                dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceRGB")
 | 
					 | 
				
			||||||
                procset = "ImageC"  # color images
 | 
					 | 
				
			||||||
            elif im.mode == "RGBA":
 | 
					 | 
				
			||||||
                filter = "JPXDecode"
 | 
					 | 
				
			||||||
                procset = "ImageC"  # color images
 | 
					 | 
				
			||||||
                dict_obj["SMaskInData"] = 1
 | 
					 | 
				
			||||||
            elif im.mode == "CMYK":
 | 
					 | 
				
			||||||
                filter = "DCTDecode"
 | 
					 | 
				
			||||||
                dict_obj["ColorSpace"] = PdfParser.PdfName("DeviceCMYK")
 | 
					 | 
				
			||||||
                procset = "ImageC"  # color images
 | 
					 | 
				
			||||||
                decode = [1, 0, 1, 0, 1, 0, 1, 0]
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                msg = f"cannot save mode {im.mode}"
 | 
					 | 
				
			||||||
                raise ValueError(msg)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            #
 | 
					 | 
				
			||||||
            # image
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            op = io.BytesIO()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            if filter == "ASCIIHexDecode":
 | 
					 | 
				
			||||||
                ImageFile._save(im, op, [("hex", (0, 0) + im.size, 0, im.mode)])
 | 
					 | 
				
			||||||
            elif filter == "CCITTFaxDecode":
 | 
					 | 
				
			||||||
                im.save(
 | 
					 | 
				
			||||||
                    op,
 | 
					 | 
				
			||||||
                    "TIFF",
 | 
					 | 
				
			||||||
                    compression="group4",
 | 
					 | 
				
			||||||
                    # use a single strip
 | 
					 | 
				
			||||||
                    strip_size=math.ceil(im.width / 8) * im.height,
 | 
					 | 
				
			||||||
                )
 | 
					 | 
				
			||||||
            elif filter == "DCTDecode":
 | 
					 | 
				
			||||||
                Image.SAVE["JPEG"](im, op, filename)
 | 
					 | 
				
			||||||
            elif filter == "JPXDecode":
 | 
					 | 
				
			||||||
                del dict_obj["BitsPerComponent"]
 | 
					 | 
				
			||||||
                Image.SAVE["JPEG2000"](im, op, filename)
 | 
					 | 
				
			||||||
            elif filter == "FlateDecode":
 | 
					 | 
				
			||||||
                ImageFile._save(im, op, [("zip", (0, 0) + im.size, 0, im.mode)])
 | 
					 | 
				
			||||||
            elif filter == "RunLengthDecode":
 | 
					 | 
				
			||||||
                ImageFile._save(im, op, [("packbits", (0, 0) + im.size, 0, im.mode)])
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                msg = f"unsupported PDF filter ({filter})"
 | 
					 | 
				
			||||||
                raise ValueError(msg)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            stream = op.getvalue()
 | 
					 | 
				
			||||||
            if filter == "CCITTFaxDecode":
 | 
					 | 
				
			||||||
                stream = stream[8:]
 | 
					 | 
				
			||||||
                filter = PdfParser.PdfArray([PdfParser.PdfName(filter)])
 | 
					 | 
				
			||||||
            else:
 | 
					 | 
				
			||||||
                filter = PdfParser.PdfName(filter)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            existing_pdf.write_obj(
 | 
					 | 
				
			||||||
                image_refs[page_number],
 | 
					 | 
				
			||||||
                stream=stream,
 | 
					 | 
				
			||||||
                Type=PdfParser.PdfName("XObject"),
 | 
					 | 
				
			||||||
                Subtype=PdfParser.PdfName("Image"),
 | 
					 | 
				
			||||||
                Width=width,  # * 72.0 / x_resolution,
 | 
					 | 
				
			||||||
                Height=height,  # * 72.0 / y_resolution,
 | 
					 | 
				
			||||||
                Filter=filter,
 | 
					 | 
				
			||||||
                Decode=decode,
 | 
					 | 
				
			||||||
                DecodeParms=params,
 | 
					 | 
				
			||||||
                **dict_obj,
 | 
					 | 
				
			||||||
            )
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
            #
 | 
					            #
 | 
				
			||||||
            # page
 | 
					            # page
 | 
				
			||||||
| 
						 | 
					@ -246,13 +259,13 @@ def _save(im, fp, filename, save_all=False):
 | 
				
			||||||
                page_refs[page_number],
 | 
					                page_refs[page_number],
 | 
				
			||||||
                Resources=PdfParser.PdfDict(
 | 
					                Resources=PdfParser.PdfDict(
 | 
				
			||||||
                    ProcSet=[PdfParser.PdfName("PDF"), PdfParser.PdfName(procset)],
 | 
					                    ProcSet=[PdfParser.PdfName("PDF"), PdfParser.PdfName(procset)],
 | 
				
			||||||
                    XObject=PdfParser.PdfDict(image=image_refs[page_number]),
 | 
					                    XObject=PdfParser.PdfDict(image=image_ref),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
                MediaBox=[
 | 
					                MediaBox=[
 | 
				
			||||||
                    0,
 | 
					                    0,
 | 
				
			||||||
                    0,
 | 
					                    0,
 | 
				
			||||||
                    width * 72.0 / x_resolution,
 | 
					                    im.width * 72.0 / x_resolution,
 | 
				
			||||||
                    height * 72.0 / y_resolution,
 | 
					                    im.height * 72.0 / y_resolution,
 | 
				
			||||||
                ],
 | 
					                ],
 | 
				
			||||||
                Contents=contents_refs[page_number],
 | 
					                Contents=contents_refs[page_number],
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
| 
						 | 
					@ -261,8 +274,8 @@ def _save(im, fp, filename, save_all=False):
 | 
				
			||||||
            # page contents
 | 
					            # page contents
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            page_contents = b"q %f 0 0 %f 0 0 cm /image Do Q\n" % (
 | 
					            page_contents = b"q %f 0 0 %f 0 0 cm /image Do Q\n" % (
 | 
				
			||||||
                width * 72.0 / x_resolution,
 | 
					                im.width * 72.0 / x_resolution,
 | 
				
			||||||
                height * 72.0 / y_resolution,
 | 
					                im.height * 72.0 / y_resolution,
 | 
				
			||||||
            )
 | 
					            )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            existing_pdf.write_obj(contents_refs[page_number], stream=page_contents)
 | 
					            existing_pdf.write_obj(contents_refs[page_number], stream=page_contents)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,6 +74,9 @@ class WebPImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        self.info["background"] = (bg_r, bg_g, bg_b, bg_a)
 | 
					        self.info["background"] = (bg_r, bg_g, bg_b, bg_a)
 | 
				
			||||||
        self.n_frames = frame_count
 | 
					        self.n_frames = frame_count
 | 
				
			||||||
        self.is_animated = self.n_frames > 1
 | 
					        self.is_animated = self.n_frames > 1
 | 
				
			||||||
 | 
					        ret = self._decoder.get_next()
 | 
				
			||||||
 | 
					        if ret is not None:
 | 
				
			||||||
 | 
					            self.info["duration"] = ret[1]
 | 
				
			||||||
        self._mode = "RGB" if mode == "RGBX" else mode
 | 
					        self._mode = "RGB" if mode == "RGBX" else mode
 | 
				
			||||||
        self.rawmode = mode
 | 
					        self.rawmode = mode
 | 
				
			||||||
        self.tile = []
 | 
					        self.tile = []
 | 
				
			||||||
| 
						 | 
					@ -90,7 +93,7 @@ class WebPImageFile(ImageFile.ImageFile):
 | 
				
			||||||
            self.info["xmp"] = xmp
 | 
					            self.info["xmp"] = xmp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        # Initialize seek state
 | 
					        # Initialize seek state
 | 
				
			||||||
        self._reset(reset=False)
 | 
					        self._reset()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _getexif(self):
 | 
					    def _getexif(self):
 | 
				
			||||||
        if "exif" not in self.info:
 | 
					        if "exif" not in self.info:
 | 
				
			||||||
| 
						 | 
					@ -113,9 +116,8 @@ class WebPImageFile(ImageFile.ImageFile):
 | 
				
			||||||
        # Set logical frame to requested position
 | 
					        # Set logical frame to requested position
 | 
				
			||||||
        self.__logical_frame = frame
 | 
					        self.__logical_frame = frame
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def _reset(self, reset=True):
 | 
					    def _reset(self):
 | 
				
			||||||
        if reset:
 | 
					        self._decoder.reset()
 | 
				
			||||||
            self._decoder.reset()
 | 
					 | 
				
			||||||
        self.__physical_frame = 0
 | 
					        self.__physical_frame = 0
 | 
				
			||||||
        self.__loaded = -1
 | 
					        self.__loaded = -1
 | 
				
			||||||
        self.__timestamp = 0
 | 
					        self.__timestamp = 0
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -130,9 +130,9 @@ deps = {
 | 
				
			||||||
        "bins": ["cjpeg.exe", "djpeg.exe"],
 | 
					        "bins": ["cjpeg.exe", "djpeg.exe"],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "zlib": {
 | 
					    "zlib": {
 | 
				
			||||||
        "url": "https://zlib.net/zlib1213.zip",
 | 
					        "url": "https://zlib.net/zlib13.zip",
 | 
				
			||||||
        "filename": "zlib1213.zip",
 | 
					        "filename": "zlib13.zip",
 | 
				
			||||||
        "dir": "zlib-1.2.13",
 | 
					        "dir": "zlib-1.3",
 | 
				
			||||||
        "license": "README",
 | 
					        "license": "README",
 | 
				
			||||||
        "license_pattern": "Copyright notice:\n\n(.+)$",
 | 
					        "license_pattern": "Copyright notice:\n\n(.+)$",
 | 
				
			||||||
        "build": [
 | 
					        "build": [
 | 
				
			||||||
| 
						 | 
					@ -335,9 +335,9 @@ deps = {
 | 
				
			||||||
        "libs": [r"imagequant.lib"],
 | 
					        "libs": [r"imagequant.lib"],
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "harfbuzz": {
 | 
					    "harfbuzz": {
 | 
				
			||||||
        "url": "https://github.com/harfbuzz/harfbuzz/archive/8.0.0.zip",
 | 
					        "url": "https://github.com/harfbuzz/harfbuzz/archive/8.1.1.zip",
 | 
				
			||||||
        "filename": "harfbuzz-8.0.0.zip",
 | 
					        "filename": "harfbuzz-8.1.1.zip",
 | 
				
			||||||
        "dir": "harfbuzz-8.0.0",
 | 
					        "dir": "harfbuzz-8.1.1",
 | 
				
			||||||
        "license": "COPYING",
 | 
					        "license": "COPYING",
 | 
				
			||||||
        "build": [
 | 
					        "build": [
 | 
				
			||||||
            *cmds_cmake(
 | 
					            *cmds_cmake(
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue
	
	Block a user