mirror of
				https://github.com/python-pillow/Pillow.git
				synced 2025-11-04 01:47:47 +03:00 
			
		
		
		
	Merge branch 'main' into jpeg2000_cmyk_save
This commit is contained in:
		
						commit
						439d5cf2a0
					
				| 
						 | 
				
			
			@ -21,7 +21,7 @@ set -e
 | 
			
		|||
 | 
			
		||||
if [[ $(uname) != CYGWIN* ]]; then
 | 
			
		||||
    sudo apt-get -qq install libfreetype6-dev liblcms2-dev python3-tk\
 | 
			
		||||
                             ghostscript libjpeg-turbo-progs libopenjp2-7-dev\
 | 
			
		||||
                             ghostscript libjpeg-turbo8-dev libopenjp2-7-dev\
 | 
			
		||||
                             cmake meson imagemagick libharfbuzz-dev libfribidi-dev\
 | 
			
		||||
                             sway wl-clipboard libopenblas-dev
 | 
			
		||||
fi
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,4 +1,4 @@
 | 
			
		|||
mypy==1.13.0
 | 
			
		||||
mypy==1.14.0
 | 
			
		||||
IceSpringPySideStubs-PyQt6
 | 
			
		||||
IceSpringPySideStubs-PySide6
 | 
			
		||||
ipython
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								.github/workflows/macos-install.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/macos-install.sh
									
									
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -8,8 +8,8 @@ fi
 | 
			
		|||
brew install \
 | 
			
		||||
    freetype \
 | 
			
		||||
    ghostscript \
 | 
			
		||||
    jpeg-turbo \
 | 
			
		||||
    libimagequant \
 | 
			
		||||
    libjpeg \
 | 
			
		||||
    libtiff \
 | 
			
		||||
    little-cms2 \
 | 
			
		||||
    openjpeg \
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										26
									
								
								.github/workflows/wheels-dependencies.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										26
									
								
								.github/workflows/wheels-dependencies.sh
									
									
									
									
										vendored
									
									
								
							| 
						 | 
				
			
			@ -40,7 +40,7 @@ ARCHIVE_SDIR=pillow-depends-main
 | 
			
		|||
FREETYPE_VERSION=2.13.2
 | 
			
		||||
HARFBUZZ_VERSION=10.1.0
 | 
			
		||||
LIBPNG_VERSION=1.6.44
 | 
			
		||||
JPEGTURBO_VERSION=3.0.4
 | 
			
		||||
JPEGTURBO_VERSION=3.1.0
 | 
			
		||||
OPENJPEG_VERSION=2.5.3
 | 
			
		||||
XZ_VERSION=5.6.3
 | 
			
		||||
TIFF_VERSION=4.6.0
 | 
			
		||||
| 
						 | 
				
			
			@ -50,12 +50,8 @@ if [[ -n "$IS_MACOS" ]]; then
 | 
			
		|||
else
 | 
			
		||||
    GIFLIB_VERSION=5.2.1
 | 
			
		||||
fi
 | 
			
		||||
if [[ -n "$IS_MACOS" ]] || [[ "$MB_ML_VER" != 2014 ]]; then
 | 
			
		||||
    ZLIB_VERSION=1.3.1
 | 
			
		||||
else
 | 
			
		||||
    ZLIB_VERSION=1.2.8
 | 
			
		||||
fi
 | 
			
		||||
LIBWEBP_VERSION=1.4.0
 | 
			
		||||
ZLIB_NG_VERSION=2.2.2
 | 
			
		||||
LIBWEBP_VERSION=1.5.0
 | 
			
		||||
BZIP2_VERSION=1.0.8
 | 
			
		||||
LIBXCB_VERSION=1.17.0
 | 
			
		||||
BROTLI_VERSION=1.1.0
 | 
			
		||||
| 
						 | 
				
			
			@ -74,6 +70,16 @@ function build_pkg_config {
 | 
			
		|||
    touch pkg-config-stamp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function build_zlib_ng {
 | 
			
		||||
    if [ -e zlib-stamp ]; then return; fi
 | 
			
		||||
    fetch_unpack https://github.com/zlib-ng/zlib-ng/archive/$ZLIB_NG_VERSION.tar.gz zlib-ng-$ZLIB_NG_VERSION.tar.gz
 | 
			
		||||
    (cd zlib-ng-$ZLIB_NG_VERSION \
 | 
			
		||||
        && ./configure --prefix=$BUILD_PREFIX --zlib-compat \
 | 
			
		||||
        && make -j4 \
 | 
			
		||||
        && make install)
 | 
			
		||||
    touch zlib-stamp
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function build_brotli {
 | 
			
		||||
    if [ -e brotli-stamp ]; then return; fi
 | 
			
		||||
    local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz)
 | 
			
		||||
| 
						 | 
				
			
			@ -87,7 +93,7 @@ function build_harfbuzz {
 | 
			
		|||
    if [ -e harfbuzz-stamp ]; then return; fi
 | 
			
		||||
    python3 -m pip install meson ninja
 | 
			
		||||
 | 
			
		||||
    local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
 | 
			
		||||
    local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz)
 | 
			
		||||
    (cd $out_dir \
 | 
			
		||||
        && meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=release -Dfreetype=enabled -Dglib=disabled)
 | 
			
		||||
    (cd $out_dir/build \
 | 
			
		||||
| 
						 | 
				
			
			@ -100,12 +106,12 @@ function build {
 | 
			
		|||
    if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then
 | 
			
		||||
        yum remove -y zlib-devel
 | 
			
		||||
    fi
 | 
			
		||||
    build_new_zlib
 | 
			
		||||
    build_zlib_ng
 | 
			
		||||
 | 
			
		||||
    build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto
 | 
			
		||||
    if [ -n "$IS_MACOS" ]; then
 | 
			
		||||
        build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto
 | 
			
		||||
        build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib
 | 
			
		||||
        build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib
 | 
			
		||||
        build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist
 | 
			
		||||
    else
 | 
			
		||||
        sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,6 +34,7 @@ def test_wheel_features() -> None:
 | 
			
		|||
        "fribidi",
 | 
			
		||||
        "harfbuzz",
 | 
			
		||||
        "libjpeg_turbo",
 | 
			
		||||
        "zlib_ng",
 | 
			
		||||
        "xcb",
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											Binary file not shown.
										
									
								
							| 
		 Before Width: | Height: | Size: 486 B After Width: | Height: | Size: 533 B  | 
| 
						 | 
				
			
			@ -388,10 +388,12 @@ class TestColorLut3DFilter:
 | 
			
		|||
 | 
			
		||||
        table = numpy.ones((7 * 6 * 5, 3), dtype=numpy.float16)
 | 
			
		||||
        lut = ImageFilter.Color3DLUT((5, 6, 7), table)
 | 
			
		||||
        assert isinstance(lut.table, numpy.ndarray)
 | 
			
		||||
        assert lut.table.shape == (table.size,)
 | 
			
		||||
 | 
			
		||||
        table = numpy.ones((7 * 6 * 5 * 3), dtype=numpy.float16)
 | 
			
		||||
        lut = ImageFilter.Color3DLUT((5, 6, 7), table)
 | 
			
		||||
        assert isinstance(lut.table, numpy.ndarray)
 | 
			
		||||
        assert lut.table.shape == (table.size,)
 | 
			
		||||
 | 
			
		||||
        # Check application
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -36,10 +36,11 @@ def test_version() -> None:
 | 
			
		|||
        else:
 | 
			
		||||
            assert function(name) == version
 | 
			
		||||
            if name != "PIL":
 | 
			
		||||
                if name == "zlib" and version is not None:
 | 
			
		||||
                    version = re.sub(".zlib-ng$", "", version)
 | 
			
		||||
                elif name == "libtiff" and version is not None:
 | 
			
		||||
                    version = re.sub("t$", "", version)
 | 
			
		||||
                if version is not None:
 | 
			
		||||
                    if name == "zlib" and features.check_feature("zlib_ng"):
 | 
			
		||||
                        version = re.sub(".zlib-ng$", "", version)
 | 
			
		||||
                    elif name == "libtiff":
 | 
			
		||||
                        version = re.sub("t$", "", version)
 | 
			
		||||
                assert version is None or re.search(r"\d+(\.\d+)*$", version)
 | 
			
		||||
 | 
			
		||||
    for module in features.modules:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,4 +83,4 @@ def test_handler(tmp_path: Path) -> None:
 | 
			
		|||
        im.save(temp_file)
 | 
			
		||||
        assert handler.saved
 | 
			
		||||
 | 
			
		||||
    BufrStubImagePlugin._handler = None
 | 
			
		||||
    BufrStubImagePlugin.register_handler(None)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,8 +4,6 @@ import pytest
 | 
			
		|||
 | 
			
		||||
from PIL import ContainerIO, Image
 | 
			
		||||
 | 
			
		||||
from .helper import hopper
 | 
			
		||||
 | 
			
		||||
TEST_FILE = "Tests/images/dummy.container"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -15,15 +13,15 @@ def test_sanity() -> None:
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
def test_isatty() -> None:
 | 
			
		||||
    with hopper() as im:
 | 
			
		||||
        container = ContainerIO.ContainerIO(im, 0, 0)
 | 
			
		||||
    with open(TEST_FILE, "rb") as fh:
 | 
			
		||||
        container = ContainerIO.ContainerIO(fh, 0, 0)
 | 
			
		||||
 | 
			
		||||
    assert container.isatty() is False
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_seekable() -> None:
 | 
			
		||||
    with hopper() as im:
 | 
			
		||||
        container = ContainerIO.ContainerIO(im, 0, 0)
 | 
			
		||||
    with open(TEST_FILE, "rb") as fh:
 | 
			
		||||
        container = ContainerIO.ContainerIO(fh, 0, 0)
 | 
			
		||||
 | 
			
		||||
    assert container.seekable() is True
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,6 +4,7 @@ import warnings
 | 
			
		|||
from collections.abc import Generator
 | 
			
		||||
from io import BytesIO
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from typing import Any
 | 
			
		||||
 | 
			
		||||
import pytest
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1435,7 +1436,8 @@ def test_saving_rgba(tmp_path: Path) -> None:
 | 
			
		|||
        assert reloaded_rgba.load()[0, 0][3] == 0
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_optimizing_p_rgba(tmp_path: Path) -> None:
 | 
			
		||||
@pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False}))
 | 
			
		||||
def test_p_rgba(tmp_path: Path, params: dict[str, Any]) -> None:
 | 
			
		||||
    out = str(tmp_path / "temp.gif")
 | 
			
		||||
 | 
			
		||||
    im1 = Image.new("P", (100, 100))
 | 
			
		||||
| 
						 | 
				
			
			@ -1447,7 +1449,7 @@ def test_optimizing_p_rgba(tmp_path: Path) -> None:
 | 
			
		|||
    im2 = Image.new("P", (100, 100))
 | 
			
		||||
    im2.putpalette(data, "RGBA")
 | 
			
		||||
 | 
			
		||||
    im1.save(out, save_all=True, append_images=[im2])
 | 
			
		||||
    im1.save(out, save_all=True, append_images=[im2], **params)
 | 
			
		||||
 | 
			
		||||
    with Image.open(out) as reloaded:
 | 
			
		||||
        assert reloaded.n_frames == 2
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -83,4 +83,4 @@ def test_handler(tmp_path: Path) -> None:
 | 
			
		|||
        im.save(temp_file)
 | 
			
		||||
        assert handler.saved
 | 
			
		||||
 | 
			
		||||
    GribStubImagePlugin._handler = None
 | 
			
		||||
    GribStubImagePlugin.register_handler(None)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -85,4 +85,4 @@ def test_handler(tmp_path: Path) -> None:
 | 
			
		|||
        im.save(temp_file)
 | 
			
		||||
        assert handler.saved
 | 
			
		||||
 | 
			
		||||
    Hdf5StubImagePlugin._handler = None
 | 
			
		||||
    Hdf5StubImagePlugin.register_handler(None)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1000,8 +1000,13 @@ class TestFileJpeg:
 | 
			
		|||
        with Image.open(f) as reloaded:
 | 
			
		||||
            assert reloaded.info["xmp"] == b"XMP test"
 | 
			
		||||
 | 
			
		||||
        im.info["xmp"] = b"1" * 65504
 | 
			
		||||
        im.save(f)
 | 
			
		||||
            # Check that XMP is not saved from image info
 | 
			
		||||
            reloaded.save(f)
 | 
			
		||||
 | 
			
		||||
        with Image.open(f) as reloaded:
 | 
			
		||||
            assert "xmp" not in reloaded.info
 | 
			
		||||
 | 
			
		||||
        im.save(f, xmp=b"1" * 65504)
 | 
			
		||||
        with Image.open(f) as reloaded:
 | 
			
		||||
            assert reloaded.info["xmp"] == b"1" * 65504
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -297,3 +297,15 @@ def test_save_all() -> None:
 | 
			
		|||
    # Test that a single frame image will not be saved as an MPO
 | 
			
		||||
    jpg = roundtrip(im, save_all=True)
 | 
			
		||||
    assert "mp" not in jpg.info
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_save_xmp() -> None:
 | 
			
		||||
    im = Image.new("RGB", (1, 1))
 | 
			
		||||
    im2 = Image.new("RGB", (1, 1), "#f00")
 | 
			
		||||
    im2.encoderinfo = {"xmp": b"Second frame"}
 | 
			
		||||
    im_reloaded = roundtrip(im, xmp=b"First frame", save_all=True, append_images=[im2])
 | 
			
		||||
 | 
			
		||||
    assert im_reloaded.info["xmp"] == b"First frame"
 | 
			
		||||
 | 
			
		||||
    im_reloaded.seek(1)
 | 
			
		||||
    assert im_reloaded.info["xmp"] == b"Second frame"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -35,6 +35,13 @@ def test_load() -> None:
 | 
			
		|||
            assert im.load()[0, 0] == (255, 255, 255)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_load_zero_inch() -> None:
 | 
			
		||||
    b = BytesIO(b"\xd7\xcd\xc6\x9a\x00\x00" + b"\x00" * 10)
 | 
			
		||||
    with pytest.raises(ValueError):
 | 
			
		||||
        with Image.open(b):
 | 
			
		||||
            pass
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_register_handler(tmp_path: Path) -> None:
 | 
			
		||||
    class TestHandler(ImageFile.StubHandler):
 | 
			
		||||
        methodCalled = False
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -104,20 +104,20 @@ def test_transposed() -> None:
 | 
			
		|||
        assert im.size == (590, 88)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_load_first_unless_jpeg() -> None:
 | 
			
		||||
def test_load_first_unless_jpeg(monkeypatch: pytest.MonkeyPatch) -> None:
 | 
			
		||||
    # Test that thumbnail() still uses draft() for JPEG
 | 
			
		||||
    with Image.open("Tests/images/hopper.jpg") as im:
 | 
			
		||||
        draft = im.draft
 | 
			
		||||
        original_draft = im.draft
 | 
			
		||||
 | 
			
		||||
        def im_draft(
 | 
			
		||||
            mode: str, size: tuple[int, int]
 | 
			
		||||
            mode: str | None, size: tuple[int, int] | None
 | 
			
		||||
        ) -> tuple[str, tuple[int, int, float, float]] | None:
 | 
			
		||||
            result = draft(mode, size)
 | 
			
		||||
            result = original_draft(mode, size)
 | 
			
		||||
            assert result is not None
 | 
			
		||||
 | 
			
		||||
            return result
 | 
			
		||||
 | 
			
		||||
        im.draft = im_draft
 | 
			
		||||
        monkeypatch.setattr(im, "draft", im_draft)
 | 
			
		||||
 | 
			
		||||
        im.thumbnail((64, 64))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1674,6 +1674,9 @@ def test_continuous_horizontal_edges_polygon() -> None:
 | 
			
		|||
def test_discontiguous_corners_polygon() -> None:
 | 
			
		||||
    img, draw = create_base_image_draw((84, 68))
 | 
			
		||||
    draw.polygon(((1, 21), (34, 4), (71, 1), (38, 18)), BLACK)
 | 
			
		||||
    draw.polygon(
 | 
			
		||||
        ((82, 29), (82, 26), (82, 24), (67, 22), (52, 29), (52, 15), (67, 22)), BLACK
 | 
			
		||||
    )
 | 
			
		||||
    draw.polygon(((71, 44), (38, 27), (1, 24)), BLACK)
 | 
			
		||||
    draw.polygon(
 | 
			
		||||
        ((38, 66), (5, 49), (77, 49), (47, 66), (82, 63), (82, 47), (1, 47), (1, 63)),
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -93,6 +93,19 @@ class TestImageFile:
 | 
			
		|||
            assert p.image is not None
 | 
			
		||||
            assert (48, 48) == p.image.size
 | 
			
		||||
 | 
			
		||||
    @pytest.mark.filterwarnings("ignore:Corrupt EXIF data")
 | 
			
		||||
    def test_incremental_tiff(self) -> None:
 | 
			
		||||
        with ImageFile.Parser() as p:
 | 
			
		||||
            with open("Tests/images/hopper.tif", "rb") as f:
 | 
			
		||||
                p.feed(f.read(1024))
 | 
			
		||||
 | 
			
		||||
                # Check that insufficient data was given in the first feed
 | 
			
		||||
                assert not p.image
 | 
			
		||||
 | 
			
		||||
                p.feed(f.read())
 | 
			
		||||
            assert p.image is not None
 | 
			
		||||
            assert (128, 128) == p.image.size
 | 
			
		||||
 | 
			
		||||
    @skip_unless_feature("webp")
 | 
			
		||||
    def test_incremental_webp(self) -> None:
 | 
			
		||||
        with ImageFile.Parser() as p:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -74,6 +74,17 @@ def test_pickle_image(
 | 
			
		|||
    helper_pickle_file(tmp_path, protocol, test_file, test_mode)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_pickle_jpeg() -> None:
 | 
			
		||||
    # Arrange
 | 
			
		||||
    with Image.open("Tests/images/hopper.jpg") as image:
 | 
			
		||||
        # Act: roundtrip
 | 
			
		||||
        unpickled_image = pickle.loads(pickle.dumps(image))
 | 
			
		||||
 | 
			
		||||
    # Assert
 | 
			
		||||
    assert len(unpickled_image.layer) == 3
 | 
			
		||||
    assert unpickled_image.layers == 3
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_pickle_la_mode_with_palette(tmp_path: Path) -> None:
 | 
			
		||||
    # Arrange
 | 
			
		||||
    filename = str(tmp_path / "temp.pkl")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,7 @@
 | 
			
		|||
#!/bin/bash
 | 
			
		||||
# install webp
 | 
			
		||||
 | 
			
		||||
archive=libwebp-1.4.0
 | 
			
		||||
archive=libwebp-1.5.0
 | 
			
		||||
 | 
			
		||||
./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -54,6 +54,7 @@ Feature version numbers are available only where stated.
 | 
			
		|||
Support for the following features can be checked:
 | 
			
		||||
 | 
			
		||||
* ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. Compile-time version number is available.
 | 
			
		||||
* ``zlib_ng``: (compile time) Whether Pillow was compiled against the zlib-ng version of zlib. Compile-time version number is available.
 | 
			
		||||
* ``raqm``: Raqm library, required for ``ImageFont.Layout.RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer.
 | 
			
		||||
* ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available.
 | 
			
		||||
* ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library.
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										59
									
								
								docs/releasenotes/11.1.0.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										59
									
								
								docs/releasenotes/11.1.0.rst
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,59 @@
 | 
			
		|||
11.1.0
 | 
			
		||||
------
 | 
			
		||||
 | 
			
		||||
Security
 | 
			
		||||
========
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
^^^^
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
 | 
			
		||||
:cve:`YYYY-XXXXX`: TODO
 | 
			
		||||
^^^^^^^^^^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
 | 
			
		||||
Backwards Incompatible Changes
 | 
			
		||||
==============================
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
^^^^
 | 
			
		||||
 | 
			
		||||
Deprecations
 | 
			
		||||
============
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
^^^^
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
 | 
			
		||||
API Changes
 | 
			
		||||
===========
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
^^^^
 | 
			
		||||
 | 
			
		||||
TODO
 | 
			
		||||
 | 
			
		||||
API Additions
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
Check for zlib-ng
 | 
			
		||||
^^^^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
You can check if Pillow has been built against the zlib-ng version of the
 | 
			
		||||
zlib library, and what version of zlib-ng is being used::
 | 
			
		||||
 | 
			
		||||
    from PIL import features
 | 
			
		||||
    features.check_feature("zlib_ng")  # True or False
 | 
			
		||||
    features.version_feature("zlib_ng")  # "2.2.2" for example, or None
 | 
			
		||||
 | 
			
		||||
Other Changes
 | 
			
		||||
=============
 | 
			
		||||
 | 
			
		||||
zlib-ng in wheels
 | 
			
		||||
^^^^^^^^^^^^^^^^^
 | 
			
		||||
 | 
			
		||||
Wheels are now built against zlib-ng for improved speed. In tests, saving a PNG
 | 
			
		||||
was found to be more than twice as fast at higher compression levels.
 | 
			
		||||
| 
						 | 
				
			
			@ -14,6 +14,7 @@ expected to be backported to earlier versions.
 | 
			
		|||
.. toctree::
 | 
			
		||||
  :maxdepth: 2
 | 
			
		||||
 | 
			
		||||
  11.1.0
 | 
			
		||||
  11.0.0
 | 
			
		||||
  10.4.0
 | 
			
		||||
  10.3.0
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -104,6 +104,7 @@ test-extras = "tests"
 | 
			
		|||
 | 
			
		||||
[tool.cibuildwheel.macos.environment]
 | 
			
		||||
PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin"
 | 
			
		||||
DYLD_LIBRARY_PATH = "$(pwd)/build/deps/darwin/lib"
 | 
			
		||||
 | 
			
		||||
[tool.black]
 | 
			
		||||
exclude = "wheels/multibuild"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								setup.py
									
									
									
									
									
								
							| 
						 | 
				
			
			@ -344,7 +344,7 @@ class pil_build_ext(build_ext):
 | 
			
		|||
            for x in ("raqm", "fribidi")
 | 
			
		||||
        ]
 | 
			
		||||
        + [
 | 
			
		||||
            ("disable-platform-guessing", None, "Disable platform guessing on Linux"),
 | 
			
		||||
            ("disable-platform-guessing", None, "Disable platform guessing"),
 | 
			
		||||
            ("debug", None, "Debug logging"),
 | 
			
		||||
        ]
 | 
			
		||||
        + [("add-imaging-libs=", None, "Add libs to _imaging build")]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -273,7 +273,7 @@ class BlpImageFile(ImageFile.ImageFile):
 | 
			
		|||
            raise BLPFormatError(msg)
 | 
			
		||||
 | 
			
		||||
        self._mode = "RGBA" if self._blp_alpha_depth else "RGB"
 | 
			
		||||
        self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, 0, (self.mode, 0, 1))]
 | 
			
		||||
        self.tile = [ImageFile._Tile(decoder, (0, 0) + self.size, 0, self.mode)]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class _BLPBaseDecoder(ImageFile.PyDecoder):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -560,9 +560,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
 | 
			
		|||
        + struct.pack("<4I", *rgba_mask)  # dwRGBABitMask
 | 
			
		||||
        + struct.pack("<5I", DDSCAPS.TEXTURE, 0, 0, 0, 0)
 | 
			
		||||
    )
 | 
			
		||||
    ImageFile._save(
 | 
			
		||||
        im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
 | 
			
		||||
    )
 | 
			
		||||
    ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _accept(prefix: bytes) -> bool:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -454,7 +454,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes, eps: int = 1) -
 | 
			
		|||
    if hasattr(fp, "flush"):
 | 
			
		||||
        fp.flush()
 | 
			
		||||
 | 
			
		||||
    ImageFile._save(im, fp, [ImageFile._Tile("eps", (0, 0) + im.size, 0, None)])
 | 
			
		||||
    ImageFile._save(im, fp, [ImageFile._Tile("eps", (0, 0) + im.size)])
 | 
			
		||||
 | 
			
		||||
    fp.write(b"\n%%%%EndBinary\n")
 | 
			
		||||
    fp.write(b"grestore end\n")
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -303,38 +303,38 @@ TAGS = {
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class GPS(IntEnum):
 | 
			
		||||
    GPSVersionID = 0
 | 
			
		||||
    GPSLatitudeRef = 1
 | 
			
		||||
    GPSLatitude = 2
 | 
			
		||||
    GPSLongitudeRef = 3
 | 
			
		||||
    GPSLongitude = 4
 | 
			
		||||
    GPSAltitudeRef = 5
 | 
			
		||||
    GPSAltitude = 6
 | 
			
		||||
    GPSTimeStamp = 7
 | 
			
		||||
    GPSSatellites = 8
 | 
			
		||||
    GPSStatus = 9
 | 
			
		||||
    GPSMeasureMode = 10
 | 
			
		||||
    GPSDOP = 11
 | 
			
		||||
    GPSSpeedRef = 12
 | 
			
		||||
    GPSSpeed = 13
 | 
			
		||||
    GPSTrackRef = 14
 | 
			
		||||
    GPSTrack = 15
 | 
			
		||||
    GPSImgDirectionRef = 16
 | 
			
		||||
    GPSImgDirection = 17
 | 
			
		||||
    GPSMapDatum = 18
 | 
			
		||||
    GPSDestLatitudeRef = 19
 | 
			
		||||
    GPSDestLatitude = 20
 | 
			
		||||
    GPSDestLongitudeRef = 21
 | 
			
		||||
    GPSDestLongitude = 22
 | 
			
		||||
    GPSDestBearingRef = 23
 | 
			
		||||
    GPSDestBearing = 24
 | 
			
		||||
    GPSDestDistanceRef = 25
 | 
			
		||||
    GPSDestDistance = 26
 | 
			
		||||
    GPSProcessingMethod = 27
 | 
			
		||||
    GPSAreaInformation = 28
 | 
			
		||||
    GPSDateStamp = 29
 | 
			
		||||
    GPSDifferential = 30
 | 
			
		||||
    GPSHPositioningError = 31
 | 
			
		||||
    GPSVersionID = 0x00
 | 
			
		||||
    GPSLatitudeRef = 0x01
 | 
			
		||||
    GPSLatitude = 0x02
 | 
			
		||||
    GPSLongitudeRef = 0x03
 | 
			
		||||
    GPSLongitude = 0x04
 | 
			
		||||
    GPSAltitudeRef = 0x05
 | 
			
		||||
    GPSAltitude = 0x06
 | 
			
		||||
    GPSTimeStamp = 0x07
 | 
			
		||||
    GPSSatellites = 0x08
 | 
			
		||||
    GPSStatus = 0x09
 | 
			
		||||
    GPSMeasureMode = 0x0A
 | 
			
		||||
    GPSDOP = 0x0B
 | 
			
		||||
    GPSSpeedRef = 0x0C
 | 
			
		||||
    GPSSpeed = 0x0D
 | 
			
		||||
    GPSTrackRef = 0x0E
 | 
			
		||||
    GPSTrack = 0x0F
 | 
			
		||||
    GPSImgDirectionRef = 0x10
 | 
			
		||||
    GPSImgDirection = 0x11
 | 
			
		||||
    GPSMapDatum = 0x12
 | 
			
		||||
    GPSDestLatitudeRef = 0x13
 | 
			
		||||
    GPSDestLatitude = 0x14
 | 
			
		||||
    GPSDestLongitudeRef = 0x15
 | 
			
		||||
    GPSDestLongitude = 0x16
 | 
			
		||||
    GPSDestBearingRef = 0x17
 | 
			
		||||
    GPSDestBearing = 0x18
 | 
			
		||||
    GPSDestDistanceRef = 0x19
 | 
			
		||||
    GPSDestDistance = 0x1A
 | 
			
		||||
    GPSProcessingMethod = 0x1B
 | 
			
		||||
    GPSAreaInformation = 0x1C
 | 
			
		||||
    GPSDateStamp = 0x1D
 | 
			
		||||
    GPSDifferential = 0x1E
 | 
			
		||||
    GPSHPositioningError = 0x1F
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
"""Maps EXIF GPS tags to tag names."""
 | 
			
		||||
| 
						 | 
				
			
			@ -342,40 +342,40 @@ GPSTAGS = {i.value: i.name for i in GPS}
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
class Interop(IntEnum):
 | 
			
		||||
    InteropIndex = 1
 | 
			
		||||
    InteropVersion = 2
 | 
			
		||||
    RelatedImageFileFormat = 4096
 | 
			
		||||
    RelatedImageWidth = 4097
 | 
			
		||||
    RelatedImageHeight = 4098
 | 
			
		||||
    InteropIndex = 0x0001
 | 
			
		||||
    InteropVersion = 0x0002
 | 
			
		||||
    RelatedImageFileFormat = 0x1000
 | 
			
		||||
    RelatedImageWidth = 0x1001
 | 
			
		||||
    RelatedImageHeight = 0x1002
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class IFD(IntEnum):
 | 
			
		||||
    Exif = 34665
 | 
			
		||||
    GPSInfo = 34853
 | 
			
		||||
    Makernote = 37500
 | 
			
		||||
    Interop = 40965
 | 
			
		||||
    Exif = 0x8769
 | 
			
		||||
    GPSInfo = 0x8825
 | 
			
		||||
    MakerNote = 0x927C
 | 
			
		||||
    Interop = 0xA005
 | 
			
		||||
    IFD1 = -1
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class LightSource(IntEnum):
 | 
			
		||||
    Unknown = 0
 | 
			
		||||
    Daylight = 1
 | 
			
		||||
    Fluorescent = 2
 | 
			
		||||
    Tungsten = 3
 | 
			
		||||
    Flash = 4
 | 
			
		||||
    Fine = 9
 | 
			
		||||
    Cloudy = 10
 | 
			
		||||
    Shade = 11
 | 
			
		||||
    DaylightFluorescent = 12
 | 
			
		||||
    DayWhiteFluorescent = 13
 | 
			
		||||
    CoolWhiteFluorescent = 14
 | 
			
		||||
    WhiteFluorescent = 15
 | 
			
		||||
    StandardLightA = 17
 | 
			
		||||
    StandardLightB = 18
 | 
			
		||||
    StandardLightC = 19
 | 
			
		||||
    D55 = 20
 | 
			
		||||
    D65 = 21
 | 
			
		||||
    D75 = 22
 | 
			
		||||
    D50 = 23
 | 
			
		||||
    ISO = 24
 | 
			
		||||
    Other = 255
 | 
			
		||||
    Unknown = 0x00
 | 
			
		||||
    Daylight = 0x01
 | 
			
		||||
    Fluorescent = 0x02
 | 
			
		||||
    Tungsten = 0x03
 | 
			
		||||
    Flash = 0x04
 | 
			
		||||
    Fine = 0x09
 | 
			
		||||
    Cloudy = 0x0A
 | 
			
		||||
    Shade = 0x0B
 | 
			
		||||
    DaylightFluorescent = 0x0C
 | 
			
		||||
    DayWhiteFluorescent = 0x0D
 | 
			
		||||
    CoolWhiteFluorescent = 0x0E
 | 
			
		||||
    WhiteFluorescent = 0x0F
 | 
			
		||||
    StandardLightA = 0x11
 | 
			
		||||
    StandardLightB = 0x12
 | 
			
		||||
    StandardLightC = 0x13
 | 
			
		||||
    D55 = 0x14
 | 
			
		||||
    D65 = 0x15
 | 
			
		||||
    D75 = 0x16
 | 
			
		||||
    D50 = 0x17
 | 
			
		||||
    ISO = 0x18
 | 
			
		||||
    Other = 0xFF
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -159,7 +159,7 @@ class FliImageFile(ImageFile.ImageFile):
 | 
			
		|||
        framesize = i32(s)
 | 
			
		||||
 | 
			
		||||
        self.decodermaxblock = framesize
 | 
			
		||||
        self.tile = [ImageFile._Tile("fli", (0, 0) + self.size, self.__offset, None)]
 | 
			
		||||
        self.tile = [ImageFile._Tile("fli", (0, 0) + self.size, self.__offset)]
 | 
			
		||||
 | 
			
		||||
        self.__offset += framesize
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -170,7 +170,7 @@ class FpxImageFile(ImageFile.ImageFile):
 | 
			
		|||
                        "raw",
 | 
			
		||||
                        (x, y, x1, y1),
 | 
			
		||||
                        i32(s, i) + 28,
 | 
			
		||||
                        (self.rawmode,),
 | 
			
		||||
                        self.rawmode,
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -95,7 +95,7 @@ class FtexImageFile(ImageFile.ImageFile):
 | 
			
		|||
            self._mode = "RGBA"
 | 
			
		||||
            self.tile = [ImageFile._Tile("bcn", (0, 0) + self.size, 0, (1,))]
 | 
			
		||||
        elif format == Format.UNCOMPRESSED:
 | 
			
		||||
            self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, ("RGB", 0, 1))]
 | 
			
		||||
            self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 0, "RGB")]
 | 
			
		||||
        else:
 | 
			
		||||
            msg = f"Invalid texture compression format: {repr(format)}"
 | 
			
		||||
            raise ValueError(msg)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -76,7 +76,7 @@ class GdImageFile(ImageFile.ImageFile):
 | 
			
		|||
                "raw",
 | 
			
		||||
                (0, 0) + self.size,
 | 
			
		||||
                7 + true_color_offset + 4 + 256 * 4,
 | 
			
		||||
                ("L", 0, 1),
 | 
			
		||||
                "L",
 | 
			
		||||
            )
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -695,8 +695,9 @@ def _write_multiple_frames(
 | 
			
		|||
                        )
 | 
			
		||||
                        background = _get_background(im_frame, color)
 | 
			
		||||
                        background_im = Image.new("P", im_frame.size, background)
 | 
			
		||||
                        assert im_frames[0].im.palette is not None
 | 
			
		||||
                        background_im.putpalette(im_frames[0].im.palette)
 | 
			
		||||
                        first_palette = im_frames[0].im.palette
 | 
			
		||||
                        assert first_palette is not None
 | 
			
		||||
                        background_im.putpalette(first_palette, first_palette.mode)
 | 
			
		||||
                    bbox = _getbbox(background_im, im_frame)[1]
 | 
			
		||||
                elif encoderinfo.get("optimize") and im_frame.mode != "1":
 | 
			
		||||
                    if "transparency" not in encoderinfo:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -754,7 +754,7 @@ class Image:
 | 
			
		|||
 | 
			
		||||
    def __setstate__(self, state: list[Any]) -> None:
 | 
			
		||||
        Image.__init__(self)
 | 
			
		||||
        info, mode, size, palette, data = state
 | 
			
		||||
        info, mode, size, palette, data = state[:5]
 | 
			
		||||
        self.info = info
 | 
			
		||||
        self._mode = mode
 | 
			
		||||
        self._size = size
 | 
			
		||||
| 
						 | 
				
			
			@ -1565,7 +1565,7 @@ class Image:
 | 
			
		|||
                for subifd_offset in subifd_offsets:
 | 
			
		||||
                    ifds.append((exif._get_ifd_dict(subifd_offset), subifd_offset))
 | 
			
		||||
        ifd1 = exif.get_ifd(ExifTags.IFD.IFD1)
 | 
			
		||||
        if ifd1 and ifd1.get(513):
 | 
			
		||||
        if ifd1 and ifd1.get(ExifTags.Base.JpegIFOffset):
 | 
			
		||||
            assert exif._info is not None
 | 
			
		||||
            ifds.append((ifd1, exif._info.next))
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -1577,11 +1577,11 @@ class Image:
 | 
			
		|||
 | 
			
		||||
            fp = self.fp
 | 
			
		||||
            if ifd is not None:
 | 
			
		||||
                thumbnail_offset = ifd.get(513)
 | 
			
		||||
                thumbnail_offset = ifd.get(ExifTags.Base.JpegIFOffset)
 | 
			
		||||
                if thumbnail_offset is not None:
 | 
			
		||||
                    thumbnail_offset += getattr(self, "_exif_offset", 0)
 | 
			
		||||
                    self.fp.seek(thumbnail_offset)
 | 
			
		||||
                    data = self.fp.read(ifd.get(514))
 | 
			
		||||
                    data = self.fp.read(ifd.get(ExifTags.Base.JpegIFByteCount))
 | 
			
		||||
                    fp = io.BytesIO(data)
 | 
			
		||||
 | 
			
		||||
            with open(fp) as im:
 | 
			
		||||
| 
						 | 
				
			
			@ -2556,7 +2556,7 @@ class Image:
 | 
			
		|||
        self._ensure_mutable()
 | 
			
		||||
 | 
			
		||||
        save_all = params.pop("save_all", False)
 | 
			
		||||
        self.encoderinfo = params
 | 
			
		||||
        self.encoderinfo = {**getattr(self, "encoderinfo", {}), **params}
 | 
			
		||||
        self.encoderconfig: tuple[Any, ...] = ()
 | 
			
		||||
 | 
			
		||||
        preinit()
 | 
			
		||||
| 
						 | 
				
			
			@ -2603,6 +2603,11 @@ class Image:
 | 
			
		|||
                except PermissionError:
 | 
			
		||||
                    pass
 | 
			
		||||
            raise
 | 
			
		||||
        finally:
 | 
			
		||||
            try:
 | 
			
		||||
                del self.encoderinfo
 | 
			
		||||
            except AttributeError:
 | 
			
		||||
                pass
 | 
			
		||||
        if open_fp:
 | 
			
		||||
            fp.close()
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -3884,7 +3889,7 @@ class Exif(_ExifBase):
 | 
			
		|||
      gps_ifd = exif.get_ifd(ExifTags.IFD.GPSInfo)
 | 
			
		||||
      print(gps_ifd)
 | 
			
		||||
 | 
			
		||||
    Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.Makernote``,
 | 
			
		||||
    Other IFDs include ``ExifTags.IFD.Exif``, ``ExifTags.IFD.MakerNote``,
 | 
			
		||||
    ``ExifTags.IFD.Interop`` and ``ExifTags.IFD.IFD1``.
 | 
			
		||||
 | 
			
		||||
    :py:mod:`~PIL.ExifTags` also has enum classes to provide names for data::
 | 
			
		||||
| 
						 | 
				
			
			@ -4047,11 +4052,11 @@ class Exif(_ExifBase):
 | 
			
		|||
                    ifd = self._get_ifd_dict(offset, tag)
 | 
			
		||||
                    if ifd is not None:
 | 
			
		||||
                        self._ifds[tag] = ifd
 | 
			
		||||
            elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.Makernote]:
 | 
			
		||||
            elif tag in [ExifTags.IFD.Interop, ExifTags.IFD.MakerNote]:
 | 
			
		||||
                if ExifTags.IFD.Exif not in self._ifds:
 | 
			
		||||
                    self.get_ifd(ExifTags.IFD.Exif)
 | 
			
		||||
                tag_data = self._ifds[ExifTags.IFD.Exif][tag]
 | 
			
		||||
                if tag == ExifTags.IFD.Makernote:
 | 
			
		||||
                if tag == ExifTags.IFD.MakerNote:
 | 
			
		||||
                    from .TiffImagePlugin import ImageFileDirectory_v2
 | 
			
		||||
 | 
			
		||||
                    if tag_data[:8] == b"FUJIFILM":
 | 
			
		||||
| 
						 | 
				
			
			@ -4138,7 +4143,7 @@ class Exif(_ExifBase):
 | 
			
		|||
            ifd = {
 | 
			
		||||
                k: v
 | 
			
		||||
                for (k, v) in ifd.items()
 | 
			
		||||
                if k not in (ExifTags.IFD.Interop, ExifTags.IFD.Makernote)
 | 
			
		||||
                if k not in (ExifTags.IFD.Interop, ExifTags.IFD.MakerNote)
 | 
			
		||||
            }
 | 
			
		||||
        return ifd
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,8 +98,8 @@ def _tilesort(t: _Tile) -> int:
 | 
			
		|||
class _Tile(NamedTuple):
 | 
			
		||||
    codec_name: str
 | 
			
		||||
    extents: tuple[int, int, int, int] | None
 | 
			
		||||
    offset: int
 | 
			
		||||
    args: tuple[Any, ...] | str | None
 | 
			
		||||
    offset: int = 0
 | 
			
		||||
    args: tuple[Any, ...] | str | None = None
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -553,7 +553,7 @@ class Color3DLUT(MultibandFilter):
 | 
			
		|||
        ch_out = channels or ch_in
 | 
			
		||||
        size_1d, size_2d, size_3d = self.size
 | 
			
		||||
 | 
			
		||||
        table = [0] * (size_1d * size_2d * size_3d * ch_out)
 | 
			
		||||
        table: list[float] = [0] * (size_1d * size_2d * size_3d * ch_out)
 | 
			
		||||
        idx_in = 0
 | 
			
		||||
        idx_out = 0
 | 
			
		||||
        for b in range(size_3d):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -698,10 +698,11 @@ def exif_transpose(image: Image.Image, *, in_place: bool = False) -> Image.Image
 | 
			
		|||
        8: Image.Transpose.ROTATE_90,
 | 
			
		||||
    }.get(orientation)
 | 
			
		||||
    if method is not None:
 | 
			
		||||
        transposed_image = image.transpose(method)
 | 
			
		||||
        if in_place:
 | 
			
		||||
            image.im = transposed_image.im
 | 
			
		||||
            image._size = transposed_image._size
 | 
			
		||||
            image.im = image.im.transpose(method)
 | 
			
		||||
            image._size = image.im.size
 | 
			
		||||
        else:
 | 
			
		||||
            transposed_image = image.transpose(method)
 | 
			
		||||
        exif_image = image if in_place else transposed_image
 | 
			
		||||
 | 
			
		||||
        exif = exif_image.getexif()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -62,7 +62,7 @@ class ImtImageFile(ImageFile.ImageFile):
 | 
			
		|||
                        "raw",
 | 
			
		||||
                        (0, 0) + self.size,
 | 
			
		||||
                        self.fp.tell() - len(buffer),
 | 
			
		||||
                        (self.mode, 0, 1),
 | 
			
		||||
                        self.mode,
 | 
			
		||||
                    )
 | 
			
		||||
                ]
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -395,6 +395,13 @@ class JpegImageFile(ImageFile.ImageFile):
 | 
			
		|||
            return getattr(self, "_" + name)
 | 
			
		||||
        raise AttributeError(name)
 | 
			
		||||
 | 
			
		||||
    def __getstate__(self) -> list[Any]:
 | 
			
		||||
        return super().__getstate__() + [self.layers, self.layer]
 | 
			
		||||
 | 
			
		||||
    def __setstate__(self, state: list[Any]) -> None:
 | 
			
		||||
        super().__setstate__(state)
 | 
			
		||||
        self.layers, self.layer = state[5:]
 | 
			
		||||
 | 
			
		||||
    def load_read(self, read_bytes: int) -> bytes:
 | 
			
		||||
        """
 | 
			
		||||
        internal: read more image data
 | 
			
		||||
| 
						 | 
				
			
			@ -751,7 +758,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
 | 
			
		|||
    extra = info.get("extra", b"")
 | 
			
		||||
 | 
			
		||||
    MAX_BYTES_IN_MARKER = 65533
 | 
			
		||||
    xmp = info.get("xmp", im.info.get("xmp"))
 | 
			
		||||
    xmp = info.get("xmp")
 | 
			
		||||
    if xmp:
 | 
			
		||||
        overhead_len = 29  # b"http://ns.adobe.com/xap/1.0/\x00"
 | 
			
		||||
        max_data_bytes_in_marker = MAX_BYTES_IN_MARKER - overhead_len
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -70,9 +70,9 @@ class MspImageFile(ImageFile.ImageFile):
 | 
			
		|||
        self._size = i16(s, 4), i16(s, 6)
 | 
			
		||||
 | 
			
		||||
        if s[:4] == b"DanM":
 | 
			
		||||
            self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 32, ("1", 0, 1))]
 | 
			
		||||
            self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 32, "1")]
 | 
			
		||||
        else:
 | 
			
		||||
            self.tile = [ImageFile._Tile("MSP", (0, 0) + self.size, 32, None)]
 | 
			
		||||
            self.tile = [ImageFile._Tile("MSP", (0, 0) + self.size, 32)]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class MspDecoder(ImageFile.PyDecoder):
 | 
			
		||||
| 
						 | 
				
			
			@ -188,7 +188,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
 | 
			
		|||
        fp.write(o16(h))
 | 
			
		||||
 | 
			
		||||
    # image body
 | 
			
		||||
    ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 32, ("1", 0, 1))])
 | 
			
		||||
    ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 32, "1")])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -47,7 +47,7 @@ class PcdImageFile(ImageFile.ImageFile):
 | 
			
		|||
 | 
			
		||||
        self._mode = "RGB"
 | 
			
		||||
        self._size = 768, 512  # FIXME: not correct for rotated images!
 | 
			
		||||
        self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048, None)]
 | 
			
		||||
        self.tile = [ImageFile._Tile("pcd", (0, 0) + self.size, 96 * 2048)]
 | 
			
		||||
 | 
			
		||||
    def load_end(self) -> None:
 | 
			
		||||
        if self.tile_post_rotate:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -61,9 +61,7 @@ class PixarImageFile(ImageFile.ImageFile):
 | 
			
		|||
        # FIXME: to be continued...
 | 
			
		||||
 | 
			
		||||
        # create tile descriptor (assuming "dumped")
 | 
			
		||||
        self.tile = [
 | 
			
		||||
            ImageFile._Tile("raw", (0, 0) + self.size, 1024, (self.mode, 0, 1))
 | 
			
		||||
        ]
 | 
			
		||||
        self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, 1024, self.mode)]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -32,7 +32,7 @@ class QoiImageFile(ImageFile.ImageFile):
 | 
			
		|||
        self._mode = "RGB" if channels == 3 else "RGBA"
 | 
			
		||||
 | 
			
		||||
        self.fp.seek(1, os.SEEK_CUR)  # colorspace
 | 
			
		||||
        self.tile = [ImageFile._Tile("qoi", (0, 0) + self._size, self.fp.tell(), None)]
 | 
			
		||||
        self.tile = [ImageFile._Tile("qoi", (0, 0) + self._size, self.fp.tell())]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class QoiDecoder(ImageFile.PyDecoder):
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -154,9 +154,7 @@ class SpiderImageFile(ImageFile.ImageFile):
 | 
			
		|||
            self.rawmode = "F;32F"
 | 
			
		||||
        self._mode = "F"
 | 
			
		||||
 | 
			
		||||
        self.tile = [
 | 
			
		||||
            ImageFile._Tile("raw", (0, 0) + self.size, offset, (self.rawmode, 0, 1))
 | 
			
		||||
        ]
 | 
			
		||||
        self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, offset, self.rawmode)]
 | 
			
		||||
        self._fp = self.fp  # FIXME: hack
 | 
			
		||||
 | 
			
		||||
    @property
 | 
			
		||||
| 
						 | 
				
			
			@ -211,26 +209,27 @@ class SpiderImageFile(ImageFile.ImageFile):
 | 
			
		|||
 | 
			
		||||
 | 
			
		||||
# given a list of filenames, return a list of images
 | 
			
		||||
def loadImageSeries(filelist: list[str] | None = None) -> list[SpiderImageFile] | None:
 | 
			
		||||
def loadImageSeries(filelist: list[str] | None = None) -> list[Image.Image] | None:
 | 
			
		||||
    """create a list of :py:class:`~PIL.Image.Image` objects for use in a montage"""
 | 
			
		||||
    if filelist is None or len(filelist) < 1:
 | 
			
		||||
        return None
 | 
			
		||||
 | 
			
		||||
    imglist = []
 | 
			
		||||
    byte_imgs = []
 | 
			
		||||
    for img in filelist:
 | 
			
		||||
        if not os.path.exists(img):
 | 
			
		||||
            print(f"unable to find {img}")
 | 
			
		||||
            continue
 | 
			
		||||
        try:
 | 
			
		||||
            with Image.open(img) as im:
 | 
			
		||||
                im = im.convert2byte()
 | 
			
		||||
                assert isinstance(im, SpiderImageFile)
 | 
			
		||||
                byte_im = im.convert2byte()
 | 
			
		||||
        except Exception:
 | 
			
		||||
            if not isSpiderImage(img):
 | 
			
		||||
                print(f"{img} is not a Spider image file")
 | 
			
		||||
            continue
 | 
			
		||||
        im.info["filename"] = img
 | 
			
		||||
        imglist.append(im)
 | 
			
		||||
    return imglist
 | 
			
		||||
        byte_im.info["filename"] = img
 | 
			
		||||
        byte_imgs.append(byte_im)
 | 
			
		||||
    return byte_imgs
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# --------------------------------------------------------------------
 | 
			
		||||
| 
						 | 
				
			
			@ -280,9 +279,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
 | 
			
		|||
    fp.writelines(hdr)
 | 
			
		||||
 | 
			
		||||
    rawmode = "F;32NF"  # 32-bit native floating point
 | 
			
		||||
    ImageFile._save(
 | 
			
		||||
        im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]
 | 
			
		||||
    )
 | 
			
		||||
    ImageFile._save(im, fp, [ImageFile._Tile("raw", (0, 0) + im.size, 0, rawmode)])
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _save_spider(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -935,8 +935,8 @@ class ImageFileDirectory_v2(_IFDv2Base):
 | 
			
		|||
                self._tagdata[tag] = data
 | 
			
		||||
                self.tagtype[tag] = typ
 | 
			
		||||
 | 
			
		||||
                bytes_value = size if size > 32 else repr(data)
 | 
			
		||||
                msg += f" - value: <table: {bytes_value} bytes>"
 | 
			
		||||
                msg += " - value: "
 | 
			
		||||
                msg += f"<table: {size} bytes>" if size > 32 else repr(data)
 | 
			
		||||
 | 
			
		||||
                logger.debug(msg)
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -981,11 +981,8 @@ class ImageFileDirectory_v2(_IFDv2Base):
 | 
			
		|||
 | 
			
		||||
            tagname = TiffTags.lookup(tag, self.group).name
 | 
			
		||||
            typname = "ifd" if is_ifd else TYPES.get(typ, "unknown")
 | 
			
		||||
            bytes_value = len(data) if len(data) >= 16 else str(values)
 | 
			
		||||
            msg = (
 | 
			
		||||
                f"save: {tagname} ({tag}) - type: {typname} ({typ})"
 | 
			
		||||
                f" - value: <table: {bytes_value} bytes>"
 | 
			
		||||
            )
 | 
			
		||||
            msg = f"save: {tagname} ({tag}) - type: {typname} ({typ}) - value: "
 | 
			
		||||
            msg += f"<table: {len(data)} bytes>" if len(data) >= 16 else str(values)
 | 
			
		||||
            logger.debug(msg)
 | 
			
		||||
 | 
			
		||||
            # count is sum of lengths for string and arbitrary data
 | 
			
		||||
| 
						 | 
				
			
			@ -1435,8 +1432,12 @@ class TiffImageFile(ImageFile.ImageFile):
 | 
			
		|||
        logger.debug("- YCbCr subsampling: %s", self.tag_v2.get(YCBCRSUBSAMPLING))
 | 
			
		||||
 | 
			
		||||
        # size
 | 
			
		||||
        xsize = self.tag_v2.get(IMAGEWIDTH)
 | 
			
		||||
        ysize = self.tag_v2.get(IMAGELENGTH)
 | 
			
		||||
        try:
 | 
			
		||||
            xsize = self.tag_v2[IMAGEWIDTH]
 | 
			
		||||
            ysize = self.tag_v2[IMAGELENGTH]
 | 
			
		||||
        except KeyError as e:
 | 
			
		||||
            msg = "Missing dimensions"
 | 
			
		||||
            raise TypeError(msg) from e
 | 
			
		||||
        if not isinstance(xsize, int) or not isinstance(ysize, int):
 | 
			
		||||
            msg = "Invalid dimensions"
 | 
			
		||||
            raise ValueError(msg)
 | 
			
		||||
| 
						 | 
				
			
			@ -1917,7 +1918,9 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
 | 
			
		|||
                if not getattr(Image.core, "libtiff_support_custom_tags", False):
 | 
			
		||||
                    continue
 | 
			
		||||
 | 
			
		||||
                if tag in ifd.tagtype:
 | 
			
		||||
                if tag in TiffTags.TAGS_V2_GROUPS:
 | 
			
		||||
                    types[tag] = TiffTags.LONG8
 | 
			
		||||
                elif tag in ifd.tagtype:
 | 
			
		||||
                    types[tag] = ifd.tagtype[tag]
 | 
			
		||||
                elif not (isinstance(value, (int, float, str, bytes))):
 | 
			
		||||
                    continue
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -92,6 +92,9 @@ class WmfStubImageFile(ImageFile.StubImageFile):
 | 
			
		|||
 | 
			
		||||
            # get units per inch
 | 
			
		||||
            self._inch = word(s, 14)
 | 
			
		||||
            if self._inch == 0:
 | 
			
		||||
                msg = "Invalid inch"
 | 
			
		||||
                raise ValueError(msg)
 | 
			
		||||
 | 
			
		||||
            # get bounding box
 | 
			
		||||
            x0 = short(s, 6)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -74,9 +74,7 @@ class XVThumbImageFile(ImageFile.ImageFile):
 | 
			
		|||
        self.palette = ImagePalette.raw("RGB", PALETTE)
 | 
			
		||||
 | 
			
		||||
        self.tile = [
 | 
			
		||||
            ImageFile._Tile(
 | 
			
		||||
                "raw", (0, 0) + self.size, self.fp.tell(), (self.mode, 0, 1)
 | 
			
		||||
            )
 | 
			
		||||
            ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), self.mode)
 | 
			
		||||
        ]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -67,7 +67,7 @@ class XbmImageFile(ImageFile.ImageFile):
 | 
			
		|||
        self._mode = "1"
 | 
			
		||||
        self._size = xsize, ysize
 | 
			
		||||
 | 
			
		||||
        self.tile = [ImageFile._Tile("xbm", (0, 0) + self.size, m.end(), None)]
 | 
			
		||||
        self.tile = [ImageFile._Tile("xbm", (0, 0) + self.size, m.end())]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
 | 
			
		||||
| 
						 | 
				
			
			@ -85,7 +85,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None:
 | 
			
		|||
 | 
			
		||||
    fp.write(b"static char im_bits[] = {\n")
 | 
			
		||||
 | 
			
		||||
    ImageFile._save(im, fp, [ImageFile._Tile("xbm", (0, 0) + im.size, 0, None)])
 | 
			
		||||
    ImageFile._save(im, fp, [ImageFile._Tile("xbm", (0, 0) + im.size)])
 | 
			
		||||
 | 
			
		||||
    fp.write(b"};\n")
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -101,9 +101,7 @@ class XpmImageFile(ImageFile.ImageFile):
 | 
			
		|||
        self._mode = "P"
 | 
			
		||||
        self.palette = ImagePalette.raw("RGB", b"".join(palette))
 | 
			
		||||
 | 
			
		||||
        self.tile = [
 | 
			
		||||
            ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), ("P", 0, 1))
 | 
			
		||||
        ]
 | 
			
		||||
        self.tile = [ImageFile._Tile("raw", (0, 0) + self.size, self.fp.tell(), "P")]
 | 
			
		||||
 | 
			
		||||
    def load_read(self, read_bytes: int) -> bytes:
 | 
			
		||||
        #
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -127,6 +127,7 @@ features: dict[str, tuple[str, str | bool, str | None]] = {
 | 
			
		|||
    "fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"),
 | 
			
		||||
    "harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"),
 | 
			
		||||
    "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"),
 | 
			
		||||
    "zlib_ng": ("PIL._imaging", "HAVE_ZLIBNG", "zlib_ng_version"),
 | 
			
		||||
    "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"),
 | 
			
		||||
    "xcb": ("PIL._imaging", "HAVE_XCB", None),
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -308,7 +309,11 @@ def pilinfo(out: IO[str] | None = None, supported_formats: bool = True) -> None:
 | 
			
		|||
                    # this check is also in src/_imagingcms.c:setup_module()
 | 
			
		||||
                    version_static = tuple(int(x) for x in v.split(".")) < (2, 7)
 | 
			
		||||
                t = "compiled for" if version_static else "loaded"
 | 
			
		||||
                if name == "raqm":
 | 
			
		||||
                if name == "zlib":
 | 
			
		||||
                    zlib_ng_version = version_feature("zlib_ng")
 | 
			
		||||
                    if zlib_ng_version is not None:
 | 
			
		||||
                        v += ", compiled for zlib-ng " + zlib_ng_version
 | 
			
		||||
                elif name == "raqm":
 | 
			
		||||
                    for f in ("fribidi", "harfbuzz"):
 | 
			
		||||
                        v2 = version_feature(f)
 | 
			
		||||
                        if v2 is not None:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4397,6 +4397,20 @@ setup_module(PyObject *m) {
 | 
			
		|||
    }
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
    PyObject *have_zlibng;
 | 
			
		||||
#ifdef ZLIBNG_VERSION
 | 
			
		||||
    have_zlibng = Py_True;
 | 
			
		||||
    {
 | 
			
		||||
        PyObject *v = PyUnicode_FromString(ZLIBNG_VERSION);
 | 
			
		||||
        PyDict_SetItemString(d, "zlib_ng_version", v ? v : Py_None);
 | 
			
		||||
        Py_XDECREF(v);
 | 
			
		||||
    }
 | 
			
		||||
#else
 | 
			
		||||
    have_zlibng = Py_False;
 | 
			
		||||
#endif
 | 
			
		||||
    Py_INCREF(have_zlibng);
 | 
			
		||||
    PyModule_AddObject(m, "HAVE_ZLIBNG", have_zlibng);
 | 
			
		||||
 | 
			
		||||
#ifdef HAVE_LIBTIFF
 | 
			
		||||
    {
 | 
			
		||||
        extern const char *ImagingTiffVersion(void);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -346,10 +346,10 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
 | 
			
		|||
        return -1;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    Py_BEGIN_ALLOW_THREADS
 | 
			
		||||
    Py_BEGIN_ALLOW_THREADS;
 | 
			
		||||
 | 
			
		||||
        // transform color channels only
 | 
			
		||||
        for (i = 0; i < im->ysize; i++) {
 | 
			
		||||
    // transform color channels only
 | 
			
		||||
    for (i = 0; i < im->ysize; i++) {
 | 
			
		||||
        cmsDoTransform(hTransform, im->image[i], imOut->image[i], im->xsize);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -362,9 +362,9 @@ pyCMSdoTransform(Imaging im, Imaging imOut, cmsHTRANSFORM hTransform) {
 | 
			
		|||
    // enough available on all platforms, so we polyfill it here for now.
 | 
			
		||||
    pyCMScopyAux(hTransform, imOut, im);
 | 
			
		||||
 | 
			
		||||
    Py_END_ALLOW_THREADS
 | 
			
		||||
    Py_END_ALLOW_THREADS;
 | 
			
		||||
 | 
			
		||||
        return 0;
 | 
			
		||||
    return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static cmsHTRANSFORM
 | 
			
		||||
| 
						 | 
				
			
			@ -378,17 +378,17 @@ _buildTransform(
 | 
			
		|||
) {
 | 
			
		||||
    cmsHTRANSFORM hTransform;
 | 
			
		||||
 | 
			
		||||
    Py_BEGIN_ALLOW_THREADS
 | 
			
		||||
    Py_BEGIN_ALLOW_THREADS;
 | 
			
		||||
 | 
			
		||||
        /* create the transform */
 | 
			
		||||
        hTransform = cmsCreateTransform(
 | 
			
		||||
            hInputProfile,
 | 
			
		||||
            findLCMStype(sInMode),
 | 
			
		||||
            hOutputProfile,
 | 
			
		||||
            findLCMStype(sOutMode),
 | 
			
		||||
            iRenderingIntent,
 | 
			
		||||
            cmsFLAGS
 | 
			
		||||
        );
 | 
			
		||||
    /* create the transform */
 | 
			
		||||
    hTransform = cmsCreateTransform(
 | 
			
		||||
        hInputProfile,
 | 
			
		||||
        findLCMStype(sInMode),
 | 
			
		||||
        hOutputProfile,
 | 
			
		||||
        findLCMStype(sOutMode),
 | 
			
		||||
        iRenderingIntent,
 | 
			
		||||
        cmsFLAGS
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    Py_END_ALLOW_THREADS;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -412,19 +412,19 @@ _buildProofTransform(
 | 
			
		|||
) {
 | 
			
		||||
    cmsHTRANSFORM hTransform;
 | 
			
		||||
 | 
			
		||||
    Py_BEGIN_ALLOW_THREADS
 | 
			
		||||
    Py_BEGIN_ALLOW_THREADS;
 | 
			
		||||
 | 
			
		||||
        /* create the transform */
 | 
			
		||||
        hTransform = cmsCreateProofingTransform(
 | 
			
		||||
            hInputProfile,
 | 
			
		||||
            findLCMStype(sInMode),
 | 
			
		||||
            hOutputProfile,
 | 
			
		||||
            findLCMStype(sOutMode),
 | 
			
		||||
            hProofProfile,
 | 
			
		||||
            iRenderingIntent,
 | 
			
		||||
            iProofIntent,
 | 
			
		||||
            cmsFLAGS
 | 
			
		||||
        );
 | 
			
		||||
    /* create the transform */
 | 
			
		||||
    hTransform = cmsCreateProofingTransform(
 | 
			
		||||
        hInputProfile,
 | 
			
		||||
        findLCMStype(sInMode),
 | 
			
		||||
        hOutputProfile,
 | 
			
		||||
        findLCMStype(sOutMode),
 | 
			
		||||
        hProofProfile,
 | 
			
		||||
        iRenderingIntent,
 | 
			
		||||
        iProofIntent,
 | 
			
		||||
        cmsFLAGS
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    Py_END_ALLOW_THREADS;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -690,24 +690,26 @@ PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) {
 | 
			
		|||
    SetWindowLongPtr(wnd, 0, (LONG_PTR)callback);
 | 
			
		||||
    SetWindowLongPtr(wnd, sizeof(callback), (LONG_PTR)PyThreadState_Get());
 | 
			
		||||
 | 
			
		||||
    Py_BEGIN_ALLOW_THREADS ShowWindow(wnd, SW_SHOWNORMAL);
 | 
			
		||||
    Py_BEGIN_ALLOW_THREADS;
 | 
			
		||||
    ShowWindow(wnd, SW_SHOWNORMAL);
 | 
			
		||||
    SetForegroundWindow(wnd); /* to make sure it's visible */
 | 
			
		||||
    Py_END_ALLOW_THREADS
 | 
			
		||||
    Py_END_ALLOW_THREADS;
 | 
			
		||||
 | 
			
		||||
        return Py_BuildValue(F_HANDLE, wnd);
 | 
			
		||||
    return Py_BuildValue(F_HANDLE, wnd);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PyObject *
 | 
			
		||||
PyImaging_EventLoopWin32(PyObject *self, PyObject *args) {
 | 
			
		||||
    MSG msg;
 | 
			
		||||
 | 
			
		||||
    Py_BEGIN_ALLOW_THREADS while (mainloop && GetMessage(&msg, NULL, 0, 0)) {
 | 
			
		||||
    Py_BEGIN_ALLOW_THREADS;
 | 
			
		||||
    while (mainloop && GetMessage(&msg, NULL, 0, 0)) {
 | 
			
		||||
        TranslateMessage(&msg);
 | 
			
		||||
        DispatchMessage(&msg);
 | 
			
		||||
    }
 | 
			
		||||
    Py_END_ALLOW_THREADS
 | 
			
		||||
    Py_END_ALLOW_THREADS;
 | 
			
		||||
 | 
			
		||||
        Py_INCREF(Py_None);
 | 
			
		||||
    Py_INCREF(Py_None);
 | 
			
		||||
    return Py_None;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -736,7 +736,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
 | 
			
		|||
            }
 | 
			
		||||
            if (tag_type) {
 | 
			
		||||
                int type_int = PyLong_AsLong(tag_type);
 | 
			
		||||
                if (type_int >= TIFF_BYTE && type_int <= TIFF_DOUBLE) {
 | 
			
		||||
                if (type_int >= TIFF_BYTE && type_int <= TIFF_LONG8) {
 | 
			
		||||
                    type = (TIFFDataType)type_int;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
| 
						 | 
				
			
			@ -929,7 +929,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
 | 
			
		|||
                );
 | 
			
		||||
            } else if (type == TIFF_LONG) {
 | 
			
		||||
                status = ImagingLibTiffSetField(
 | 
			
		||||
                    &encoder->state, (ttag_t)key_int, PyLong_AsLongLong(value)
 | 
			
		||||
                    &encoder->state, (ttag_t)key_int, (UINT32)PyLong_AsLong(value)
 | 
			
		||||
                );
 | 
			
		||||
            } else if (type == TIFF_SSHORT) {
 | 
			
		||||
                status = ImagingLibTiffSetField(
 | 
			
		||||
| 
						 | 
				
			
			@ -959,6 +959,10 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) {
 | 
			
		|||
                status = ImagingLibTiffSetField(
 | 
			
		||||
                    &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)
 | 
			
		||||
                );
 | 
			
		||||
            } else if (type == TIFF_LONG8) {
 | 
			
		||||
                status = ImagingLibTiffSetField(
 | 
			
		||||
                    &encoder->state, (ttag_t)key_int, (uint64_t)PyLong_AsLongLong(value)
 | 
			
		||||
                );
 | 
			
		||||
            } else {
 | 
			
		||||
                TRACE(
 | 
			
		||||
                    ("Unhandled type for key %d : %s \n",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -501,7 +501,8 @@ polygon_generic(
 | 
			
		|||
                    // Needed to draw consistent polygons
 | 
			
		||||
                    xx[j] = xx[j - 1];
 | 
			
		||||
                    j++;
 | 
			
		||||
                } else if (current->dx != 0 && roundf(xx[j - 1]) == xx[j - 1]) {
 | 
			
		||||
                } else if (current->dx != 0 && j % 2 == 1 &&
 | 
			
		||||
                           roundf(xx[j - 1]) == xx[j - 1]) {
 | 
			
		||||
                    // Connect discontiguous corners
 | 
			
		||||
                    for (k = 0; k < i; k++) {
 | 
			
		||||
                        Edge *other_edge = edge_table[k];
 | 
			
		||||
| 
						 | 
				
			
			@ -510,10 +511,8 @@ polygon_generic(
 | 
			
		|||
                            continue;
 | 
			
		||||
                        }
 | 
			
		||||
                        // Check if the two edges join to make a corner
 | 
			
		||||
                        if (((ymin == current->ymin && ymin == other_edge->ymin) ||
 | 
			
		||||
                             (ymin == current->ymax && ymin == other_edge->ymax)) &&
 | 
			
		||||
                            xx[j - 1] == (ymin - other_edge->y0) * other_edge->dx +
 | 
			
		||||
                                             other_edge->x0) {
 | 
			
		||||
                        if (xx[j - 1] ==
 | 
			
		||||
                            (ymin - other_edge->y0) * other_edge->dx + other_edge->x0) {
 | 
			
		||||
                            // Determine points from the edges on the next row
 | 
			
		||||
                            // Or if this is the last row, check the previous row
 | 
			
		||||
                            int offset = ymin == ymax ? -1 : 1;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1 +1 @@
 | 
			
		|||
Subproject commit 9a9d1275f025f737cdaa3c451ba07129dd95f361
 | 
			
		||||
Subproject commit 42d761728d141d8462cd9943f4329f12fe62b155
 | 
			
		||||
| 
						 | 
				
			
			@ -7,6 +7,7 @@ import re
 | 
			
		|||
import shutil
 | 
			
		||||
import struct
 | 
			
		||||
import subprocess
 | 
			
		||||
import sys
 | 
			
		||||
from typing import Any
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -113,26 +114,24 @@ V = {
 | 
			
		|||
    "FREETYPE": "2.13.3",
 | 
			
		||||
    "FRIBIDI": "1.0.16",
 | 
			
		||||
    "HARFBUZZ": "10.1.0",
 | 
			
		||||
    "JPEGTURBO": "3.0.4",
 | 
			
		||||
    "JPEGTURBO": "3.1.0",
 | 
			
		||||
    "LCMS2": "2.16",
 | 
			
		||||
    "LIBPNG": "1.6.44",
 | 
			
		||||
    "LIBWEBP": "1.4.0",
 | 
			
		||||
    "LIBWEBP": "1.5.0",
 | 
			
		||||
    "OPENJPEG": "2.5.3",
 | 
			
		||||
    "TIFF": "4.6.0",
 | 
			
		||||
    "XZ": "5.6.3",
 | 
			
		||||
    "ZLIB": "1.3.1",
 | 
			
		||||
    "ZLIBNG": "2.2.2",
 | 
			
		||||
}
 | 
			
		||||
V["LIBPNG_DOTLESS"] = V["LIBPNG"].replace(".", "")
 | 
			
		||||
V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2])
 | 
			
		||||
V["ZLIB_DOTLESS"] = V["ZLIB"].replace(".", "")
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# dependencies, listed in order of compilation
 | 
			
		||||
DEPS: dict[str, dict[str, Any]] = {
 | 
			
		||||
    "libjpeg": {
 | 
			
		||||
        "url": f"{SF_PROJECTS}/libjpeg-turbo/files/{V['JPEGTURBO']}/FILENAME/download",
 | 
			
		||||
        "url": f"https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/{V['JPEGTURBO']}/libjpeg-turbo-{V['JPEGTURBO']}.tar.gz",
 | 
			
		||||
        "filename": f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz",
 | 
			
		||||
        "dir": f"libjpeg-turbo-{V['JPEGTURBO']}",
 | 
			
		||||
        "license": ["README.ijg", "LICENSE.md"],
 | 
			
		||||
        "license_pattern": (
 | 
			
		||||
            "(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n=========="
 | 
			
		||||
| 
						 | 
				
			
			@ -155,28 +154,30 @@ DEPS: dict[str, dict[str, Any]] = {
 | 
			
		|||
            cmd_copy("cjpeg-static.exe", "cjpeg.exe"),
 | 
			
		||||
            cmd_copy("djpeg-static.exe", "djpeg.exe"),
 | 
			
		||||
        ],
 | 
			
		||||
        "headers": ["j*.h"],
 | 
			
		||||
        "headers": ["jconfig.h", r"src\j*.h"],
 | 
			
		||||
        "libs": ["libjpeg.lib"],
 | 
			
		||||
        "bins": ["cjpeg.exe", "djpeg.exe"],
 | 
			
		||||
    },
 | 
			
		||||
    "zlib": {
 | 
			
		||||
        "url": "https://zlib.net/FILENAME",
 | 
			
		||||
        "filename": f"zlib{V['ZLIB_DOTLESS']}.zip",
 | 
			
		||||
        "dir": f"zlib-{V['ZLIB']}",
 | 
			
		||||
        "license": "README",
 | 
			
		||||
        "license_pattern": "Copyright notice:\n\n(.+)$",
 | 
			
		||||
        "url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['ZLIBNG']}.tar.gz",
 | 
			
		||||
        "filename": f"zlib-ng-{V['ZLIBNG']}.tar.gz",
 | 
			
		||||
        "license": "LICENSE.md",
 | 
			
		||||
        "patch": {
 | 
			
		||||
            r"CMakeLists.txt": {
 | 
			
		||||
                "set_target_properties(zlib PROPERTIES OUTPUT_NAME zlibstatic${{SUFFIX}})": "set_target_properties(zlib PROPERTIES OUTPUT_NAME zlib)",  # noqa: E501
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
        "build": [
 | 
			
		||||
            cmd_nmake(r"win32\Makefile.msc", "clean"),
 | 
			
		||||
            cmd_nmake(r"win32\Makefile.msc", "zlib.lib"),
 | 
			
		||||
            cmd_copy("zlib.lib", "z.lib"),
 | 
			
		||||
            *cmds_cmake(
 | 
			
		||||
                "zlib", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DZLIB_COMPAT:BOOL=ON"
 | 
			
		||||
            ),
 | 
			
		||||
        ],
 | 
			
		||||
        "headers": [r"z*.h"],
 | 
			
		||||
        "libs": [r"*.lib"],
 | 
			
		||||
        "libs": [r"zlib.lib"],
 | 
			
		||||
    },
 | 
			
		||||
    "xz": {
 | 
			
		||||
        "url": f"https://github.com/tukaani-project/xz/releases/download/v{V['XZ']}/FILENAME",
 | 
			
		||||
        "filename": f"xz-{V['XZ']}.tar.gz",
 | 
			
		||||
        "dir": f"xz-{V['XZ']}",
 | 
			
		||||
        "license": "COPYING",
 | 
			
		||||
        "build": [
 | 
			
		||||
            *cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"),
 | 
			
		||||
| 
						 | 
				
			
			@ -189,7 +190,6 @@ DEPS: dict[str, dict[str, Any]] = {
 | 
			
		|||
    "libwebp": {
 | 
			
		||||
        "url": "http://downloads.webmproject.org/releases/webp/FILENAME",
 | 
			
		||||
        "filename": f"libwebp-{V['LIBWEBP']}.tar.gz",
 | 
			
		||||
        "dir": f"libwebp-{V['LIBWEBP']}",
 | 
			
		||||
        "license": "COPYING",
 | 
			
		||||
        "patch": {
 | 
			
		||||
            r"src\enc\picture_csp_enc.c": {
 | 
			
		||||
| 
						 | 
				
			
			@ -211,7 +211,6 @@ DEPS: dict[str, dict[str, Any]] = {
 | 
			
		|||
    "libtiff": {
 | 
			
		||||
        "url": "https://download.osgeo.org/libtiff/FILENAME",
 | 
			
		||||
        "filename": f"tiff-{V['TIFF']}.tar.gz",
 | 
			
		||||
        "dir": f"tiff-{V['TIFF']}",
 | 
			
		||||
        "license": "LICENSE.md",
 | 
			
		||||
        "patch": {
 | 
			
		||||
            r"libtiff\tif_lzma.c": {
 | 
			
		||||
| 
						 | 
				
			
			@ -244,7 +243,6 @@ DEPS: dict[str, dict[str, Any]] = {
 | 
			
		|||
        "url": f"{SF_PROJECTS}/libpng/files/libpng{V['LIBPNG_XY']}/{V['LIBPNG']}/"
 | 
			
		||||
        f"lpng{V['LIBPNG_DOTLESS']}.zip/download",
 | 
			
		||||
        "filename": f"lpng{V['LIBPNG_DOTLESS']}.zip",
 | 
			
		||||
        "dir": f"lpng{V['LIBPNG_DOTLESS']}",
 | 
			
		||||
        "license": "LICENSE",
 | 
			
		||||
        "build": [
 | 
			
		||||
            *cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"),
 | 
			
		||||
| 
						 | 
				
			
			@ -258,7 +256,6 @@ DEPS: dict[str, dict[str, Any]] = {
 | 
			
		|||
    "brotli": {
 | 
			
		||||
        "url": f"https://github.com/google/brotli/archive/refs/tags/v{V['BROTLI']}.tar.gz",
 | 
			
		||||
        "filename": f"brotli-{V['BROTLI']}.tar.gz",
 | 
			
		||||
        "dir": f"brotli-{V['BROTLI']}",
 | 
			
		||||
        "license": "LICENSE",
 | 
			
		||||
        "build": [
 | 
			
		||||
            *cmds_cmake(("brotlicommon", "brotlidec"), "-DBUILD_SHARED_LIBS:BOOL=OFF"),
 | 
			
		||||
| 
						 | 
				
			
			@ -269,7 +266,6 @@ DEPS: dict[str, dict[str, Any]] = {
 | 
			
		|||
    "freetype": {
 | 
			
		||||
        "url": "https://download.savannah.gnu.org/releases/freetype/FILENAME",
 | 
			
		||||
        "filename": f"freetype-{V['FREETYPE']}.tar.gz",
 | 
			
		||||
        "dir": f"freetype-{V['FREETYPE']}",
 | 
			
		||||
        "license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"],
 | 
			
		||||
        "patch": {
 | 
			
		||||
            r"builds\windows\vc2010\freetype.vcxproj": {
 | 
			
		||||
| 
						 | 
				
			
			@ -304,7 +300,6 @@ DEPS: dict[str, dict[str, Any]] = {
 | 
			
		|||
    "lcms2": {
 | 
			
		||||
        "url": f"{SF_PROJECTS}/lcms/files/lcms/{V['LCMS2']}/FILENAME/download",
 | 
			
		||||
        "filename": f"lcms2-{V['LCMS2']}.tar.gz",
 | 
			
		||||
        "dir": f"lcms2-{V['LCMS2']}",
 | 
			
		||||
        "license": "LICENSE",
 | 
			
		||||
        "patch": {
 | 
			
		||||
            r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": {
 | 
			
		||||
| 
						 | 
				
			
			@ -330,7 +325,6 @@ DEPS: dict[str, dict[str, Any]] = {
 | 
			
		|||
    "openjpeg": {
 | 
			
		||||
        "url": f"https://github.com/uclouvain/openjpeg/archive/v{V['OPENJPEG']}.tar.gz",
 | 
			
		||||
        "filename": f"openjpeg-{V['OPENJPEG']}.tar.gz",
 | 
			
		||||
        "dir": f"openjpeg-{V['OPENJPEG']}",
 | 
			
		||||
        "license": "LICENSE",
 | 
			
		||||
        "build": [
 | 
			
		||||
            *cmds_cmake(
 | 
			
		||||
| 
						 | 
				
			
			@ -345,7 +339,6 @@ DEPS: dict[str, dict[str, Any]] = {
 | 
			
		|||
        # commit: Merge branch 'master' into msvc (matches 2.17.0 tag)
 | 
			
		||||
        "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip",
 | 
			
		||||
        "filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip",
 | 
			
		||||
        "dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab",
 | 
			
		||||
        "license": "COPYRIGHT",
 | 
			
		||||
        "patch": {
 | 
			
		||||
            "CMakeLists.txt": {
 | 
			
		||||
| 
						 | 
				
			
			@ -365,7 +358,6 @@ DEPS: dict[str, dict[str, Any]] = {
 | 
			
		|||
    "harfbuzz": {
 | 
			
		||||
        "url": f"https://github.com/harfbuzz/harfbuzz/archive/{V['HARFBUZZ']}.zip",
 | 
			
		||||
        "filename": f"harfbuzz-{V['HARFBUZZ']}.zip",
 | 
			
		||||
        "dir": f"harfbuzz-{V['HARFBUZZ']}",
 | 
			
		||||
        "license": "COPYING",
 | 
			
		||||
        "build": [
 | 
			
		||||
            *cmds_cmake(
 | 
			
		||||
| 
						 | 
				
			
			@ -380,7 +372,6 @@ DEPS: dict[str, dict[str, Any]] = {
 | 
			
		|||
    "fribidi": {
 | 
			
		||||
        "url": f"https://github.com/fribidi/fribidi/archive/v{V['FRIBIDI']}.zip",
 | 
			
		||||
        "filename": f"fribidi-{V['FRIBIDI']}.zip",
 | 
			
		||||
        "dir": f"fribidi-{V['FRIBIDI']}",
 | 
			
		||||
        "license": "COPYING",
 | 
			
		||||
        "build": [
 | 
			
		||||
            cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{V['FRIBIDI']}-COPYING"),
 | 
			
		||||
| 
						 | 
				
			
			@ -517,7 +508,10 @@ def extract_dep(url: str, filename: str, prefs: dict[str, str]) -> None:
 | 
			
		|||
                if sources_dir_abs != member_prefix:
 | 
			
		||||
                    msg = "Attempted Path Traversal in Tar File"
 | 
			
		||||
                    raise RuntimeError(msg)
 | 
			
		||||
            tgz.extractall(sources_dir)
 | 
			
		||||
            if sys.version_info >= (3, 12):
 | 
			
		||||
                tgz.extractall(sources_dir, filter="data")
 | 
			
		||||
            else:
 | 
			
		||||
                tgz.extractall(sources_dir)
 | 
			
		||||
    else:
 | 
			
		||||
        msg = "Unknown archive type: " + filename
 | 
			
		||||
        raise RuntimeError(msg)
 | 
			
		||||
| 
						 | 
				
			
			@ -760,6 +754,8 @@ def main() -> None:
 | 
			
		|||
    }
 | 
			
		||||
 | 
			
		||||
    for k, v in DEPS.items():
 | 
			
		||||
        if "dir" not in v:
 | 
			
		||||
            v["dir"] = re.sub(r"\.(tar\.gz|zip)", "", v["filename"])
 | 
			
		||||
        prefs[f"dir_{k}"] = os.path.join(sources_dir, v["dir"])
 | 
			
		||||
 | 
			
		||||
    print()
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue
	
	Block a user