From d4f1d252efa5e2ca54b2d43eaa92880cdf4ad8dc Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 4 Jul 2024 15:45:58 +0300 Subject: [PATCH 01/61] Test 3.13 free-threaded build on CI --- .ci/install.sh | 6 ++++-- .github/workflows/test.yml | 10 ++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.ci/install.sh b/.ci/install.sh index 1eb098be9..0dc738ed8 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -37,12 +37,14 @@ python3 -m pip install -U pytest-timeout python3 -m pip install pyroma if [[ $(uname) != CYGWIN* ]]; then - python3 -m pip install numpy + # TODO Update condition when NumPy supports free-threading + if ! [[ "$GHA_PYTHON_VERSION" == "3.13-dev" ]]; then python3 -m pip install numpy ; fi # PyQt6 doesn't support PyPy3 if [[ $GHA_PYTHON_VERSION == 3.* ]]; then sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0 - python3 -m pip install pyqt6 + # TODO Update condition when pyqt6 supports free-threading + if ! [[ "$GHA_PYTHON_VERSION" == "3.13-dev" ]]; then python3 -m pip install pyqt6 ; fi fi # Pyroma uses non-isolated build and fails with old setuptools diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0972459b0..75165909c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -55,6 +55,8 @@ jobs: REVERSE: "--reverse" - python-version: "3.10" PYTHONOPTIMIZE: 2 + # Free-threaded + - { os: "ubuntu-latest", python-version: "3.13-dev", disable-gil: true } # M1 only available for 3.10+ - os: "macos-13" python-version: "3.9" @@ -70,6 +72,7 @@ jobs: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 + if: "!endsWith(matrix.python-version, '-dev')" with: python-version: ${{ matrix.python-version }} allow-prereleases: true @@ -78,6 +81,13 @@ jobs: ".ci/*.sh" "pyproject.toml" + - name: Set up Python ${{ matrix.python-version }} (free-threaded) + uses: deadsnakes/action@v3.1.0 + if: endsWith(matrix.python-version, '-dev') + with: + python-version: ${{ matrix.python-version }} + nogil: ${{ matrix.disable-gil }} + - name: Build system information run: python3 .github/workflows/system-info.py From e76c31b67db5e1cdfe28b593862bbe2d83a81e23 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 4 Jul 2024 15:46:46 +0300 Subject: [PATCH 02/61] Refactor to single line for readability --- .github/workflows/test.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 75165909c..d858dcfff 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -50,19 +50,14 @@ jobs: "3.9", ] include: - - python-version: "3.11" - PYTHONOPTIMIZE: 1 - REVERSE: "--reverse" - - python-version: "3.10" - PYTHONOPTIMIZE: 2 + - { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" } + - { python-version: "3.10", PYTHONOPTIMIZE: 2 } # Free-threaded - { os: "ubuntu-latest", python-version: "3.13-dev", disable-gil: true } # M1 only available for 3.10+ - - os: "macos-13" - python-version: "3.9" + - { os: "macos-13", python-version: "3.9" } exclude: - - os: "macos-14" - python-version: "3.9" + - { os: "macos-14", python-version: "3.9" } runs-on: ${{ matrix.os }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} From 701539d017356d8624fba6a6efc97b8e2ebf75e2 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 5 Jul 2024 00:38:50 +0300 Subject: [PATCH 03/61] Add PYTHON_GIL=0 env var to keep GIL disabled --- .github/workflows/test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d858dcfff..fc8be6ced 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -83,6 +83,11 @@ jobs: python-version: ${{ matrix.python-version }} nogil: ${{ matrix.disable-gil }} + - name: Set PYTHON_GIL + if: "${{ matrix.disable-gil }}" + run: | + echo "PYTHON_GIL=0" >> $GITHUB_ENV + - name: Build system information run: python3 .github/workflows/system-info.py From 2d2889e6178ab619bae30952f4d477ac66352dea Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 5 Jul 2024 00:49:27 +0300 Subject: [PATCH 04/61] Install nightly NumPy for free-threaded --- .ci/install.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.ci/install.sh b/.ci/install.sh index 0dc738ed8..e1274f41c 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -38,7 +38,11 @@ python3 -m pip install pyroma if [[ $(uname) != CYGWIN* ]]; then # TODO Update condition when NumPy supports free-threading - if ! [[ "$GHA_PYTHON_VERSION" == "3.13-dev" ]]; then python3 -m pip install numpy ; fi + if [[ "$GHA_PYTHON_VERSION" == "3.13-dev" ]]; then + python3 -m pip install numpy --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple + else + python3 -m pip install numpy + fi # PyQt6 doesn't support PyPy3 if [[ $GHA_PYTHON_VERSION == 3.* ]]; then From dcd833280538d8cd00b3c2a4617b534efdfa4ff0 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 5 Jul 2024 19:36:58 +0300 Subject: [PATCH 05/61] Include 'free-threading' in job name --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index fc8be6ced..702841814 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -60,7 +60,7 @@ jobs: - { os: "macos-14", python-version: "3.9" } runs-on: ${{ matrix.os }} - name: ${{ matrix.os }} Python ${{ matrix.python-version }} + name: ${{ matrix.os }} Python ${{ matrix.python-version }} ${{ matrix.disable-gil && 'free-threaded' || '' }} steps: - uses: actions/checkout@v4 From 6883018725bbaa5f616c1ec190393efcaef7a2ce Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 6 Jul 2024 22:06:47 +1000 Subject: [PATCH 06/61] Added type hint --- Tests/test_file_pdf.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Tests/test_file_pdf.py b/Tests/test_file_pdf.py index 3729ca58b..1d5001b1a 100644 --- a/Tests/test_file_pdf.py +++ b/Tests/test_file_pdf.py @@ -229,6 +229,7 @@ def test_pdf_append_fails_on_nonexistent_file() -> None: def check_pdf_pages_consistency(pdf: PdfParser.PdfParser) -> None: + assert pdf.pages_ref is not None pages_info = pdf.read_indirect(pdf.pages_ref) assert b"Parent" not in pages_info assert b"Kids" in pages_info From 41eb218a6829ddc58a282d6952d9c0b5af25688d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 7 Jul 2024 22:04:50 +1000 Subject: [PATCH 07/61] Check GIL, rather than Python version --- .ci/install.sh | 4 ++-- .github/workflows/test.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.ci/install.sh b/.ci/install.sh index e1274f41c..8e65f64c4 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -38,7 +38,7 @@ python3 -m pip install pyroma if [[ $(uname) != CYGWIN* ]]; then # TODO Update condition when NumPy supports free-threading - if [[ "$GHA_PYTHON_VERSION" == "3.13-dev" ]]; then + if [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install numpy --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple else python3 -m pip install numpy @@ -48,7 +48,7 @@ if [[ $(uname) != CYGWIN* ]]; then if [[ $GHA_PYTHON_VERSION == 3.* ]]; then sudo apt-get -qq install libegl1 libxcb-cursor0 libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-randr0 libxcb-render-util0 libxcb-shape0 libxkbcommon-x11-0 # TODO Update condition when pyqt6 supports free-threading - if ! [[ "$GHA_PYTHON_VERSION" == "3.13-dev" ]]; then python3 -m pip install pyqt6 ; fi + if ! [[ "$PYTHON_GIL" == "0" ]]; then python3 -m pip install pyqt6 ; fi fi # Pyroma uses non-isolated build and fails with old setuptools diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 702841814..6e63333b0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -67,7 +67,7 @@ jobs: - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 - if: "!endsWith(matrix.python-version, '-dev')" + if: "${{ !matrix.disable-gil }}" with: python-version: ${{ matrix.python-version }} allow-prereleases: true @@ -78,7 +78,7 @@ jobs: - name: Set up Python ${{ matrix.python-version }} (free-threaded) uses: deadsnakes/action@v3.1.0 - if: endsWith(matrix.python-version, '-dev') + if: "${{ matrix.disable-gil }}" with: python-version: ${{ matrix.python-version }} nogil: ${{ matrix.disable-gil }} From 8a05e32336bfbece4aed16a63d009efa55a1b4bb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 8 Jul 2024 20:09:45 +1000 Subject: [PATCH 08/61] Added type hints --- Tests/test_file_mpo.py | 16 +++++++++---- Tests/test_imagefile.py | 5 +++- Tests/test_imagetk.py | 3 +++ src/PIL/BlpImagePlugin.py | 1 + src/PIL/BmpImagePlugin.py | 44 +++++++++++++++++++++++++----------- src/PIL/ImageFile.py | 18 +++++++-------- src/PIL/ImageTk.py | 24 ++++++++++++++------ src/PIL/Jpeg2KImagePlugin.py | 40 +++++++++++++++++++++++++++----- src/PIL/JpegImagePlugin.py | 18 ++++++++------- src/PIL/MpoImagePlugin.py | 17 ++++++++++---- src/PIL/PngImagePlugin.py | 42 +++++++++++++++++++++++----------- src/PIL/PsdImagePlugin.py | 2 +- src/PIL/WebPImagePlugin.py | 2 +- src/PIL/WmfImagePlugin.py | 2 +- src/PIL/_imagingtk.pyi | 3 +++ 15 files changed, 168 insertions(+), 69 deletions(-) create mode 100644 src/PIL/_imagingtk.pyi diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 39b9c60b7..5402fcb44 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -2,11 +2,11 @@ from __future__ import annotations import warnings from io import BytesIO -from typing import Any, cast +from typing import Any import pytest -from PIL import Image, MpoImagePlugin +from PIL import Image, ImageFile, MpoImagePlugin from .helper import ( assert_image_equal, @@ -20,11 +20,11 @@ test_files = ["Tests/images/sugarshack.mpo", "Tests/images/frozenpond.mpo"] pytestmark = skip_unless_feature("jpg") -def roundtrip(im: Image.Image, **options: Any) -> MpoImagePlugin.MpoImageFile: +def roundtrip(im: Image.Image, **options: Any) -> ImageFile.ImageFile: out = BytesIO() im.save(out, "MPO", **options) out.seek(0) - return cast(MpoImagePlugin.MpoImageFile, Image.open(out)) + return Image.open(out) @pytest.mark.parametrize("test_file", test_files) @@ -226,6 +226,12 @@ def test_eoferror() -> None: im.seek(n_frames - 1) +def test_adopt_jpeg() -> None: + with Image.open("Tests/images/hopper.jpg") as im: + with pytest.raises(ValueError): + MpoImagePlugin.MpoImageFile.adopt(im) + + def test_ultra_hdr() -> None: with Image.open("Tests/images/ultrahdr.jpg") as im: assert im.format == "JPEG" @@ -275,6 +281,8 @@ def test_save_all() -> None: im_reloaded = roundtrip(im, save_all=True, append_images=[im2]) assert_image_equal(im, im_reloaded) + assert isinstance(im_reloaded, MpoImagePlugin.MpoImageFile) + assert im_reloaded.mpinfo is not None assert im_reloaded.mpinfo[45056] == b"0100" im_reloaded.seek(1) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index bb686bb3b..b996860ce 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -90,6 +90,7 @@ class TestImageFile: data = f.read() with ImageFile.Parser() as p: p.feed(data) + assert p.image is not None assert (48, 48) == p.image.size @skip_unless_feature("webp") @@ -103,6 +104,7 @@ class TestImageFile: assert not p.image p.feed(f.read()) + assert p.image is not None assert (128, 128) == p.image.size @skip_unless_feature("zlib") @@ -393,8 +395,9 @@ class TestPyEncoder(CodecsTest): with pytest.raises(NotImplementedError): encoder.encode_to_pyfd() + fh = BytesIO() with pytest.raises(NotImplementedError): - encoder.encode_to_file(None, None) + encoder.encode_to_file(fh, 0) def test_zero_height(self) -> None: with pytest.raises(UnidentifiedImageError): diff --git a/Tests/test_imagetk.py b/Tests/test_imagetk.py index f84c6c03a..4484dca10 100644 --- a/Tests/test_imagetk.py +++ b/Tests/test_imagetk.py @@ -109,3 +109,6 @@ def test_bitmapimage() -> None: # reloaded = ImageTk.getimage(im_tk) # assert_image_equal(reloaded, im) + + with pytest.raises(ValueError): + ImageTk.BitmapImage() diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index b9cefafdd..6d71049a9 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -313,6 +313,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder): self._blp_lengths = struct.unpack("<16I", self._safe_read(16 * 4)) def _safe_read(self, length: int) -> bytes: + assert self.fd is not None return ImageFile._safe_read(self.fd, length) def _read_palette(self) -> list[tuple[int, int, int, int]]: diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index ef2045337..48bdd9830 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -25,7 +25,7 @@ from __future__ import annotations import os -from typing import IO +from typing import IO, Any from . import Image, ImageFile, ImagePalette from ._binary import i16le as i16 @@ -72,16 +72,20 @@ class BmpImageFile(ImageFile.ImageFile): for k, v in COMPRESSIONS.items(): vars()[k] = v - def _bitmap(self, header=0, offset=0): + def _bitmap(self, header: int = 0, offset: int = 0) -> None: """Read relevant info about the BMP""" read, seek = self.fp.read, self.fp.seek if header: seek(header) # read bmp header size @offset 14 (this is part of the header size) - file_info = {"header_size": i32(read(4)), "direction": -1} + file_info: dict[str, bool | int | tuple[int, ...]] = { + "header_size": i32(read(4)), + "direction": -1, + } # -------------------- If requested, read header at a specific position # read the rest of the bmp header, without its size + assert isinstance(file_info["header_size"], int) header_data = ImageFile._safe_read(self.fp, file_info["header_size"] - 4) # ------------------------------- Windows Bitmap v2, IBM OS/2 Bitmap v1 @@ -92,7 +96,7 @@ class BmpImageFile(ImageFile.ImageFile): file_info["height"] = i16(header_data, 2) file_info["planes"] = i16(header_data, 4) file_info["bits"] = i16(header_data, 6) - file_info["compression"] = self.RAW + file_info["compression"] = self.COMPRESSIONS["RAW"] file_info["palette_padding"] = 3 # --------------------------------------------- Windows Bitmap v3 to v5 @@ -122,8 +126,9 @@ class BmpImageFile(ImageFile.ImageFile): ) file_info["colors"] = i32(header_data, 28) file_info["palette_padding"] = 4 + assert isinstance(file_info["pixels_per_meter"], tuple) self.info["dpi"] = tuple(x / 39.3701 for x in file_info["pixels_per_meter"]) - if file_info["compression"] == self.BITFIELDS: + if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]: masks = ["r_mask", "g_mask", "b_mask"] if len(header_data) >= 48: if len(header_data) >= 52: @@ -144,6 +149,10 @@ class BmpImageFile(ImageFile.ImageFile): file_info["a_mask"] = 0x0 for mask in masks: file_info[mask] = i32(read(4)) + assert isinstance(file_info["r_mask"], int) + assert isinstance(file_info["g_mask"], int) + assert isinstance(file_info["b_mask"], int) + assert isinstance(file_info["a_mask"], int) file_info["rgb_mask"] = ( file_info["r_mask"], file_info["g_mask"], @@ -164,24 +173,26 @@ class BmpImageFile(ImageFile.ImageFile): self._size = file_info["width"], file_info["height"] # ------- If color count was not found in the header, compute from bits + assert isinstance(file_info["bits"], int) file_info["colors"] = ( file_info["colors"] if file_info.get("colors", 0) else (1 << file_info["bits"]) ) + assert isinstance(file_info["colors"], int) if offset == 14 + file_info["header_size"] and file_info["bits"] <= 8: offset += 4 * file_info["colors"] # ---------------------- Check bit depth for unusual unsupported values - self._mode, raw_mode = BIT2MODE.get(file_info["bits"], (None, None)) - if self.mode is None: + self._mode, raw_mode = BIT2MODE.get(file_info["bits"], ("", "")) + if not self.mode: msg = f"Unsupported BMP pixel depth ({file_info['bits']})" raise OSError(msg) # ---------------- Process BMP with Bitfields compression (not palette) decoder_name = "raw" - if file_info["compression"] == self.BITFIELDS: - SUPPORTED = { + if file_info["compression"] == self.COMPRESSIONS["BITFIELDS"]: + SUPPORTED: dict[int, list[tuple[int, ...]]] = { 32: [ (0xFF0000, 0xFF00, 0xFF, 0x0), (0xFF000000, 0xFF0000, 0xFF00, 0x0), @@ -213,12 +224,14 @@ class BmpImageFile(ImageFile.ImageFile): file_info["bits"] == 32 and file_info["rgba_mask"] in SUPPORTED[file_info["bits"]] ): + assert isinstance(file_info["rgba_mask"], tuple) raw_mode = MASK_MODES[(file_info["bits"], file_info["rgba_mask"])] self._mode = "RGBA" if "A" in raw_mode else self.mode elif ( file_info["bits"] in (24, 16) and file_info["rgb_mask"] in SUPPORTED[file_info["bits"]] ): + assert isinstance(file_info["rgb_mask"], tuple) raw_mode = MASK_MODES[(file_info["bits"], file_info["rgb_mask"])] else: msg = "Unsupported BMP bitfields layout" @@ -226,10 +239,13 @@ class BmpImageFile(ImageFile.ImageFile): else: msg = "Unsupported BMP bitfields layout" raise OSError(msg) - elif file_info["compression"] == self.RAW: + elif file_info["compression"] == self.COMPRESSIONS["RAW"]: if file_info["bits"] == 32 and header == 22: # 32-bit .cur offset raw_mode, self._mode = "BGRA", "RGBA" - elif file_info["compression"] in (self.RLE8, self.RLE4): + elif file_info["compression"] in ( + self.COMPRESSIONS["RLE8"], + self.COMPRESSIONS["RLE4"], + ): decoder_name = "bmp_rle" else: msg = f"Unsupported BMP compression ({file_info['compression']})" @@ -242,6 +258,7 @@ class BmpImageFile(ImageFile.ImageFile): msg = f"Unsupported BMP Palette size ({file_info['colors']})" raise OSError(msg) else: + assert isinstance(file_info["palette_padding"], int) padding = file_info["palette_padding"] palette = read(padding * file_info["colors"]) grayscale = True @@ -269,10 +286,11 @@ class BmpImageFile(ImageFile.ImageFile): # ---------------------------- Finally set the tile data for the plugin self.info["compression"] = file_info["compression"] - args = [raw_mode] + args: list[Any] = [raw_mode] if decoder_name == "bmp_rle": - args.append(file_info["compression"] == self.RLE4) + args.append(file_info["compression"] == self.COMPRESSIONS["RLE4"]) else: + assert isinstance(file_info["width"], int) args.append(((file_info["width"] * file_info["bits"] + 31) >> 3) & (~3)) args.append(file_info["direction"]) self.tile = [ diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 69e7ee548..99d7e73f1 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -485,7 +485,7 @@ class Parser: self.image = im - def __enter__(self): + def __enter__(self) -> Parser: return self def __exit__(self, *args: object) -> None: @@ -580,7 +580,7 @@ def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None): encoder.cleanup() -def _safe_read(fp, size): +def _safe_read(fp: IO[bytes], size: int) -> bytes: """ Reads large blocks in a safe way. Unlike fp.read(n), this function doesn't trust the user. If the requested size is larger than @@ -601,18 +601,18 @@ def _safe_read(fp, size): msg = "Truncated File Read" raise OSError(msg) return data - data = [] + blocks: list[bytes] = [] remaining_size = size while remaining_size > 0: block = fp.read(min(remaining_size, SAFEBLOCK)) if not block: break - data.append(block) + blocks.append(block) remaining_size -= len(block) - if sum(len(d) for d in data) < size: + if sum(len(block) for block in blocks) < size: msg = "Truncated File Read" raise OSError(msg) - return b"".join(data) + return b"".join(blocks) class PyCodecState: @@ -636,7 +636,7 @@ class PyCodec: self.mode = mode self.init(args) - def init(self, args): + def init(self, args) -> None: """ Override to perform codec specific initialization @@ -653,7 +653,7 @@ class PyCodec: """ pass - def setfd(self, fd): + def setfd(self, fd) -> None: """ Called from ImageFile to set the Python file-like object @@ -793,7 +793,7 @@ class PyEncoder(PyCodec): self.fd.write(data) return bytes_consumed, errcode - def encode_to_file(self, fh, bufsize): + def encode_to_file(self, fh: IO[bytes], bufsize: int) -> int: """ :param fh: File handle. :param bufsize: Buffer size. diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 6aa70ced3..6b13e57a0 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -28,7 +28,7 @@ from __future__ import annotations import tkinter from io import BytesIO -from typing import Any +from typing import TYPE_CHECKING, Any, cast from . import Image, ImageFile @@ -61,7 +61,9 @@ def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None: return Image.open(source) -def _pyimagingtkcall(command, photo, id): +def _pyimagingtkcall( + command: str, photo: PhotoImage | tkinter.PhotoImage, id: int +) -> None: tk = photo.tk try: tk.call(command, photo, id) @@ -215,11 +217,14 @@ class BitmapImage: :param image: A PIL image. """ - def __init__(self, image=None, **kw): + def __init__(self, image: Image.Image | None = None, **kw: Any) -> None: # Tk compatibility: file or data if image is None: image = _get_image_from_kw(kw) + if image is None: + msg = "Image is required" + raise ValueError(msg) self.__mode = image.mode self.__size = image.size @@ -278,18 +283,23 @@ def getimage(photo: PhotoImage) -> Image.Image: return im -def _show(image, title): +def _show(image: Image.Image, title: str | None) -> None: """Helper for the Image.show method.""" class UI(tkinter.Label): - def __init__(self, master, im): + def __init__(self, master: tkinter.Toplevel, im: Image.Image) -> None: + self.image: BitmapImage | PhotoImage if im.mode == "1": self.image = BitmapImage(im, foreground="white", master=master) else: self.image = PhotoImage(im, master=master) - super().__init__(master, image=self.image, bg="black", bd=0) + if TYPE_CHECKING: + image = cast(tkinter._Image, self.image) + else: + image = self.image + super().__init__(master, image=image, bg="black", bd=0) - if not tkinter._default_root: + if not getattr(tkinter, "_default_root"): msg = "tkinter not initialized" raise OSError(msg) top = tkinter.Toplevel() diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 992b9ccaf..eeec41686 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -29,7 +29,7 @@ class BoxReader: and to easily step into and read sub-boxes. """ - def __init__(self, fp, length=-1): + def __init__(self, fp: IO[bytes], length: int = -1) -> None: self.fp = fp self.has_length = length >= 0 self.length = length @@ -97,7 +97,7 @@ class BoxReader: return tbox -def _parse_codestream(fp) -> tuple[tuple[int, int], str]: +def _parse_codestream(fp: IO[bytes]) -> tuple[tuple[int, int], str]: """Parse the JPEG 2000 codestream to extract the size and component count from the SIZ marker segment, returning a PIL (size, mode) tuple.""" @@ -137,7 +137,15 @@ def _res_to_dpi(num: int, denom: int, exp: int) -> float | None: return (254 * num * (10**exp)) / (10000 * denom) -def _parse_jp2_header(fp): +def _parse_jp2_header( + fp: IO[bytes], +) -> tuple[ + tuple[int, int], + str, + str | None, + tuple[float, float] | None, + ImagePalette.ImagePalette | None, +]: """Parse the JP2 header box to extract size, component count, color space information, and optionally DPI information, returning a (size, mode, mimetype, dpi) tuple.""" @@ -155,6 +163,7 @@ def _parse_jp2_header(fp): elif tbox == b"ftyp": if reader.read_fields(">4s")[0] == b"jpx ": mimetype = "image/jpx" + assert header is not None size = None mode = None @@ -168,6 +177,9 @@ def _parse_jp2_header(fp): if tbox == b"ihdr": height, width, nc, bpc = header.read_fields(">IIHB") + assert isinstance(height, int) + assert isinstance(width, int) + assert isinstance(bpc, int) size = (width, height) if nc == 1 and (bpc & 0x7F) > 8: mode = "I;16" @@ -185,11 +197,21 @@ def _parse_jp2_header(fp): mode = "CMYK" elif tbox == b"pclr" and mode in ("L", "LA"): ne, npc = header.read_fields(">HB") - bitdepths = header.read_fields(">" + ("B" * npc)) - if max(bitdepths) <= 8: + assert isinstance(ne, int) + assert isinstance(npc, int) + max_bitdepth = 0 + for bitdepth in header.read_fields(">" + ("B" * npc)): + assert isinstance(bitdepth, int) + if bitdepth > max_bitdepth: + max_bitdepth = bitdepth + if max_bitdepth <= 8: palette = ImagePalette.ImagePalette() for i in range(ne): - palette.getcolor(header.read_fields(">" + ("B" * npc))) + color: list[int] = [] + for value in header.read_fields(">" + ("B" * npc)): + assert isinstance(value, int) + color.append(value) + palette.getcolor(tuple(color)) mode = "P" if mode == "L" else "PA" elif tbox == b"res ": res = header.read_boxes() @@ -197,6 +219,12 @@ def _parse_jp2_header(fp): tres = res.next_box_type() if tres == b"resc": vrcn, vrcd, hrcn, hrcd, vrce, hrce = res.read_fields(">HHHHBB") + assert isinstance(vrcn, int) + assert isinstance(vrcd, int) + assert isinstance(hrcn, int) + assert isinstance(hrcd, int) + assert isinstance(vrce, int) + assert isinstance(hrce, int) hres = _res_to_dpi(hrcn, hrcd, hrce) vres = _res_to_dpi(vrcn, vrcd, vrce) if hres is not None and vres is not None: diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index b15bf06d2..4916727be 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -60,7 +60,7 @@ def Skip(self: JpegImageFile, marker: int) -> None: ImageFile._safe_read(self.fp, n) -def APP(self, marker): +def APP(self: JpegImageFile, marker: int) -> None: # # Application marker. Store these in the APP dictionary. # Also look for well-known application markers. @@ -133,13 +133,14 @@ def APP(self, marker): offset += 4 data = s[offset : offset + size] if code == 0x03ED: # ResolutionInfo - data = { + photoshop[code] = { "XResolution": i32(data, 0) / 65536, "DisplayedUnitsX": i16(data, 4), "YResolution": i32(data, 8) / 65536, "DisplayedUnitsY": i16(data, 12), } - photoshop[code] = data + else: + photoshop[code] = data offset += size offset += offset & 1 # align except struct.error: @@ -338,6 +339,7 @@ class JpegImageFile(ImageFile.ImageFile): # Create attributes self.bits = self.layers = 0 + self._exif_offset = 0 # JPEG specifics (internal) self.layer = [] @@ -498,17 +500,17 @@ class JpegImageFile(ImageFile.ImageFile): ): self.info["dpi"] = 72, 72 - def _getmp(self): + def _getmp(self) -> dict[int, Any] | None: return _getmp(self) -def _getexif(self) -> dict[str, Any] | None: +def _getexif(self: JpegImageFile) -> dict[str, Any] | None: if "exif" not in self.info: return None return self.getexif()._get_merged_dict() -def _getmp(self): +def _getmp(self: JpegImageFile) -> dict[int, Any] | None: # Extract MP information. This method was inspired by the "highly # experimental" _getexif version that's been in use for years now, # itself based on the ImageFileDirectory class in the TIFF plugin. @@ -616,7 +618,7 @@ samplings = { # fmt: on -def get_sampling(im): +def get_sampling(im: Image.Image) -> int: # There's no subsampling when images have only 1 layer # (grayscale images) or when they are CMYK (4 layers), # so set subsampling to the default value. @@ -624,7 +626,7 @@ def get_sampling(im): # NOTE: currently Pillow can't encode JPEG to YCCK format. # If YCCK support is added in the future, subsampling code will have # to be updated (here and in JpegEncode.c) to deal with 4 layers. - if not hasattr(im, "layers") or im.layers in (1, 4): + if not isinstance(im, JpegImageFile) or im.layers in (1, 4): return -1 sampling = im.layer[0][1:3] + im.layer[1][1:3] + im.layer[2][1:3] return samplings.get(sampling, -1) diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index f21570661..5ed9f56a1 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -22,7 +22,7 @@ from __future__ import annotations import itertools import os import struct -from typing import IO +from typing import IO, Any, cast from . import ( Image, @@ -101,8 +101,11 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): JpegImagePlugin.JpegImageFile._open(self) self._after_jpeg_open() - def _after_jpeg_open(self, mpheader=None): + def _after_jpeg_open(self, mpheader: dict[int, Any] | None = None) -> None: self.mpinfo = mpheader if mpheader is not None else self._getmp() + if self.mpinfo is None: + msg = "Image appears to be a malformed MPO file" + raise ValueError(msg) self.n_frames = self.mpinfo[0xB001] self.__mpoffsets = [ mpent["DataOffset"] + self.info["mpoffset"] for mpent in self.mpinfo[0xB002] @@ -149,7 +152,10 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): return self.__frame @staticmethod - def adopt(jpeg_instance, mpheader=None): + def adopt( + jpeg_instance: JpegImagePlugin.JpegImageFile, + mpheader: dict[int, Any] | None = None, + ) -> MpoImageFile: """ Transform the instance of JpegImageFile into an instance of MpoImageFile. @@ -161,8 +167,9 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): double call to _open. """ jpeg_instance.__class__ = MpoImageFile - jpeg_instance._after_jpeg_open(mpheader) - return jpeg_instance + mpo_instance = cast(MpoImageFile, jpeg_instance) + mpo_instance._after_jpeg_open(mpheader) + return mpo_instance # --------------------------------------------------------------------- diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 34ea77c5e..fa117d19a 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -230,6 +230,7 @@ class ChunkStream: cids = [] + assert self.fp is not None while True: try: cid, pos, length = self.read() @@ -407,6 +408,7 @@ class PngStream(ChunkStream): def chunk_iCCP(self, pos: int, length: int) -> bytes: # ICC profile + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) # according to PNG spec, the iCCP chunk contains: # Profile name 1-79 bytes (character string) @@ -434,6 +436,7 @@ class PngStream(ChunkStream): def chunk_IHDR(self, pos: int, length: int) -> bytes: # image header + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if length < 13: if ImageFile.LOAD_TRUNCATED_IMAGES: @@ -471,6 +474,7 @@ class PngStream(ChunkStream): def chunk_PLTE(self, pos: int, length: int) -> bytes: # palette + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if self.im_mode == "P": self.im_palette = "RGB", s @@ -478,6 +482,7 @@ class PngStream(ChunkStream): def chunk_tRNS(self, pos: int, length: int) -> bytes: # transparency + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if self.im_mode == "P": if _simple_palette.match(s): @@ -498,6 +503,7 @@ class PngStream(ChunkStream): def chunk_gAMA(self, pos: int, length: int) -> bytes: # gamma setting + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) self.im_info["gamma"] = i32(s) / 100000.0 return s @@ -506,6 +512,7 @@ class PngStream(ChunkStream): # chromaticity, 8 unsigned ints, actual value is scaled by 100,000 # WP x,y, Red x,y, Green x,y Blue x,y + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) raw_vals = struct.unpack(">%dI" % (len(s) // 4), s) self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals) @@ -518,6 +525,7 @@ class PngStream(ChunkStream): # 2 saturation # 3 absolute colorimetric + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if length < 1: if ImageFile.LOAD_TRUNCATED_IMAGES: @@ -529,6 +537,7 @@ class PngStream(ChunkStream): def chunk_pHYs(self, pos: int, length: int) -> bytes: # pixels per unit + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if length < 9: if ImageFile.LOAD_TRUNCATED_IMAGES: @@ -546,6 +555,7 @@ class PngStream(ChunkStream): def chunk_tEXt(self, pos: int, length: int) -> bytes: # text + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) try: k, v = s.split(b"\0", 1) @@ -554,17 +564,18 @@ class PngStream(ChunkStream): k = s v = b"" if k: - k = k.decode("latin-1", "strict") + k_str = k.decode("latin-1", "strict") v_str = v.decode("latin-1", "replace") - self.im_info[k] = v if k == "exif" else v_str - self.im_text[k] = v_str + self.im_info[k_str] = v if k == b"exif" else v_str + self.im_text[k_str] = v_str self.check_text_memory(len(v_str)) return s def chunk_zTXt(self, pos: int, length: int) -> bytes: # compressed text + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) try: k, v = s.split(b"\0", 1) @@ -589,16 +600,17 @@ class PngStream(ChunkStream): v = b"" if k: - k = k.decode("latin-1", "strict") - v = v.decode("latin-1", "replace") + k_str = k.decode("latin-1", "strict") + v_str = v.decode("latin-1", "replace") - self.im_info[k] = self.im_text[k] = v - self.check_text_memory(len(v)) + self.im_info[k_str] = self.im_text[k_str] = v_str + self.check_text_memory(len(v_str)) return s def chunk_iTXt(self, pos: int, length: int) -> bytes: # international text + assert self.fp is not None r = s = ImageFile._safe_read(self.fp, length) try: k, r = r.split(b"\0", 1) @@ -627,25 +639,27 @@ class PngStream(ChunkStream): if k == b"XML:com.adobe.xmp": self.im_info["xmp"] = v try: - k = k.decode("latin-1", "strict") - lang = lang.decode("utf-8", "strict") - tk = tk.decode("utf-8", "strict") - v = v.decode("utf-8", "strict") + k_str = k.decode("latin-1", "strict") + lang_str = lang.decode("utf-8", "strict") + tk_str = tk.decode("utf-8", "strict") + v_str = v.decode("utf-8", "strict") except UnicodeError: return s - self.im_info[k] = self.im_text[k] = iTXt(v, lang, tk) - self.check_text_memory(len(v)) + self.im_info[k_str] = self.im_text[k_str] = iTXt(v_str, lang_str, tk_str) + self.check_text_memory(len(v_str)) return s def chunk_eXIf(self, pos: int, length: int) -> bytes: + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) self.im_info["exif"] = b"Exif\x00\x00" + s return s # APNG chunks def chunk_acTL(self, pos: int, length: int) -> bytes: + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if length < 8: if ImageFile.LOAD_TRUNCATED_IMAGES: @@ -666,6 +680,7 @@ class PngStream(ChunkStream): return s def chunk_fcTL(self, pos: int, length: int) -> bytes: + assert self.fp is not None s = ImageFile._safe_read(self.fp, length) if length < 26: if ImageFile.LOAD_TRUNCATED_IMAGES: @@ -695,6 +710,7 @@ class PngStream(ChunkStream): return s def chunk_fdAT(self, pos: int, length: int) -> bytes: + assert self.fp is not None if length < 4: if ImageFile.LOAD_TRUNCATED_IMAGES: s = ImageFile._safe_read(self.fp, length) diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index edf698bf0..31dfd4d12 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -185,7 +185,7 @@ def _layerinfo(fp, ct_bytes): # read layerinfo block layers = [] - def read(size): + def read(size: int) -> bytes: return ImageFile._safe_read(fp, size) ct = si16(read(2)) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 530b88c8b..011de9c6a 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -115,7 +115,7 @@ class WebPImageFile(ImageFile.ImageFile): self.__loaded = -1 self.__timestamp = 0 - def _get_next(self): + def _get_next(self) -> tuple[bytes, int, int]: # Get next frame ret = self._decoder.get_next() self.__physical_frame += 1 diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 3d5cddcc8..68f8a74f5 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -152,7 +152,7 @@ class WmfStubImageFile(ImageFile.StubImageFile): def _load(self) -> ImageFile.StubHandler | None: return _handler - def load(self, dpi=None): + def load(self, dpi: int | None = None) -> Image.core.PixelAccess | None: if dpi is not None and self._inch is not None: self.info["dpi"] = dpi x0, y0, x1, y1 = self.info["wmf_bbox"] diff --git a/src/PIL/_imagingtk.pyi b/src/PIL/_imagingtk.pyi new file mode 100644 index 000000000..e27843e53 --- /dev/null +++ b/src/PIL/_imagingtk.pyi @@ -0,0 +1,3 @@ +from typing import Any + +def __getattr__(name: str) -> Any: ... From 69f967301d11a5b37495542ba64633b085c9b357 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 2 Jul 2024 17:48:46 +0300 Subject: [PATCH 09/61] Call PyUnstable_Module_SetGIL() to indicate support of running with GIL disabled --- src/_imaging.c | 4 ++++ src/_imagingcms.c | 4 ++++ src/_imagingft.c | 4 ++++ src/_imagingmath.c | 4 ++++ src/_imagingmorph.c | 4 ++++ src/_imagingtk.c | 5 +++++ src/_webp.c | 4 ++++ 7 files changed, 29 insertions(+) diff --git a/src/_imaging.c b/src/_imaging.c index ddc8d2885..03e10e547 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -4448,5 +4448,9 @@ PyInit__imaging(void) { return NULL; } +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); +#endif + return m; } diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 590e1b983..628662b30 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -1538,5 +1538,9 @@ PyInit__imagingcms(void) { PyDateTime_IMPORT; +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); +#endif + return m; } diff --git a/src/_imagingft.c b/src/_imagingft.c index ba36cc72c..1bef876e1 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1576,5 +1576,9 @@ PyInit__imagingft(void) { return NULL; } +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); +#endif + return m; } diff --git a/src/_imagingmath.c b/src/_imagingmath.c index 067c165b2..a2ddc91b9 100644 --- a/src/_imagingmath.c +++ b/src/_imagingmath.c @@ -290,5 +290,9 @@ PyInit__imagingmath(void) { return NULL; } +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); +#endif + return m; } diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index 8815c2b7e..a95ce75bf 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -269,5 +269,9 @@ PyInit__imagingmorph(void) { m = PyModule_Create(&module_def); +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); +#endif + return m; } diff --git a/src/_imagingtk.c b/src/_imagingtk.c index efa7fc1b6..c70d044bb 100644 --- a/src/_imagingtk.c +++ b/src/_imagingtk.c @@ -62,5 +62,10 @@ PyInit__imagingtk(void) { Py_DECREF(m); return NULL; } + +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); +#endif + return m; } diff --git a/src/_webp.c b/src/_webp.c index 0a70e3357..dfa24da41 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -1005,5 +1005,9 @@ PyInit__webp(void) { return NULL; } +#ifdef Py_GIL_DISABLED + PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); +#endif + return m; } From 377bdc0db1415dcbd664b1b9c467d995500809d2 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:27:09 +0300 Subject: [PATCH 10/61] encode: Replace PyList_GetItem with PyList_GetItemRef --- src/encode.c | 5 +- src/thirdparty/pythoncapi_compat.h | 1360 ++++++++++++++++++++++++++++ 2 files changed, 1363 insertions(+), 2 deletions(-) create mode 100644 src/thirdparty/pythoncapi_compat.h diff --git a/src/encode.c b/src/encode.c index 442b5d04f..72ad3fa07 100644 --- a/src/encode.c +++ b/src/encode.c @@ -25,6 +25,7 @@ #define PY_SSIZE_T_CLEAN #include "Python.h" +#include "thirdparty/pythoncapi_compat.h" #include "libImaging/Imaging.h" #include "libImaging/Gif.h" @@ -671,7 +672,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { tags_size = PyList_Size(tags); TRACE(("tags size: %d\n", (int)tags_size)); for (pos = 0; pos < tags_size; pos++) { - item = PyList_GetItem(tags, pos); + item = PyList_GetItemRef(tags, pos); if (!PyTuple_Check(item) || PyTuple_Size(item) != 2) { PyErr_SetString(PyExc_ValueError, "Invalid tags list"); return NULL; @@ -703,7 +704,7 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { num_core_tags = sizeof(core_tags) / sizeof(int); for (pos = 0; pos < tags_size; pos++) { - item = PyList_GetItem(tags, pos); + item = PyList_GetItemRef(tags, pos); // We already checked that tags is a 2-tuple list. key = PyTuple_GetItem(item, 0); key_int = (int)PyLong_AsLong(key); diff --git a/src/thirdparty/pythoncapi_compat.h b/src/thirdparty/pythoncapi_compat.h new file mode 100644 index 000000000..51e8c0de7 --- /dev/null +++ b/src/thirdparty/pythoncapi_compat.h @@ -0,0 +1,1360 @@ +// Header file providing new C API functions to old Python versions. +// +// File distributed under the Zero Clause BSD (0BSD) license. +// Copyright Contributors to the pythoncapi_compat project. +// +// Homepage: +// https://github.com/python/pythoncapi_compat +// +// Latest version: +// https://raw.githubusercontent.com/python/pythoncapi_compat/master/pythoncapi_compat.h +// +// SPDX-License-Identifier: 0BSD + +#ifndef PYTHONCAPI_COMPAT +#define PYTHONCAPI_COMPAT + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +// Python 3.11.0b4 added PyFrame_Back() to Python.h +#if PY_VERSION_HEX < 0x030b00B4 && !defined(PYPY_VERSION) +# include "frameobject.h" // PyFrameObject, PyFrame_GetBack() +#endif + + +#ifndef _Py_CAST +# define _Py_CAST(type, expr) ((type)(expr)) +#endif + +// Static inline functions should use _Py_NULL rather than using directly NULL +// to prevent C++ compiler warnings. On C23 and newer and on C++11 and newer, +// _Py_NULL is defined as nullptr. +#if (defined (__STDC_VERSION__) && __STDC_VERSION__ > 201710L) \ + || (defined(__cplusplus) && __cplusplus >= 201103) +# define _Py_NULL nullptr +#else +# define _Py_NULL NULL +#endif + +// Cast argument to PyObject* type. +#ifndef _PyObject_CAST +# define _PyObject_CAST(op) _Py_CAST(PyObject*, op) +#endif + + +// bpo-42262 added Py_NewRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef) +static inline PyObject* _Py_NewRef(PyObject *obj) +{ + Py_INCREF(obj); + return obj; +} +#define Py_NewRef(obj) _Py_NewRef(_PyObject_CAST(obj)) +#endif + + +// bpo-42262 added Py_XNewRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_XNewRef) +static inline PyObject* _Py_XNewRef(PyObject *obj) +{ + Py_XINCREF(obj); + return obj; +} +#define Py_XNewRef(obj) _Py_XNewRef(_PyObject_CAST(obj)) +#endif + + +// bpo-39573 added Py_SET_REFCNT() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_REFCNT) +static inline void _Py_SET_REFCNT(PyObject *ob, Py_ssize_t refcnt) +{ + ob->ob_refcnt = refcnt; +} +#define Py_SET_REFCNT(ob, refcnt) _Py_SET_REFCNT(_PyObject_CAST(ob), refcnt) +#endif + + +// Py_SETREF() and Py_XSETREF() were added to Python 3.5.2. +// It is excluded from the limited C API. +#if (PY_VERSION_HEX < 0x03050200 && !defined(Py_SETREF)) && !defined(Py_LIMITED_API) +#define Py_SETREF(dst, src) \ + do { \ + PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \ + PyObject *_tmp_dst = (*_tmp_dst_ptr); \ + *_tmp_dst_ptr = _PyObject_CAST(src); \ + Py_DECREF(_tmp_dst); \ + } while (0) + +#define Py_XSETREF(dst, src) \ + do { \ + PyObject **_tmp_dst_ptr = _Py_CAST(PyObject**, &(dst)); \ + PyObject *_tmp_dst = (*_tmp_dst_ptr); \ + *_tmp_dst_ptr = _PyObject_CAST(src); \ + Py_XDECREF(_tmp_dst); \ + } while (0) +#endif + + +// bpo-43753 added Py_Is(), Py_IsNone(), Py_IsTrue() and Py_IsFalse() +// to Python 3.10.0b1. +#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_Is) +# define Py_Is(x, y) ((x) == (y)) +#endif +#if PY_VERSION_HEX < 0x030A00B1 && !defined(Py_IsNone) +# define Py_IsNone(x) Py_Is(x, Py_None) +#endif +#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && !defined(Py_IsTrue) +# define Py_IsTrue(x) Py_Is(x, Py_True) +#endif +#if (PY_VERSION_HEX < 0x030A00B1 || defined(PYPY_VERSION)) && !defined(Py_IsFalse) +# define Py_IsFalse(x) Py_Is(x, Py_False) +#endif + + +// bpo-39573 added Py_SET_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_TYPE) +static inline void _Py_SET_TYPE(PyObject *ob, PyTypeObject *type) +{ + ob->ob_type = type; +} +#define Py_SET_TYPE(ob, type) _Py_SET_TYPE(_PyObject_CAST(ob), type) +#endif + + +// bpo-39573 added Py_SET_SIZE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_SET_SIZE) +static inline void _Py_SET_SIZE(PyVarObject *ob, Py_ssize_t size) +{ + ob->ob_size = size; +} +#define Py_SET_SIZE(ob, size) _Py_SET_SIZE((PyVarObject*)(ob), size) +#endif + + +// bpo-40421 added PyFrame_GetCode() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 || defined(PYPY_VERSION) +static inline PyCodeObject* PyFrame_GetCode(PyFrameObject *frame) +{ + assert(frame != _Py_NULL); + assert(frame->f_code != _Py_NULL); + return _Py_CAST(PyCodeObject*, Py_NewRef(frame->f_code)); +} +#endif + +static inline PyCodeObject* _PyFrame_GetCodeBorrow(PyFrameObject *frame) +{ + PyCodeObject *code = PyFrame_GetCode(frame); + Py_DECREF(code); + return code; +} + + +// bpo-40421 added PyFrame_GetBack() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION) +static inline PyFrameObject* PyFrame_GetBack(PyFrameObject *frame) +{ + assert(frame != _Py_NULL); + return _Py_CAST(PyFrameObject*, Py_XNewRef(frame->f_back)); +} +#endif + +#if !defined(PYPY_VERSION) +static inline PyFrameObject* _PyFrame_GetBackBorrow(PyFrameObject *frame) +{ + PyFrameObject *back = PyFrame_GetBack(frame); + Py_XDECREF(back); + return back; +} +#endif + + +// bpo-40421 added PyFrame_GetLocals() to Python 3.11.0a7 +#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +static inline PyObject* PyFrame_GetLocals(PyFrameObject *frame) +{ +#if PY_VERSION_HEX >= 0x030400B1 + if (PyFrame_FastToLocalsWithError(frame) < 0) { + return NULL; + } +#else + PyFrame_FastToLocals(frame); +#endif + return Py_NewRef(frame->f_locals); +} +#endif + + +// bpo-40421 added PyFrame_GetGlobals() to Python 3.11.0a7 +#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +static inline PyObject* PyFrame_GetGlobals(PyFrameObject *frame) +{ + return Py_NewRef(frame->f_globals); +} +#endif + + +// bpo-40421 added PyFrame_GetBuiltins() to Python 3.11.0a7 +#if PY_VERSION_HEX < 0x030B00A7 && !defined(PYPY_VERSION) +static inline PyObject* PyFrame_GetBuiltins(PyFrameObject *frame) +{ + return Py_NewRef(frame->f_builtins); +} +#endif + + +// bpo-40421 added PyFrame_GetLasti() to Python 3.11.0b1 +#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION) +static inline int PyFrame_GetLasti(PyFrameObject *frame) +{ +#if PY_VERSION_HEX >= 0x030A00A7 + // bpo-27129: Since Python 3.10.0a7, f_lasti is an instruction offset, + // not a bytes offset anymore. Python uses 16-bit "wordcode" (2 bytes) + // instructions. + if (frame->f_lasti < 0) { + return -1; + } + return frame->f_lasti * 2; +#else + return frame->f_lasti; +#endif +} +#endif + + +// gh-91248 added PyFrame_GetVar() to Python 3.12.0a2 +#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION) +static inline PyObject* PyFrame_GetVar(PyFrameObject *frame, PyObject *name) +{ + PyObject *locals, *value; + + locals = PyFrame_GetLocals(frame); + if (locals == NULL) { + return NULL; + } +#if PY_VERSION_HEX >= 0x03000000 + value = PyDict_GetItemWithError(locals, name); +#else + value = _PyDict_GetItemWithError(locals, name); +#endif + Py_DECREF(locals); + + if (value == NULL) { + if (PyErr_Occurred()) { + return NULL; + } +#if PY_VERSION_HEX >= 0x03000000 + PyErr_Format(PyExc_NameError, "variable %R does not exist", name); +#else + PyErr_SetString(PyExc_NameError, "variable does not exist"); +#endif + return NULL; + } + return Py_NewRef(value); +} +#endif + + +// gh-91248 added PyFrame_GetVarString() to Python 3.12.0a2 +#if PY_VERSION_HEX < 0x030C00A2 && !defined(PYPY_VERSION) +static inline PyObject* +PyFrame_GetVarString(PyFrameObject *frame, const char *name) +{ + PyObject *name_obj, *value; +#if PY_VERSION_HEX >= 0x03000000 + name_obj = PyUnicode_FromString(name); +#else + name_obj = PyString_FromString(name); +#endif + if (name_obj == NULL) { + return NULL; + } + value = PyFrame_GetVar(frame, name_obj); + Py_DECREF(name_obj); + return value; +} +#endif + + +// bpo-39947 added PyThreadState_GetInterpreter() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) +static inline PyInterpreterState * +PyThreadState_GetInterpreter(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return tstate->interp; +} +#endif + + +// bpo-40429 added PyThreadState_GetFrame() to Python 3.9.0b1 +#if PY_VERSION_HEX < 0x030900B1 && !defined(PYPY_VERSION) +static inline PyFrameObject* PyThreadState_GetFrame(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return _Py_CAST(PyFrameObject *, Py_XNewRef(tstate->frame)); +} +#endif + +#if !defined(PYPY_VERSION) +static inline PyFrameObject* +_PyThreadState_GetFrameBorrow(PyThreadState *tstate) +{ + PyFrameObject *frame = PyThreadState_GetFrame(tstate); + Py_XDECREF(frame); + return frame; +} +#endif + + +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 || defined(PYPY_VERSION) +static inline PyInterpreterState* PyInterpreterState_Get(void) +{ + PyThreadState *tstate; + PyInterpreterState *interp; + + tstate = PyThreadState_GET(); + if (tstate == _Py_NULL) { + Py_FatalError("GIL released (tstate is NULL)"); + } + interp = tstate->interp; + if (interp == _Py_NULL) { + Py_FatalError("no current interpreter"); + } + return interp; +} +#endif + + +// bpo-39947 added PyInterpreterState_Get() to Python 3.9.0a6 +#if 0x030700A1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION) +static inline uint64_t PyThreadState_GetID(PyThreadState *tstate) +{ + assert(tstate != _Py_NULL); + return tstate->id; +} +#endif + +// bpo-43760 added PyThreadState_EnterTracing() to Python 3.11.0a2 +#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) +static inline void PyThreadState_EnterTracing(PyThreadState *tstate) +{ + tstate->tracing++; +#if PY_VERSION_HEX >= 0x030A00A1 + tstate->cframe->use_tracing = 0; +#else + tstate->use_tracing = 0; +#endif +} +#endif + +// bpo-43760 added PyThreadState_LeaveTracing() to Python 3.11.0a2 +#if PY_VERSION_HEX < 0x030B00A2 && !defined(PYPY_VERSION) +static inline void PyThreadState_LeaveTracing(PyThreadState *tstate) +{ + int use_tracing = (tstate->c_tracefunc != _Py_NULL + || tstate->c_profilefunc != _Py_NULL); + tstate->tracing--; +#if PY_VERSION_HEX >= 0x030A00A1 + tstate->cframe->use_tracing = use_tracing; +#else + tstate->use_tracing = use_tracing; +#endif +} +#endif + + +// bpo-37194 added PyObject_CallNoArgs() to Python 3.9.0a1 +// PyObject_CallNoArgs() added to PyPy 3.9.16-v7.3.11 +#if !defined(PyObject_CallNoArgs) && PY_VERSION_HEX < 0x030900A1 +static inline PyObject* PyObject_CallNoArgs(PyObject *func) +{ + return PyObject_CallFunctionObjArgs(func, NULL); +} +#endif + + +// bpo-39245 made PyObject_CallOneArg() public (previously called +// _PyObject_CallOneArg) in Python 3.9.0a4 +// PyObject_CallOneArg() added to PyPy 3.9.16-v7.3.11 +#if !defined(PyObject_CallOneArg) && PY_VERSION_HEX < 0x030900A4 +static inline PyObject* PyObject_CallOneArg(PyObject *func, PyObject *arg) +{ + return PyObject_CallFunctionObjArgs(func, arg, NULL); +} +#endif + + +// bpo-1635741 added PyModule_AddObjectRef() to Python 3.10.0a3 +#if PY_VERSION_HEX < 0x030A00A3 +static inline int +PyModule_AddObjectRef(PyObject *module, const char *name, PyObject *value) +{ + int res; + + if (!value && !PyErr_Occurred()) { + // PyModule_AddObject() raises TypeError in this case + PyErr_SetString(PyExc_SystemError, + "PyModule_AddObjectRef() must be called " + "with an exception raised if value is NULL"); + return -1; + } + + Py_XINCREF(value); + res = PyModule_AddObject(module, name, value); + if (res < 0) { + Py_XDECREF(value); + } + return res; +} +#endif + + +// bpo-40024 added PyModule_AddType() to Python 3.9.0a5 +#if PY_VERSION_HEX < 0x030900A5 +static inline int PyModule_AddType(PyObject *module, PyTypeObject *type) +{ + const char *name, *dot; + + if (PyType_Ready(type) < 0) { + return -1; + } + + // inline _PyType_Name() + name = type->tp_name; + assert(name != _Py_NULL); + dot = strrchr(name, '.'); + if (dot != _Py_NULL) { + name = dot + 1; + } + + return PyModule_AddObjectRef(module, name, _PyObject_CAST(type)); +} +#endif + + +// bpo-40241 added PyObject_GC_IsTracked() to Python 3.9.0a6. +// bpo-4688 added _PyObject_GC_IS_TRACKED() to Python 2.7.0a2. +#if PY_VERSION_HEX < 0x030900A6 && !defined(PYPY_VERSION) +static inline int PyObject_GC_IsTracked(PyObject* obj) +{ + return (PyObject_IS_GC(obj) && _PyObject_GC_IS_TRACKED(obj)); +} +#endif + +// bpo-40241 added PyObject_GC_IsFinalized() to Python 3.9.0a6. +// bpo-18112 added _PyGCHead_FINALIZED() to Python 3.4.0 final. +#if PY_VERSION_HEX < 0x030900A6 && PY_VERSION_HEX >= 0x030400F0 && !defined(PYPY_VERSION) +static inline int PyObject_GC_IsFinalized(PyObject *obj) +{ + PyGC_Head *gc = _Py_CAST(PyGC_Head*, obj) - 1; + return (PyObject_IS_GC(obj) && _PyGCHead_FINALIZED(gc)); +} +#endif + + +// bpo-39573 added Py_IS_TYPE() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 && !defined(Py_IS_TYPE) +static inline int _Py_IS_TYPE(PyObject *ob, PyTypeObject *type) { + return Py_TYPE(ob) == type; +} +#define Py_IS_TYPE(ob, type) _Py_IS_TYPE(_PyObject_CAST(ob), type) +#endif + + +// bpo-46906 added PyFloat_Pack2() and PyFloat_Unpack2() to Python 3.11a7. +// bpo-11734 added _PyFloat_Pack2() and _PyFloat_Unpack2() to Python 3.6.0b1. +// Python 3.11a2 moved _PyFloat_Pack2() and _PyFloat_Unpack2() to the internal +// C API: Python 3.11a2-3.11a6 versions are not supported. +#if 0x030600B1 <= PY_VERSION_HEX && PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) +static inline int PyFloat_Pack2(double x, char *p, int le) +{ return _PyFloat_Pack2(x, (unsigned char*)p, le); } + +static inline double PyFloat_Unpack2(const char *p, int le) +{ return _PyFloat_Unpack2((const unsigned char *)p, le); } +#endif + + +// bpo-46906 added PyFloat_Pack4(), PyFloat_Pack8(), PyFloat_Unpack4() and +// PyFloat_Unpack8() to Python 3.11a7. +// Python 3.11a2 moved _PyFloat_Pack4(), _PyFloat_Pack8(), _PyFloat_Unpack4() +// and _PyFloat_Unpack8() to the internal C API: Python 3.11a2-3.11a6 versions +// are not supported. +#if PY_VERSION_HEX <= 0x030B00A1 && !defined(PYPY_VERSION) +static inline int PyFloat_Pack4(double x, char *p, int le) +{ return _PyFloat_Pack4(x, (unsigned char*)p, le); } + +static inline int PyFloat_Pack8(double x, char *p, int le) +{ return _PyFloat_Pack8(x, (unsigned char*)p, le); } + +static inline double PyFloat_Unpack4(const char *p, int le) +{ return _PyFloat_Unpack4((const unsigned char *)p, le); } + +static inline double PyFloat_Unpack8(const char *p, int le) +{ return _PyFloat_Unpack8((const unsigned char *)p, le); } +#endif + + +// gh-92154 added PyCode_GetCode() to Python 3.11.0b1 +#if PY_VERSION_HEX < 0x030B00B1 && !defined(PYPY_VERSION) +static inline PyObject* PyCode_GetCode(PyCodeObject *code) +{ + return Py_NewRef(code->co_code); +} +#endif + + +// gh-95008 added PyCode_GetVarnames() to Python 3.11.0rc1 +#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION) +static inline PyObject* PyCode_GetVarnames(PyCodeObject *code) +{ + return Py_NewRef(code->co_varnames); +} +#endif + +// gh-95008 added PyCode_GetFreevars() to Python 3.11.0rc1 +#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION) +static inline PyObject* PyCode_GetFreevars(PyCodeObject *code) +{ + return Py_NewRef(code->co_freevars); +} +#endif + +// gh-95008 added PyCode_GetCellvars() to Python 3.11.0rc1 +#if PY_VERSION_HEX < 0x030B00C1 && !defined(PYPY_VERSION) +static inline PyObject* PyCode_GetCellvars(PyCodeObject *code) +{ + return Py_NewRef(code->co_cellvars); +} +#endif + + +// Py_UNUSED() was added to Python 3.4.0b2. +#if PY_VERSION_HEX < 0x030400B2 && !defined(Py_UNUSED) +# if defined(__GNUC__) || defined(__clang__) +# define Py_UNUSED(name) _unused_ ## name __attribute__((unused)) +# else +# define Py_UNUSED(name) _unused_ ## name +# endif +#endif + + +// gh-105922 added PyImport_AddModuleRef() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A0 +static inline PyObject* PyImport_AddModuleRef(const char *name) +{ + return Py_XNewRef(PyImport_AddModule(name)); +} +#endif + + +// gh-105927 added PyWeakref_GetRef() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D0000 +static inline int PyWeakref_GetRef(PyObject *ref, PyObject **pobj) +{ + PyObject *obj; + if (ref != NULL && !PyWeakref_Check(ref)) { + *pobj = NULL; + PyErr_SetString(PyExc_TypeError, "expected a weakref"); + return -1; + } + obj = PyWeakref_GetObject(ref); + if (obj == NULL) { + // SystemError if ref is NULL + *pobj = NULL; + return -1; + } + if (obj == Py_None) { + *pobj = NULL; + return 0; + } + *pobj = Py_NewRef(obj); + return (*pobj != NULL); +} +#endif + + +// bpo-36974 added PY_VECTORCALL_ARGUMENTS_OFFSET to Python 3.8b1 +#ifndef PY_VECTORCALL_ARGUMENTS_OFFSET +# define PY_VECTORCALL_ARGUMENTS_OFFSET (_Py_CAST(size_t, 1) << (8 * sizeof(size_t) - 1)) +#endif + +// bpo-36974 added PyVectorcall_NARGS() to Python 3.8b1 +#if PY_VERSION_HEX < 0x030800B1 +static inline Py_ssize_t PyVectorcall_NARGS(size_t n) +{ + return n & ~PY_VECTORCALL_ARGUMENTS_OFFSET; +} +#endif + + +// gh-105922 added PyObject_Vectorcall() to Python 3.9.0a4 +#if PY_VERSION_HEX < 0x030900A4 +static inline PyObject* +PyObject_Vectorcall(PyObject *callable, PyObject *const *args, + size_t nargsf, PyObject *kwnames) +{ +#if PY_VERSION_HEX >= 0x030800B1 && !defined(PYPY_VERSION) + // bpo-36974 added _PyObject_Vectorcall() to Python 3.8.0b1 + return _PyObject_Vectorcall(callable, args, nargsf, kwnames); +#else + PyObject *posargs = NULL, *kwargs = NULL; + PyObject *res; + Py_ssize_t nposargs, nkwargs, i; + + if (nargsf != 0 && args == NULL) { + PyErr_BadInternalCall(); + goto error; + } + if (kwnames != NULL && !PyTuple_Check(kwnames)) { + PyErr_BadInternalCall(); + goto error; + } + + nposargs = (Py_ssize_t)PyVectorcall_NARGS(nargsf); + if (kwnames) { + nkwargs = PyTuple_GET_SIZE(kwnames); + } + else { + nkwargs = 0; + } + + posargs = PyTuple_New(nposargs); + if (posargs == NULL) { + goto error; + } + if (nposargs) { + for (i=0; i < nposargs; i++) { + PyTuple_SET_ITEM(posargs, i, Py_NewRef(*args)); + args++; + } + } + + if (nkwargs) { + kwargs = PyDict_New(); + if (kwargs == NULL) { + goto error; + } + + for (i = 0; i < nkwargs; i++) { + PyObject *key = PyTuple_GET_ITEM(kwnames, i); + PyObject *value = *args; + args++; + if (PyDict_SetItem(kwargs, key, value) < 0) { + goto error; + } + } + } + else { + kwargs = NULL; + } + + res = PyObject_Call(callable, posargs, kwargs); + Py_DECREF(posargs); + Py_XDECREF(kwargs); + return res; + +error: + Py_DECREF(posargs); + Py_XDECREF(kwargs); + return NULL; +#endif +} +#endif + + +// gh-106521 added PyObject_GetOptionalAttr() and +// PyObject_GetOptionalAttrString() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyObject_GetOptionalAttr(PyObject *obj, PyObject *attr_name, PyObject **result) +{ + // bpo-32571 added _PyObject_LookupAttr() to Python 3.7.0b1 +#if PY_VERSION_HEX >= 0x030700B1 && !defined(PYPY_VERSION) + return _PyObject_LookupAttr(obj, attr_name, result); +#else + *result = PyObject_GetAttr(obj, attr_name); + if (*result != NULL) { + return 1; + } + if (!PyErr_Occurred()) { + return 0; + } + if (PyErr_ExceptionMatches(PyExc_AttributeError)) { + PyErr_Clear(); + return 0; + } + return -1; +#endif +} + +static inline int +PyObject_GetOptionalAttrString(PyObject *obj, const char *attr_name, PyObject **result) +{ + PyObject *name_obj; + int rc; +#if PY_VERSION_HEX >= 0x03000000 + name_obj = PyUnicode_FromString(attr_name); +#else + name_obj = PyString_FromString(attr_name); +#endif + if (name_obj == NULL) { + *result = NULL; + return -1; + } + rc = PyObject_GetOptionalAttr(obj, name_obj, result); + Py_DECREF(name_obj); + return rc; +} +#endif + + +// gh-106307 added PyObject_GetOptionalAttr() and +// PyMapping_GetOptionalItemString() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyMapping_GetOptionalItem(PyObject *obj, PyObject *key, PyObject **result) +{ + *result = PyObject_GetItem(obj, key); + if (*result) { + return 1; + } + if (!PyErr_ExceptionMatches(PyExc_KeyError)) { + return -1; + } + PyErr_Clear(); + return 0; +} + +static inline int +PyMapping_GetOptionalItemString(PyObject *obj, const char *key, PyObject **result) +{ + PyObject *key_obj; + int rc; +#if PY_VERSION_HEX >= 0x03000000 + key_obj = PyUnicode_FromString(key); +#else + key_obj = PyString_FromString(key); +#endif + if (key_obj == NULL) { + *result = NULL; + return -1; + } + rc = PyMapping_GetOptionalItem(obj, key_obj, result); + Py_DECREF(key_obj); + return rc; +} +#endif + +// gh-108511 added PyMapping_HasKeyWithError() and +// PyMapping_HasKeyStringWithError() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyMapping_HasKeyWithError(PyObject *obj, PyObject *key) +{ + PyObject *res; + int rc = PyMapping_GetOptionalItem(obj, key, &res); + Py_XDECREF(res); + return rc; +} + +static inline int +PyMapping_HasKeyStringWithError(PyObject *obj, const char *key) +{ + PyObject *res; + int rc = PyMapping_GetOptionalItemString(obj, key, &res); + Py_XDECREF(res); + return rc; +} +#endif + + +// gh-108511 added PyObject_HasAttrWithError() and +// PyObject_HasAttrStringWithError() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyObject_HasAttrWithError(PyObject *obj, PyObject *attr) +{ + PyObject *res; + int rc = PyObject_GetOptionalAttr(obj, attr, &res); + Py_XDECREF(res); + return rc; +} + +static inline int +PyObject_HasAttrStringWithError(PyObject *obj, const char *attr) +{ + PyObject *res; + int rc = PyObject_GetOptionalAttrString(obj, attr, &res); + Py_XDECREF(res); + return rc; +} +#endif + + +// gh-106004 added PyDict_GetItemRef() and PyDict_GetItemStringRef() +// to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyDict_GetItemRef(PyObject *mp, PyObject *key, PyObject **result) +{ +#if PY_VERSION_HEX >= 0x03000000 + PyObject *item = PyDict_GetItemWithError(mp, key); +#else + PyObject *item = _PyDict_GetItemWithError(mp, key); +#endif + if (item != NULL) { + *result = Py_NewRef(item); + return 1; // found + } + if (!PyErr_Occurred()) { + *result = NULL; + return 0; // not found + } + *result = NULL; + return -1; +} + +static inline int +PyDict_GetItemStringRef(PyObject *mp, const char *key, PyObject **result) +{ + int res; +#if PY_VERSION_HEX >= 0x03000000 + PyObject *key_obj = PyUnicode_FromString(key); +#else + PyObject *key_obj = PyString_FromString(key); +#endif + if (key_obj == NULL) { + *result = NULL; + return -1; + } + res = PyDict_GetItemRef(mp, key_obj, result); + Py_DECREF(key_obj); + return res; +} +#endif + + +// gh-106307 added PyModule_Add() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyModule_Add(PyObject *mod, const char *name, PyObject *value) +{ + int res = PyModule_AddObjectRef(mod, name, value); + Py_XDECREF(value); + return res; +} +#endif + + +// gh-108014 added Py_IsFinalizing() to Python 3.13.0a1 +// bpo-1856 added _Py_Finalizing to Python 3.2.1b1. +// _Py_IsFinalizing() was added to PyPy 7.3.0. +#if (0x030201B1 <= PY_VERSION_HEX && PY_VERSION_HEX < 0x030D00A1) \ + && (!defined(PYPY_VERSION_NUM) || PYPY_VERSION_NUM >= 0x7030000) +static inline int Py_IsFinalizing(void) +{ +#if PY_VERSION_HEX >= 0x030700A1 + // _Py_IsFinalizing() was added to Python 3.7.0a1. + return _Py_IsFinalizing(); +#else + return (_Py_Finalizing != NULL); +#endif +} +#endif + + +// gh-108323 added PyDict_ContainsString() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyDict_ContainsString(PyObject *op, const char *key) +{ + PyObject *key_obj = PyUnicode_FromString(key); + if (key_obj == NULL) { + return -1; + } + int res = PyDict_Contains(op, key_obj); + Py_DECREF(key_obj); + return res; +} +#endif + + +// gh-108445 added PyLong_AsInt() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int PyLong_AsInt(PyObject *obj) +{ +#ifdef PYPY_VERSION + long value = PyLong_AsLong(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + if (value < (long)INT_MIN || (long)INT_MAX < value) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C int"); + return -1; + } + return (int)value; +#else + return _PyLong_AsInt(obj); +#endif +} +#endif + + +// gh-107073 added PyObject_VisitManagedDict() to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyObject_VisitManagedDict(PyObject *obj, visitproc visit, void *arg) +{ + PyObject **dict = _PyObject_GetDictPtr(obj); + if (*dict == NULL) { + return -1; + } + Py_VISIT(*dict); + return 0; +} + +static inline void +PyObject_ClearManagedDict(PyObject *obj) +{ + PyObject **dict = _PyObject_GetDictPtr(obj); + if (*dict == NULL) { + return; + } + Py_CLEAR(*dict); +} +#endif + +// gh-108867 added PyThreadState_GetUnchecked() to Python 3.13.0a1 +// Python 3.5.2 added _PyThreadState_UncheckedGet(). +#if PY_VERSION_HEX >= 0x03050200 && PY_VERSION_HEX < 0x030D00A1 +static inline PyThreadState* +PyThreadState_GetUnchecked(void) +{ + return _PyThreadState_UncheckedGet(); +} +#endif + +// gh-110289 added PyUnicode_EqualToUTF8() and PyUnicode_EqualToUTF8AndSize() +// to Python 3.13.0a1 +#if PY_VERSION_HEX < 0x030D00A1 +static inline int +PyUnicode_EqualToUTF8AndSize(PyObject *unicode, const char *str, Py_ssize_t str_len) +{ + Py_ssize_t len; + const void *utf8; + PyObject *exc_type, *exc_value, *exc_tb; + int res; + + // API cannot report errors so save/restore the exception + PyErr_Fetch(&exc_type, &exc_value, &exc_tb); + + // Python 3.3.0a1 added PyUnicode_AsUTF8AndSize() +#if PY_VERSION_HEX >= 0x030300A1 + if (PyUnicode_IS_ASCII(unicode)) { + utf8 = PyUnicode_DATA(unicode); + len = PyUnicode_GET_LENGTH(unicode); + } + else { + utf8 = PyUnicode_AsUTF8AndSize(unicode, &len); + if (utf8 == NULL) { + // Memory allocation failure. The API cannot report error, + // so ignore the exception and return 0. + res = 0; + goto done; + } + } + + if (len != str_len) { + res = 0; + goto done; + } + res = (memcmp(utf8, str, (size_t)len) == 0); +#else + PyObject *bytes = PyUnicode_AsUTF8String(unicode); + if (bytes == NULL) { + // Memory allocation failure. The API cannot report error, + // so ignore the exception and return 0. + res = 0; + goto done; + } + +#if PY_VERSION_HEX >= 0x03000000 + len = PyBytes_GET_SIZE(bytes); + utf8 = PyBytes_AS_STRING(bytes); +#else + len = PyString_GET_SIZE(bytes); + utf8 = PyString_AS_STRING(bytes); +#endif + if (len != str_len) { + Py_DECREF(bytes); + res = 0; + goto done; + } + + res = (memcmp(utf8, str, (size_t)len) == 0); + Py_DECREF(bytes); +#endif + +done: + PyErr_Restore(exc_type, exc_value, exc_tb); + return res; +} + +static inline int +PyUnicode_EqualToUTF8(PyObject *unicode, const char *str) +{ + return PyUnicode_EqualToUTF8AndSize(unicode, str, (Py_ssize_t)strlen(str)); +} +#endif + + +// gh-111138 added PyList_Extend() and PyList_Clear() to Python 3.13.0a2 +#if PY_VERSION_HEX < 0x030D00A2 +static inline int +PyList_Extend(PyObject *list, PyObject *iterable) +{ + return PyList_SetSlice(list, PY_SSIZE_T_MAX, PY_SSIZE_T_MAX, iterable); +} + +static inline int +PyList_Clear(PyObject *list) +{ + return PyList_SetSlice(list, 0, PY_SSIZE_T_MAX, NULL); +} +#endif + +// gh-111262 added PyDict_Pop() and PyDict_PopString() to Python 3.13.0a2 +#if PY_VERSION_HEX < 0x030D00A2 +static inline int +PyDict_Pop(PyObject *dict, PyObject *key, PyObject **result) +{ + PyObject *value; + + if (!PyDict_Check(dict)) { + PyErr_BadInternalCall(); + if (result) { + *result = NULL; + } + return -1; + } + + // bpo-16991 added _PyDict_Pop() to Python 3.5.0b2. + // Python 3.6.0b3 changed _PyDict_Pop() first argument type to PyObject*. + // Python 3.13.0a1 removed _PyDict_Pop(). +#if defined(PYPY_VERSION) || PY_VERSION_HEX < 0x030500b2 || PY_VERSION_HEX >= 0x030D0000 + value = PyObject_CallMethod(dict, "pop", "O", key); +#elif PY_VERSION_HEX < 0x030600b3 + value = _PyDict_Pop(_Py_CAST(PyDictObject*, dict), key, NULL); +#else + value = _PyDict_Pop(dict, key, NULL); +#endif + if (value == NULL) { + if (result) { + *result = NULL; + } + if (PyErr_Occurred() && !PyErr_ExceptionMatches(PyExc_KeyError)) { + return -1; + } + PyErr_Clear(); + return 0; + } + if (result) { + *result = value; + } + else { + Py_DECREF(value); + } + return 1; +} + +static inline int +PyDict_PopString(PyObject *dict, const char *key, PyObject **result) +{ + PyObject *key_obj = PyUnicode_FromString(key); + if (key_obj == NULL) { + if (result != NULL) { + *result = NULL; + } + return -1; + } + + int res = PyDict_Pop(dict, key_obj, result); + Py_DECREF(key_obj); + return res; +} +#endif + + +#if PY_VERSION_HEX < 0x030200A4 +// Python 3.2.0a4 added Py_hash_t type +typedef Py_ssize_t Py_hash_t; +#endif + + +// gh-111545 added Py_HashPointer() to Python 3.13.0a3 +#if PY_VERSION_HEX < 0x030D00A3 +static inline Py_hash_t Py_HashPointer(const void *ptr) +{ +#if PY_VERSION_HEX >= 0x030900A4 && !defined(PYPY_VERSION) + return _Py_HashPointer(ptr); +#else + return _Py_HashPointer(_Py_CAST(void*, ptr)); +#endif +} +#endif + + +// Python 3.13a4 added a PyTime API. +// Use the private API added to Python 3.5. +#if PY_VERSION_HEX < 0x030D00A4 && PY_VERSION_HEX >= 0x03050000 +typedef _PyTime_t PyTime_t; +#define PyTime_MIN _PyTime_MIN +#define PyTime_MAX _PyTime_MAX + +static inline double PyTime_AsSecondsDouble(PyTime_t t) +{ return _PyTime_AsSecondsDouble(t); } + +static inline int PyTime_Monotonic(PyTime_t *result) +{ return _PyTime_GetMonotonicClockWithInfo(result, NULL); } + +static inline int PyTime_Time(PyTime_t *result) +{ return _PyTime_GetSystemClockWithInfo(result, NULL); } + +static inline int PyTime_PerfCounter(PyTime_t *result) +{ +#if PY_VERSION_HEX >= 0x03070000 && !defined(PYPY_VERSION) + return _PyTime_GetPerfCounterWithInfo(result, NULL); +#elif PY_VERSION_HEX >= 0x03070000 + // Call time.perf_counter_ns() and convert Python int object to PyTime_t. + // Cache time.perf_counter_ns() function for best performance. + static PyObject *func = NULL; + if (func == NULL) { + PyObject *mod = PyImport_ImportModule("time"); + if (mod == NULL) { + return -1; + } + + func = PyObject_GetAttrString(mod, "perf_counter_ns"); + Py_DECREF(mod); + if (func == NULL) { + return -1; + } + } + + PyObject *res = PyObject_CallNoArgs(func); + if (res == NULL) { + return -1; + } + long long value = PyLong_AsLongLong(res); + Py_DECREF(res); + + if (value == -1 && PyErr_Occurred()) { + return -1; + } + + Py_BUILD_ASSERT(sizeof(value) >= sizeof(PyTime_t)); + *result = (PyTime_t)value; + return 0; +#else + // Call time.perf_counter() and convert C double to PyTime_t. + // Cache time.perf_counter() function for best performance. + static PyObject *func = NULL; + if (func == NULL) { + PyObject *mod = PyImport_ImportModule("time"); + if (mod == NULL) { + return -1; + } + + func = PyObject_GetAttrString(mod, "perf_counter"); + Py_DECREF(mod); + if (func == NULL) { + return -1; + } + } + + PyObject *res = PyObject_CallNoArgs(func); + if (res == NULL) { + return -1; + } + double d = PyFloat_AsDouble(res); + Py_DECREF(res); + + if (d == -1.0 && PyErr_Occurred()) { + return -1; + } + + // Avoid floor() to avoid having to link to libm + *result = (PyTime_t)(d * 1e9); + return 0; +#endif +} + +#endif + +// gh-111389 added hash constants to Python 3.13.0a5. These constants were +// added first as private macros to Python 3.4.0b1 and PyPy 7.3.9. +#if (!defined(PyHASH_BITS) \ + && ((!defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x030400B1) \ + || (defined(PYPY_VERSION) && PY_VERSION_HEX >= 0x03070000 \ + && PYPY_VERSION_NUM >= 0x07090000))) +# define PyHASH_BITS _PyHASH_BITS +# define PyHASH_MODULUS _PyHASH_MODULUS +# define PyHASH_INF _PyHASH_INF +# define PyHASH_IMAG _PyHASH_IMAG +#endif + + +// gh-111545 added Py_GetConstant() and Py_GetConstantBorrowed() +// to Python 3.13.0a6 +#if PY_VERSION_HEX < 0x030D00A6 && !defined(Py_CONSTANT_NONE) + +#define Py_CONSTANT_NONE 0 +#define Py_CONSTANT_FALSE 1 +#define Py_CONSTANT_TRUE 2 +#define Py_CONSTANT_ELLIPSIS 3 +#define Py_CONSTANT_NOT_IMPLEMENTED 4 +#define Py_CONSTANT_ZERO 5 +#define Py_CONSTANT_ONE 6 +#define Py_CONSTANT_EMPTY_STR 7 +#define Py_CONSTANT_EMPTY_BYTES 8 +#define Py_CONSTANT_EMPTY_TUPLE 9 + +static inline PyObject* Py_GetConstant(unsigned int constant_id) +{ + static PyObject* constants[Py_CONSTANT_EMPTY_TUPLE + 1] = {NULL}; + + if (constants[Py_CONSTANT_NONE] == NULL) { + constants[Py_CONSTANT_NONE] = Py_None; + constants[Py_CONSTANT_FALSE] = Py_False; + constants[Py_CONSTANT_TRUE] = Py_True; + constants[Py_CONSTANT_ELLIPSIS] = Py_Ellipsis; + constants[Py_CONSTANT_NOT_IMPLEMENTED] = Py_NotImplemented; + + constants[Py_CONSTANT_ZERO] = PyLong_FromLong(0); + if (constants[Py_CONSTANT_ZERO] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_ONE] = PyLong_FromLong(1); + if (constants[Py_CONSTANT_ONE] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_STR] = PyUnicode_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_STR] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_BYTES] = PyBytes_FromStringAndSize("", 0); + if (constants[Py_CONSTANT_EMPTY_BYTES] == NULL) { + goto fatal_error; + } + + constants[Py_CONSTANT_EMPTY_TUPLE] = PyTuple_New(0); + if (constants[Py_CONSTANT_EMPTY_TUPLE] == NULL) { + goto fatal_error; + } + // goto dance to avoid compiler warnings about Py_FatalError() + goto init_done; + +fatal_error: + // This case should never happen + Py_FatalError("Py_GetConstant() failed to get constants"); + } + +init_done: + if (constant_id <= Py_CONSTANT_EMPTY_TUPLE) { + return Py_NewRef(constants[constant_id]); + } + else { + PyErr_BadInternalCall(); + return NULL; + } +} + +static inline PyObject* Py_GetConstantBorrowed(unsigned int constant_id) +{ + PyObject *obj = Py_GetConstant(constant_id); + Py_XDECREF(obj); + return obj; +} +#endif + + +// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4 +#if PY_VERSION_HEX < 0x030D00A4 +static inline PyObject * +PyList_GetItemRef(PyObject *op, Py_ssize_t index) +{ + PyObject *item = PyList_GetItem(op, index); + Py_XINCREF(item); + return item; +} +#endif + + +// gh-114329 added PyList_GetItemRef() to Python 3.13.0a4 +#if PY_VERSION_HEX < 0x030D00A4 +static inline int +PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, + PyObject **result) +{ + PyObject *value; + if (PyDict_GetItemRef(d, key, &value) < 0) { + // get error + if (result) { + *result = NULL; + } + return -1; + } + if (value != NULL) { + // present + if (result) { + *result = value; + } + else { + Py_DECREF(value); + } + return 1; + } + + // missing: set the item + if (PyDict_SetItem(d, key, default_value) < 0) { + // set error + if (result) { + *result = NULL; + } + return -1; + } + if (result) { + *result = Py_NewRef(default_value); + } + return 0; +} +#endif + + +// gh-116560 added PyLong_GetSign() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyLong_GetSign(PyObject *obj, int *sign) +{ + if (!PyLong_Check(obj)) { + PyErr_Format(PyExc_TypeError, "expect int, got %s", Py_TYPE(obj)->tp_name); + return -1; + } + + *sign = _PyLong_Sign(obj); + return 0; +} +#endif + + +#ifdef __cplusplus +} +#endif +#endif // PYTHONCAPI_COMPAT From 7c64ae0c73666924fa1e298f1be55abbf6973586 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:46:52 +0300 Subject: [PATCH 11/61] encode: Replace PyDict_GetItem with PyDict_GetItemRef --- src/encode.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/encode.c b/src/encode.c index 72ad3fa07..620774f67 100644 --- a/src/encode.c +++ b/src/encode.c @@ -722,7 +722,10 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { } if (!is_core_tag) { - PyObject *tag_type = PyDict_GetItem(types, key); + PyObject *tag_type; + if (PyDict_GetItemRef(types, key, &tag_type) == 0) { + PyErr_SetString(PyExc_KeyError, "unknown tag type"); + } if (tag_type) { int type_int = PyLong_AsLong(tag_type); if (type_int >= TIFF_BYTE && type_int <= TIFF_DOUBLE) { From 87596bd747f1cfadd2f7ec6beb5bca67e6aeba0a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 2 Jul 2024 18:51:15 +0300 Subject: [PATCH 12/61] imagingft: Replace PyDict_GetItem with PyDict_GetItemRef --- src/_imagingft.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 1bef876e1..468f16884 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -20,6 +20,7 @@ #define PY_SSIZE_T_CLEAN #include "Python.h" +#include "thirdparty/pythoncapi_compat.h" #include "libImaging/Imaging.h" #include @@ -1219,7 +1220,7 @@ font_getvarnames(FontObject *self) { } for (j = 0; j < num_namedstyles; j++) { - if (PyList_GetItem(list_names, j) != NULL) { + if (PyList_GetItemRef(list_names, j) != NULL) { continue; } From 40e7f511b33204a23faa0f26140dd7b74c13be97 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Fri, 5 Jul 2024 16:30:48 +0200 Subject: [PATCH 13/61] Don't use PyList_GetItemRef immediately after PyList_New --- src/_imagingft.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 468f16884..ddcf28f97 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1205,6 +1205,16 @@ font_getvarnames(FontObject *self) { num_namedstyles = master->num_namedstyles; list_names = PyList_New(num_namedstyles); + + int *list_names_filled = PyMem_Malloc(num_namedstyles * sizeof(int)); + if (list_names_filled == NULL) { + return PyErr_NoMemory(); + } + + for (int i = 0; i < num_namedstyles; i++) { + list_names_filled[i] = 0; + } + if (list_names == NULL) { FT_Done_MM_Var(library, master); return NULL; @@ -1220,13 +1230,14 @@ font_getvarnames(FontObject *self) { } for (j = 0; j < num_namedstyles; j++) { - if (PyList_GetItemRef(list_names, j) != NULL) { + if (list_names_filled[j]) { continue; } if (master->namedstyle[j].strid == name.name_id) { list_name = Py_BuildValue("y#", name.string, name.string_len); PyList_SetItem(list_names, j, list_name); + list_names_filled[j] = 1; break; } } From c416f0ea1db1dfe5b3a96a0d24af0f0318883317 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 10 Jul 2024 06:11:11 +1000 Subject: [PATCH 14/61] Build wheels with free threading --- .github/workflows/wheels-test.sh | 6 ++++++ .github/workflows/wheels.yml | 2 ++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index 3fbf3be69..023a33824 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -12,8 +12,14 @@ elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then else yum install -y fribidi fi + if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ]; then + # TODO Update condition when NumPy supports free-threading + if [ $(python3 -c "import sysconfig;print(sysconfig.get_config_var('Py_GIL_DISABLED'))") == "1" ]; then + python3 -m pip install numpy --index-url https://pypi.anaconda.org/scientific-python-nightly-wheels/simple + else python3 -m pip install numpy + fi fi if [ ! -d "test-images-main" ]; then diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index f8dff3a3e..e32f14529 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -129,6 +129,7 @@ jobs: env: CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_BUILD: ${{ matrix.build }} + CIBW_FREE_THREADED_SUPPORT: True CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_PRERELEASE_PYTHONS: True @@ -201,6 +202,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" CIBW_CACHE_PATH: "C:\\cibw" + CIBW_FREE_THREADED_SUPPORT: True CIBW_PRERELEASE_PYTHONS: True CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_COMMAND: 'docker run --rm From 5bae9343171d653dcf5dff0669c3665fd97dea3e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 12 Jul 2024 21:16:56 +1000 Subject: [PATCH 15/61] Added type hints --- Tests/test_file_png.py | 2 +- Tests/test_imagefile.py | 6 +++--- src/PIL/ImageFile.py | 34 +++++++++++++++--------------- src/PIL/ImageSequence.py | 2 +- src/PIL/JpegImagePlugin.py | 4 ++-- src/PIL/PdfImagePlugin.py | 20 +++++++++--------- src/PIL/PngImagePlugin.py | 36 ++++++++++++++++--------------- src/PIL/TiffImagePlugin.py | 43 +++++++++++++++++++------------------- src/PIL/WalImageFile.py | 5 ++++- 9 files changed, 79 insertions(+), 73 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index dfe8f9e99..e2913e944 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -41,7 +41,7 @@ MAGIC = PngImagePlugin._MAGIC def chunk(cid: bytes, *data: bytes) -> bytes: test_file = BytesIO() - PngImagePlugin.putchunk(*(test_file, cid) + data) + PngImagePlugin.putchunk(test_file, cid, *data) return test_file.getvalue() diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index b996860ce..44a6e6a42 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -127,7 +127,7 @@ class TestImageFile: def test_raise_typeerror(self) -> None: with pytest.raises(TypeError): parser = ImageFile.Parser() - parser.feed(1) + parser.feed(1) # type: ignore[arg-type] def test_negative_stride(self) -> None: with open("Tests/images/raw_negative_stride.bin", "rb") as f: @@ -305,7 +305,7 @@ class TestPyDecoder(CodecsTest): im.load() def test_decode(self) -> None: - decoder = ImageFile.PyDecoder(None) + decoder = ImageFile.PyDecoder("") with pytest.raises(NotImplementedError): decoder.decode(b"") @@ -383,7 +383,7 @@ class TestPyEncoder(CodecsTest): ) def test_encode(self) -> None: - encoder = ImageFile.PyEncoder(None) + encoder = ImageFile.PyEncoder("") with pytest.raises(NotImplementedError): encoder.encode(0) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 99d7e73f1..6b2953451 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -86,7 +86,7 @@ def raise_oserror(error: int) -> OSError: raise _get_oserror(error, encoder=False) -def _tilesort(t): +def _tilesort(t) -> int: # sort on offset return t[2] @@ -161,7 +161,7 @@ class ImageFile(Image.Image): return Image.MIME.get(self.format.upper()) return None - def __setstate__(self, state): + def __setstate__(self, state) -> None: self.tile = [] super().__setstate__(state) @@ -333,14 +333,14 @@ class ImageFile(Image.Image): # def load_read(self, read_bytes: int) -> bytes: # pass - def _seek_check(self, frame): + def _seek_check(self, frame: int) -> bool: if ( frame < self._min_frame # Only check upper limit on frames if additional seek operations # are not required to do so or ( not (hasattr(self, "_n_frames") and self._n_frames is None) - and frame >= self.n_frames + self._min_frame + and frame >= getattr(self, "n_frames") + self._min_frame ) ): msg = "attempt to seek outside sequence" @@ -370,7 +370,7 @@ class StubImageFile(ImageFile): msg = "StubImageFile subclass must implement _open" raise NotImplementedError(msg) - def load(self): + def load(self) -> Image.core.PixelAccess | None: loader = self._load() if loader is None: msg = f"cannot find loader for this {self.format} file" @@ -378,7 +378,7 @@ class StubImageFile(ImageFile): image = loader.load(self) assert image is not None # become the other object (!) - self.__class__ = image.__class__ + self.__class__ = image.__class__ # type: ignore[assignment] self.__dict__ = image.__dict__ return image.load() @@ -396,8 +396,8 @@ class Parser: incremental = None image: Image.Image | None = None - data = None - decoder = None + data: bytes | None = None + decoder: Image.core.ImagingDecoder | PyDecoder | None = None offset = 0 finished = 0 @@ -409,7 +409,7 @@ class Parser: """ assert self.data is None, "cannot reuse parsers" - def feed(self, data): + def feed(self, data: bytes) -> None: """ (Consumer) Feed data to the parser. @@ -491,7 +491,7 @@ class Parser: def __exit__(self, *args: object) -> None: self.close() - def close(self): + def close(self) -> Image.Image: """ (Consumer) Close the stream. @@ -525,7 +525,7 @@ class Parser: # -------------------------------------------------------------------- -def _save(im, fp, tile, bufsize=0) -> None: +def _save(im, fp, tile, bufsize: int = 0) -> None: """Helper to save image based on tile list :param im: Image object. @@ -553,7 +553,7 @@ def _save(im, fp, tile, bufsize=0) -> None: fp.flush() -def _encode_tile(im, fp, tile: list[_Tile], bufsize, fh, exc=None): +def _encode_tile(im, fp, tile: list[_Tile], bufsize: int, fh, exc=None) -> None: for encoder_name, extents, offset, args in tile: if offset > 0: fp.seek(offset) @@ -629,18 +629,18 @@ class PyCodecState: class PyCodec: fd: IO[bytes] | None - def __init__(self, mode, *args): - self.im = None + def __init__(self, mode: str, *args: Any) -> None: + self.im: Image.core.ImagingCore | None = None self.state = PyCodecState() self.fd = None self.mode = mode self.init(args) - def init(self, args) -> None: + def init(self, args: tuple[Any, ...]) -> None: """ Override to perform codec specific initialization - :param args: Array of args items from the tile entry + :param args: Tuple of arg items from the tile entry :returns: None """ self.args = args @@ -662,7 +662,7 @@ class PyCodec: """ self.fd = fd - def setimage(self, im, extents: tuple[int, int, int, int] | None = None) -> None: + def setimage(self, im, extents=None): """ Called from ImageFile to set the core output image for the codec diff --git a/src/PIL/ImageSequence.py b/src/PIL/ImageSequence.py index 2c1850276..a6fc340d5 100644 --- a/src/PIL/ImageSequence.py +++ b/src/PIL/ImageSequence.py @@ -33,7 +33,7 @@ class Iterator: :param im: An image object. """ - def __init__(self, im: Image.Image): + def __init__(self, im: Image.Image) -> None: if not hasattr(im, "seek"): msg = "im must have seek method" raise AttributeError(msg) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 4916727be..54f756014 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -827,11 +827,11 @@ def _save_cjpeg(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: ## # Factory for making JPEG and MPO instances -def jpeg_factory(fp=None, filename=None): +def jpeg_factory(fp: IO[bytes] | None = None, filename: str | bytes | None = None): im = JpegImageFile(fp, filename) try: mpheader = im._getmp() - if mpheader[45057] > 1: + if mpheader is not None and mpheader[45057] > 1: for segment, content in im.applist: if segment == "APP1" and b' hdrgm:Version="' in content: # Ultra HDR images are not yet supported diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index f0da1e047..e0f732199 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -174,12 +174,15 @@ def _write_image(im, filename, existing_pdf, image_refs): return image_ref, procset -def _save(im, fp, filename, save_all=False): +def _save( + im: Image.Image, fp: IO[bytes], filename: str | bytes, save_all: bool = False +) -> None: is_appending = im.encoderinfo.get("append", False) + filename_str = filename.decode() if isinstance(filename, bytes) else filename if is_appending: - existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="r+b") + existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode="r+b") else: - existing_pdf = PdfParser.PdfParser(f=fp, filename=filename, mode="w+b") + existing_pdf = PdfParser.PdfParser(f=fp, filename=filename_str, mode="w+b") dpi = im.encoderinfo.get("dpi") if dpi: @@ -228,12 +231,7 @@ def _save(im, fp, filename, save_all=False): for im in ims: im_number_of_pages = 1 if save_all: - try: - im_number_of_pages = im.n_frames - except AttributeError: - # Image format does not have n_frames. - # It is a single frame image - pass + im_number_of_pages = getattr(im, "n_frames", 1) number_of_pages += im_number_of_pages for i in range(im_number_of_pages): image_refs.append(existing_pdf.next_object_id(0)) @@ -250,7 +248,9 @@ def _save(im, fp, filename, save_all=False): page_number = 0 for im_sequence in ims: - im_pages = ImageSequence.Iterator(im_sequence) if save_all else [im_sequence] + im_pages: ImageSequence.Iterator | list[Image.Image] = ( + ImageSequence.Iterator(im_sequence) if save_all else [im_sequence] + ) for im in im_pages: image_ref, procset = _write_image(im, filename, existing_pdf, image_refs) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index fa117d19a..6990b6d05 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -144,7 +144,7 @@ def _safe_zlib_decompress(s): return plaintext -def _crc32(data, seed=0): +def _crc32(data: bytes, seed: int = 0) -> int: return zlib.crc32(data, seed) & 0xFFFFFFFF @@ -191,7 +191,7 @@ class ChunkStream: assert self.queue is not None self.queue.append((cid, pos, length)) - def call(self, cid, pos, length): + def call(self, cid: bytes, pos: int, length: int) -> bytes: """Call the appropriate chunk handler""" logger.debug("STREAM %r %s %s", cid, pos, length) @@ -1091,21 +1091,21 @@ _OUTMODES = { } -def putchunk(fp, cid, *data): +def putchunk(fp: IO[bytes], cid: bytes, *data: bytes) -> None: """Write a PNG chunk (including CRC field)""" - data = b"".join(data) + byte_data = b"".join(data) - fp.write(o32(len(data)) + cid) - fp.write(data) - crc = _crc32(data, _crc32(cid)) + fp.write(o32(len(byte_data)) + cid) + fp.write(byte_data) + crc = _crc32(byte_data, _crc32(cid)) fp.write(o32(crc)) class _idat: # wrap output from the encoder in IDAT chunks - def __init__(self, fp, chunk): + def __init__(self, fp, chunk) -> None: self.fp = fp self.chunk = chunk @@ -1116,7 +1116,7 @@ class _idat: class _fdat: # wrap encoder output in fdAT chunks - def __init__(self, fp, chunk, seq_num): + def __init__(self, fp: IO[bytes], chunk, seq_num: int) -> None: self.fp = fp self.chunk = chunk self.seq_num = seq_num @@ -1259,7 +1259,9 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: _save(im, fp, filename, save_all=True) -def _save(im, fp, filename, chunk=putchunk, save_all=False): +def _save( + im: Image.Image, fp, filename: str | bytes, chunk=putchunk, save_all: bool = False +) -> None: # save an image to disk (called by the save method) if save_all: @@ -1461,7 +1463,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): # PNG chunk converter -def getchunks(im, **params): +def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes]]: """Return a list of PNG chunks representing this image.""" class collector: @@ -1470,19 +1472,19 @@ def getchunks(im, **params): def write(self, data: bytes) -> None: pass - def append(self, chunk: bytes) -> None: + def append(self, chunk: tuple[bytes, bytes, bytes]) -> None: self.data.append(chunk) - def append(fp, cid, *data): - data = b"".join(data) - crc = o32(_crc32(data, _crc32(cid))) - fp.append((cid, data, crc)) + def append(fp: collector, cid: bytes, *data: bytes) -> None: + byte_data = b"".join(data) + crc = o32(_crc32(byte_data, _crc32(cid))) + fp.append((cid, byte_data, crc)) fp = collector() try: im.encoderinfo = params - _save(im, fp, None, append) + _save(im, fp, "", append) finally: del im.encoderinfo diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index b89144803..253f64852 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -334,12 +334,13 @@ class IFDRational(Rational): __slots__ = ("_numerator", "_denominator", "_val") - def __init__(self, value, denominator=1): + def __init__(self, value, denominator: int = 1) -> None: """ :param value: either an integer numerator, a float/rational/other number, or an IFDRational :param denominator: Optional integer denominator """ + self._val: Fraction | float if isinstance(value, IFDRational): self._numerator = value.numerator self._denominator = value.denominator @@ -636,13 +637,13 @@ class ImageFileDirectory_v2(_IFDv2Base): val = (val,) return val - def __contains__(self, tag): + def __contains__(self, tag: object) -> bool: return tag in self._tags_v2 or tag in self._tagdata - def __setitem__(self, tag, value): + def __setitem__(self, tag, value) -> None: self._setitem(tag, value, self.legacy_api) - def _setitem(self, tag, value, legacy_api): + def _setitem(self, tag, value, legacy_api) -> None: basetypes = (Number, bytes, str) info = TiffTags.lookup(tag, self.group) @@ -758,7 +759,7 @@ class ImageFileDirectory_v2(_IFDv2Base): return data @_register_writer(1) # Basic type, except for the legacy API. - def write_byte(self, data): + def write_byte(self, data) -> bytes: if isinstance(data, IFDRational): data = int(data) if isinstance(data, int): @@ -772,7 +773,7 @@ class ImageFileDirectory_v2(_IFDv2Base): return data.decode("latin-1", "replace") @_register_writer(2) - def write_string(self, value): + def write_string(self, value) -> bytes: # remerge of https://github.com/python-pillow/Pillow/pull/1416 if isinstance(value, int): value = str(value) @@ -790,7 +791,7 @@ class ImageFileDirectory_v2(_IFDv2Base): return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2])) @_register_writer(5) - def write_rational(self, *values): + def write_rational(self, *values) -> bytes: return b"".join( self._pack("2L", *_limit_rational(frac, 2**32 - 1)) for frac in values ) @@ -800,7 +801,7 @@ class ImageFileDirectory_v2(_IFDv2Base): return data @_register_writer(7) - def write_undefined(self, value): + def write_undefined(self, value) -> bytes: if isinstance(value, IFDRational): value = int(value) if isinstance(value, int): @@ -817,13 +818,13 @@ class ImageFileDirectory_v2(_IFDv2Base): return tuple(combine(num, denom) for num, denom in zip(vals[::2], vals[1::2])) @_register_writer(10) - def write_signed_rational(self, *values): + def write_signed_rational(self, *values) -> bytes: return b"".join( self._pack("2l", *_limit_signed_rational(frac, 2**31 - 1, -(2**31))) for frac in values ) - def _ensure_read(self, fp, size): + def _ensure_read(self, fp: IO[bytes], size: int) -> bytes: ret = fp.read(size) if len(ret) != size: msg = ( @@ -977,7 +978,7 @@ class ImageFileDirectory_v2(_IFDv2Base): return result - def save(self, fp): + def save(self, fp: IO[bytes]) -> int: if fp.tell() == 0: # skip TIFF header on subsequent pages # tiff header -- PIL always starts the first IFD at offset 8 fp.write(self._prefix + self._pack("HL", 42, 8)) @@ -1017,7 +1018,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): .. deprecated:: 3.0.0 """ - def __init__(self, *args, **kwargs): + def __init__(self, *args, **kwargs) -> None: super().__init__(*args, **kwargs) self._legacy_api = True @@ -1029,7 +1030,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): """Dictionary of tag types""" @classmethod - def from_v2(cls, original): + def from_v2(cls, original) -> ImageFileDirectory_v1: """Returns an :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` instance with the same data as is contained in the original @@ -1063,7 +1064,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): ifd._tags_v2 = dict(self._tags_v2) return ifd - def __contains__(self, tag): + def __contains__(self, tag: object) -> bool: return tag in self._tags_v1 or tag in self._tagdata def __len__(self) -> int: @@ -1072,7 +1073,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): def __iter__(self): return iter(set(self._tagdata) | set(self._tags_v1)) - def __setitem__(self, tag, value): + def __setitem__(self, tag, value) -> None: for legacy_api in (False, True): self._setitem(tag, value, legacy_api) @@ -1122,7 +1123,7 @@ class TiffImageFile(ImageFile.ImageFile): self.tag_v2 = ImageFileDirectory_v2(ifh) # legacy IFD entries will be filled in later - self.ifd = None + self.ifd: ImageFileDirectory_v1 | None = None # setup frame pointers self.__first = self.__next = self.tag_v2.next @@ -1343,7 +1344,7 @@ class TiffImageFile(ImageFile.ImageFile): return Image.Image.load(self) - def _setup(self): + def _setup(self) -> None: """Setup this image object based on current tags""" if 0xBC01 in self.tag_v2: @@ -1537,13 +1538,13 @@ class TiffImageFile(ImageFile.ImageFile): # adjust stride width accordingly stride /= bps_count - a = (tile_rawmode, int(stride), 1) + args = (tile_rawmode, int(stride), 1) self.tile.append( ( self._compression, (x, y, min(x + w, xsize), min(y + h, ysize)), offset, - a, + args, ) ) x = x + w @@ -1938,7 +1939,7 @@ class AppendingTiffWriter: 521, # JPEGACTables } - def __init__(self, fn, new=False): + def __init__(self, fn, new: bool = False) -> None: if hasattr(fn, "read"): self.f = fn self.close_fp = False @@ -2015,7 +2016,7 @@ class AppendingTiffWriter: def tell(self) -> int: return self.f.tell() - self.offsetOfNewPage - def seek(self, offset, whence=io.SEEK_SET): + def seek(self, offset: int, whence=io.SEEK_SET) -> int: if whence == os.SEEK_SET: offset += self.offsetOfNewPage diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py index 895d5616a..ec5c74900 100644 --- a/src/PIL/WalImageFile.py +++ b/src/PIL/WalImageFile.py @@ -24,8 +24,11 @@ and has been tested with a few sample files found using google. """ from __future__ import annotations +from typing import IO + from . import Image, ImageFile from ._binary import i32le as i32 +from ._typing import StrOrBytesPath class WalImageFile(ImageFile.ImageFile): @@ -58,7 +61,7 @@ class WalImageFile(ImageFile.ImageFile): return Image.Image.load(self) -def open(filename): +def open(filename: StrOrBytesPath | IO[bytes]) -> WalImageFile: """ Load texture from a Quake2 WAL texture file. From f5313db9ce5f800de29cb40af12c0722ecbe7173 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Sat, 13 Jul 2024 11:00:57 +0200 Subject: [PATCH 16/61] Add necessary PyMem_Free and fix PyDict_GetItemRef call --- src/_imagingft.c | 2 +- src/encode.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index ddcf28f97..eb04945f0 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1243,8 +1243,8 @@ font_getvarnames(FontObject *self) { } } + PyMem_Free(list_names_filled); FT_Done_MM_Var(library, master); - return list_names; } diff --git a/src/encode.c b/src/encode.c index 620774f67..82aed7783 100644 --- a/src/encode.c +++ b/src/encode.c @@ -723,8 +723,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { if (!is_core_tag) { PyObject *tag_type; - if (PyDict_GetItemRef(types, key, &tag_type) == 0) { - PyErr_SetString(PyExc_KeyError, "unknown tag type"); + if (PyDict_GetItemRef(types, key, &tag_type) < 0) { + return NULL; // Exception has been already set } if (tag_type) { int type_int = PyLong_AsLong(tag_type); From 9c576d63c3e79964d12ebe07f43144db735cdd35 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Sat, 13 Jul 2024 12:24:02 +0200 Subject: [PATCH 17/61] Fix refcounts after porting to GetItemRef & better error checking --- src/_imaging.c | 5 +++++ src/_imagingft.c | 35 ++++++++++++++++++++++++++++------- src/encode.c | 16 ++++++++++++++-- src/path.c | 12 +++++++++--- 4 files changed, 56 insertions(+), 12 deletions(-) diff --git a/src/_imaging.c b/src/_imaging.c index 03e10e547..ac6310a44 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -2251,6 +2251,11 @@ _getcolors(ImagingObject *self, PyObject *args) { ImagingColorItem *v = &items[i]; PyObject *item = Py_BuildValue( "iN", v->count, getpixel(self->image, self->access, v->x, v->y)); + if (item == NULL) { + Py_DECREF(out); + free(items); + return NULL; + } PyList_SetItem(out, i, item); } } diff --git a/src/_imagingft.c b/src/_imagingft.c index eb04945f0..6c2885c61 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1205,9 +1205,15 @@ font_getvarnames(FontObject *self) { num_namedstyles = master->num_namedstyles; list_names = PyList_New(num_namedstyles); + if (list_names == NULL) { + FT_Done_MM_Var(library, master); + return NULL; + } int *list_names_filled = PyMem_Malloc(num_namedstyles * sizeof(int)); if (list_names_filled == NULL) { + Py_DECREF(list_names); + FT_Done_MM_Var(library, master); return PyErr_NoMemory(); } @@ -1215,15 +1221,11 @@ font_getvarnames(FontObject *self) { list_names_filled[i] = 0; } - if (list_names == NULL) { - FT_Done_MM_Var(library, master); - return NULL; - } - name_count = FT_Get_Sfnt_Name_Count(self->face); for (i = 0; i < name_count; i++) { error = FT_Get_Sfnt_Name(self->face, i, &name); if (error) { + PyMem_Free(list_names_filled); Py_DECREF(list_names); FT_Done_MM_Var(library, master); return geterror(error); @@ -1236,6 +1238,12 @@ font_getvarnames(FontObject *self) { if (master->namedstyle[j].strid == name.name_id) { list_name = Py_BuildValue("y#", name.string, name.string_len); + if (list_name == NULL) { + PyMem_Free(list_names_filled); + Py_DECREF(list_names); + FT_Done_MM_Var(library, master); + return NULL; + } PyList_SetItem(list_names, j, list_name); list_names_filled[j] = 1; break; @@ -1301,9 +1309,15 @@ font_getvaraxes(FontObject *self) { if (name.name_id == axis.strid) { axis_name = Py_BuildValue("y#", name.string, name.string_len); + if (axis_name == NULL) { + Py_DECREF(list_axis); + Py_DECREF(list_axes); + FT_Done_MM_Var(library, master); + return NULL; + } PyDict_SetItemString( list_axis, "name", axis_name ? axis_name : Py_None); - Py_XDECREF(axis_name); + Py_DECREF(axis_name); break; } } @@ -1357,7 +1371,12 @@ font_setvaraxes(FontObject *self, PyObject *args) { return PyErr_NoMemory(); } for (i = 0; i < num_coords; i++) { - item = PyList_GET_ITEM(axes, i); + item = PyList_GetItemRef(axes, i); + if (item == NULL) { + free(coords); + return NULL; + } + if (PyFloat_Check(item)) { coord = PyFloat_AS_DOUBLE(item); } else if (PyLong_Check(item)) { @@ -1365,10 +1384,12 @@ font_setvaraxes(FontObject *self, PyObject *args) { } else if (PyNumber_Check(item)) { coord = PyFloat_AsDouble(item); } else { + Py_DECREF(item); free(coords); PyErr_SetString(PyExc_TypeError, "list must contain numbers"); return NULL; } + Py_DECREF(item); coords[i] = coord * 65536; } diff --git a/src/encode.c b/src/encode.c index 82aed7783..2c95b7ebc 100644 --- a/src/encode.c +++ b/src/encode.c @@ -673,10 +673,16 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { TRACE(("tags size: %d\n", (int)tags_size)); for (pos = 0; pos < tags_size; pos++) { item = PyList_GetItemRef(tags, pos); + if (item == NULL) { + return NULL; + } + if (!PyTuple_Check(item) || PyTuple_Size(item) != 2) { + Py_DECREF(item); PyErr_SetString(PyExc_ValueError, "Invalid tags list"); return NULL; } + Py_DECREF(item); } pos = 0; } @@ -705,10 +711,16 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { num_core_tags = sizeof(core_tags) / sizeof(int); for (pos = 0; pos < tags_size; pos++) { item = PyList_GetItemRef(tags, pos); + if (item == NULL) { + return NULL; + } + // We already checked that tags is a 2-tuple list. - key = PyTuple_GetItem(item, 0); + key = PyTuple_GET_ITEM(item, 0); key_int = (int)PyLong_AsLong(key); - value = PyTuple_GetItem(item, 1); + value = PyTuple_GET_ITEM(item, 1); + Py_DECREF(item); + status = 0; is_core_tag = 0; is_var_length = 0; diff --git a/src/path.c b/src/path.c index 6bc90abed..f8a99eb5b 100644 --- a/src/path.c +++ b/src/path.c @@ -179,14 +179,21 @@ PyPath_Flatten(PyObject *data, double **pxy) { } \ free(xy); \ return -1; \ + } \ + if (decref) { \ + Py_DECREF(op); \ } /* Copy table to path array */ if (PyList_Check(data)) { for (i = 0; i < n; i++) { double x, y; - PyObject *op = PyList_GET_ITEM(data, i); - assign_item_to_array(op, 0); + PyObject *op = PyList_GetItemRef(data, i); + if (op == NULL) { + free(xy); + return -1; + } + assign_item_to_array(op, 1); } } else if (PyTuple_Check(data)) { for (i = 0; i < n; i++) { @@ -209,7 +216,6 @@ PyPath_Flatten(PyObject *data, double **pxy) { } } assign_item_to_array(op, 1); - Py_DECREF(op); } } From 8854e4677e0d5d07bfa3d8725e85b681f81a704a Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Sat, 13 Jul 2024 12:34:17 +0200 Subject: [PATCH 18/61] Add include --- src/path.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/path.c b/src/path.c index f8a99eb5b..bd6ad2259 100644 --- a/src/path.c +++ b/src/path.c @@ -26,6 +26,7 @@ */ #include "Python.h" +#include "thirdparty/pythoncapi_compat.h" #include "libImaging/Imaging.h" #include From 01529d8b0979597902d26fcf4c0dc4f3da2a667e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Jul 2024 19:23:36 +1000 Subject: [PATCH 19/61] Added type hints --- Tests/test_imagewin.py | 3 ++ src/PIL/EpsImagePlugin.py | 2 +- src/PIL/IcoImagePlugin.py | 84 ++++++++++++++++++++------------------ src/PIL/Image.py | 14 +++---- src/PIL/ImageFile.py | 6 ++- src/PIL/ImageQt.py | 11 +++-- src/PIL/ImageWin.py | 28 ++++++++----- src/PIL/IptcImagePlugin.py | 16 ++++---- src/PIL/JpegImagePlugin.py | 10 +++-- src/PIL/PngImagePlugin.py | 56 ++++++++++++++++--------- 10 files changed, 138 insertions(+), 92 deletions(-) diff --git a/Tests/test_imagewin.py b/Tests/test_imagewin.py index b43c31b52..a836bb90b 100644 --- a/Tests/test_imagewin.py +++ b/Tests/test_imagewin.py @@ -57,6 +57,9 @@ class TestImageWinDib: # Assert assert dib.size == (128, 128) + with pytest.raises(ValueError): + ImageWin.Dib(mode) + def test_dib_paste(self) -> None: # Arrange im = hopper() diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 59bb8594d..7a73d1f69 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -65,7 +65,7 @@ def has_ghostscript() -> bool: return gs_binary is not False -def Ghostscript(tile, size, fp, scale=1, transparency=False): +def Ghostscript(tile, size, fp, scale=1, transparency: bool = False) -> Image.Image: """Render an image using Ghostscript""" global gs_binary if not has_ghostscript(): diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index 650f5e4f1..8be1bd316 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -25,7 +25,7 @@ from __future__ import annotations import warnings from io import BytesIO from math import ceil, log -from typing import IO +from typing import IO, NamedTuple from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin from ._binary import i16le as i16 @@ -119,8 +119,22 @@ def _accept(prefix: bytes) -> bool: return prefix[:4] == _MAGIC +class IconHeader(NamedTuple): + width: int + height: int + nb_color: int + reserved: int + planes: int + bpp: int + size: int + offset: int + dim: tuple[int, int] + square: int + color_depth: int + + class IcoFile: - def __init__(self, buf) -> None: + def __init__(self, buf: IO[bytes]) -> None: """ Parse image from file-like object containing ico file data """ @@ -141,51 +155,44 @@ class IcoFile: for i in range(self.nb_items): s = buf.read(16) - icon_header = { - "width": s[0], - "height": s[1], - "nb_color": s[2], # No. of colors in image (0 if >=8bpp) - "reserved": s[3], - "planes": i16(s, 4), - "bpp": i16(s, 6), - "size": i32(s, 8), - "offset": i32(s, 12), - } - # See Wikipedia - for j in ("width", "height"): - if not icon_header[j]: - icon_header[j] = 256 + width = s[0] or 256 + height = s[1] or 256 - # See Wikipedia notes about color depth. - # We need this just to differ images with equal sizes - icon_header["color_depth"] = ( - icon_header["bpp"] - or ( - icon_header["nb_color"] != 0 - and ceil(log(icon_header["nb_color"], 2)) - ) - or 256 + # No. of colors in image (0 if >=8bpp) + nb_color = s[2] + bpp = i16(s, 6) + icon_header = IconHeader( + width=width, + height=height, + nb_color=nb_color, + reserved=s[3], + planes=i16(s, 4), + bpp=i16(s, 6), + size=i32(s, 8), + offset=i32(s, 12), + dim=(width, height), + square=width * height, + # See Wikipedia notes about color depth. + # We need this just to differ images with equal sizes + color_depth=bpp or (nb_color != 0 and ceil(log(nb_color, 2))) or 256, ) - icon_header["dim"] = (icon_header["width"], icon_header["height"]) - icon_header["square"] = icon_header["width"] * icon_header["height"] - self.entry.append(icon_header) - self.entry = sorted(self.entry, key=lambda x: x["color_depth"]) + self.entry = sorted(self.entry, key=lambda x: x.color_depth) # ICO images are usually squares - self.entry = sorted(self.entry, key=lambda x: x["square"], reverse=True) + self.entry = sorted(self.entry, key=lambda x: x.square, reverse=True) def sizes(self) -> set[tuple[int, int]]: """ - Get a list of all available icon sizes and color depths. + Get a set of all available icon sizes and color depths. """ - return {(h["width"], h["height"]) for h in self.entry} + return {(h.width, h.height) for h in self.entry} def getentryindex(self, size: tuple[int, int], bpp: int | bool = False) -> int: for i, h in enumerate(self.entry): - if size == h["dim"] and (bpp is False or bpp == h["color_depth"]): + if size == h.dim and (bpp is False or bpp == h.color_depth): return i return 0 @@ -202,9 +209,9 @@ class IcoFile: header = self.entry[idx] - self.buf.seek(header["offset"]) + self.buf.seek(header.offset) data = self.buf.read(8) - self.buf.seek(header["offset"]) + self.buf.seek(header.offset) im: Image.Image if data[:8] == PngImagePlugin._MAGIC: @@ -222,8 +229,7 @@ class IcoFile: im.tile[0] = d, (0, 0) + im.size, o, a # figure out where AND mask image starts - bpp = header["bpp"] - if 32 == bpp: + if 32 == header.bpp: # 32-bit color depth icon image allows semitransparent areas # PIL's DIB format ignores transparency bits, recover them. # The DIB is packed in BGRX byte order where X is the alpha @@ -253,7 +259,7 @@ class IcoFile: # padded row size * height / bits per char total_bytes = int((w * im.size[1]) / 8) - and_mask_offset = header["offset"] + header["size"] - total_bytes + and_mask_offset = header.offset + header.size - total_bytes self.buf.seek(and_mask_offset) mask_data = self.buf.read(total_bytes) @@ -307,7 +313,7 @@ class IcoImageFile(ImageFile.ImageFile): def _open(self) -> None: self.ico = IcoFile(self.fp) self.info["sizes"] = self.ico.sizes() - self.size = self.ico.entry[0]["dim"] + self.size = self.ico.entry[0].dim self.load() @property diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 565abe71d..9d901e028 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -3286,7 +3286,7 @@ def fromarray(obj: SupportsArrayInterface, mode: str | None = None) -> Image: return frombuffer(mode, size, obj, "raw", rawmode, 0, 1) -def fromqimage(im): +def fromqimage(im) -> ImageFile.ImageFile: """Creates an image instance from a QImage image""" from . import ImageQt @@ -3296,7 +3296,7 @@ def fromqimage(im): return ImageQt.fromqimage(im) -def fromqpixmap(im): +def fromqpixmap(im) -> ImageFile.ImageFile: """Creates an image instance from a QPixmap image""" from . import ImageQt @@ -3867,7 +3867,7 @@ class Exif(_ExifBase): # returns a dict with any single item tuples/lists as individual values return {k: self._fixup(v) for k, v in src_dict.items()} - def _get_ifd_dict(self, offset, group=None): + def _get_ifd_dict(self, offset: int, group=None): try: # an offset pointer to the location of the nested embedded IFD. # It should be a long, but may be corrupted. @@ -3881,7 +3881,7 @@ class Exif(_ExifBase): info.load(self.fp) return self._fixup_dict(info) - def _get_head(self): + def _get_head(self) -> bytes: version = b"\x2B" if self.bigtiff else b"\x2A" if self.endian == "<": head = b"II" + version + b"\x00" + o32le(8) @@ -4102,16 +4102,16 @@ class Exif(_ExifBase): keys.update(self._info) return len(keys) - def __getitem__(self, tag): + def __getitem__(self, tag: int): if self._info is not None and tag not in self._data and tag in self._info: self._data[tag] = self._fixup(self._info[tag]) del self._info[tag] return self._data[tag] - def __contains__(self, tag) -> bool: + def __contains__(self, tag: object) -> bool: return tag in self._data or (self._info is not None and tag in self._info) - def __setitem__(self, tag, value) -> None: + def __setitem__(self, tag: int, value) -> None: if self._info is not None and tag in self._info: del self._info[tag] self._data[tag] = value diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 6b2953451..e4a7dba44 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -553,7 +553,9 @@ def _save(im, fp, tile, bufsize: int = 0) -> None: fp.flush() -def _encode_tile(im, fp, tile: list[_Tile], bufsize: int, fh, exc=None) -> None: +def _encode_tile( + im, fp: IO[bytes], tile: list[_Tile], bufsize: int, fh, exc=None +) -> None: for encoder_name, extents, offset, args in tile: if offset > 0: fp.seek(offset) @@ -653,7 +655,7 @@ class PyCodec: """ pass - def setfd(self, fd) -> None: + def setfd(self, fd: IO[bytes]) -> None: """ Called from ImageFile to set the Python file-like object diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 35a37760c..346fe49d3 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -19,11 +19,14 @@ from __future__ import annotations import sys from io import BytesIO -from typing import Callable +from typing import TYPE_CHECKING, Callable from . import Image from ._util import is_path +if TYPE_CHECKING: + from . import ImageFile + qt_version: str | None qt_versions = [ ["6", "PyQt6"], @@ -90,11 +93,11 @@ def fromqimage(im): return Image.open(b) -def fromqpixmap(im): +def fromqpixmap(im) -> ImageFile.ImageFile: return fromqimage(im) -def align8to32(bytes, width, mode): +def align8to32(bytes: bytes, width: int, mode: str) -> bytes: """ converts each scanline of data from 8 bit to 32 bit aligned """ @@ -172,7 +175,7 @@ def _toqclass_helper(im): if qt_is_installed: class ImageQt(QImage): - def __init__(self, im): + def __init__(self, im) -> None: """ An PIL image wrapper for Qt. This is a subclass of PyQt's QImage class. diff --git a/src/PIL/ImageWin.py b/src/PIL/ImageWin.py index 978c5a9d1..4f9956087 100644 --- a/src/PIL/ImageWin.py +++ b/src/PIL/ImageWin.py @@ -70,11 +70,14 @@ class Dib: """ def __init__( - self, image: Image.Image | str, size: tuple[int, int] | list[int] | None = None + self, image: Image.Image | str, size: tuple[int, int] | None = None ) -> None: if isinstance(image, str): mode = image image = "" + if size is None: + msg = "If first argument is mode, size is required" + raise ValueError(msg) else: mode = image.mode size = image.size @@ -105,7 +108,12 @@ class Dib: result = self.image.expose(handle) return result - def draw(self, handle, dst, src=None): + def draw( + self, + handle, + dst: tuple[int, int, int, int], + src: tuple[int, int, int, int] | None = None, + ): """ Same as expose, but allows you to specify where to draw the image, and what part of it to draw. @@ -115,7 +123,7 @@ class Dib: the destination have different sizes, the image is resized as necessary. """ - if not src: + if src is None: src = (0, 0) + self.size if isinstance(handle, HWND): dc = self.image.getdc(handle) @@ -202,22 +210,22 @@ class Window: title, self.__dispatcher, width or 0, height or 0 ) - def __dispatcher(self, action, *args): + def __dispatcher(self, action: str, *args): return getattr(self, f"ui_handle_{action}")(*args) - def ui_handle_clear(self, dc, x0, y0, x1, y1): + def ui_handle_clear(self, dc, x0, y0, x1, y1) -> None: pass - def ui_handle_damage(self, x0, y0, x1, y1): + def ui_handle_damage(self, x0, y0, x1, y1) -> None: pass def ui_handle_destroy(self) -> None: pass - def ui_handle_repair(self, dc, x0, y0, x1, y1): + def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None: pass - def ui_handle_resize(self, width, height): + def ui_handle_resize(self, width, height) -> None: pass def mainloop(self) -> None: @@ -227,12 +235,12 @@ class Window: class ImageWindow(Window): """Create an image window which displays the given image.""" - def __init__(self, image, title="PIL"): + def __init__(self, image, title: str = "PIL") -> None: if not isinstance(image, Dib): image = Dib(image) self.image = image width, height = image.size super().__init__(title, width=width, height=height) - def ui_handle_repair(self, dc, x0, y0, x1, y1): + def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None: self.image.draw(dc, (x0, y0, x1, y1)) diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index a04616fbd..16a18ddfa 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -18,6 +18,7 @@ from __future__ import annotations from collections.abc import Sequence from io import BytesIO +from typing import cast from . import Image, ImageFile from ._binary import i16be as i16 @@ -184,7 +185,7 @@ Image.register_open(IptcImageFile.format, IptcImageFile) Image.register_extension(IptcImageFile.format, ".iim") -def getiptcinfo(im): +def getiptcinfo(im: ImageFile.ImageFile): """ Get IPTC information from TIFF, JPEG, or IPTC file. @@ -221,16 +222,17 @@ def getiptcinfo(im): class FakeImage: pass - im = FakeImage() - im.__class__ = IptcImageFile + fake_im = FakeImage() + fake_im.__class__ = IptcImageFile # type: ignore[assignment] + iptc_im = cast(IptcImageFile, fake_im) # parse the IPTC information chunk - im.info = {} - im.fp = BytesIO(data) + iptc_im.info = {} + iptc_im.fp = BytesIO(data) try: - im._open() + iptc_im._open() except (IndexError, KeyError): pass # expected failure - return im.info + return iptc_im.info diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 54f756014..af24faa5d 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -685,7 +685,11 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: raise ValueError(msg) subsampling = get_sampling(im) - def validate_qtables(qtables): + def validate_qtables( + qtables: ( + str | tuple[list[int], ...] | list[list[int]] | dict[int, list[int]] | None + ) + ) -> list[list[int]] | None: if qtables is None: return qtables if isinstance(qtables, str): @@ -715,12 +719,12 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: if len(table) != 64: msg = "Invalid quantization table" raise TypeError(msg) - table = array.array("H", table) + table_array = array.array("H", table) except TypeError as e: msg = "Invalid quantization table" raise ValueError(msg) from e else: - qtables[idx] = list(table) + qtables[idx] = list(table_array) return qtables if qtables == "keep": diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 6990b6d05..247f908ed 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -39,7 +39,7 @@ import struct import warnings import zlib from enum import IntEnum -from typing import IO, TYPE_CHECKING, Any, NoReturn +from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence from ._binary import i16be as i16 @@ -1126,7 +1126,21 @@ class _fdat: self.seq_num += 1 -def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_images): +class _Frame(NamedTuple): + im: Image.Image + bbox: tuple[int, int, int, int] | None + encoderinfo: dict[str, Any] + + +def _write_multiple_frames( + im: Image.Image, + fp: IO[bytes], + chunk, + mode: str, + rawmode: str, + default_image: Image.Image | None, + append_images: list[Image.Image], +) -> Image.Image | None: duration = im.encoderinfo.get("duration") loop = im.encoderinfo.get("loop", im.info.get("loop", 0)) disposal = im.encoderinfo.get("disposal", im.info.get("disposal", Disposal.OP_NONE)) @@ -1137,7 +1151,7 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i else: chain = itertools.chain([im], append_images) - im_frames = [] + im_frames: list[_Frame] = [] frame_count = 0 for im_seq in chain: for im_frame in ImageSequence.Iterator(im_seq): @@ -1158,24 +1172,24 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i if im_frames: previous = im_frames[-1] - prev_disposal = previous["encoderinfo"].get("disposal") - prev_blend = previous["encoderinfo"].get("blend") + prev_disposal = previous.encoderinfo.get("disposal") + prev_blend = previous.encoderinfo.get("blend") if prev_disposal == Disposal.OP_PREVIOUS and len(im_frames) < 2: prev_disposal = Disposal.OP_BACKGROUND if prev_disposal == Disposal.OP_BACKGROUND: - base_im = previous["im"].copy() + base_im = previous.im.copy() dispose = Image.core.fill("RGBA", im.size, (0, 0, 0, 0)) - bbox = previous["bbox"] + bbox = previous.bbox if bbox: dispose = dispose.crop(bbox) else: bbox = (0, 0) + im.size base_im.paste(dispose, bbox) elif prev_disposal == Disposal.OP_PREVIOUS: - base_im = im_frames[-2]["im"] + base_im = im_frames[-2].im else: - base_im = previous["im"] + base_im = previous.im delta = ImageChops.subtract_modulo( im_frame.convert("RGBA"), base_im.convert("RGBA") ) @@ -1186,14 +1200,14 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i and prev_blend == encoderinfo.get("blend") and "duration" in encoderinfo ): - previous["encoderinfo"]["duration"] += encoderinfo["duration"] + previous.encoderinfo["duration"] += encoderinfo["duration"] continue else: bbox = None - im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) + im_frames.append(_Frame(im_frame, bbox, encoderinfo)) if len(im_frames) == 1 and not default_image: - return im_frames[0]["im"] + return im_frames[0].im # animation control chunk( @@ -1211,14 +1225,14 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i seq_num = 0 for frame, frame_data in enumerate(im_frames): - im_frame = frame_data["im"] - if not frame_data["bbox"]: + im_frame = frame_data.im + if not frame_data.bbox: bbox = (0, 0) + im_frame.size else: - bbox = frame_data["bbox"] + bbox = frame_data.bbox im_frame = im_frame.crop(bbox) size = im_frame.size - encoderinfo = frame_data["encoderinfo"] + encoderinfo = frame_data.encoderinfo frame_duration = int(round(encoderinfo.get("duration", 0))) frame_disposal = encoderinfo.get("disposal", disposal) frame_blend = encoderinfo.get("blend", blend) @@ -1253,6 +1267,7 @@ def _write_multiple_frames(im, fp, chunk, mode, rawmode, default_image, append_i [("zip", (0, 0) + im_frame.size, 0, rawmode)], ) seq_num = fdat_chunks.seq_num + return None def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: @@ -1437,12 +1452,15 @@ def _save( exif = exif[6:] chunk(fp, b"eXIf", exif) + single_im: Image.Image | None = im if save_all: - im = _write_multiple_frames( + single_im = _write_multiple_frames( im, fp, chunk, mode, rawmode, default_image, append_images ) - if im: - ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) + if single_im: + ImageFile._save( + single_im, _idat(fp, chunk), [("zip", (0, 0) + single_im.size, 0, rawmode)] + ) if info: for info_chunk in info.chunks: From 76e5e12f9855e59eaf393c40e68f4e737f1175fd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Jul 2024 20:48:39 +1000 Subject: [PATCH 20/61] Simplified code --- src/_imagingft.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 6c2885c61..c6d20fe45 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -1315,8 +1315,7 @@ font_getvaraxes(FontObject *self) { FT_Done_MM_Var(library, master); return NULL; } - PyDict_SetItemString( - list_axis, "name", axis_name ? axis_name : Py_None); + PyDict_SetItemString(list_axis, "name", axis_name); Py_DECREF(axis_name); break; } From 3eeef83517b12d4e7af826b3aa0f6ebc82faf1fa Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 16 Jul 2024 16:40:17 +1000 Subject: [PATCH 21/61] Updated condition Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- src/PIL/IcoImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index 8be1bd316..c891024f5 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -229,7 +229,7 @@ class IcoFile: im.tile[0] = d, (0, 0) + im.size, o, a # figure out where AND mask image starts - if 32 == header.bpp: + if header.bpp == 32: # 32-bit color depth icon image allows semitransparent areas # PIL's DIB format ignores transparency bits, recover them. # The DIB is packed in BGRX byte order where X is the alpha From e89be771452731e1387f2a9c2fcb47a6d8e3350d Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Tue, 16 Jul 2024 10:21:24 +0200 Subject: [PATCH 22/61] Upload wheels to scientific-python-nightly-wheels index --- .github/workflows/wheels.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index e32f14529..3013f0c37 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -1,6 +1,14 @@ name: Wheels on: + schedule: + # ┌───────────── minute (0 - 59) + # │ ┌───────────── hour (0 - 23) + # │ │ ┌───────────── day of the month (1 - 31) + # │ │ │ ┌───────────── month (1 - 12 or JAN-DEC) + # │ │ │ │ ┌───────────── day of the week (0 - 6 or SUN-SAT) + # │ │ │ │ │ + - cron: "42 1 * * 0,3" push: paths: - ".ci/requirements-cibw.txt" @@ -140,6 +148,13 @@ jobs: name: dist-${{ matrix.os }}-${{ matrix.cibw_arch }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }} path: ./wheelhouse/*.whl + - name: Upload wheels to scientific-python-nightly-wheels + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + uses: scientific-python/upload-nightly-action@b67d7fcc0396e1128a474d1ab2b48aa94680f9fc # 0.5.0 + with: + artifacts_path: ./wheelhouse + anaconda_nightly_upload_token: ${{ secrets.PILLOW_NIGHTLY_UPLOAD_TOKEN }} + windows: name: Windows ${{ matrix.cibw_arch }} runs-on: windows-latest @@ -226,6 +241,13 @@ jobs: name: fribidi-windows-${{ matrix.cibw_arch }} path: winbuild\build\bin\fribidi* + - name: Upload wheels to scientific-python-nightly-wheels + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + uses: scientific-python/upload-nightly-action@b67d7fcc0396e1128a474d1ab2b48aa94680f9fc # 0.5.0 + with: + artifacts_path: ./wheelhouse + anaconda_nightly_upload_token: ${{ secrets.PILLOW_NIGHTLY_UPLOAD_TOKEN }} + sdist: runs-on: ubuntu-latest steps: From 784a87449063cb2ee2c65988574e83399b018f60 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 16 Jul 2024 18:38:50 +1000 Subject: [PATCH 23/61] Trim whitespace --- .github/workflows/wheels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 3013f0c37..191eaffeb 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -149,7 +149,7 @@ jobs: path: ./wheelhouse/*.whl - name: Upload wheels to scientific-python-nightly-wheels - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' uses: scientific-python/upload-nightly-action@b67d7fcc0396e1128a474d1ab2b48aa94680f9fc # 0.5.0 with: artifacts_path: ./wheelhouse @@ -242,7 +242,7 @@ jobs: path: winbuild\build\bin\fribidi* - name: Upload wheels to scientific-python-nightly-wheels - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' uses: scientific-python/upload-nightly-action@b67d7fcc0396e1128a474d1ab2b48aa94680f9fc # 0.5.0 with: artifacts_path: ./wheelhouse From 68c3542d096e314921d25539bb65e24e3760a1f3 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Tue, 16 Jul 2024 10:42:18 +0200 Subject: [PATCH 24/61] Rename secret --- .github/workflows/wheels.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 191eaffeb..4501802cb 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -153,7 +153,7 @@ jobs: uses: scientific-python/upload-nightly-action@b67d7fcc0396e1128a474d1ab2b48aa94680f9fc # 0.5.0 with: artifacts_path: ./wheelhouse - anaconda_nightly_upload_token: ${{ secrets.PILLOW_NIGHTLY_UPLOAD_TOKEN }} + anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} windows: name: Windows ${{ matrix.cibw_arch }} @@ -246,7 +246,7 @@ jobs: uses: scientific-python/upload-nightly-action@b67d7fcc0396e1128a474d1ab2b48aa94680f9fc # 0.5.0 with: artifacts_path: ./wheelhouse - anaconda_nightly_upload_token: ${{ secrets.PILLOW_NIGHTLY_UPLOAD_TOKEN }} + anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} sdist: runs-on: ubuntu-latest From d83c7b38c42952dbd3fcab1807732363d6e64607 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 16 Jul 2024 20:18:25 +1000 Subject: [PATCH 25/61] Skip other jobs on schedule (#1) Co-authored-by: Andrew Murray --- .github/workflows/wheels.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 4501802cb..fa1825e45 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -41,6 +41,7 @@ env: jobs: build-1-QEMU-emulated-wheels: + if: github.event_name != 'schedule' name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }} runs-on: ubuntu-latest strategy: @@ -249,6 +250,7 @@ jobs: anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} sdist: + if: github.event_name != 'schedule' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From 87b23d0207fb5f703e6c143e3ca8ed7d174930ff Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 19 May 2024 01:54:21 -0500 Subject: [PATCH 26/61] change AlignAfterOpenBracket in .clang-format to BlockIndent to match the Python code --- .clang-format | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 3199e330b..143dde82c 100644 --- a/.clang-format +++ b/.clang-format @@ -3,7 +3,7 @@ BasedOnStyle: Google AlwaysBreakAfterReturnType: All AllowShortIfStatementsOnASingleLine: false -AlignAfterOpenBracket: AlwaysBreak +AlignAfterOpenBracket: BlockIndent BinPackArguments: false BinPackParameters: false BreakBeforeBraces: Attach From 2973b041c76482da213fd93e151c542577178fe3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 12:58:00 +0000 Subject: [PATCH 27/61] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/Tk/_tkmini.h | 9 +- src/Tk/tkImaging.c | 24 +++-- src/_imaging.c | 175 ++++++++++++++++++++------------- src/_imagingcms.c | 79 ++++++++++----- src/_imagingft.c | 91 +++++++++++------ src/_imagingmath.c | 3 +- src/_imagingmorph.c | 3 +- src/_webp.c | 45 ++++++--- src/decode.c | 6 +- src/display.c | 50 ++++++---- src/encode.c | 108 ++++++++++++-------- src/libImaging/Access.c | 3 +- src/libImaging/BcnDecode.c | 39 +++++--- src/libImaging/BoxBlur.c | 18 ++-- src/libImaging/Chops.c | 9 +- src/libImaging/ColorLUT.c | 30 ++++-- src/libImaging/Convert.c | 34 +++---- src/libImaging/ConvertYCbCr.c | 36 ++++--- src/libImaging/Dib.c | 12 ++- src/libImaging/Draw.c | 134 ++++++++++++++----------- src/libImaging/Fill.c | 4 +- src/libImaging/Filter.c | 6 +- src/libImaging/Geometry.c | 34 ++++--- src/libImaging/GetBBox.c | 24 ++--- src/libImaging/GifEncode.c | 9 +- src/libImaging/HexDecode.c | 3 +- src/libImaging/Imaging.h | 60 +++++++---- src/libImaging/Jpeg2KDecode.c | 57 +++++------ src/libImaging/Jpeg2KEncode.c | 14 +-- src/libImaging/JpegDecode.c | 8 +- src/libImaging/JpegEncode.c | 18 ++-- src/libImaging/PackDecode.c | 6 +- src/libImaging/Paste.c | 54 +++++----- src/libImaging/PcxDecode.c | 6 +- src/libImaging/PcxEncode.c | 3 +- src/libImaging/Point.c | 4 +- src/libImaging/Quant.c | 105 +++++++++++++------- src/libImaging/QuantHash.c | 6 +- src/libImaging/QuantHash.h | 12 +-- src/libImaging/QuantOctree.c | 40 +++++--- src/libImaging/QuantPngQuant.c | 3 +- src/libImaging/QuantPngQuant.h | 3 +- src/libImaging/RankFilter.c | 3 +- src/libImaging/RawDecode.c | 3 +- src/libImaging/RawEncode.c | 3 +- src/libImaging/Reduce.c | 105 +++++++++++++------- src/libImaging/Resample.c | 36 ++++--- src/libImaging/SgiRleDecode.c | 9 +- src/libImaging/Storage.c | 8 +- src/libImaging/SunRleDecode.c | 3 +- src/libImaging/TgaRleDecode.c | 3 +- src/libImaging/TgaRleEncode.c | 6 +- src/libImaging/TiffDecode.c | 112 ++++++++++++++------- src/libImaging/TiffDecode.h | 3 +- src/libImaging/Unpack.c | 24 +++-- src/libImaging/UnpackYCC.c | 15 ++- src/libImaging/UnsharpMask.c | 3 +- src/libImaging/XbmEncode.c | 3 +- src/libImaging/ZipDecode.c | 9 +- src/libImaging/ZipEncode.c | 9 +- src/map.c | 3 +- src/path.c | 9 +- 62 files changed, 1088 insertions(+), 668 deletions(-) diff --git a/src/Tk/_tkmini.h b/src/Tk/_tkmini.h index 68247bc47..a260fa0d1 100644 --- a/src/Tk/_tkmini.h +++ b/src/Tk/_tkmini.h @@ -80,7 +80,8 @@ typedef struct Tcl_Command_ *Tcl_Command; typedef void *ClientData; typedef int(Tcl_CmdProc)( - ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[]); + ClientData clientData, Tcl_Interp *interp, int argc, const char *argv[] +); typedef void(Tcl_CmdDeleteProc)(ClientData clientData); /* Typedefs derived from function signatures in Tcl header */ @@ -90,7 +91,8 @@ typedef Tcl_Command (*Tcl_CreateCommand_t)( const char *cmdName, Tcl_CmdProc *proc, ClientData clientData, - Tcl_CmdDeleteProc *deleteProc); + Tcl_CmdDeleteProc *deleteProc +); /* Tcl_AppendResult */ typedef void (*Tcl_AppendResult_t)(Tcl_Interp *interp, ...); @@ -127,7 +129,8 @@ typedef int (*Tk_PhotoPutBlock_t)( int y, int width, int height, - int compRule); + int compRule +); /* Tk_FindPhoto */ typedef Tk_PhotoHandle (*Tk_FindPhoto_t)(Tcl_Interp *interp, const char *imageName); /* Tk_PhotoGetImage */ diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index ef1c00a94..727ee6bed 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -73,14 +73,16 @@ ImagingFind(const char *name) { static int PyImagingPhotoPut( - ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv) { + ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv +) { Imaging im; Tk_PhotoHandle photo; Tk_PhotoImageBlock block; if (argc != 3) { TCL_APPEND_RESULT( - interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL); + interp, "usage: ", argv[0], " destPhoto srcImage", (char *)NULL + ); return TCL_ERROR; } @@ -128,14 +130,16 @@ PyImagingPhotoPut( block.pixelPtr = (unsigned char *)im->block; TK_PHOTO_PUT_BLOCK( - interp, photo, &block, 0, 0, block.width, block.height, TK_PHOTO_COMPOSITE_SET); + interp, photo, &block, 0, 0, block.width, block.height, TK_PHOTO_COMPOSITE_SET + ); return TCL_OK; } static int PyImagingPhotoGet( - ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv) { + ClientData clientdata, Tcl_Interp *interp, int argc, const char **argv +) { Imaging im; Tk_PhotoHandle photo; Tk_PhotoImageBlock block; @@ -143,7 +147,8 @@ PyImagingPhotoGet( if (argc != 3) { TCL_APPEND_RESULT( - interp, "usage: ", argv[0], " srcPhoto destImage", (char *)NULL); + interp, "usage: ", argv[0], " srcPhoto destImage", (char *)NULL + ); return TCL_ERROR; } @@ -183,13 +188,15 @@ TkImaging_Init(Tcl_Interp *interp) { "PyImagingPhoto", PyImagingPhotoPut, (ClientData)0, - (Tcl_CmdDeleteProc *)NULL); + (Tcl_CmdDeleteProc *)NULL + ); TCL_CREATE_COMMAND( interp, "PyImagingPhotoGet", PyImagingPhotoGet, (ClientData)0, - (Tcl_CmdDeleteProc *)NULL); + (Tcl_CmdDeleteProc *)NULL + ); } /* @@ -394,7 +401,8 @@ _func_loader(void *lib) { } return ( (TK_PHOTO_PUT_BLOCK = (Tk_PhotoPutBlock_t)_dfunc(lib, "Tk_PhotoPutBlock")) == - NULL); + NULL + ); } int diff --git a/src/_imaging.c b/src/_imaging.c index ac6310a44..5c2f7b4b6 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -290,7 +290,8 @@ ImagingError_ModeError(void) { void * ImagingError_ValueError(const char *message) { PyErr_SetString( - PyExc_ValueError, (message) ? (char *)message : "unrecognized argument value"); + PyExc_ValueError, (message) ? (char *)message : "unrecognized argument value" + ); return NULL; } @@ -467,7 +468,8 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) { return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]); case 4: return Py_BuildValue( - "BBBB", pixel.b[0], pixel.b[1], pixel.b[2], pixel.b[3]); + "BBBB", pixel.b[0], pixel.b[1], pixel.b[2], pixel.b[3] + ); } break; case IMAGING_TYPE_INT32: @@ -518,7 +520,8 @@ getink(PyObject *color, Imaging im, char *ink) { rIsInt = 1; } else if (im->bands == 1) { PyErr_SetString( - PyExc_TypeError, "color must be int or single-element tuple"); + PyExc_TypeError, "color must be int or single-element tuple" + ); return NULL; } else if (tupleSize == -1) { PyErr_SetString(PyExc_TypeError, "color must be int or tuple"); @@ -534,8 +537,8 @@ getink(PyObject *color, Imaging im, char *ink) { if (rIsInt != 1) { if (tupleSize != 1) { PyErr_SetString( - PyExc_TypeError, - "color must be int or single-element tuple"); + PyExc_TypeError, "color must be int or single-element tuple" + ); return NULL; } else if (!PyArg_ParseTuple(color, "L", &r)) { return NULL; @@ -556,7 +559,8 @@ getink(PyObject *color, Imaging im, char *ink) { if (tupleSize != 1 && tupleSize != 2) { PyErr_SetString( PyExc_TypeError, - "color must be int, or tuple of one or two elements"); + "color must be int, or tuple of one or two elements" + ); return NULL; } else if (!PyArg_ParseTuple(color, "L|i", &r, &a)) { return NULL; @@ -567,7 +571,8 @@ getink(PyObject *color, Imaging im, char *ink) { PyErr_SetString( PyExc_TypeError, "color must be int, or tuple of one, three or four " - "elements"); + "elements" + ); return NULL; } else if (!PyArg_ParseTuple(color, "Lii|i", &r, &g, &b, &a)) { return NULL; @@ -608,7 +613,8 @@ getink(PyObject *color, Imaging im, char *ink) { } else if (tupleSize != 3) { PyErr_SetString( PyExc_TypeError, - "color must be int, or tuple of one or three elements"); + "color must be int, or tuple of one or three elements" + ); return NULL; } else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) { return NULL; @@ -733,7 +739,8 @@ _alpha_composite(ImagingObject *self, PyObject *args) { ImagingObject *imagep2; if (!PyArg_ParseTuple( - args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) { + args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2 + )) { return NULL; } @@ -748,7 +755,8 @@ _blend(ImagingObject *self, PyObject *args) { alpha = 0.5; if (!PyArg_ParseTuple( - args, "O!O!|d", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2, &alpha)) { + args, "O!O!|d", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2, &alpha + )) { return NULL; } @@ -827,7 +835,8 @@ _prepare_lut_table(PyObject *table, Py_ssize_t table_size) { break; case TYPE_FLOAT32: memcpy( - &item, ((char *)table_data) + i * sizeof(FLOAT32), sizeof(FLOAT32)); + &item, ((char *)table_data) + i * sizeof(FLOAT32), sizeof(FLOAT32) + ); break; case TYPE_DOUBLE: memcpy(&dtmp, ((char *)table_data) + i * sizeof(dtmp), sizeof(dtmp)); @@ -878,7 +887,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) { &size1D, &size2D, &size3D, - &table)) { + &table + )) { return NULL; } @@ -896,7 +906,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) { if (2 > size1D || size1D > 65 || 2 > size2D || size2D > 65 || 2 > size3D || size3D > 65) { PyErr_SetString( - PyExc_ValueError, "Table size in any dimension should be from 2 to 65"); + PyExc_ValueError, "Table size in any dimension should be from 2 to 65" + ); return NULL; } @@ -913,13 +924,8 @@ _color_lut_3d(ImagingObject *self, PyObject *args) { } if (!ImagingColorLUT3D_linear( - imOut, - self->image, - table_channels, - size1D, - size2D, - size3D, - prepared_table)) { + imOut, self->image, table_channels, size1D, size2D, size3D, prepared_table + )) { free(prepared_table); ImagingDelete(imOut); return NULL; @@ -943,7 +949,8 @@ _convert(ImagingObject *self, PyObject *args) { if (!PyImaging_Check(paletteimage)) { PyObject_Print((PyObject *)paletteimage, stderr, 0); PyErr_SetString( - PyExc_ValueError, "palette argument must be image with mode 'P'"); + PyExc_ValueError, "palette argument must be image with mode 'P'" + ); return NULL; } if (paletteimage->image->palette == NULL) { @@ -953,7 +960,8 @@ _convert(ImagingObject *self, PyObject *args) { } return PyImagingNew(ImagingConvert( - self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither)); + self->image, mode, paletteimage ? paletteimage->image->palette : NULL, dither + )); } static PyObject * @@ -961,7 +969,8 @@ _convert2(ImagingObject *self, PyObject *args) { ImagingObject *imagep1; ImagingObject *imagep2; if (!PyArg_ParseTuple( - args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2)) { + args, "O!O!", &Imaging_Type, &imagep1, &Imaging_Type, &imagep2 + )) { return NULL; } @@ -994,7 +1003,8 @@ _convert_matrix(ImagingObject *self, PyObject *args) { m + 8, m + 9, m + 10, - m + 11)) { + m + 11 + )) { return NULL; } } @@ -1055,7 +1065,8 @@ _filter(ImagingObject *self, PyObject *args) { float divisor, offset; PyObject *kernel = NULL; if (!PyArg_ParseTuple( - args, "(ii)ffO", &xsize, &ysize, &divisor, &offset, &kernel)) { + args, "(ii)ffO", &xsize, &ysize, &divisor, &offset, &kernel + )) { return NULL; } @@ -1138,7 +1149,8 @@ _getpalette(ImagingObject *self, PyObject *args) { } pack( - (UINT8 *)PyBytes_AsString(palette), self->image->palette->palette, palettesize); + (UINT8 *)PyBytes_AsString(palette), self->image->palette->palette, palettesize + ); return palette; } @@ -1232,7 +1244,8 @@ union hist_extrema { static union hist_extrema * parse_histogram_extremap( - ImagingObject *self, PyObject *extremap, union hist_extrema *ep) { + ImagingObject *self, PyObject *extremap, union hist_extrema *ep +) { int i0, i1; double f0, f1; @@ -1392,7 +1405,8 @@ _paste(ImagingObject *self, PyObject *args) { int x0, y0, x1, y1; ImagingObject *maskp = NULL; if (!PyArg_ParseTuple( - args, "O(iiii)|O!", &source, &x0, &y0, &x1, &y1, &Imaging_Type, &maskp)) { + args, "O(iiii)|O!", &source, &x0, &y0, &x1, &y1, &Imaging_Type, &maskp + )) { return NULL; } @@ -1404,14 +1418,16 @@ _paste(ImagingObject *self, PyObject *args) { x0, y0, x1, - y1); + y1 + ); } else { if (!getink(source, self->image, ink)) { return NULL; } status = ImagingFill2( - self->image, ink, (maskp) ? maskp->image : NULL, x0, y0, x1, y1); + self->image, ink, (maskp) ? maskp->image : NULL, x0, y0, x1, y1 + ); } if (status < 0) { @@ -1729,7 +1745,8 @@ _putpalette(ImagingObject *self, PyObject *args) { UINT8 *palette; Py_ssize_t palettesize; if (!PyArg_ParseTuple( - args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize)) { + args, "ssy#", &palette_mode, &rawmode, &palette, &palettesize + )) { return NULL; } @@ -1887,7 +1904,8 @@ _resize(ImagingObject *self, PyObject *args) { &box[0], &box[1], &box[2], - &box[3])) { + &box[3] + )) { return NULL; } @@ -1923,7 +1941,8 @@ _resize(ImagingObject *self, PyObject *args) { imOut = ImagingNewDirty(imIn->mode, xsize, ysize); imOut = ImagingTransform( - imOut, imIn, IMAGING_TRANSFORM_AFFINE, 0, 0, xsize, ysize, a, filter, 1); + imOut, imIn, IMAGING_TRANSFORM_AFFINE, 0, 0, xsize, ysize, a, filter, 1 + ); } else { imOut = ImagingResample(imIn, xsize, ysize, filter, box); } @@ -1944,14 +1963,8 @@ _reduce(ImagingObject *self, PyObject *args) { box[3] = imIn->ysize; if (!PyArg_ParseTuple( - args, - "(ii)|(iiii)", - &xscale, - &yscale, - &box[0], - &box[1], - &box[2], - &box[3])) { + args, "(ii)|(iiii)", &xscale, &yscale, &box[0], &box[1], &box[2], &box[3] + )) { return NULL; } @@ -2053,7 +2066,8 @@ _transform(ImagingObject *self, PyObject *args) { &method, &data, &filter, - &fill)) { + &fill + )) { return NULL; } @@ -2077,7 +2091,8 @@ _transform(ImagingObject *self, PyObject *args) { } imOut = ImagingTransform( - self->image, imagep->image, method, x0, y0, x1, y1, a, filter, fill); + self->image, imagep->image, method, x0, y0, x1, y1, a, filter, fill + ); free(a); @@ -2250,7 +2265,8 @@ _getcolors(ImagingObject *self, PyObject *args) { for (i = 0; i < colors; i++) { ImagingColorItem *v = &items[i]; PyObject *item = Py_BuildValue( - "iN", v->count, getpixel(self->image, self->access, v->x, v->y)); + "iN", v->count, getpixel(self->image, self->access, v->x, v->y) + ); if (item == NULL) { Py_DECREF(out); free(items); @@ -2316,14 +2332,16 @@ _getprojection(ImagingObject *self) { } ImagingGetProjection( - self->image, (unsigned char *)xprofile, (unsigned char *)yprofile); + self->image, (unsigned char *)xprofile, (unsigned char *)yprofile + ); result = Py_BuildValue( "y#y#", xprofile, (Py_ssize_t)self->image->xsize, yprofile, - (Py_ssize_t)self->image->ysize); + (Py_ssize_t)self->image->ysize + ); free(xprofile); free(yprofile); @@ -2397,7 +2415,8 @@ _merge(PyObject *self, PyObject *args) { &Imaging_Type, &band2, &Imaging_Type, - &band3)) { + &band3 + )) { return NULL; } @@ -2643,7 +2662,8 @@ _font_new(PyObject *self_, PyObject *args) { unsigned char *glyphdata; Py_ssize_t glyphdata_length; if (!PyArg_ParseTuple( - args, "O!y#", &Imaging_Type, &imagep, &glyphdata, &glyphdata_length)) { + args, "O!y#", &Imaging_Type, &imagep, &glyphdata, &glyphdata_length + )) { return NULL; } @@ -2801,7 +2821,8 @@ _font_getmask(ImagingFontObject *self, PyObject *args) { if (i == 0 || text[i] != text[i - 1]) { ImagingDelete(bitmap); bitmap = ImagingCrop( - self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1); + self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1 + ); if (!bitmap) { goto failed; } @@ -2813,7 +2834,8 @@ _font_getmask(ImagingFontObject *self, PyObject *args) { glyph->dx0 + x, glyph->dy0 + b, glyph->dx1 + x, - glyph->dy1 + b); + glyph->dy1 + b + ); if (status < 0) { goto failed; } @@ -2952,7 +2974,8 @@ _draw_arc(ImagingDrawObject *self, PyObject *args) { end, &ink, width, - self->blend); + self->blend + ); free(xy); @@ -2982,13 +3005,15 @@ _draw_bitmap(ImagingDrawObject *self, PyObject *args) { } if (n != 1) { PyErr_SetString( - PyExc_TypeError, "coordinate list must contain exactly 1 coordinate"); + PyExc_TypeError, "coordinate list must contain exactly 1 coordinate" + ); free(xy); return NULL; } n = ImagingDrawBitmap( - self->image->image, (int)xy[0], (int)xy[1], bitmap->image, &ink, self->blend); + self->image->image, (int)xy[0], (int)xy[1], bitmap->image, &ink, self->blend + ); free(xy); @@ -3044,7 +3069,8 @@ _draw_chord(ImagingDrawObject *self, PyObject *args) { &ink, fill, width, - self->blend); + self->blend + ); free(xy); @@ -3098,7 +3124,8 @@ _draw_ellipse(ImagingDrawObject *self, PyObject *args) { &ink, fill, width, - self->blend); + self->blend + ); free(xy); @@ -3138,14 +3165,16 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) { (int)p[2], (int)p[3], &ink, - self->blend) < 0) { + self->blend + ) < 0) { free(xy); return NULL; } } if (p) { /* draw last point */ ImagingDrawPoint( - self->image->image, (int)p[2], (int)p[3], &ink, self->blend); + self->image->image, (int)p[2], (int)p[3], &ink, self->blend + ); } } else { for (i = 0; i < n - 1; i++) { @@ -3158,7 +3187,8 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) { (int)p[3], &ink, width, - self->blend) < 0) { + self->blend + ) < 0) { free(xy); return NULL; } @@ -3190,7 +3220,8 @@ _draw_points(ImagingDrawObject *self, PyObject *args) { for (i = 0; i < n; i++) { double *p = &xy[i + i]; if (ImagingDrawPoint( - self->image->image, (int)p[0], (int)p[1], &ink, self->blend) < 0) { + self->image->image, (int)p[0], (int)p[1], &ink, self->blend + ) < 0) { free(xy); return NULL; } @@ -3279,7 +3310,8 @@ _draw_pieslice(ImagingDrawObject *self, PyObject *args) { &ink, fill, width, - self->blend); + self->blend + ); free(xy); @@ -3311,7 +3343,8 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) { } if (n < 2) { PyErr_SetString( - PyExc_TypeError, "coordinate list must contain at least 2 coordinates"); + PyExc_TypeError, "coordinate list must contain at least 2 coordinates" + ); free(xy); return NULL; } @@ -3384,7 +3417,8 @@ _draw_rectangle(ImagingDrawObject *self, PyObject *args) { &ink, fill, width, - self->blend); + self->blend + ); free(xy); @@ -3524,7 +3558,8 @@ _effect_mandelbrot(ImagingObject *self, PyObject *args) { &extent[1], &extent[2], &extent[3], - &quality)) { + &quality + )) { return NULL; } @@ -3752,7 +3787,8 @@ _getattr_unsafe_ptrs(ImagingObject *self, void *closure) { "image32", self->image->image32, "image", - self->image->image); + self->image->image + ); } static struct PyGetSetDef getsetters[] = { @@ -3762,7 +3798,8 @@ static struct PyGetSetDef getsetters[] = { {"id", (getter)_getattr_id}, {"ptr", (getter)_getattr_ptr}, {"unsafe_ptrs", (getter)_getattr_unsafe_ptrs}, - {NULL}}; + {NULL} +}; /* basic sequence semantics */ @@ -4071,9 +4108,8 @@ _set_blocks_max(PyObject *self, PyObject *args) { if (blocks_max < 0) { PyErr_SetString(PyExc_ValueError, "blocks_max should be greater than 0"); return NULL; - } else if ( - (unsigned long)blocks_max > - SIZE_MAX / sizeof(ImagingDefaultArena.blocks_pool[0])) { + } else if ((unsigned long)blocks_max > + SIZE_MAX / sizeof(ImagingDefaultArena.blocks_pool[0])) { PyErr_SetString(PyExc_ValueError, "blocks_max is too large"); return NULL; } @@ -4428,7 +4464,8 @@ setup_module(PyObject *m) { PyObject *pillow_version = PyUnicode_FromString(version); PyDict_SetItemString( - d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None); + d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None + ); Py_XDECREF(pillow_version); return 0; diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 628662b30..bafe787a7 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -331,7 +331,8 @@ pyCMScopyAux(cmsHTRANSFORM hTransform, Imaging imDst, const Imaging imSrc) { memcpy( pDstExtras + x * dstChunkSize, pSrcExtras + x * srcChunkSize, - channelSize); + channelSize + ); } } } @@ -373,7 +374,8 @@ _buildTransform( char *sInMode, char *sOutMode, int iRenderingIntent, - cmsUInt32Number cmsFLAGS) { + cmsUInt32Number cmsFLAGS +) { cmsHTRANSFORM hTransform; Py_BEGIN_ALLOW_THREADS @@ -385,7 +387,8 @@ _buildTransform( hOutputProfile, findLCMStype(sOutMode), iRenderingIntent, - cmsFLAGS); + cmsFLAGS + ); Py_END_ALLOW_THREADS; @@ -405,7 +408,8 @@ _buildProofTransform( char *sOutMode, int iRenderingIntent, int iProofIntent, - cmsUInt32Number cmsFLAGS) { + cmsUInt32Number cmsFLAGS +) { cmsHTRANSFORM hTransform; Py_BEGIN_ALLOW_THREADS @@ -419,7 +423,8 @@ _buildProofTransform( hProofProfile, iRenderingIntent, iProofIntent, - cmsFLAGS); + cmsFLAGS + ); Py_END_ALLOW_THREADS; @@ -454,7 +459,8 @@ buildTransform(PyObject *self, PyObject *args) { &sInMode, &sOutMode, &iRenderingIntent, - &cmsFLAGS)) { + &cmsFLAGS + )) { return NULL; } @@ -464,7 +470,8 @@ buildTransform(PyObject *self, PyObject *args) { sInMode, sOutMode, iRenderingIntent, - cmsFLAGS); + cmsFLAGS + ); if (!transform) { return NULL; @@ -499,7 +506,8 @@ buildProofTransform(PyObject *self, PyObject *args) { &sOutMode, &iRenderingIntent, &iProofIntent, - &cmsFLAGS)) { + &cmsFLAGS + )) { return NULL; } @@ -511,7 +519,8 @@ buildProofTransform(PyObject *self, PyObject *args) { sOutMode, iRenderingIntent, iProofIntent, - cmsFLAGS); + cmsFLAGS + ); if (!transform) { return NULL; @@ -563,7 +572,8 @@ createProfile(PyObject *self, PyObject *args) { PyErr_SetString( PyExc_ValueError, "ERROR: Could not calculate white point from color temperature " - "provided, must be float in degrees Kelvin"); + "provided, must be float in degrees Kelvin" + ); return NULL; } hProfile = cmsCreateLab2Profile(&whitePoint); @@ -624,7 +634,8 @@ cms_get_display_profile_win32(PyObject *self, PyObject *args) { HANDLE handle = 0; int is_dc = 0; if (!PyArg_ParseTuple( - args, "|" F_HANDLE "i:get_display_profile", &handle, &is_dc)) { + args, "|" F_HANDLE "i:get_display_profile", &handle, &is_dc + )) { return NULL; } @@ -729,7 +740,8 @@ _xyz_py(cmsCIEXYZ *XYZ) { cmsCIExyY xyY; cmsXYZ2xyY(&xyY, XYZ); return Py_BuildValue( - "((d,d,d),(d,d,d))", XYZ->X, XYZ->Y, XYZ->Z, xyY.x, xyY.y, xyY.Y); + "((d,d,d),(d,d,d))", XYZ->X, XYZ->Y, XYZ->Z, xyY.x, xyY.y, xyY.Y + ); } static PyObject * @@ -758,7 +770,8 @@ _xyz3_py(cmsCIEXYZ *XYZ) { xyY[1].Y, xyY[2].x, xyY[2].y, - xyY[2].Y); + xyY[2].Y + ); } static PyObject * @@ -809,7 +822,8 @@ _profile_read_ciexyy_triple(CmsProfileObject *self, cmsTagSignature info) { triple->Green.Y, triple->Blue.x, triple->Blue.y, - triple->Blue.Y); + triple->Blue.Y + ); } static PyObject * @@ -873,7 +887,8 @@ _calculate_rgb_primaries(CmsProfileObject *self, cmsCIEXYZTRIPLE *result) { hXYZ, TYPE_XYZ_DBL, INTENT_RELATIVE_COLORIMETRIC, - cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE); + cmsFLAGS_NOCACHE | cmsFLAGS_NOOPTIMIZE + ); cmsCloseProfile(hXYZ); if (hTransform == NULL) { return 0; @@ -889,7 +904,8 @@ _check_intent( int clut, cmsHPROFILE hProfile, cmsUInt32Number Intent, - cmsUInt32Number UsedDirection) { + cmsUInt32Number UsedDirection +) { if (clut) { return cmsIsCLUT(hProfile, Intent, UsedDirection); } else { @@ -934,7 +950,8 @@ _is_intent_supported(CmsProfileObject *self, int clut) { _check_intent(clut, self->profile, intent, LCMS_USED_AS_OUTPUT) ? Py_True : Py_False, _check_intent(clut, self->profile, intent, LCMS_USED_AS_PROOF) ? Py_True - : Py_False); + : Py_False + ); if (id == NULL || entry == NULL) { Py_XDECREF(id); Py_XDECREF(entry); @@ -968,7 +985,8 @@ static PyMethodDef pyCMSdll_methods[] = { {"get_display_profile_win32", cms_get_display_profile_win32, METH_VARARGS}, #endif - {NULL, NULL}}; + {NULL, NULL} +}; static struct PyMethodDef cms_profile_methods[] = { {"is_intent_supported", (PyCFunction)cms_profile_is_intent_supported, METH_VARARGS}, @@ -1028,7 +1046,8 @@ cms_profile_getattr_creation_date(CmsProfileObject *self, void *closure) { } return PyDateTime_FromDateAndTime( - 1900 + ct.tm_year, ct.tm_mon, ct.tm_mday, ct.tm_hour, ct.tm_min, ct.tm_sec, 0); + 1900 + ct.tm_year, ct.tm_mon, ct.tm_mday, ct.tm_hour, ct.tm_min, ct.tm_sec, 0 + ); } static PyObject * @@ -1106,13 +1125,15 @@ cms_profile_getattr_colorimetric_intent(CmsProfileObject *self, void *closure) { static PyObject * cms_profile_getattr_perceptual_rendering_intent_gamut( - CmsProfileObject *self, void *closure) { + CmsProfileObject *self, void *closure +) { return _profile_read_signature(self, cmsSigPerceptualRenderingIntentGamutTag); } static PyObject * cms_profile_getattr_saturation_rendering_intent_gamut( - CmsProfileObject *self, void *closure) { + CmsProfileObject *self, void *closure +) { return _profile_read_signature(self, cmsSigSaturationRenderingIntentGamutTag); } @@ -1145,7 +1166,8 @@ cms_profile_getattr_blue_colorant(CmsProfileObject *self, void *closure) { static PyObject * cms_profile_getattr_media_white_point_temperature( - CmsProfileObject *self, void *closure) { + CmsProfileObject *self, void *closure +) { cmsCIEXYZ *XYZ; cmsCIExyY xyY; cmsFloat64Number tempK; @@ -1329,7 +1351,8 @@ cms_profile_getattr_icc_measurement_condition(CmsProfileObject *self, void *clos "flare", mc->Flare, "illuminant_type", - _illu_map(mc->IlluminantType)); + _illu_map(mc->IlluminantType) + ); } static PyObject * @@ -1359,7 +1382,8 @@ cms_profile_getattr_icc_viewing_condition(CmsProfileObject *self, void *closure) vc->SurroundXYZ.Y, vc->SurroundXYZ.Z, "illuminant_type", - _illu_map(vc->IlluminantType)); + _illu_map(vc->IlluminantType) + ); } static struct PyGetSetDef cms_profile_getsetters[] = { @@ -1407,11 +1431,12 @@ static struct PyGetSetDef cms_profile_getsetters[] = { {"colorant_table_out", (getter)cms_profile_getattr_colorant_table_out}, {"intent_supported", (getter)cms_profile_getattr_is_intent_supported}, {"clut", (getter)cms_profile_getattr_is_clut}, - {"icc_measurement_condition", - (getter)cms_profile_getattr_icc_measurement_condition}, + {"icc_measurement_condition", (getter)cms_profile_getattr_icc_measurement_condition + }, {"icc_viewing_condition", (getter)cms_profile_getattr_icc_viewing_condition}, - {NULL}}; + {NULL} +}; static PyTypeObject CmsProfile_Type = { PyVarObject_HEAD_INIT(NULL, 0) "PIL.ImageCms.core.CmsProfile", /*tp_name*/ diff --git a/src/_imagingft.c b/src/_imagingft.c index c6d20fe45..da03e3ba9 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -126,7 +126,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { unsigned char *font_bytes; Py_ssize_t font_bytes_size = 0; static char *kwlist[] = { - "filename", "size", "index", "encoding", "font_bytes", "layout_engine", NULL}; + "filename", "size", "index", "encoding", "font_bytes", "layout_engine", NULL + }; if (!library) { PyErr_SetString(PyExc_OSError, "failed to initialize FreeType library"); @@ -148,7 +149,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { &encoding, &font_bytes, &font_bytes_size, - &layout_engine)) { + &layout_engine + )) { PyConfig_Clear(&config); return NULL; } @@ -166,7 +168,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { &encoding, &font_bytes, &font_bytes_size, - &layout_engine)) { + &layout_engine + )) { return NULL; } #endif @@ -199,7 +202,8 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { (FT_Byte *)self->font_bytes, font_bytes_size, index, - &self->face); + &self->face + ); } } @@ -243,7 +247,8 @@ text_layout_raqm( const char *dir, PyObject *features, const char *lang, - GlyphInfo **glyph_info) { + GlyphInfo **glyph_info +) { size_t i = 0, count = 0, start = 0; raqm_t *rq; raqm_glyph_t *glyphs = NULL; @@ -297,13 +302,14 @@ text_layout_raqm( #if !defined(RAQM_VERSION_ATLEAST) /* RAQM_VERSION_ATLEAST was added in Raqm 0.7.0 */ PyErr_SetString( - PyExc_ValueError, - "libraqm 0.7 or greater required for 'ttb' direction"); + PyExc_ValueError, "libraqm 0.7 or greater required for 'ttb' direction" + ); goto failed; #endif } else { PyErr_SetString( - PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'"); + PyExc_ValueError, "direction must be either 'rtl', 'ltr' or 'ttb'" + ); goto failed; } } @@ -399,7 +405,8 @@ text_layout_fallback( const char *lang, GlyphInfo **glyph_info, int mask, - int color) { + int color +) { int error, load_flags, i; char *buffer = NULL; FT_ULong ch; @@ -412,7 +419,8 @@ text_layout_fallback( PyErr_SetString( PyExc_KeyError, "setting text direction, language or font features is not supported " - "without libraqm"); + "without libraqm" + ); } if (PyUnicode_Check(string)) { @@ -459,7 +467,8 @@ text_layout_fallback( last_index, (*glyph_info)[i].index, ft_kerning_default, - &delta) == 0) { + &delta + ) == 0) { (*glyph_info)[i - 1].x_advance += PIXEL(delta.x); (*glyph_info)[i - 1].y_advance += PIXEL(delta.y); } @@ -483,7 +492,8 @@ text_layout( const char *lang, GlyphInfo **glyph_info, int mask, - int color) { + int color +) { size_t count; #ifdef HAVE_RAQM if (have_raqm && self->layout_engine == LAYOUT_RAQM) { @@ -492,7 +502,8 @@ text_layout( #endif { count = text_layout_fallback( - string, self, dir, features, lang, glyph_info, mask, color); + string, self, dir, features, lang, glyph_info, mask, color + ); } return count; } @@ -514,7 +525,8 @@ font_getlength(FontObject *self, PyObject *args) { /* calculate size and bearing for a given string */ if (!PyArg_ParseTuple( - args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang)) { + args, "O|zzOz:getlength", &string, &mode, &dir, &features, &lang + )) { return NULL; } @@ -556,7 +568,8 @@ bounding_box_and_anchors( int *width, int *height, int *x_offset, - int *y_offset) { + int *y_offset +) { int position; /* pen position along primary axis, in 26.6 precision */ int advanced; /* pen position along primary axis, in pixels */ int px, py; /* position of current glyph, in pixels */ @@ -661,7 +674,8 @@ bounding_box_and_anchors( case 'm': // middle (ascender + descender) / 2 y_anchor = PIXEL( (face->size->metrics.ascender + face->size->metrics.descender) / - 2); + 2 + ); break; case 's': // horizontal baseline y_anchor = 0; @@ -741,7 +755,8 @@ font_getsize(FontObject *self, PyObject *args) { /* calculate size and bearing for a given string */ if (!PyArg_ParseTuple( - args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor)) { + args, "O|zzOzz:getsize", &string, &mode, &dir, &features, &lang, &anchor + )) { return NULL; } @@ -773,7 +788,8 @@ font_getsize(FontObject *self, PyObject *args) { &width, &height, &x_offset, - &y_offset); + &y_offset + ); if (glyph_info) { PyMem_Free(glyph_info); glyph_info = NULL; @@ -842,7 +858,8 @@ font_render(FontObject *self, PyObject *args) { &anchor, &foreground_ink_long, &x_start, - &y_start)) { + &y_start + )) { return NULL; } @@ -889,7 +906,8 @@ font_render(FontObject *self, PyObject *args) { &width, &height, &x_offset, - &y_offset); + &y_offset + ); if (error) { PyMem_Del(glyph_info); return NULL; @@ -929,7 +947,8 @@ font_render(FontObject *self, PyObject *args) { (FT_Fixed)stroke_width * 64, FT_STROKER_LINECAP_ROUND, FT_STROKER_LINEJOIN_ROUND, - 0); + 0 + ); } /* @@ -1104,8 +1123,8 @@ font_render(FontObject *self, PyObject *args) { BLEND(src_alpha, target[k * 4 + 2], src_blue, tmp); target[k * 4 + 3] = CLIP8( src_alpha + - MULDIV255( - target[k * 4 + 3], (255 - src_alpha), tmp)); + MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp) + ); } else { /* paste unpremultiplied RGBA values */ target[k * 4 + 0] = src_red; @@ -1123,15 +1142,20 @@ font_render(FontObject *self, PyObject *args) { if (src_alpha > 0) { if (target[k * 4 + 3] > 0) { target[k * 4 + 0] = BLEND( - src_alpha, target[k * 4 + 0], ink[0], tmp); + src_alpha, target[k * 4 + 0], ink[0], tmp + ); target[k * 4 + 1] = BLEND( - src_alpha, target[k * 4 + 1], ink[1], tmp); + src_alpha, target[k * 4 + 1], ink[1], tmp + ); target[k * 4 + 2] = BLEND( - src_alpha, target[k * 4 + 2], ink[2], tmp); + src_alpha, target[k * 4 + 2], ink[2], tmp + ); target[k * 4 + 3] = CLIP8( src_alpha + MULDIV255( - target[k * 4 + 3], (255 - src_alpha), tmp)); + target[k * 4 + 3], (255 - src_alpha), tmp + ) + ); } else { target[k * 4 + 0] = ink[0]; target[k * 4 + 1] = ink[1]; @@ -1149,7 +1173,9 @@ font_render(FontObject *self, PyObject *args) { ? CLIP8( src_alpha + MULDIV255( - target[k], (255 - src_alpha), tmp)) + target[k], (255 - src_alpha), tmp + ) + ) : src_alpha; } } @@ -1425,7 +1451,8 @@ static PyMethodDef font_methods[] = { {"setvarname", (PyCFunction)font_setvarname, METH_VARARGS}, {"setvaraxes", (PyCFunction)font_setvaraxes, METH_VARARGS}, #endif - {NULL, NULL}}; + {NULL, NULL} +}; static PyObject * font_getattr_family(FontObject *self, void *closure) { @@ -1482,7 +1509,8 @@ static struct PyGetSetDef font_getsetters[] = { {"x_ppem", (getter)font_getattr_x_ppem}, {"y_ppem", (getter)font_getattr_y_ppem}, {"glyphs", (getter)font_getattr_glyphs}, - {NULL}}; + {NULL} +}; static PyTypeObject Font_Type = { PyVarObject_HEAD_INIT(NULL, 0) "Font", /*tp_name*/ @@ -1518,7 +1546,8 @@ static PyTypeObject Font_Type = { }; static PyMethodDef _functions[] = { - {"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS}, {NULL, NULL}}; + {"getfont", (PyCFunction)getfont, METH_VARARGS | METH_KEYWORDS}, {NULL, NULL} +}; static int setup_module(PyObject *m) { diff --git a/src/_imagingmath.c b/src/_imagingmath.c index a2ddc91b9..550a10903 100644 --- a/src/_imagingmath.c +++ b/src/_imagingmath.c @@ -209,7 +209,8 @@ _binop(PyObject *self, PyObject *args) { } static PyMethodDef _functions[] = { - {"unop", _unop, 1}, {"binop", _binop, 1}, {NULL, NULL}}; + {"unop", _unop, 1}, {"binop", _binop, 1}, {NULL, NULL} +}; static void install(PyObject *d, char *name, void *value) { diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index a95ce75bf..614dfbe7f 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -253,7 +253,8 @@ static PyMethodDef functions[] = { {"apply", (PyCFunction)apply, METH_VARARGS, NULL}, {"get_on_pixels", (PyCFunction)get_on_pixels, METH_VARARGS, NULL}, {"match", (PyCFunction)match, METH_VARARGS, NULL}, - {NULL, NULL, 0, NULL}}; + {NULL, NULL, 0, NULL} +}; PyMODINIT_FUNC PyInit__imagingmorph(void) { diff --git a/src/_webp.c b/src/_webp.c index dfa24da41..e686ec820 100644 --- a/src/_webp.c +++ b/src/_webp.c @@ -42,7 +42,8 @@ static const char *const kErrorMessages[-WEBP_MUX_NOT_ENOUGH_DATA + 1] = { "WEBP_MUX_INVALID_ARGUMENT", "WEBP_MUX_BAD_DATA", "WEBP_MUX_MEMORY_ERROR", - "WEBP_MUX_NOT_ENOUGH_DATA"}; + "WEBP_MUX_NOT_ENOUGH_DATA" +}; PyObject * HandleMuxError(WebPMuxError err, char *chunk) { @@ -61,7 +62,8 @@ HandleMuxError(WebPMuxError err, char *chunk) { sprintf(message, "could not assemble chunks: %s", kErrorMessages[-err]); } else { message_len = sprintf( - message, "could not set %.4s chunk: %s", chunk, kErrorMessages[-err]); + message, "could not set %.4s chunk: %s", chunk, kErrorMessages[-err] + ); } if (message_len < 0) { PyErr_SetString(PyExc_RuntimeError, "failed to construct error message"); @@ -138,7 +140,8 @@ _anim_encoder_new(PyObject *self, PyObject *args) { &kmin, &kmax, &allow_mixed, - &verbose)) { + &verbose + )) { return NULL; } @@ -214,7 +217,8 @@ _anim_encoder_add(PyObject *self, PyObject *args) { &lossless, &quality_factor, &alpha_quality_factor, - &method)) { + &method + )) { return NULL; } @@ -283,7 +287,8 @@ _anim_encoder_assemble(PyObject *self, PyObject *args) { &exif_bytes, &exif_size, &xmp_bytes, - &xmp_size)) { + &xmp_size + )) { return NULL; } @@ -421,7 +426,8 @@ _anim_decoder_get_info(PyObject *self) { info->loop_count, info->bgcolor, info->frame_count, - decp->mode); + decp->mode + ); } PyObject * @@ -466,7 +472,8 @@ _anim_decoder_get_next(PyObject *self) { } bytes = PyBytes_FromStringAndSize( - (char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height); + (char *)buf, decp->info.canvas_width * 4 * decp->info.canvas_height + ); ret = Py_BuildValue("Si", bytes, timestamp); @@ -621,7 +628,8 @@ WebPEncode_wrapper(PyObject *self, PyObject *args) { &exif_bytes, &exif_size, &xmp_bytes, - &xmp_size)) { + &xmp_size + )) { return NULL; } @@ -828,12 +836,14 @@ WebPDecode_wrapper(PyObject *self, PyObject *args) { if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "ICCP", &icc_profile_data)) { icc_profile = PyBytes_FromStringAndSize( - (const char *)icc_profile_data.bytes, icc_profile_data.size); + (const char *)icc_profile_data.bytes, icc_profile_data.size + ); } if (WEBP_MUX_OK == WebPMuxGetChunk(mux, "EXIF", &exif_data)) { exif = PyBytes_FromStringAndSize( - (const char *)exif_data.bytes, exif_data.size); + (const char *)exif_data.bytes, exif_data.size + ); } WebPDataClear(&image.bitstream); @@ -848,12 +858,14 @@ WebPDecode_wrapper(PyObject *self, PyObject *args) { if (config.output.colorspace < MODE_YUV) { bytes = PyBytes_FromStringAndSize( - (char *)config.output.u.RGBA.rgba, config.output.u.RGBA.size); + (char *)config.output.u.RGBA.rgba, config.output.u.RGBA.size + ); } else { // Skipping YUV for now. Need Test Images. // UNDONE -- unclear if we'll ever get here if we set mode_rgb* bytes = PyBytes_FromStringAndSize( - (char *)config.output.u.YUVA.y, config.output.u.YUVA.y_size); + (char *)config.output.u.YUVA.y, config.output.u.YUVA.y_size + ); } pymode = PyUnicode_FromString(mode); @@ -864,7 +876,8 @@ WebPDecode_wrapper(PyObject *self, PyObject *args) { config.output.height, pymode, NULL == icc_profile ? Py_None : icc_profile, - NULL == exif ? Py_None : exif); + NULL == exif ? Py_None : exif + ); end: WebPFreeDecBuffer(&config.output); @@ -898,7 +911,8 @@ WebPDecoderVersion_str(void) { "%d.%d.%d", version_number >> 16, (version_number >> 8) % 0x100, - version_number % 0x100); + version_number % 0x100 + ); return version; } @@ -932,7 +946,8 @@ static PyMethodDef webpMethods[] = { WebPDecoderBuggyAlpha_wrapper, METH_NOARGS, "WebPDecoderBuggyAlpha"}, - {NULL, NULL}}; + {NULL, NULL} +}; void addMuxFlagToModule(PyObject *m) { diff --git a/src/decode.c b/src/decode.c index ea2f3af80..51d0aced2 100644 --- a/src/decode.c +++ b/src/decode.c @@ -46,7 +46,8 @@ typedef struct { PyObject_HEAD int (*decode)( - Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes + ); int (*cleanup)(ImagingCodecState state); struct ImagingCodecStateInstance state; Imaging im; @@ -889,7 +890,8 @@ PyImaging_Jpeg2KDecoderNew(PyObject *self, PyObject *args) { PY_LONG_LONG length = -1; if (!PyArg_ParseTuple( - args, "ss|iiiL", &mode, &format, &reduce, &layers, &fd, &length)) { + args, "ss|iiiL", &mode, &format, &reduce, &layers, &fd, &length + )) { return NULL; } diff --git a/src/display.c b/src/display.c index 990f4b0a5..b4e2e3899 100644 --- a/src/display.c +++ b/src/display.c @@ -105,7 +105,8 @@ _draw(ImagingDisplayObject *display, PyObject *args) { src + 0, src + 1, src + 2, - src + 3)) { + src + 3 + )) { return NULL; } @@ -221,7 +222,8 @@ _tobytes(ImagingDisplayObject *display, PyObject *args) { } return PyBytes_FromStringAndSize( - display->dib->bits, display->dib->ysize * display->dib->linesize); + display->dib->bits, display->dib->ysize * display->dib->linesize + ); } static struct PyMethodDef methods[] = { @@ -247,7 +249,8 @@ _getattr_size(ImagingDisplayObject *self, void *closure) { } static struct PyGetSetDef getsetters[] = { - {"mode", (getter)_getattr_mode}, {"size", (getter)_getattr_size}, {NULL}}; + {"mode", (getter)_getattr_mode}, {"size", (getter)_getattr_size}, {NULL} +}; static PyTypeObject ImagingDisplayType = { PyVarObject_HEAD_INIT(NULL, 0) "ImagingDisplay", /*tp_name*/ @@ -341,9 +344,8 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) { // added in Windows 10 (1607) // loaded dynamically to avoid link errors user32 = LoadLibraryA("User32.dll"); - SetThreadDpiAwarenessContext_function = - (Func_SetThreadDpiAwarenessContext)GetProcAddress( - user32, "SetThreadDpiAwarenessContext"); + SetThreadDpiAwarenessContext_function = (Func_SetThreadDpiAwarenessContext + )GetProcAddress(user32, "SetThreadDpiAwarenessContext"); if (SetThreadDpiAwarenessContext_function != NULL) { // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE = ((DPI_CONTEXT_HANDLE)-3) dpiAwareness = SetThreadDpiAwarenessContext_function((HANDLE)-3); @@ -403,7 +405,8 @@ PyImaging_GrabScreenWin32(PyObject *self, PyObject *args) { height, PyBytes_AS_STRING(buffer), (BITMAPINFO *)&core, - DIB_RGB_COLORS)) { + DIB_RGB_COLORS + )) { goto error; } @@ -547,7 +550,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) { ps.rcPaint.left, ps.rcPaint.top, ps.rcPaint.right, - ps.rcPaint.bottom); + ps.rcPaint.bottom + ); if (result) { Py_DECREF(result); } else { @@ -562,7 +566,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) { 0, 0, rect.right - rect.left, - rect.bottom - rect.top); + rect.bottom - rect.top + ); if (result) { Py_DECREF(result); } else { @@ -577,7 +582,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) { 0, 0, rect.right - rect.left, - rect.bottom - rect.top); + rect.bottom - rect.top + ); if (result) { Py_DECREF(result); } else { @@ -591,7 +597,8 @@ windowCallback(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam) { case WM_SIZE: /* resize window */ result = PyObject_CallFunction( - callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam)); + callback, "sii", "resize", LOWORD(lParam), HIWORD(lParam) + ); if (result) { InvalidateRect(wnd, NULL, 1); Py_DECREF(result); @@ -670,7 +677,8 @@ PyImaging_CreateWindowWin32(PyObject *self, PyObject *args) { HWND_DESKTOP, NULL, NULL, - NULL); + NULL + ); if (!wnd) { PyErr_SetString(PyExc_OSError, "failed to create window"); @@ -732,7 +740,8 @@ PyImaging_DrawWmf(PyObject *self, PyObject *args) { &x0, &x1, &y0, - &y1)) { + &y1 + )) { return NULL; } @@ -844,7 +853,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) { PyErr_Format( PyExc_OSError, "X connection failed: error %i", - xcb_connection_has_error(connection)); + xcb_connection_has_error(connection) + ); xcb_disconnect(connection); return NULL; } @@ -878,8 +888,10 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) { 0, width, height, - 0x00ffffff), - &error); + 0x00ffffff + ), + &error + ); if (reply == NULL) { PyErr_Format( PyExc_OSError, @@ -887,7 +899,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) { error->error_code, error->major_code, error->minor_code, - error->resource_id); + error->resource_id + ); free(error); xcb_disconnect(connection); return NULL; @@ -897,7 +910,8 @@ PyImaging_GrabScreenX11(PyObject *self, PyObject *args) { if (reply->depth == 24) { buffer = PyBytes_FromStringAndSize( - (char *)xcb_get_image_data(reply), xcb_get_image_data_length(reply)); + (char *)xcb_get_image_data(reply), xcb_get_image_data_length(reply) + ); } else { PyErr_Format(PyExc_OSError, "unsupported bit depth: %i", reply->depth); } diff --git a/src/encode.c b/src/encode.c index 2c95b7ebc..f711865d5 100644 --- a/src/encode.c +++ b/src/encode.c @@ -39,7 +39,8 @@ typedef struct { PyObject_HEAD int (*encode)( - Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); + Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes + ); int (*cleanup)(ImagingCodecState state); struct ImagingCodecStateInstance state; Imaging im; @@ -135,7 +136,8 @@ _encode(ImagingEncoderObject *encoder, PyObject *args) { } status = encoder->encode( - encoder->im, &encoder->state, (UINT8 *)PyBytes_AsString(buf), bufsize); + encoder->im, &encoder->state, (UINT8 *)PyBytes_AsString(buf), bufsize + ); /* adjust string length to avoid slicing in encoder */ if (_PyBytes_Resize(&buf, (status > 0) ? status : 0) < 0) { @@ -572,7 +574,8 @@ PyImaging_ZipEncoderNew(PyObject *self, PyObject *args) { &compress_level, &compress_type, &dictionary, - &dictionary_size)) { + &dictionary_size + )) { return NULL; } @@ -653,15 +656,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { PyObject *item; if (!PyArg_ParseTuple( - args, - "sssnsOO", - &mode, - &rawmode, - &compname, - &fp, - &filename, - &tags, - &types)) { + args, "sssnsOO", &mode, &rawmode, &compname, &fp, &filename, &tags, &types + )) { return NULL; } @@ -785,7 +781,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { is_var_length = 1; } if (ImagingLibTiffMergeFieldInfo( - &encoder->state, type, key_int, is_var_length)) { + &encoder->state, type, key_int, is_var_length + )) { continue; } } @@ -795,7 +792,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { &encoder->state, (ttag_t)key_int, PyBytes_Size(value), - PyBytes_AsString(value)); + PyBytes_AsString(value) + ); } else if (is_var_length) { Py_ssize_t len, i; TRACE(("Setting from Tuple: %d \n", key_int)); @@ -805,7 +803,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { int stride = 256; if (len != 768) { PyErr_SetString( - PyExc_ValueError, "Requiring 768 items for Colormap"); + PyExc_ValueError, "Requiring 768 items for Colormap" + ); return NULL; } UINT16 *av; @@ -820,7 +819,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { (ttag_t)key_int, av, av + stride, - av + stride * 2); + av + stride * 2 + ); free(av); } } else if (key_int == TIFFTAG_YCBCRSUBSAMPLING) { @@ -828,7 +828,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { &encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(PyTuple_GetItem(value, 0)), - (UINT16)PyLong_AsLong(PyTuple_GetItem(value, 1))); + (UINT16)PyLong_AsLong(PyTuple_GetItem(value, 1)) + ); } else if (type == TIFF_SHORT) { UINT16 *av; /* malloc check ok, calloc checks for overflow */ @@ -838,7 +839,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { av[i] = (UINT16)PyLong_AsLong(PyTuple_GetItem(value, i)); } status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, len, av); + &encoder->state, (ttag_t)key_int, len, av + ); free(av); } } else if (type == TIFF_LONG) { @@ -850,7 +852,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { av[i] = (UINT32)PyLong_AsLong(PyTuple_GetItem(value, i)); } status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, len, av); + &encoder->state, (ttag_t)key_int, len, av + ); free(av); } } else if (type == TIFF_SBYTE) { @@ -862,7 +865,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { av[i] = (INT8)PyLong_AsLong(PyTuple_GetItem(value, i)); } status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, len, av); + &encoder->state, (ttag_t)key_int, len, av + ); free(av); } } else if (type == TIFF_SSHORT) { @@ -874,7 +878,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { av[i] = (INT16)PyLong_AsLong(PyTuple_GetItem(value, i)); } status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, len, av); + &encoder->state, (ttag_t)key_int, len, av + ); free(av); } } else if (type == TIFF_SLONG) { @@ -886,7 +891,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { av[i] = (INT32)PyLong_AsLong(PyTuple_GetItem(value, i)); } status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, len, av); + &encoder->state, (ttag_t)key_int, len, av + ); free(av); } } else if (type == TIFF_FLOAT) { @@ -898,7 +904,8 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { av[i] = (FLOAT32)PyFloat_AsDouble(PyTuple_GetItem(value, i)); } status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, len, av); + &encoder->state, (ttag_t)key_int, len, av + ); free(av); } } else if (type == TIFF_DOUBLE) { @@ -910,43 +917,54 @@ PyImaging_LibTiffEncoderNew(PyObject *self, PyObject *args) { av[i] = PyFloat_AsDouble(PyTuple_GetItem(value, i)); } status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, len, av); + &encoder->state, (ttag_t)key_int, len, av + ); free(av); } } } else { if (type == TIFF_SHORT) { status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(value)); + &encoder->state, (ttag_t)key_int, (UINT16)PyLong_AsLong(value) + ); } else if (type == TIFF_LONG) { status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, PyLong_AsLongLong(value)); + &encoder->state, (ttag_t)key_int, PyLong_AsLongLong(value) + ); } else if (type == TIFF_SSHORT) { status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, (INT16)PyLong_AsLong(value)); + &encoder->state, (ttag_t)key_int, (INT16)PyLong_AsLong(value) + ); } else if (type == TIFF_SLONG) { status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, (INT32)PyLong_AsLong(value)); + &encoder->state, (ttag_t)key_int, (INT32)PyLong_AsLong(value) + ); } else if (type == TIFF_FLOAT) { status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, (FLOAT32)PyFloat_AsDouble(value)); + &encoder->state, (ttag_t)key_int, (FLOAT32)PyFloat_AsDouble(value) + ); } else if (type == TIFF_DOUBLE) { status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)); + &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value) + ); } else if (type == TIFF_SBYTE) { status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, (INT8)PyLong_AsLong(value)); + &encoder->state, (ttag_t)key_int, (INT8)PyLong_AsLong(value) + ); } else if (type == TIFF_ASCII) { status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, PyBytes_AsString(value)); + &encoder->state, (ttag_t)key_int, PyBytes_AsString(value) + ); } else if (type == TIFF_RATIONAL) { status = ImagingLibTiffSetField( - &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value)); + &encoder->state, (ttag_t)key_int, (FLOAT64)PyFloat_AsDouble(value) + ); } else { TRACE( ("Unhandled type for key %d : %s \n", key_int, - PyBytes_AsString(PyObject_Str(value)))); + PyBytes_AsString(PyObject_Str(value))) + ); } } if (!status) { @@ -1007,7 +1025,8 @@ get_qtables_arrays(PyObject *qtables, int *qtablesLen) { if (num_tables < 1 || num_tables > NUM_QUANT_TBLS) { PyErr_SetString( PyExc_ValueError, - "Not a valid number of quantization tables. Should be between 1 and 4."); + "Not a valid number of quantization tables. Should be between 1 and 4." + ); Py_DECREF(tables); return NULL; } @@ -1096,7 +1115,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { &extra, &extra_size, &rawExif, - &rawExifLen)) { + &rawExifLen + )) { return NULL; } @@ -1266,7 +1286,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) { &fd, &comment, &comment_size, - &plt)) { + &plt + )) { return NULL; } @@ -1323,7 +1344,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) { j2k_decode_coord_tuple(offset, &context->offset_x, &context->offset_y); j2k_decode_coord_tuple( - tile_offset, &context->tile_offset_x, &context->tile_offset_y); + tile_offset, &context->tile_offset_x, &context->tile_offset_y + ); j2k_decode_coord_tuple(tile_size, &context->tile_size_x, &context->tile_size_y); /* Error on illegal tile offsets */ @@ -1333,7 +1355,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) { PyErr_SetString( PyExc_ValueError, "JPEG 2000 tile offset too small; top left tile must " - "intersect image area"); + "intersect image area" + ); Py_DECREF(encoder); return NULL; } @@ -1341,8 +1364,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) { if (context->tile_offset_x > context->offset_x || context->tile_offset_y > context->offset_y) { PyErr_SetString( - PyExc_ValueError, - "JPEG 2000 tile offset too large to cover image area"); + PyExc_ValueError, "JPEG 2000 tile offset too large to cover image area" + ); Py_DECREF(encoder); return NULL; } @@ -1376,7 +1399,8 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) { j2k_decode_coord_tuple(cblk_size, &context->cblk_width, &context->cblk_height); j2k_decode_coord_tuple( - precinct_size, &context->precinct_width, &context->precinct_height); + precinct_size, &context->precinct_width, &context->precinct_height + ); context->irreversible = PyObject_IsTrue(irreversible); context->progression = prog_order; diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c index 3a5e918e8..bf7db281e 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -36,7 +36,8 @@ add_item(const char *mode) { "AccessInit: hash collision: %d for both %s and %s\n", i, mode, - access_table[i].mode); + access_table[i].mode + ); exit(1); } access_table[i].mode = mode; diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 72f478d8d..9a41febc7 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -243,7 +243,8 @@ static const bc7_mode_info bc7_modes[] = { {1, 0, 2, 1, 5, 6, 0, 0, 2, 3}, {1, 0, 2, 0, 7, 8, 0, 0, 2, 2}, {1, 0, 0, 0, 7, 7, 1, 0, 4, 0}, - {2, 6, 0, 0, 5, 5, 1, 0, 2, 0}}; + {2, 6, 0, 0, 5, 5, 1, 0, 2, 0} +}; /* Subset indices: Table.P2, 1 bit per index */ @@ -254,7 +255,8 @@ static const UINT16 bc7_si2[] = { 0x718e, 0x399c, 0xaaaa, 0xf0f0, 0x5a5a, 0x33cc, 0x3c3c, 0x55aa, 0x9696, 0xa55a, 0x73ce, 0x13c8, 0x324c, 0x3bdc, 0x6996, 0xc33c, 0x9966, 0x0660, 0x0272, 0x04e4, 0x4e40, 0x2720, 0xc936, 0x936c, 0x39c6, 0x639c, 0x9336, 0x9cc6, 0x817e, 0xe718, - 0xccf0, 0x0fcc, 0x7744, 0xee22}; + 0xccf0, 0x0fcc, 0x7744, 0xee22 +}; /* Table.P3, 2 bits per index */ static const UINT32 bc7_si3[] = { @@ -267,20 +269,23 @@ static const UINT32 bc7_si3[] = { 0x66660000, 0xa5a0a5a0, 0x50a050a0, 0x69286928, 0x44aaaa44, 0x66666600, 0xaa444444, 0x54a854a8, 0x95809580, 0x96969600, 0xa85454a8, 0x80959580, 0xaa141414, 0x96960000, 0xaaaa1414, 0xa05050a0, 0xa0a5a5a0, 0x96000000, 0x40804080, 0xa9a8a9a8, 0xaaaaaa44, - 0x2a4a5254}; + 0x2a4a5254 +}; /* Anchor indices: Table.A2 */ -static const char bc7_ai0[] = { - 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 2, 8, 2, 2, 8, - 8, 15, 2, 8, 2, 2, 8, 8, 2, 2, 15, 15, 6, 8, 2, 8, 15, 15, 2, 8, 2, 2, - 2, 15, 15, 6, 6, 2, 6, 8, 15, 15, 2, 2, 15, 15, 15, 15, 15, 2, 2, 15}; +static const char bc7_ai0[] = {15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15, + 15, 15, 15, 15, 2, 8, 2, 2, 8, 8, 15, 2, 8, + 2, 2, 8, 8, 2, 2, 15, 15, 6, 8, 2, 8, 15, + 15, 2, 8, 2, 2, 2, 15, 15, 6, 6, 2, 6, 8, + 15, 15, 2, 2, 15, 15, 15, 15, 15, 2, 2, 15}; /* Table.A3a */ -static const char bc7_ai1[] = { - 3, 3, 15, 15, 8, 3, 15, 15, 8, 8, 6, 6, 6, 5, 3, 3, 3, 3, 8, 15, 3, 3, - 6, 10, 5, 8, 8, 6, 8, 5, 15, 15, 8, 15, 3, 5, 6, 10, 8, 15, 15, 3, 15, 5, - 15, 15, 15, 15, 3, 15, 5, 5, 5, 8, 5, 10, 5, 10, 8, 13, 15, 12, 3, 3}; +static const char bc7_ai1[] = {3, 3, 15, 15, 8, 3, 15, 15, 8, 8, 6, 6, 6, + 5, 3, 3, 3, 3, 8, 15, 3, 3, 6, 10, 5, 8, + 8, 6, 8, 5, 15, 15, 8, 15, 3, 5, 6, 10, 8, + 15, 15, 3, 15, 5, 15, 15, 15, 15, 3, 15, 5, 5, + 5, 8, 5, 10, 5, 10, 8, 13, 15, 12, 3, 3}; /* Table.A3b */ static const char bc7_ai2[] = {15, 8, 8, 3, 15, 15, 3, 8, 15, 15, 15, 15, 15, @@ -293,7 +298,8 @@ static const char bc7_ai2[] = {15, 8, 8, 3, 15, 15, 3, 8, 15, 15, 15, 15, 1 static const char bc7_weights2[] = {0, 21, 43, 64}; static const char bc7_weights3[] = {0, 9, 18, 27, 37, 46, 55, 64}; static const char bc7_weights4[] = { - 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64}; + 0, 4, 9, 13, 17, 21, 26, 30, 34, 38, 43, 47, 51, 55, 60, 64 +}; static const char * bc7_get_weights(int n) { @@ -526,7 +532,8 @@ static const bc6_mode_info bc6_modes[] = { {1, 0, 0, 10, 10, 10, 10}, {1, 1, 0, 11, 9, 9, 9}, {1, 1, 0, 12, 8, 8, 8}, - {1, 1, 0, 16, 4, 4, 4}}; + {1, 1, 0, 16, 4, 4, 4} +}; /* Table.F, encoded as a sequence of bit indices */ static const UINT8 bc6_bit_packings[][75] = { @@ -591,7 +598,8 @@ static const UINT8 bc6_bit_packings[][75] = { 64, 65, 66, 67, 68, 69, 70, 71, 27, 26, 80, 81, 82, 83, 84, 85, 86, 87, 43, 42}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 15, 14, 13, 12, 11, 10, - 64, 65, 66, 67, 31, 30, 29, 28, 27, 26, 80, 81, 82, 83, 47, 46, 45, 44, 43, 42}}; + 64, 65, 66, 67, 31, 30, 29, 28, 27, 26, 80, 81, 82, 83, 47, 46, 45, 44, 43, 42} +}; static void bc6_sign_extend(UINT16 *v, int prec) { @@ -830,7 +838,8 @@ decode_bcn( int bytes, int N, int C, - char *pixel_format) { + char *pixel_format +) { int ymax = state->ysize + state->yoff; const UINT8 *ptr = src; switch (N) { diff --git a/src/libImaging/BoxBlur.c b/src/libImaging/BoxBlur.c index 4ea9c7717..51cb7e102 100644 --- a/src/libImaging/BoxBlur.c +++ b/src/libImaging/BoxBlur.c @@ -13,7 +13,8 @@ void static inline ImagingLineBoxBlur32( int edgeA, int edgeB, UINT32 ww, - UINT32 fw) { + UINT32 fw +) { int x; UINT32 acc[4]; UINT32 bulk[4]; @@ -109,7 +110,8 @@ void static inline ImagingLineBoxBlur8( int edgeA, int edgeB, UINT32 ww, - UINT32 fw) { + UINT32 fw +) { int x; UINT32 acc; UINT32 bulk; @@ -198,7 +200,8 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) { edgeA, edgeB, ww, - fw); + fw + ); if (imIn == imOut) { // Commit. memcpy(imOut->image8[y], lineOut, imIn->xsize); @@ -214,7 +217,8 @@ ImagingHorizontalBoxBlur(Imaging imOut, Imaging imIn, float floatRadius) { edgeA, edgeB, ww, - fw); + fw + ); if (imIn == imOut) { // Commit. memcpy(imOut->image32[y], lineOut, imIn->xsize * 4); @@ -314,11 +318,13 @@ _gaussian_blur_radius(float radius, int passes) { Imaging ImagingGaussianBlur( - Imaging imOut, Imaging imIn, float xradius, float yradius, int passes) { + Imaging imOut, Imaging imIn, float xradius, float yradius, int passes +) { return ImagingBoxBlur( imOut, imIn, _gaussian_blur_radius(xradius, passes), _gaussian_blur_radius(yradius, passes), - passes); + passes + ); } diff --git a/src/libImaging/Chops.c b/src/libImaging/Chops.c index f9c005efe..f326d402f 100644 --- a/src/libImaging/Chops.c +++ b/src/libImaging/Chops.c @@ -142,7 +142,8 @@ ImagingChopSoftLight(Imaging imIn1, Imaging imIn2) { CHOP2( (((255 - in1[x]) * (in1[x] * in2[x])) / 65536) + (in1[x] * (255 - ((255 - in1[x]) * (255 - in2[x]) / 255))) / 255, - NULL); + NULL + ); } Imaging @@ -150,7 +151,8 @@ ImagingChopHardLight(Imaging imIn1, Imaging imIn2) { CHOP2( (in2[x] < 128) ? ((in1[x] * in2[x]) / 127) : 255 - (((255 - in2[x]) * (255 - in1[x])) / 127), - NULL); + NULL + ); } Imaging @@ -158,5 +160,6 @@ ImagingOverlay(Imaging imIn1, Imaging imIn2) { CHOP2( (in1[x] < 128) ? ((in1[x] * in2[x]) / 127) : 255 - (((255 - in1[x]) * (255 - in2[x])) / 127), - NULL); + NULL + ); } diff --git a/src/libImaging/ColorLUT.c b/src/libImaging/ColorLUT.c index aee7cda06..5559de689 100644 --- a/src/libImaging/ColorLUT.c +++ b/src/libImaging/ColorLUT.c @@ -63,7 +63,8 @@ ImagingColorLUT3D_linear( int size1D, int size2D, int size3D, - INT16 *table) { + INT16 *table +) { /* This float to int conversion doesn't have rounding error compensation (+0.5) for two reasons: 1. As we don't hit the highest value, @@ -112,7 +113,8 @@ ImagingColorLUT3D_linear( index2D >> SCALE_BITS, index3D >> SCALE_BITS, size1D, - size1D_2D); + size1D_2D + ); INT16 result[4], left[4], right[4]; INT16 leftleft[4], leftright[4], rightleft[4], rightright[4]; @@ -123,19 +125,22 @@ ImagingColorLUT3D_linear( leftright, &table[idx + size1D * 3], &table[idx + size1D * 3 + 3], - shift1D); + shift1D + ); interpolate3(left, leftleft, leftright, shift2D); interpolate3( rightleft, &table[idx + size1D_2D * 3], &table[idx + size1D_2D * 3 + 3], - shift1D); + shift1D + ); interpolate3( rightright, &table[idx + size1D_2D * 3 + size1D * 3], &table[idx + size1D_2D * 3 + size1D * 3 + 3], - shift1D); + shift1D + ); interpolate3(right, rightleft, rightright, shift2D); interpolate3(result, left, right, shift3D); @@ -144,7 +149,8 @@ ImagingColorLUT3D_linear( clip8(result[0]), clip8(result[1]), clip8(result[2]), - rowIn[x * 4 + 3]); + rowIn[x * 4 + 3] + ); memcpy(rowOut + x * sizeof(v), &v, sizeof(v)); } @@ -155,19 +161,22 @@ ImagingColorLUT3D_linear( leftright, &table[idx + size1D * 4], &table[idx + size1D * 4 + 4], - shift1D); + shift1D + ); interpolate4(left, leftleft, leftright, shift2D); interpolate4( rightleft, &table[idx + size1D_2D * 4], &table[idx + size1D_2D * 4 + 4], - shift1D); + shift1D + ); interpolate4( rightright, &table[idx + size1D_2D * 4 + size1D * 4], &table[idx + size1D_2D * 4 + size1D * 4 + 4], - shift1D); + shift1D + ); interpolate4(right, rightleft, rightright, shift2D); interpolate4(result, left, right, shift3D); @@ -176,7 +185,8 @@ ImagingColorLUT3D_linear( clip8(result[0]), clip8(result[1]), clip8(result[2]), - clip8(result[3])); + clip8(result[3]) + ); memcpy(rowOut + x * sizeof(v), &v, sizeof(v)); } } diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index fcb5f7ad9..c8f234261 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -1044,7 +1044,8 @@ static struct { {"I;16L", "F", I16L_F}, {"I;16B", "F", I16B_F}, - {NULL}}; + {NULL} +}; /* FIXME: translate indexed versions to pointer versions below this line */ @@ -1316,7 +1317,8 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) { (UINT8 *)imOut->image[y], (UINT8 *)imIn->image[y], imIn->xsize, - imIn->palette); + imIn->palette + ); } ImagingSectionLeave(&cookie); @@ -1328,11 +1330,8 @@ frompalette(Imaging imOut, Imaging imIn, const char *mode) { #endif static Imaging topalette( - Imaging imOut, - Imaging imIn, - const char *mode, - ImagingPalette inpalette, - int dither) { + Imaging imOut, Imaging imIn, const char *mode, ImagingPalette inpalette, int dither +) { ImagingSectionCookie cookie; int alpha; int x, y; @@ -1623,7 +1622,8 @@ tobilevel(Imaging imOut, Imaging imIn) { static Imaging convert( - Imaging imOut, Imaging imIn, const char *mode, ImagingPalette palette, int dither) { + Imaging imOut, Imaging imIn, const char *mode, ImagingPalette palette, int dither +) { ImagingSectionCookie cookie; ImagingShuffler convert; int y; @@ -1677,7 +1677,8 @@ convert( #else static char buf[100]; snprintf( - buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode); + buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode + ); return (Imaging)ImagingError_ValueError(buf); #endif } @@ -1727,18 +1728,16 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { if (strcmp(mode, "RGBa") == 0) { premultiplied = 1; } - } else if ( - strcmp(imIn->mode, "RGB") == 0 && - (strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) { + } else if (strcmp(imIn->mode, "RGB") == 0 && + (strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) { convert = rgb2la; source_transparency = 1; if (strcmp(mode, "La") == 0) { premultiplied = 1; } - } else if ( - (strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 || - strcmp(imIn->mode, "I;16") == 0 || strcmp(imIn->mode, "L") == 0) && - (strcmp(mode, "RGBA") == 0 || strcmp(mode, "LA") == 0)) { + } else if ((strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 || + strcmp(imIn->mode, "I;16") == 0 || strcmp(imIn->mode, "L") == 0) && + (strcmp(mode, "RGBA") == 0 || strcmp(mode, "LA") == 0)) { if (strcmp(imIn->mode, "1") == 0) { convert = bit2rgb; } else if (strcmp(imIn->mode, "I") == 0) { @@ -1756,7 +1755,8 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { 100, "conversion from %.10s to %.10s not supported in convert_transparent", imIn->mode, - mode); + mode + ); return (Imaging)ImagingError_ValueError(buf); } diff --git a/src/libImaging/ConvertYCbCr.c b/src/libImaging/ConvertYCbCr.c index 142f065e5..285b43327 100644 --- a/src/libImaging/ConvertYCbCr.c +++ b/src/libImaging/ConvertYCbCr.c @@ -47,7 +47,8 @@ static INT16 Y_R[] = { 4019, 4038, 4057, 4076, 4095, 4114, 4133, 4153, 4172, 4191, 4210, 4229, 4248, 4267, 4286, 4306, 4325, 4344, 4363, 4382, 4401, 4420, 4440, 4459, 4478, 4497, 4516, 4535, 4554, 4574, 4593, 4612, 4631, 4650, 4669, 4688, 4707, 4727, 4746, 4765, 4784, 4803, - 4822, 4841, 4861, 4880}; + 4822, 4841, 4861, 4880 +}; static INT16 Y_G[] = { 0, 38, 75, 113, 150, 188, 225, 263, 301, 338, 376, 413, 451, 488, @@ -68,7 +69,8 @@ static INT16 Y_G[] = { 7889, 7927, 7964, 8002, 8040, 8077, 8115, 8152, 8190, 8227, 8265, 8303, 8340, 8378, 8415, 8453, 8490, 8528, 8566, 8603, 8641, 8678, 8716, 8753, 8791, 8828, 8866, 8904, 8941, 8979, 9016, 9054, 9091, 9129, 9167, 9204, 9242, 9279, 9317, 9354, 9392, 9430, - 9467, 9505, 9542, 9580}; + 9467, 9505, 9542, 9580 +}; static INT16 Y_B[] = { 0, 7, 15, 22, 29, 36, 44, 51, 58, 66, 73, 80, 88, 95, @@ -89,7 +91,8 @@ static INT16 Y_B[] = { 1532, 1539, 1547, 1554, 1561, 1569, 1576, 1583, 1591, 1598, 1605, 1612, 1620, 1627, 1634, 1642, 1649, 1656, 1663, 1671, 1678, 1685, 1693, 1700, 1707, 1715, 1722, 1729, 1736, 1744, 1751, 1758, 1766, 1773, 1780, 1788, 1795, 1802, 1809, 1817, 1824, 1831, - 1839, 1846, 1853, 1860}; + 1839, 1846, 1853, 1860 +}; static INT16 Cb_R[] = { 0, -10, -21, -31, -42, -53, -64, -75, -85, -96, -107, -118, @@ -113,7 +116,8 @@ static INT16 Cb_R[] = { -2332, -2342, -2353, -2364, -2375, -2386, -2396, -2407, -2418, -2429, -2440, -2450, -2461, -2472, -2483, -2494, -2504, -2515, -2526, -2537, -2548, -2558, -2569, -2580, -2591, -2602, -2612, -2623, -2634, -2645, -2656, -2666, -2677, -2688, -2699, -2710, - -2720, -2731, -2742, -2753}; + -2720, -2731, -2742, -2753 +}; static INT16 Cb_G[] = { 0, -20, -41, -63, -84, -105, -126, -147, -169, -190, -211, -232, @@ -137,7 +141,8 @@ static INT16 Cb_G[] = { -4578, -4600, -4621, -4642, -4663, -4684, -4706, -4727, -4748, -4769, -4790, -4812, -4833, -4854, -4875, -4896, -4918, -4939, -4960, -4981, -5002, -5024, -5045, -5066, -5087, -5108, -5130, -5151, -5172, -5193, -5214, -5236, -5257, -5278, -5299, -5320, - -5342, -5363, -5384, -5405}; + -5342, -5363, -5384, -5405 +}; static INT16 Cb_B[] = { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, @@ -158,7 +163,8 @@ static INT16 Cb_B[] = { 6720, 6752, 6784, 6816, 6848, 6880, 6912, 6944, 6976, 7008, 7040, 7072, 7104, 7136, 7168, 7200, 7232, 7264, 7296, 7328, 7360, 7392, 7424, 7456, 7488, 7520, 7552, 7584, 7616, 7648, 7680, 7712, 7744, 7776, 7808, 7840, 7872, 7904, 7936, 7968, 8000, 8032, - 8064, 8096, 8128, 8160}; + 8064, 8096, 8128, 8160 +}; #define Cr_R Cb_B @@ -184,7 +190,8 @@ static INT16 Cr_G[] = { -5787, -5814, -5841, -5867, -5894, -5921, -5948, -5975, -6001, -6028, -6055, -6082, -6109, -6135, -6162, -6189, -6216, -6243, -6269, -6296, -6323, -6350, -6376, -6403, -6430, -6457, -6484, -6510, -6537, -6564, -6591, -6618, -6644, -6671, -6698, -6725, - -6752, -6778, -6805, -6832}; + -6752, -6778, -6805, -6832 +}; static INT16 Cr_B[] = { 0, -4, -9, -15, -20, -25, -30, -35, -41, -46, -51, -56, @@ -208,7 +215,8 @@ static INT16 Cr_B[] = { -1123, -1128, -1133, -1139, -1144, -1149, -1154, -1159, -1165, -1170, -1175, -1180, -1185, -1191, -1196, -1201, -1206, -1211, -1217, -1222, -1227, -1232, -1238, -1243, -1248, -1253, -1258, -1264, -1269, -1274, -1279, -1284, -1290, -1295, -1300, -1305, - -1310, -1316, -1321, -1326}; + -1310, -1316, -1321, -1326 +}; static INT16 R_Cr[] = { -11484, -11394, -11305, -11215, -11125, -11036, -10946, -10856, -10766, -10677, @@ -236,7 +244,8 @@ static INT16 R_Cr[] = { 8255, 8345, 8434, 8524, 8614, 8704, 8793, 8883, 8973, 9063, 9152, 9242, 9332, 9421, 9511, 9601, 9691, 9780, 9870, 9960, 10050, 10139, 10229, 10319, 10408, 10498, 10588, 10678, 10767, 10857, - 10947, 11037, 11126, 11216, 11306, 11395}; + 10947, 11037, 11126, 11216, 11306, 11395 +}; static INT16 G_Cb[] = { 2819, 2797, 2775, 2753, 2731, 2709, 2687, 2665, 2643, 2621, 2599, 2577, @@ -260,7 +269,8 @@ static INT16 G_Cb[] = { -1937, -1959, -1981, -2003, -2025, -2047, -2069, -2091, -2113, -2135, -2157, -2179, -2201, -2224, -2246, -2268, -2290, -2312, -2334, -2356, -2378, -2400, -2422, -2444, -2466, -2488, -2510, -2532, -2554, -2576, -2598, -2620, -2642, -2664, -2686, -2708, - -2730, -2752, -2774, -2796}; + -2730, -2752, -2774, -2796 +}; static INT16 G_Cr[] = { 5850, 5805, 5759, 5713, 5667, 5622, 5576, 5530, 5485, 5439, 5393, 5347, @@ -284,7 +294,8 @@ static INT16 G_Cr[] = { -4021, -4067, -4112, -4158, -4204, -4250, -4295, -4341, -4387, -4432, -4478, -4524, -4569, -4615, -4661, -4707, -4752, -4798, -4844, -4889, -4935, -4981, -5027, -5072, -5118, -5164, -5209, -5255, -5301, -5346, -5392, -5438, -5484, -5529, -5575, -5621, - -5666, -5712, -5758, -5804}; + -5666, -5712, -5758, -5804 +}; static INT16 B_Cb[] = { -14515, -14402, -14288, -14175, -14062, -13948, -13835, -13721, -13608, -13495, @@ -312,7 +323,8 @@ static INT16 B_Cb[] = { 10434, 10547, 10660, 10774, 10887, 11001, 11114, 11227, 11341, 11454, 11568, 11681, 11794, 11908, 12021, 12135, 12248, 12361, 12475, 12588, 12702, 12815, 12929, 13042, 13155, 13269, 13382, 13496, 13609, 13722, - 13836, 13949, 14063, 14176, 14289, 14403}; + 13836, 13949, 14063, 14176, 14289, 14403 +}; void ImagingConvertRGB2YCbCr(UINT8 *out, const UINT8 *in, int pixels) { diff --git a/src/libImaging/Dib.c b/src/libImaging/Dib.c index 269be1058..c69e9e552 100644 --- a/src/libImaging/Dib.c +++ b/src/libImaging/Dib.c @@ -95,7 +95,8 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) { } dib->bitmap = CreateDIBSection( - dib->dc, dib->info, DIB_RGB_COLORS, (void **)&dib->bits, NULL, 0); + dib->dc, dib->info, DIB_RGB_COLORS, (void **)&dib->bits, NULL, 0 + ); if (!dib->bitmap) { free(dib->info); free(dib); @@ -218,7 +219,8 @@ ImagingPasteDIB(ImagingDIB dib, Imaging im, int xy[4]) { dib->bits + dib->linesize * (dib->ysize - (xy[1] + y) - 1) + xy[0] * dib->pixelsize, im->image[y], - im->xsize); + im->xsize + ); } } @@ -251,7 +253,8 @@ ImagingDrawDIB(ImagingDIB dib, void *dc, int dst[4], int src[4]) { dib->bits, dib->info, DIB_RGB_COLORS, - SRCCOPY); + SRCCOPY + ); } else { /* stretchblt (displays) */ if (dib->palette != 0) { @@ -268,7 +271,8 @@ ImagingDrawDIB(ImagingDIB dib, void *dc, int dst[4], int src[4]) { src[1], src[2] - src[0], src[3] - src[1], - SRCCOPY); + SRCCOPY + ); } } diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 133696dd8..f1c8ffcff 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -120,9 +120,8 @@ hline8(Imaging im, int x0, int y0, int x1, int ink) { if (x0 <= x1) { pixelwidth = strncmp(im->mode, "I;16", 4) == 0 ? 2 : 1; memset( - im->image8[y0] + x0 * pixelwidth, - (UINT8)ink, - (x1 - x0 + 1) * pixelwidth); + im->image8[y0] + x0 * pixelwidth, (UINT8)ink, (x1 - x0 + 1) * pixelwidth + ); } } } @@ -408,7 +407,8 @@ x_cmp(const void *x0, const void *x1) { static void draw_horizontal_lines( - Imaging im, int n, Edge *e, int ink, int *x_pos, int y, hline_handler hline) { + Imaging im, int n, Edge *e, int ink, int *x_pos, int y, hline_handler hline +) { int i; for (i = 0; i < n; i++) { if (e[i].ymin == y && e[i].ymin == e[i].ymax) { @@ -440,13 +440,8 @@ draw_horizontal_lines( */ static inline int polygon_generic( - Imaging im, - int n, - Edge *e, - int ink, - int eofill, - hline_handler hline, - int hasAlpha) { + Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline, int hasAlpha +) { Edge **edge_table; float *xx; int edge_count = 0; @@ -530,25 +525,29 @@ polygon_generic( other_edge->x0; if (ymin == current->ymax) { if (current->dx > 0) { - xx[k] = fmax( - adjacent_line_x, - adjacent_line_x_other_edge) + - 1; + xx[k] = + fmax( + adjacent_line_x, adjacent_line_x_other_edge + ) + + 1; } else { - xx[k] = fmin( - adjacent_line_x, - adjacent_line_x_other_edge) - - 1; + xx[k] = + fmin( + adjacent_line_x, adjacent_line_x_other_edge + ) - + 1; } } else { if (current->dx > 0) { xx[k] = fmin( - adjacent_line_x, adjacent_line_x_other_edge); + adjacent_line_x, adjacent_line_x_other_edge + ); } else { - xx[k] = fmax( - adjacent_line_x, - adjacent_line_x_other_edge) + - 1; + xx[k] = + fmax( + adjacent_line_x, adjacent_line_x_other_edge + ) + + 1; } } break; @@ -699,7 +698,8 @@ ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink_, in int ImagingDrawWideLine( - Imaging im, int x0, int y0, int x1, int y1, const void *ink_, int width, int op) { + Imaging im, int x0, int y0, int x1, int y1, const void *ink_, int width, int op +) { DRAW *draw; INT32 ink; int dx, dy; @@ -730,7 +730,8 @@ ImagingDrawWideLine( {x0 - dxmin, y0 + dymax}, {x1 - dxmin, y1 + dymax}, {x1 + dxmax, y1 - dymin}, - {x0 + dxmax, y0 - dymin}}; + {x0 + dxmax, y0 - dymin} + }; add_edge(e + 0, vertices[0][0], vertices[0][1], vertices[1][0], vertices[1][1]); add_edge(e + 1, vertices[1][0], vertices[1][1], vertices[2][0], vertices[2][1]); @@ -752,7 +753,8 @@ ImagingDrawRectangle( const void *ink_, int fill, int width, - int op) { + int op +) { int i; int y; int tmp; @@ -800,7 +802,8 @@ ImagingDrawRectangle( int ImagingDrawPolygon( - Imaging im, int count, int *xy, const void *ink_, int fill, int width, int op) { + Imaging im, int count, int *xy, const void *ink_, int fill, int width, int op +) { int i, n, x0, y0, x1, y1; DRAW *draw; INT32 ink; @@ -851,7 +854,8 @@ ImagingDrawPolygon( if (width == 1) { for (i = 0; i < count - 1; i++) { draw->line( - im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink); + im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink + ); } draw->line(im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink); } else { @@ -864,10 +868,12 @@ ImagingDrawPolygon( xy[i * 2 + 3], ink_, width, - op); + op + ); } ImagingDrawWideLine( - im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op); + im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op + ); } } @@ -877,7 +883,8 @@ ImagingDrawPolygon( int ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void *ink, int op) { return ImagingFill2( - im, ink, bitmap, x0, y0, x0 + bitmap->xsize, y0 + bitmap->ysize); + im, ink, bitmap, x0, y0, x0 + bitmap->xsize, y0 + bitmap->ysize + ); } /* -------------------------------------------------------------------- */ @@ -1086,7 +1093,8 @@ clip_tree_transpose(clip_node *root) { // segments, i.e. something like correct bracket sequences. int clip_tree_do_clip( - clip_node *root, int32_t x0, int32_t y, int32_t x1, event_list **ret) { + clip_node *root, int32_t x0, int32_t y, int32_t x1, event_list **ret +) { if (root == NULL) { event_list *start = malloc(sizeof(event_list)); if (!start) { @@ -1223,7 +1231,8 @@ typedef struct { } clip_ellipse_state; typedef void (*clip_ellipse_init)( - clip_ellipse_state *, int32_t, int32_t, int32_t, float, float); + clip_ellipse_state *, int32_t, int32_t, int32_t, float, float +); void debug_clip_tree(clip_node *root, int space) { @@ -1335,7 +1344,8 @@ arc_init(clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float // A chord line. void chord_line_init( - clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float ar) { + clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float ar +) { ellipse_init(&s->st, a, b, a + b + 1); s->head = NULL; @@ -1362,7 +1372,8 @@ chord_line_init( // Pie side. void pie_side_init( - clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float _) { + clip_ellipse_state *s, int32_t a, int32_t b, int32_t w, float al, float _ +) { ellipse_init(&s->st, a, b, a + b + 1); s->head = NULL; @@ -1478,7 +1489,8 @@ clip_ellipse_free(clip_ellipse_state *s) { int8_t clip_ellipse_next( - clip_ellipse_state *s, int32_t *ret_x0, int32_t *ret_y, int32_t *ret_x1) { + clip_ellipse_state *s, int32_t *ret_x0, int32_t *ret_y, int32_t *ret_x1 +) { int32_t x0, y, x1; while (s->head == NULL && ellipse_next(&s->st, &x0, &y, &x1) >= 0) { if (clip_tree_do_clip(s->root, x0, y, x1, &s->head) < 0) { @@ -1512,7 +1524,8 @@ ellipseNew( const void *ink_, int fill, int width, - int op) { + int op +) { DRAW *draw; INT32 ink; DRAWINIT(); @@ -1547,7 +1560,8 @@ clipEllipseNew( const void *ink_, int width, int op, - clip_ellipse_init init) { + clip_ellipse_init init +) { DRAW *draw; INT32 ink; DRAWINIT(); @@ -1580,7 +1594,8 @@ arcNew( float end, const void *ink_, int width, - int op) { + int op +) { return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, arc_init); } @@ -1595,7 +1610,8 @@ chordNew( float end, const void *ink_, int width, - int op) { + int op +) { return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, chord_init); } @@ -1610,9 +1626,11 @@ chordLineNew( float end, const void *ink_, int width, - int op) { + int op +) { return clipEllipseNew( - im, x0, y0, x1, y1, start, end, ink_, width, op, chord_line_init); + im, x0, y0, x1, y1, start, end, ink_, width, op, chord_line_init + ); } static int @@ -1626,7 +1644,8 @@ pieNew( float end, const void *ink_, int width, - int op) { + int op +) { return clipEllipseNew(im, x0, y0, x1, y1, start, end, ink_, width, op, pie_init); } @@ -1640,7 +1659,8 @@ pieSideNew( float start, const void *ink_, int width, - int op) { + int op +) { return clipEllipseNew(im, x0, y0, x1, y1, start, 0, ink_, width, op, pie_side_init); } @@ -1654,7 +1674,8 @@ ImagingDrawEllipse( const void *ink, int fill, int width, - int op) { + int op +) { return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op); } @@ -1669,7 +1690,8 @@ ImagingDrawArc( float end, const void *ink, int width, - int op) { + int op +) { normalize_angles(&start, &end); if (start + 360 == end) { return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, 0, width, op); @@ -1692,7 +1714,8 @@ ImagingDrawChord( const void *ink, int fill, int width, - int op) { + int op +) { normalize_angles(&start, &end); if (start + 360 == end) { return ImagingDrawEllipse(im, x0, y0, x1, y1, ink, fill, width, op); @@ -1722,7 +1745,8 @@ ImagingDrawPieslice( const void *ink, int fill, int width, - int op) { + int op +) { normalize_angles(&start, &end); if (start + 360 == end) { return ellipseNew(im, x0, y0, x1, y1, ink, fill, width, op); @@ -1850,13 +1874,8 @@ ImagingOutlineLine(ImagingOutline outline, float x1, float y1) { int ImagingOutlineCurve( - ImagingOutline outline, - float x1, - float y1, - float x2, - float y2, - float x3, - float y3) { + ImagingOutline outline, float x1, float y1, float x2, float y2, float x3, float y3 +) { Edge *e; int i; float xo, yo; @@ -1970,7 +1989,8 @@ ImagingOutlineTransform(ImagingOutline outline, double a[6]) { int ImagingDrawOutline( - Imaging im, ImagingOutline outline, const void *ink_, int fill, int op) { + Imaging im, ImagingOutline outline, const void *ink_, int fill, int op +) { DRAW *draw; INT32 ink; diff --git a/src/libImaging/Fill.c b/src/libImaging/Fill.c index 5b6bfb89c..8fb481e7e 100644 --- a/src/libImaging/Fill.c +++ b/src/libImaging/Fill.c @@ -118,8 +118,8 @@ ImagingFillRadialGradient(const char *mode) { for (y = 0; y < 256; y++) { for (x = 0; x < 256; x++) { - d = (int)sqrt( - (double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0); + d = (int + )sqrt((double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0); if (d >= 255) { d = 255; } diff --git a/src/libImaging/Filter.c b/src/libImaging/Filter.c index 85de77fcb..fbd6b425f 100644 --- a/src/libImaging/Filter.c +++ b/src/libImaging/Filter.c @@ -59,7 +59,8 @@ ImagingExpand(Imaging imIn, int xmargin, int ymargin) { } imOut = ImagingNewDirty( - imIn->mode, imIn->xsize + 2 * xmargin, imIn->ysize + 2 * ymargin); + imIn->mode, imIn->xsize + 2 * xmargin, imIn->ysize + 2 * ymargin + ); if (!imOut) { return NULL; } @@ -369,7 +370,8 @@ ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) { } } memcpy( - out + x * sizeof(UINT32), in0 + x * sizeof(UINT32), sizeof(UINT32) * 2); + out + x * sizeof(UINT32), in0 + x * sizeof(UINT32), sizeof(UINT32) * 2 + ); } } memcpy(imOut->image[y], im->image[y], im->linesize); diff --git a/src/libImaging/Geometry.c b/src/libImaging/Geometry.c index cf3bc9979..2bfeed7b6 100644 --- a/src/libImaging/Geometry.c +++ b/src/libImaging/Geometry.c @@ -781,7 +781,8 @@ ImagingGenericTransform( ImagingTransformMap transform, void *transform_data, int filterid, - int fill) { + int fill +) { /* slow generic transformation. use ImagingTransformAffine or ImagingScaleAffine where possible. */ @@ -836,14 +837,8 @@ ImagingGenericTransform( static Imaging ImagingScaleAffine( - Imaging imOut, - Imaging imIn, - int x0, - int y0, - int x1, - int y1, - double a[6], - int fill) { + Imaging imOut, Imaging imIn, int x0, int y0, int x1, int y1, double a[6], int fill +) { /* scale, nearest neighbour resampling */ ImagingSectionCookie cookie; @@ -936,7 +931,8 @@ static inline int check_fixed(double a[6], int x, int y) { return ( fabs(x * a[0] + y * a[1] + a[2]) < 32768.0 && - fabs(x * a[3] + y * a[4] + a[5]) < 32768.0); + fabs(x * a[3] + y * a[4] + a[5]) < 32768.0 + ); } static inline Imaging @@ -949,7 +945,8 @@ affine_fixed( int y1, double a[6], int filterid, - int fill) { + int fill +) { /* affine transform, nearest neighbour resampling, fixed point arithmetics */ @@ -1026,7 +1023,8 @@ ImagingTransformAffine( int y1, double a[6], int filterid, - int fill) { + int fill +) { /* affine transform, nearest neighbour resampling, floating point arithmetics*/ @@ -1039,7 +1037,8 @@ ImagingTransformAffine( if (filterid || imIn->type == IMAGING_TYPE_SPECIAL) { return ImagingGenericTransform( - imOut, imIn, x0, y0, x1, y1, affine_transform, a, filterid, fill); + imOut, imIn, x0, y0, x1, y1, affine_transform, a, filterid, fill + ); } if (a[1] == 0 && a[3] == 0) { @@ -1134,13 +1133,15 @@ ImagingTransform( int y1, double a[8], int filterid, - int fill) { + int fill +) { ImagingTransformMap transform; switch (method) { case IMAGING_TRANSFORM_AFFINE: return ImagingTransformAffine( - imOut, imIn, x0, y0, x1, y1, a, filterid, fill); + imOut, imIn, x0, y0, x1, y1, a, filterid, fill + ); break; case IMAGING_TRANSFORM_PERSPECTIVE: transform = perspective_transform; @@ -1153,5 +1154,6 @@ ImagingTransform( } return ImagingGenericTransform( - imOut, imIn, x0, y0, x1, y1, transform, a, filterid, fill); + imOut, imIn, x0, y0, x1, y1, transform, a, filterid, fill + ); } diff --git a/src/libImaging/GetBBox.c b/src/libImaging/GetBBox.c index bd2a2778c..c61a27d3b 100644 --- a/src/libImaging/GetBBox.c +++ b/src/libImaging/GetBBox.c @@ -58,11 +58,10 @@ ImagingGetBBox(Imaging im, int bbox[4], int alpha_only) { INT32 mask = 0xffffffff; if (im->bands == 3) { ((UINT8 *)&mask)[3] = 0; - } else if ( - alpha_only && - (strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 || - strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 || - strcmp(im->mode, "PA") == 0)) { + } else if (alpha_only && + (strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 || + strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 || + strcmp(im->mode, "PA") == 0)) { #ifdef WORDS_BIGENDIAN mask = 0x000000ff; #else @@ -246,13 +245,14 @@ getcolors32(Imaging im, int maxcolors, int *size) { code in Python 2.1.3; the exact implementation is borrowed from Python's Unicode property database (written by yours truly) /F */ - static int SIZES[] = { - 4, 3, 8, 3, 16, 3, 32, 5, 64, 3, - 128, 3, 256, 29, 512, 17, 1024, 9, 2048, 5, - 4096, 83, 8192, 27, 16384, 43, 32768, 3, 65536, 45, - 131072, 9, 262144, 39, 524288, 39, 1048576, 9, 2097152, 5, - 4194304, 3, 8388608, 33, 16777216, 27, 33554432, 9, 67108864, 71, - 134217728, 39, 268435456, 9, 536870912, 5, 1073741824, 83, 0}; + static int SIZES[] = {4, 3, 8, 3, 16, 3, 32, 5, + 64, 3, 128, 3, 256, 29, 512, 17, + 1024, 9, 2048, 5, 4096, 83, 8192, 27, + 16384, 43, 32768, 3, 65536, 45, 131072, 9, + 262144, 39, 524288, 39, 1048576, 9, 2097152, 5, + 4194304, 3, 8388608, 33, 16777216, 27, 33554432, 9, + 67108864, 71, 134217728, 39, 268435456, 9, 536870912, 5, + 1073741824, 83, 0}; code_size = code_poly = code_mask = 0; diff --git a/src/libImaging/GifEncode.c b/src/libImaging/GifEncode.c index 45b67616d..203fb9d0a 100644 --- a/src/libImaging/GifEncode.c +++ b/src/libImaging/GifEncode.c @@ -79,7 +79,8 @@ glzwe( UINT8 *out_ptr, UINT32 *in_avail, UINT32 *out_avail, - UINT32 end_of_data) { + UINT32 end_of_data +) { switch (st->entry_state) { case LZW_TRY_IN1: get_first_byte: @@ -312,7 +313,8 @@ ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { state->buffer, (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, - state->xsize); + state->xsize + ); state->x = 0; /* step forward, according to the interlace settings */ @@ -348,7 +350,8 @@ ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { ptr, &in_avail, &out_avail, - state->state == FINISH); + state->state == FINISH + ); out_used = sub_block_limit - ptr - out_avail; *sub_block_ptr += out_used; ptr += out_used; diff --git a/src/libImaging/HexDecode.c b/src/libImaging/HexDecode.c index bd16cdbe1..e26c0e9b3 100644 --- a/src/libImaging/HexDecode.c +++ b/src/libImaging/HexDecode.c @@ -49,7 +49,8 @@ ImagingHexDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt if (++state->x >= state->bytes) { /* Got a full line, unpack it */ state->shuffle( - (UINT8 *)im->image[state->y], state->buffer, state->xsize); + (UINT8 *)im->image[state->y], state->buffer, state->xsize + ); state->x = 0; diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 1f2c03e93..b1c3aed41 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -295,7 +295,8 @@ extern Imaging ImagingFill(Imaging im, const void *ink); extern int ImagingFill2( - Imaging into, const void *ink, Imaging mask, int x0, int y0, int x1, int y1); + Imaging into, const void *ink, Imaging mask, int x0, int y0, int x1, int y1 +); extern Imaging ImagingFillBand(Imaging im, int band, int color); extern Imaging @@ -310,7 +311,8 @@ extern Imaging ImagingFlipTopBottom(Imaging imOut, Imaging imIn); extern Imaging ImagingGaussianBlur( - Imaging imOut, Imaging imIn, float xradius, float yradius, int passes); + Imaging imOut, Imaging imIn, float xradius, float yradius, int passes +); extern Imaging ImagingGetBand(Imaging im, int band); extern Imaging @@ -373,7 +375,8 @@ ImagingTransform( int y1, double a[8], int filter, - int fill); + int fill +); extern Imaging ImagingUnsharpMask(Imaging imOut, Imaging im, float radius, int percent, int threshold); extern Imaging @@ -386,7 +389,8 @@ ImagingColorLUT3D_linear( int size1D, int size2D, int size3D, - INT16 *table); + INT16 *table +); extern Imaging ImagingCopy2(Imaging imOut, Imaging imIn); @@ -440,7 +444,8 @@ ImagingDrawArc( float end, const void *ink, int width, - int op); + int op +); extern int ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void *ink, int op); extern int @@ -455,7 +460,8 @@ ImagingDrawChord( const void *ink, int fill, int width, - int op); + int op +); extern int ImagingDrawEllipse( Imaging im, @@ -466,12 +472,14 @@ ImagingDrawEllipse( const void *ink, int fill, int width, - int op); + int op +); extern int ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink, int op); extern int ImagingDrawWideLine( - Imaging im, int x0, int y0, int x1, int y1, const void *ink, int width, int op); + Imaging im, int x0, int y0, int x1, int y1, const void *ink, int width, int op +); extern int ImagingDrawPieslice( Imaging im, @@ -484,12 +492,14 @@ ImagingDrawPieslice( const void *ink, int fill, int width, - int op); + int op +); extern int ImagingDrawPoint(Imaging im, int x, int y, const void *ink, int op); extern int ImagingDrawPolygon( - Imaging im, int points, int *xy, const void *ink, int fill, int width, int op); + Imaging im, int points, int *xy, const void *ink, int fill, int width, int op +); extern int ImagingDrawRectangle( Imaging im, @@ -500,7 +510,8 @@ ImagingDrawRectangle( const void *ink, int fill, int width, - int op); + int op +); /* Level 2 graphics (WORK IN PROGRESS) */ extern ImagingOutline @@ -510,7 +521,8 @@ ImagingOutlineDelete(ImagingOutline outline); extern int ImagingDrawOutline( - Imaging im, ImagingOutline outline, const void *ink, int fill, int op); + Imaging im, ImagingOutline outline, const void *ink, int fill, int op +); extern int ImagingOutlineMove(ImagingOutline outline, float x, float y); @@ -518,7 +530,8 @@ extern int ImagingOutlineLine(ImagingOutline outline, float x, float y); extern int ImagingOutlineCurve( - ImagingOutline outline, float x1, float y1, float x2, float y2, float x3, float y3); + ImagingOutline outline, float x1, float y1, float x2, float y2, float x3, float y3 +); extern int ImagingOutlineTransform(ImagingOutline outline, double a[6]); @@ -545,7 +558,8 @@ ImagingSavePPM(Imaging im, const char *filename); /* Codecs */ typedef struct ImagingCodecStateInstance *ImagingCodecState; typedef int (*ImagingCodec)( - Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); + Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes +); extern int ImagingBcnDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); @@ -575,7 +589,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes) #ifdef HAVE_OPENJPEG extern int ImagingJpeg2KDecode( - Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes +); extern int ImagingJpeg2KDecodeCleanup(ImagingCodecState state); extern int @@ -586,7 +601,8 @@ ImagingJpeg2KEncodeCleanup(ImagingCodecState state); #ifdef HAVE_LIBTIFF extern int ImagingLibTiffDecode( - Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes +); extern int ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); #endif @@ -598,7 +614,8 @@ extern int ImagingMspDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); extern int ImagingPackbitsDecode( - Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes +); extern int ImagingPcdDecode(Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); extern int @@ -611,13 +628,16 @@ extern int ImagingRawEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); extern int ImagingSgiRleDecode( - Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes +); extern int ImagingSunRleDecode( - Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes +); extern int ImagingTgaRleDecode( - Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes); + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes +); extern int ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int bytes); extern int diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c index dd066c10b..5b3d7ffc4 100644 --- a/src/libImaging/Jpeg2KDecode.c +++ b/src/libImaging/Jpeg2KDecode.c @@ -67,7 +67,8 @@ j2k_skip(OPJ_OFF_T p_nb_bytes, void *p_user_data) { /* -------------------------------------------------------------------- */ typedef void (*j2k_unpacker_t)( - opj_image_t *in, const JPEG2KTILEINFO *tileInfo, const UINT8 *data, Imaging im); + opj_image_t *in, const JPEG2KTILEINFO *tileInfo, const UINT8 *data, Imaging im +); struct j2k_decode_unpacker { const char *mode; @@ -89,10 +90,8 @@ j2ku_shift(unsigned x, int n) { static void j2ku_gray_l( - opj_image_t *in, - const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, - Imaging im) { + opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im +) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -145,10 +144,8 @@ j2ku_gray_l( static void j2ku_gray_i( - opj_image_t *in, - const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, - Imaging im) { + opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im +) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -204,10 +201,8 @@ j2ku_gray_i( static void j2ku_gray_rgb( - opj_image_t *in, - const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, - Imaging im) { + opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im +) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -268,10 +263,8 @@ j2ku_gray_rgb( static void j2ku_graya_la( - opj_image_t *in, - const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, - Imaging im) { + opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im +) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -347,10 +340,8 @@ j2ku_graya_la( static void j2ku_srgb_rgb( - opj_image_t *in, - const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, - Imaging im) { + opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im +) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -413,10 +404,8 @@ j2ku_srgb_rgb( static void j2ku_sycc_rgb( - opj_image_t *in, - const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, - Imaging im) { + opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im +) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -482,10 +471,8 @@ j2ku_sycc_rgb( static void j2ku_srgba_rgba( - opj_image_t *in, - const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, - Imaging im) { + opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im +) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -547,10 +534,8 @@ j2ku_srgba_rgba( static void j2ku_sycca_rgba( - opj_image_t *in, - const JPEG2KTILEINFO *tileinfo, - const UINT8 *tiledata, - Imaging im) { + opj_image_t *in, const JPEG2KTILEINFO *tileinfo, const UINT8 *tiledata, Imaging im +) { unsigned x0 = tileinfo->x0 - in->x0, y0 = tileinfo->y0 - in->y0; unsigned w = tileinfo->x1 - tileinfo->x0; unsigned h = tileinfo->y1 - tileinfo->y0; @@ -815,7 +800,8 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) { &tile_info.x1, &tile_info.y1, &tile_info.nb_comps, - &should_continue)) { + &should_continue + )) { state->errcode = IMAGING_CODEC_BROKEN; state->state = J2K_STATE_FAILED; goto quick_exit; @@ -906,7 +892,8 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) { tile_info.tile_index, (OPJ_BYTE *)state->buffer, tile_info.data_size, - stream)) { + stream + )) { state->errcode = IMAGING_CODEC_BROKEN; state->state = J2K_STATE_FAILED; goto quick_exit; diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c index 7f1aeaddb..cb21a186c 100644 --- a/src/libImaging/Jpeg2KEncode.c +++ b/src/libImaging/Jpeg2KEncode.c @@ -89,7 +89,8 @@ j2k_seek(OPJ_OFF_T p_nb_bytes, void *p_user_data) { /* -------------------------------------------------------------------- */ typedef void (*j2k_pack_tile_t)( - Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h); + Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h +); static void j2k_pack_l(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) { @@ -157,7 +158,8 @@ j2k_pack_rgb(Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsig static void j2k_pack_rgba( - Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h) { + Imaging im, UINT8 *buf, unsigned x0, unsigned y0, unsigned w, unsigned h +) { UINT8 *pr = buf; UINT8 *pg = pr + w * h; UINT8 *pb = pg + w * h; @@ -205,8 +207,8 @@ j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) { if (params->cp_cinema == OPJ_CINEMA4K_24) { float max_rate = - ((float)(components * im->xsize * im->ysize * 8) / - (CINEMA_24_CS_LENGTH * 8)); + ((float)(components * im->xsize * im->ysize * 8) / (CINEMA_24_CS_LENGTH * 8) + ); params->POC[0].tile = 1; params->POC[0].resno0 = 0; @@ -241,8 +243,8 @@ j2k_set_cinema_params(Imaging im, int components, opj_cparameters_t *params) { params->max_comp_size = COMP_24_CS_MAX_LENGTH; } else { float max_rate = - ((float)(components * im->xsize * im->ysize * 8) / - (CINEMA_48_CS_LENGTH * 8)); + ((float)(components * im->xsize * im->ysize * 8) / (CINEMA_48_CS_LENGTH * 8) + ); for (n = 0; n < params->tcp_numlayers; ++n) { rate = 0; diff --git a/src/libImaging/JpegDecode.c b/src/libImaging/JpegDecode.c index 6f75d8670..30c64f235 100644 --- a/src/libImaging/JpegDecode.c +++ b/src/libImaging/JpegDecode.c @@ -206,9 +206,8 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t by context->cinfo.out_color_space = JCS_EXT_RGBX; } #endif - else if ( - strcmp(context->rawmode, "CMYK") == 0 || - strcmp(context->rawmode, "CMYK;I") == 0) { + else if (strcmp(context->rawmode, "CMYK") == 0 || + strcmp(context->rawmode, "CMYK;I") == 0) { context->cinfo.out_color_space = JCS_CMYK; } else if (strcmp(context->rawmode, "YCbCr") == 0) { context->cinfo.out_color_space = JCS_YCbCr; @@ -256,7 +255,8 @@ ImagingJpegDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t by (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, state->buffer, - state->xsize); + state->xsize + ); state->y++; } if (ok != 1) { diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c index ba8353c2d..4372d51d5 100644 --- a/src/libImaging/JpegEncode.c +++ b/src/libImaging/JpegEncode.c @@ -175,7 +175,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { i, &context->qtables[i * DCTSIZE2], quality, - FALSE); + FALSE + ); context->cinfo.comp_info[i].quant_tbl_no = i; last_q = i; } @@ -183,7 +184,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { // jpeg_set_defaults created two qtables internally, but we only // wanted one. jpeg_add_quant_table( - &context->cinfo, 1, &context->qtables[0], quality, FALSE); + &context->cinfo, 1, &context->qtables[0], quality, FALSE + ); } for (i = last_q; i < context->cinfo.num_components; i++) { context->cinfo.comp_info[i].quant_tbl_no = last_q; @@ -273,7 +275,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { &context->cinfo, JPEG_APP0 + 1, (unsigned char *)context->rawExif, - context->rawExifLen); + context->rawExifLen + ); } state->state++; @@ -289,7 +292,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { memcpy( context->destination.pub.next_output_byte, context->extra + context->extra_offset, - n); + n + ); context->destination.pub.next_output_byte += n; context->destination.pub.free_in_buffer -= n; context->extra_offset += n; @@ -309,7 +313,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { &context->cinfo, JPEG_COM, (unsigned char *)context->comment, - context->comment_size); + context->comment_size + ); } state->state++; @@ -324,7 +329,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { state->buffer, (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, - state->xsize); + state->xsize + ); ok = jpeg_write_scanlines(&context->cinfo, &state->buffer, 1); if (ok != 1) { break; diff --git a/src/libImaging/PackDecode.c b/src/libImaging/PackDecode.c index 7dd432b91..52f1ac502 100644 --- a/src/libImaging/PackDecode.c +++ b/src/libImaging/PackDecode.c @@ -17,7 +17,8 @@ int ImagingPackbitsDecode( - Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes) { + Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t bytes +) { UINT8 n; UINT8 *ptr; int i; @@ -79,7 +80,8 @@ ImagingPackbitsDecode( (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, state->buffer, - state->xsize); + state->xsize + ); state->x = 0; diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c index dc67cb41d..86085942a 100644 --- a/src/libImaging/Paste.c +++ b/src/libImaging/Paste.c @@ -33,7 +33,8 @@ paste( int sy, int xsize, int ysize, - int pixelsize) { + int pixelsize +) { /* paste opaque region */ int y; @@ -59,7 +60,8 @@ paste_mask_1( int sy, int xsize, int ysize, - int pixelsize) { + int pixelsize +) { /* paste with mode "1" mask */ int x, y; @@ -120,7 +122,8 @@ paste_mask_L( int sy, int xsize, int ysize, - int pixelsize) { + int pixelsize +) { /* paste with mode "L" matte */ int x, y; @@ -167,7 +170,8 @@ paste_mask_RGBA( int sy, int xsize, int ysize, - int pixelsize) { + int pixelsize +) { /* paste with mode "RGBA" matte */ int x, y; @@ -214,7 +218,8 @@ paste_mask_RGBa( int sy, int xsize, int ysize, - int pixelsize) { + int pixelsize +) { /* paste with mode "RGBa" matte */ int x, y; @@ -252,7 +257,8 @@ paste_mask_RGBa( int ImagingPaste( - Imaging imOut, Imaging imIn, Imaging imMask, int dx0, int dy0, int dx1, int dy1) { + Imaging imOut, Imaging imIn, Imaging imMask, int dx0, int dy0, int dx1, int dy1 +) { int xsize, ysize; int pixelsize; int sx0, sy0; @@ -315,13 +321,15 @@ ImagingPaste( } else if (strcmp(imMask->mode, "LA") == 0 || strcmp(imMask->mode, "RGBA") == 0) { ImagingSectionEnter(&cookie); paste_mask_RGBA( - imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); + imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize + ); ImagingSectionLeave(&cookie); } else if (strcmp(imMask->mode, "RGBa") == 0) { ImagingSectionEnter(&cookie); paste_mask_RGBa( - imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize); + imOut, imIn, imMask, dx0, dy0, sx0, sy0, xsize, ysize, pixelsize + ); ImagingSectionLeave(&cookie); } else { @@ -334,13 +342,8 @@ ImagingPaste( static inline void fill( - Imaging imOut, - const void *ink_, - int dx, - int dy, - int xsize, - int ysize, - int pixelsize) { + Imaging imOut, const void *ink_, int dx, int dy, int xsize, int ysize, int pixelsize +) { /* fill opaque region */ int x, y; @@ -378,7 +381,8 @@ fill_mask_1( int sy, int xsize, int ysize, - int pixelsize) { + int pixelsize +) { /* fill with mode "1" mask */ int x, y; @@ -425,7 +429,8 @@ fill_mask_L( int sy, int xsize, int ysize, - int pixelsize) { + int pixelsize +) { /* fill with mode "L" matte */ int x, y, i; @@ -484,7 +489,8 @@ fill_mask_RGBA( int sy, int xsize, int ysize, - int pixelsize) { + int pixelsize +) { /* fill with mode "RGBA" matte */ int x, y, i; @@ -529,7 +535,8 @@ fill_mask_RGBa( int sy, int xsize, int ysize, - int pixelsize) { + int pixelsize +) { /* fill with mode "RGBa" matte */ int x, y, i; @@ -565,13 +572,8 @@ fill_mask_RGBa( int ImagingFill2( - Imaging imOut, - const void *ink, - Imaging imMask, - int dx0, - int dy0, - int dx1, - int dy1) { + Imaging imOut, const void *ink, Imaging imMask, int dx0, int dy0, int dx1, int dy1 +) { ImagingSectionCookie cookie; int xsize, ysize; int pixelsize; diff --git a/src/libImaging/PcxDecode.c b/src/libImaging/PcxDecode.c index c95ffc869..942c8dc22 100644 --- a/src/libImaging/PcxDecode.c +++ b/src/libImaging/PcxDecode.c @@ -68,7 +68,8 @@ ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt memmove( &state->buffer[i * state->xsize], &state->buffer[i * stride], - state->xsize); + state->xsize + ); } } /* Got a full line, unpack it */ @@ -76,7 +77,8 @@ ImagingPcxDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, state->buffer, - state->xsize); + state->xsize + ); state->x = 0; diff --git a/src/libImaging/PcxEncode.c b/src/libImaging/PcxEncode.c index 549614bfd..625cf7ffa 100644 --- a/src/libImaging/PcxEncode.c +++ b/src/libImaging/PcxEncode.c @@ -71,7 +71,8 @@ ImagingPcxEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { state->buffer, (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, - state->xsize); + state->xsize + ); state->y += 1; diff --git a/src/libImaging/Point.c b/src/libImaging/Point.c index dd06f3940..6a4060b4b 100644 --- a/src/libImaging/Point.c +++ b/src/libImaging/Point.c @@ -197,8 +197,8 @@ ImagingPoint(Imaging imIn, const char *mode, const void *table) { return imOut; mode_mismatch: - return (Imaging)ImagingError_ValueError( - "point operation not supported for this mode"); + return (Imaging + )ImagingError_ValueError("point operation not supported for this mode"); } Imaging diff --git a/src/libImaging/Quant.c b/src/libImaging/Quant.c index cdc614536..197f9f3ee 100644 --- a/src/libImaging/Quant.c +++ b/src/libImaging/Quant.c @@ -103,7 +103,8 @@ static uint32_t pixel_hash(const HashTable *h, const Pixel pixel) { PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h); return PIXEL_HASH( - pixel.c.r >> d->scale, pixel.c.g >> d->scale, pixel.c.b >> d->scale); + pixel.c.r >> d->scale, pixel.c.g >> d->scale, pixel.c.b >> d->scale + ); } static int @@ -111,9 +112,11 @@ pixel_cmp(const HashTable *h, const Pixel pixel1, const Pixel pixel2) { PixelHashData *d = (PixelHashData *)hashtable_get_user_data(h); uint32_t A, B; A = PIXEL_HASH( - pixel1.c.r >> d->scale, pixel1.c.g >> d->scale, pixel1.c.b >> d->scale); + pixel1.c.r >> d->scale, pixel1.c.g >> d->scale, pixel1.c.b >> d->scale + ); B = PIXEL_HASH( - pixel2.c.r >> d->scale, pixel2.c.g >> d->scale, pixel2.c.b >> d->scale); + pixel2.c.r >> d->scale, pixel2.c.g >> d->scale, pixel2.c.b >> d->scale + ); return (A == B) ? 0 : ((A < B) ? -1 : 1); } @@ -129,7 +132,8 @@ new_count_func(const HashTable *h, const Pixel key, uint32_t *val) { static void rehash_collide( - const HashTable *h, Pixel *keyp, uint32_t *valp, Pixel newkey, uint32_t newval) { + const HashTable *h, Pixel *keyp, uint32_t *valp, Pixel newkey, uint32_t newval +) { *valp += newval; } @@ -157,7 +161,8 @@ create_pixel_hash(Pixel *pixelData, uint32_t nPixels) { #endif for (i = 0; i < nPixels; i++) { if (!hashtable_insert_or_update_computed( - hash, pixelData[i], new_count_func, exists_count_func)) { + hash, pixelData[i], new_count_func, exists_count_func + )) { ; } while (hashtable_get_count(hash) > MAX_HASH_ENTRIES) { @@ -335,7 +340,8 @@ splitlists( PixelList *nt[2][3], uint32_t nCount[2], int axis, - uint32_t pixelCount) { + uint32_t pixelCount +) { uint32_t left; PixelList *l, *r, *c, *n; @@ -387,7 +393,8 @@ splitlists( _prevCount[2], _nextCount[0], _nextCount[1], - _nextCount[2]); + _nextCount[2] + ); exit(1); } } @@ -531,12 +538,14 @@ split(BoxNode *node) { if (node->tail[_i]->next[_i]) { printf("tail is not tail\n"); printf( - "node->tail[%d]->next[%d]=%p\n", _i, _i, node->tail[_i]->next[_i]); + "node->tail[%d]->next[%d]=%p\n", _i, _i, node->tail[_i]->next[_i] + ); } if (node->head[_i]->prev[_i]) { printf("head is not head\n"); printf( - "node->head[%d]->prev[%d]=%p\n", _i, _i, node->head[_i]->prev[_i]); + "node->head[%d]->prev[%d]=%p\n", _i, _i, node->head[_i]->prev[_i] + ); } } @@ -573,14 +582,16 @@ split(BoxNode *node) { _prevCount[2], _nextCount[0], _nextCount[1], - _nextCount[2]); + _nextCount[2] + ); } } } #endif node->axis = axis; if (!splitlists( - node->head, node->tail, heads, tails, newCounts, axis, node->pixelCount)) { + node->head, node->tail, heads, tails, newCounts, axis, node->pixelCount + )) { #ifndef NO_OUTPUT printf("list split failed.\n"); #endif @@ -772,7 +783,8 @@ _distance_index_cmp(const void *a, const void *b) { static int resort_distance_tables( - uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) { + uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries +) { uint32_t i, j, k; uint32_t **skRow; uint32_t *skElt; @@ -801,7 +813,8 @@ resort_distance_tables( static int build_distance_tables( - uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries) { + uint32_t *avgDist, uint32_t **avgDistSortKey, Pixel *p, uint32_t nEntries +) { uint32_t i, j; DistanceWithIndex *dwi; @@ -841,7 +854,8 @@ map_image_pixels( uint32_t nPaletteEntries, uint32_t *avgDist, uint32_t **avgDistSortKey, - uint32_t *pixelArray) { + uint32_t *pixelArray +) { uint32_t *aD, **aDSK; uint32_t idx; uint32_t i, j; @@ -888,7 +902,8 @@ map_image_pixels_from_quantized_pixels( uint32_t **avgDistSortKey, uint32_t *pixelArray, uint32_t *avg[3], - uint32_t *count) { + uint32_t *count +) { uint32_t *aD, **aDSK; uint32_t idx; uint32_t i, j; @@ -946,7 +961,8 @@ map_image_pixels_from_median_box( HashTable *medianBoxHash, uint32_t *avgDist, uint32_t **avgDistSortKey, - uint32_t *pixelArray) { + uint32_t *pixelArray +) { uint32_t *aD, **aDSK; uint32_t idx; uint32_t i, j; @@ -998,7 +1014,8 @@ compute_palette_from_median_cut( uint32_t nPixels, HashTable *medianBoxHash, Pixel **palette, - uint32_t nPaletteEntries) { + uint32_t nPaletteEntries +) { uint32_t i; uint32_t paletteEntry; Pixel *p; @@ -1055,7 +1072,8 @@ compute_palette_from_median_cut( printf( "panic - paletteEntry>=nPaletteEntries (%d>=%d)\n", (int)paletteEntry, - (int)nPaletteEntries); + (int)nPaletteEntries + ); #endif for (i = 0; i < 3; i++) { free(avg[i]); @@ -1092,7 +1110,8 @@ compute_palette_from_median_cut( static int recompute_palette_from_averages( - Pixel *palette, uint32_t nPaletteEntries, uint32_t *avg[3], uint32_t *count) { + Pixel *palette, uint32_t nPaletteEntries, uint32_t *avg[3], uint32_t *count +) { uint32_t i; for (i = 0; i < nPaletteEntries; i++) { @@ -1111,7 +1130,8 @@ compute_palette_from_quantized_pixels( uint32_t nPaletteEntries, uint32_t *avg[3], uint32_t *count, - uint32_t *qp) { + uint32_t *qp +) { uint32_t i; memset(count, 0, sizeof(uint32_t) * nPaletteEntries); @@ -1145,7 +1165,8 @@ k_means( Pixel *paletteData, uint32_t nPaletteEntries, uint32_t *qp, - int threshold) { + int threshold +) { uint32_t *avg[3]; uint32_t *count; uint32_t i; @@ -1194,16 +1215,19 @@ k_means( while (1) { if (!built) { compute_palette_from_quantized_pixels( - pixelData, nPixels, paletteData, nPaletteEntries, avg, count, qp); + pixelData, nPixels, paletteData, nPaletteEntries, avg, count, qp + ); if (!build_distance_tables( - avgDist, avgDistSortKey, paletteData, nPaletteEntries)) { + avgDist, avgDistSortKey, paletteData, nPaletteEntries + )) { goto error_3; } built = 1; } else { recompute_palette_from_averages(paletteData, nPaletteEntries, avg, count); resort_distance_tables( - avgDist, avgDistSortKey, paletteData, nPaletteEntries); + avgDist, avgDistSortKey, paletteData, nPaletteEntries + ); } changes = map_image_pixels_from_quantized_pixels( pixelData, @@ -1214,7 +1238,8 @@ k_means( avgDistSortKey, qp, avg, - count); + count + ); if (changes < 0) { goto error_3; } @@ -1273,7 +1298,8 @@ quantize( Pixel **palette, uint32_t *paletteLength, uint32_t **quantizedPixels, - int kmeans) { + int kmeans +) { PixelList *hl[3]; HashTable *h; BoxNode *root; @@ -1399,7 +1425,8 @@ quantize( } if (!map_image_pixels_from_median_box( - pixelData, nPixels, p, nPaletteEntries, h, avgDist, avgDistSortKey, qp)) { + pixelData, nPixels, p, nPaletteEntries, h, avgDist, avgDistSortKey, qp + )) { goto error_7; } @@ -1445,7 +1472,8 @@ quantize( _SQR(pixelData[i].c.b - p[qp[i]].c.b))), sqrt((double)(_SQR(pixelData[i].c.r - p[bestmatch].c.r) + _SQR(pixelData[i].c.g - p[bestmatch].c.g) + - _SQR(pixelData[i].c.b - p[bestmatch].c.b)))); + _SQR(pixelData[i].c.b - p[bestmatch].c.b))) + ); } } hashtable_free(h2); @@ -1545,7 +1573,8 @@ quantize2( Pixel **palette, uint32_t *paletteLength, uint32_t **quantizedPixels, - int kmeans) { + int kmeans +) { HashTable *h; uint32_t i; uint32_t mean[3]; @@ -1609,7 +1638,8 @@ quantize2( } if (!map_image_pixels( - pixelData, nPixels, p, nQuantPixels, avgDist, avgDistSortKey, qp)) { + pixelData, nPixels, p, nQuantPixels, avgDist, avgDistSortKey, qp + )) { goto error_4; } if (kmeans > 0) { @@ -1752,7 +1782,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { &palette, &paletteLength, &newData, - kmeans); + kmeans + ); break; case 1: /* maximum coverage */ @@ -1763,7 +1794,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { &palette, &paletteLength, &newData, - kmeans); + kmeans + ); break; case 2: result = quantize_octree( @@ -1773,7 +1805,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { &palette, &paletteLength, &newData, - withAlpha); + withAlpha + ); break; case 3: #ifdef HAVE_LIBIMAGEQUANT @@ -1785,7 +1818,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { &palette, &paletteLength, &newData, - withAlpha); + withAlpha + ); #else result = -1; #endif @@ -1836,7 +1870,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { if (result == -1) { return (Imaging)ImagingError_ValueError( "dependency required by this method was not " - "enabled at compile time"); + "enabled at compile time" + ); } return (Imaging)ImagingError_ValueError("quantization error"); diff --git a/src/libImaging/QuantHash.c b/src/libImaging/QuantHash.c index ea75d6037..bf2f29fde 100644 --- a/src/libImaging/QuantHash.c +++ b/src/libImaging/QuantHash.c @@ -132,7 +132,8 @@ _hashtable_resize(HashTable *h) { static int _hashtable_insert_node( - HashTable *h, HashNode *node, int resize, int update, CollisionFunc cf) { + HashTable *h, HashNode *node, int resize, int update, CollisionFunc cf +) { uint32_t hash = h->hashFunc(h, node->key) % h->length; HashNode **n, *nv; int i; @@ -207,7 +208,8 @@ _hashtable_insert(HashTable *h, HashKey_t key, HashVal_t val, int resize, int up int hashtable_insert_or_update_computed( - HashTable *h, HashKey_t key, ComputeFunc newFunc, ComputeFunc existsFunc) { + HashTable *h, HashKey_t key, ComputeFunc newFunc, ComputeFunc existsFunc +) { HashNode **n, *nv; HashNode *t; int i; diff --git a/src/libImaging/QuantHash.h b/src/libImaging/QuantHash.h index fc1a99003..0462cfd49 100644 --- a/src/libImaging/QuantHash.h +++ b/src/libImaging/QuantHash.h @@ -20,13 +20,12 @@ typedef uint32_t HashVal_t; typedef uint32_t (*HashFunc)(const HashTable *, const HashKey_t); typedef int (*HashCmpFunc)(const HashTable *, const HashKey_t, const HashKey_t); -typedef void (*IteratorFunc)( - const HashTable *, const HashKey_t, const HashVal_t, void *); -typedef void (*IteratorUpdateFunc)( - const HashTable *, const HashKey_t, HashVal_t *, void *); +typedef void (*IteratorFunc)(const HashTable *, const HashKey_t, const HashVal_t, void *); +typedef void (*IteratorUpdateFunc)(const HashTable *, const HashKey_t, HashVal_t *, void *); typedef void (*ComputeFunc)(const HashTable *, const HashKey_t, HashVal_t *); typedef void (*CollisionFunc)( - const HashTable *, HashKey_t *, HashVal_t *, HashKey_t, HashVal_t); + const HashTable *, HashKey_t *, HashVal_t *, HashKey_t, HashVal_t +); HashTable * hashtable_new(HashFunc hf, HashCmpFunc cf); @@ -42,7 +41,8 @@ int hashtable_lookup(const HashTable *h, const HashKey_t key, HashVal_t *valp); int hashtable_insert_or_update_computed( - HashTable *h, HashKey_t key, ComputeFunc newFunc, ComputeFunc existsFunc); + HashTable *h, HashKey_t key, ComputeFunc newFunc, ComputeFunc existsFunc +); void * hashtable_set_user_data(HashTable *h, void *data); void * diff --git a/src/libImaging/QuantOctree.c b/src/libImaging/QuantOctree.c index 1331a30ad..7e02ebf65 100644 --- a/src/libImaging/QuantOctree.c +++ b/src/libImaging/QuantOctree.c @@ -107,11 +107,8 @@ free_color_cube(ColorCube cube) { static long color_bucket_offset_pos( - const ColorCube cube, - unsigned int r, - unsigned int g, - unsigned int b, - unsigned int a) { + const ColorCube cube, unsigned int r, unsigned int g, unsigned int b, unsigned int a +) { return r << cube->rOffset | g << cube->gOffset | b << cube->bOffset | a << cube->aOffset; } @@ -191,7 +188,8 @@ create_sorted_color_palette(const ColorCube cube) { buckets, cube->size, sizeof(struct _ColorBucket), - (int (*)(void const *, void const *)) & compare_bucket_count); + (int (*)(void const *, void const *)) & compare_bucket_count + ); return buckets; } @@ -212,7 +210,8 @@ copy_color_cube( unsigned int rBits, unsigned int gBits, unsigned int bBits, - unsigned int aBits) { + unsigned int aBits +) { unsigned int r, g, b, a; long src_pos, dst_pos; unsigned int src_reduce[4] = {0}, dst_reduce[4] = {0}; @@ -262,15 +261,18 @@ copy_color_cube( r >> src_reduce[0], g >> src_reduce[1], b >> src_reduce[2], - a >> src_reduce[3]); + a >> src_reduce[3] + ); dst_pos = color_bucket_offset_pos( result, r >> dst_reduce[0], g >> dst_reduce[1], b >> dst_reduce[2], - a >> dst_reduce[3]); + a >> dst_reduce[3] + ); add_bucket_values( - &cube->buckets[src_pos], &result->buckets[dst_pos]); + &cube->buckets[src_pos], &result->buckets[dst_pos] + ); } } } @@ -328,7 +330,8 @@ combined_palette( ColorBucket bucketsA, unsigned long nBucketsA, ColorBucket bucketsB, - unsigned long nBucketsB) { + unsigned long nBucketsB +) { ColorBucket result; if (nBucketsA > LONG_MAX - nBucketsB || (nBucketsA + nBucketsB) > LONG_MAX / sizeof(struct _ColorBucket)) { @@ -366,7 +369,8 @@ map_image_pixels( const Pixel *pixelData, uint32_t nPixels, const ColorCube lookupCube, - uint32_t *pixelArray) { + uint32_t *pixelArray +) { long i; for (i = 0; i < nPixels; i++) { pixelArray[i] = lookup_color(lookupCube, &pixelData[i]); @@ -384,7 +388,8 @@ quantize_octree( Pixel **palette, uint32_t *paletteLength, uint32_t **quantizedPixels, - int withAlpha) { + int withAlpha +) { ColorCube fineCube = NULL; ColorCube coarseCube = NULL; ColorCube lookupCube = NULL; @@ -461,7 +466,8 @@ quantize_octree( subtract_color_buckets( coarseCube, &paletteBucketsFine[nAlreadySubtracted], - nFineColors - nAlreadySubtracted); + nFineColors - nAlreadySubtracted + ); } /* create our palette buckets with fine and coarse combined */ @@ -470,7 +476,8 @@ quantize_octree( goto error; } paletteBuckets = combined_palette( - paletteBucketsCoarse, nCoarseColors, paletteBucketsFine, nFineColors); + paletteBucketsCoarse, nCoarseColors, paletteBucketsFine, nFineColors + ); free(paletteBucketsFine); paletteBucketsFine = NULL; @@ -491,7 +498,8 @@ quantize_octree( /* expand coarse cube (64) to larger fine cube (4k). the value of each coarse bucket is then present in the according 64 fine buckets. */ lookupCube = copy_color_cube( - coarseLookupCube, cubeBits[0], cubeBits[1], cubeBits[2], cubeBits[3]); + coarseLookupCube, cubeBits[0], cubeBits[1], cubeBits[2], cubeBits[3] + ); if (!lookupCube) { goto error; } diff --git a/src/libImaging/QuantPngQuant.c b/src/libImaging/QuantPngQuant.c index 7a36300e4..a2258c3a2 100644 --- a/src/libImaging/QuantPngQuant.c +++ b/src/libImaging/QuantPngQuant.c @@ -26,7 +26,8 @@ quantize_pngquant( Pixel **palette, uint32_t *paletteLength, uint32_t **quantizedPixels, - int withAlpha) { + int withAlpha +) { int result = 0; liq_image *image = NULL; liq_attr *attr = NULL; diff --git a/src/libImaging/QuantPngQuant.h b/src/libImaging/QuantPngQuant.h index d65e42590..ae96a52f3 100644 --- a/src/libImaging/QuantPngQuant.h +++ b/src/libImaging/QuantPngQuant.h @@ -12,6 +12,7 @@ quantize_pngquant( Pixel **, uint32_t *, uint32_t **, - int); + int +); #endif diff --git a/src/libImaging/RankFilter.c b/src/libImaging/RankFilter.c index 73a6baecb..899b1fd3a 100644 --- a/src/libImaging/RankFilter.c +++ b/src/libImaging/RankFilter.c @@ -102,7 +102,8 @@ MakeRankFunction(UINT8) MakeRankFunction(INT32) MakeRankFunction(FLOAT32) memcpy( \ buf + i * size, \ &IMAGING_PIXEL_##type(im, x, y + i), \ - size * sizeof(type)); \ + size * sizeof(type) \ + ); \ } \ IMAGING_PIXEL_##type(imOut, x, y) = Rank##type(buf, size2, rank); \ } \ diff --git a/src/libImaging/RawDecode.c b/src/libImaging/RawDecode.c index 24abe4804..80ed0d688 100644 --- a/src/libImaging/RawDecode.c +++ b/src/libImaging/RawDecode.c @@ -74,7 +74,8 @@ ImagingRawDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt state->shuffle( (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, ptr, - state->xsize); + state->xsize + ); ptr += state->bytes; bytes -= state->bytes; diff --git a/src/libImaging/RawEncode.c b/src/libImaging/RawEncode.c index 50de8d982..5e60e1106 100644 --- a/src/libImaging/RawEncode.c +++ b/src/libImaging/RawEncode.c @@ -65,7 +65,8 @@ ImagingRawEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { state->shuffle( ptr, (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, - state->xsize); + state->xsize + ); if (state->bytes > state->count) { /* zero-pad the buffer, if necessary */ diff --git a/src/libImaging/Reduce.c b/src/libImaging/Reduce.c index 61566f0c5..022daa000 100644 --- a/src/libImaging/Reduce.c +++ b/src/libImaging/Reduce.c @@ -82,7 +82,8 @@ ImagingReduceNxN(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale } } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24); + (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { @@ -124,7 +125,8 @@ ImagingReduceNxN(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, (ss2 * multiplier) >> 24, - 0); + 0 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 @@ -171,7 +173,8 @@ ImagingReduceNxN(Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, (ss2 * multiplier) >> 24, - (ss3 * multiplier) >> 24); + (ss3 * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -226,7 +229,8 @@ ImagingReduce1xN(Imaging imOut, Imaging imIn, int box[4], int yscale) { ss3 += line[xx * 4 + 3]; } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24); + (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { @@ -251,7 +255,8 @@ ImagingReduce1xN(Imaging imOut, Imaging imIn, int box[4], int yscale) { (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, (ss2 * multiplier) >> 24, - 0); + 0 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 @@ -278,7 +283,8 @@ ImagingReduce1xN(Imaging imOut, Imaging imIn, int box[4], int yscale) { (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, (ss2 * multiplier) >> 24, - (ss3 * multiplier) >> 24); + (ss3 * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -329,7 +335,8 @@ ImagingReduceNx1(Imaging imOut, Imaging imIn, int box[4], int xscale) { ss3 += line[xx * 4 + 3]; } v = MAKE_UINT32( - (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24); + (ss0 * multiplier) >> 24, 0, 0, (ss3 * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { @@ -351,7 +358,8 @@ ImagingReduceNx1(Imaging imOut, Imaging imIn, int box[4], int xscale) { (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, (ss2 * multiplier) >> 24, - 0); + 0 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 @@ -375,7 +383,8 @@ ImagingReduceNx1(Imaging imOut, Imaging imIn, int box[4], int xscale) { (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, (ss2 * multiplier) >> 24, - (ss3 * multiplier) >> 24); + (ss3 * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -425,7 +434,8 @@ ImagingReduce1x2(Imaging imOut, Imaging imIn, int box[4]) { ss1 = line0[xx * 4 + 1] + line1[xx * 4 + 1]; ss2 = line0[xx * 4 + 2] + line1[xx * 4 + 2]; v = MAKE_UINT32( - (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, 0); + (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, 0 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 @@ -440,7 +450,8 @@ ImagingReduce1x2(Imaging imOut, Imaging imIn, int box[4]) { (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, - (ss3 + amend) >> 1); + (ss3 + amend) >> 1 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -488,7 +499,8 @@ ImagingReduce2x1(Imaging imOut, Imaging imIn, int box[4]) { ss1 = line0[xx * 4 + 1] + line0[xx * 4 + 5]; ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6]; v = MAKE_UINT32( - (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, 0); + (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, 0 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 @@ -503,7 +515,8 @@ ImagingReduce2x1(Imaging imOut, Imaging imIn, int box[4]) { (ss0 + amend) >> 1, (ss1 + amend) >> 1, (ss2 + amend) >> 1, - (ss3 + amend) >> 1); + (ss3 + amend) >> 1 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -558,7 +571,8 @@ ImagingReduce2x2(Imaging imOut, Imaging imIn, int box[4]) { ss2 = line0[xx * 4 + 2] + line0[xx * 4 + 6] + line1[xx * 4 + 2] + line1[xx * 4 + 6]; v = MAKE_UINT32( - (ss0 + amend) >> 2, (ss1 + amend) >> 2, (ss2 + amend) >> 2, 0); + (ss0 + amend) >> 2, (ss1 + amend) >> 2, (ss2 + amend) >> 2, 0 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 @@ -577,7 +591,8 @@ ImagingReduce2x2(Imaging imOut, Imaging imIn, int box[4]) { (ss0 + amend) >> 2, (ss1 + amend) >> 2, (ss2 + amend) >> 2, - (ss3 + amend) >> 2); + (ss3 + amend) >> 2 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -623,7 +638,8 @@ ImagingReduce1x3(Imaging imOut, Imaging imIn, int box[4]) { ((ss0 + amend) * multiplier) >> 24, 0, 0, - ((ss3 + amend) * multiplier) >> 24); + ((ss3 + amend) * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { @@ -637,7 +653,8 @@ ImagingReduce1x3(Imaging imOut, Imaging imIn, int box[4]) { ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, ((ss2 + amend) * multiplier) >> 24, - 0); + 0 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 @@ -652,7 +669,8 @@ ImagingReduce1x3(Imaging imOut, Imaging imIn, int box[4]) { ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, ((ss2 + amend) * multiplier) >> 24, - ((ss3 + amend) * multiplier) >> 24); + ((ss3 + amend) * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -694,7 +712,8 @@ ImagingReduce3x1(Imaging imOut, Imaging imIn, int box[4]) { ((ss0 + amend) * multiplier) >> 24, 0, 0, - ((ss3 + amend) * multiplier) >> 24); + ((ss3 + amend) * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { @@ -708,7 +727,8 @@ ImagingReduce3x1(Imaging imOut, Imaging imIn, int box[4]) { ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, ((ss2 + amend) * multiplier) >> 24, - 0); + 0 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 @@ -723,7 +743,8 @@ ImagingReduce3x1(Imaging imOut, Imaging imIn, int box[4]) { ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, ((ss2 + amend) * multiplier) >> 24, - ((ss3 + amend) * multiplier) >> 24); + ((ss3 + amend) * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -775,7 +796,8 @@ ImagingReduce3x3(Imaging imOut, Imaging imIn, int box[4]) { ((ss0 + amend) * multiplier) >> 24, 0, 0, - ((ss3 + amend) * multiplier) >> 24); + ((ss3 + amend) * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { @@ -795,7 +817,8 @@ ImagingReduce3x3(Imaging imOut, Imaging imIn, int box[4]) { ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, ((ss2 + amend) * multiplier) >> 24, - 0); + 0 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 @@ -818,7 +841,8 @@ ImagingReduce3x3(Imaging imOut, Imaging imIn, int box[4]) { ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, ((ss2 + amend) * multiplier) >> 24, - ((ss3 + amend) * multiplier) >> 24); + ((ss3 + amend) * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -900,7 +924,8 @@ ImagingReduce4x4(Imaging imOut, Imaging imIn, int box[4]) { line3[xx * 4 + 2] + line3[xx * 4 + 6] + line3[xx * 4 + 10] + line3[xx * 4 + 14]; v = MAKE_UINT32( - (ss0 + amend) >> 4, (ss1 + amend) >> 4, (ss2 + amend) >> 4, 0); + (ss0 + amend) >> 4, (ss1 + amend) >> 4, (ss2 + amend) >> 4, 0 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 @@ -935,7 +960,8 @@ ImagingReduce4x4(Imaging imOut, Imaging imIn, int box[4]) { (ss0 + amend) >> 4, (ss1 + amend) >> 4, (ss2 + amend) >> 4, - (ss3 + amend) >> 4); + (ss3 + amend) >> 4 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -1007,7 +1033,8 @@ ImagingReduce5x5(Imaging imOut, Imaging imIn, int box[4]) { ((ss0 + amend) * multiplier) >> 24, 0, 0, - ((ss3 + amend) * multiplier) >> 24); + ((ss3 + amend) * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else if (imIn->bands == 3) { @@ -1045,7 +1072,8 @@ ImagingReduce5x5(Imaging imOut, Imaging imIn, int box[4]) { ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, ((ss2 + amend) * multiplier) >> 24, - 0); + 0 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } else { // bands == 4 @@ -1092,7 +1120,8 @@ ImagingReduce5x5(Imaging imOut, Imaging imIn, int box[4]) { ((ss0 + amend) * multiplier) >> 24, ((ss1 + amend) * multiplier) >> 24, ((ss2 + amend) * multiplier) >> 24, - ((ss3 + amend) * multiplier) >> 24); + ((ss3 + amend) * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -1181,7 +1210,8 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, (ss2 * multiplier) >> 24, - (ss3 * multiplier) >> 24); + (ss3 * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -1207,7 +1237,8 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, (ss2 * multiplier) >> 24, - (ss3 * multiplier) >> 24); + (ss3 * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -1232,7 +1263,8 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys (ss0 * multiplier) >> 24, (ss1 * multiplier) >> 24, (ss2 * multiplier) >> 24, - (ss3 * multiplier) >> 24); + (ss3 * multiplier) >> 24 + ); memcpy(imOut->image[y] + x * sizeof(v), &v, sizeof(v)); } } @@ -1240,7 +1272,8 @@ ImagingReduceCorners(Imaging imOut, Imaging imIn, int box[4], int xscale, int ys void ImagingReduceNxN_32bpc( - Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) { + Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale +) { /* The most general implementation for any xscale and yscale */ int x, y, xx, yy; @@ -1313,7 +1346,8 @@ ImagingReduceNxN_32bpc( void ImagingReduceCorners_32bpc( - Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale) { + Imaging imOut, Imaging imIn, int box[4], int xscale, int yscale +) { /* Fill the last row and the last column for any xscale and yscale. */ int x, y, xx, yy; @@ -1427,7 +1461,8 @@ ImagingReduce(Imaging imIn, int xscale, int yscale, int box[4]) { } imOut = ImagingNewDirty( - imIn->mode, (box[2] + xscale - 1) / xscale, (box[3] + yscale - 1) / yscale); + imIn->mode, (box[2] + xscale - 1) / xscale, (box[3] + yscale - 1) / yscale + ); if (!imOut) { return NULL; } diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c index 59c27b3f4..222d6bca4 100644 --- a/src/libImaging/Resample.c +++ b/src/libImaging/Resample.c @@ -186,7 +186,8 @@ precompute_coeffs( int outSize, struct filter *filterp, int **boundsp, - double **kkp) { + double **kkp +) { double support, scale, filterscale; double center, ww, ss; int xx, x, ksize, xmin, xmax; @@ -284,7 +285,8 @@ normalize_coeffs_8bpc(int outSize, int ksize, double *prekk) { void ImagingResampleHorizontal_8bpc( - Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *prekk) { + Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *prekk +) { ImagingSectionCookie cookie; int ss0, ss1, ss2, ss3; int xx, yy, x, xmin, xmax; @@ -376,7 +378,8 @@ ImagingResampleHorizontal_8bpc( void ImagingResampleVertical_8bpc( - Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *prekk) { + Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *prekk +) { ImagingSectionCookie cookie; int ss0, ss1, ss2, ss3; int xx, yy, y, ymin, ymax; @@ -459,7 +462,8 @@ ImagingResampleVertical_8bpc( void ImagingResampleHorizontal_32bpc( - Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk) { + Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk +) { ImagingSectionCookie cookie; double ss; int xx, yy, x, xmin, xmax; @@ -502,7 +506,8 @@ ImagingResampleHorizontal_32bpc( void ImagingResampleVertical_32bpc( - Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk) { + Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk +) { ImagingSectionCookie cookie; double ss; int xx, yy, y, ymin, ymax; @@ -544,7 +549,8 @@ ImagingResampleVertical_32bpc( } typedef void (*ResampleFunction)( - Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk); + Imaging imOut, Imaging imIn, int offset, int ksize, int *bounds, double *kk +); Imaging ImagingResampleInner( @@ -554,7 +560,8 @@ ImagingResampleInner( struct filter *filterp, float box[4], ResampleFunction ResampleHorizontal, - ResampleFunction ResampleVertical); + ResampleFunction ResampleVertical +); Imaging ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]) { @@ -609,7 +616,8 @@ ImagingResample(Imaging imIn, int xsize, int ysize, int filter, float box[4]) { } return ImagingResampleInner( - imIn, xsize, ysize, filterp, box, ResampleHorizontal, ResampleVertical); + imIn, xsize, ysize, filterp, box, ResampleHorizontal, ResampleVertical + ); } Imaging @@ -620,7 +628,8 @@ ImagingResampleInner( struct filter *filterp, float box[4], ResampleFunction ResampleHorizontal, - ResampleFunction ResampleVertical) { + ResampleFunction ResampleVertical +) { Imaging imTemp = NULL; Imaging imOut = NULL; @@ -634,13 +643,15 @@ ImagingResampleInner( need_vertical = ysize != imIn->ysize || box[1] || box[3] != ysize; ksize_horiz = precompute_coeffs( - imIn->xsize, box[0], box[2], xsize, filterp, &bounds_horiz, &kk_horiz); + imIn->xsize, box[0], box[2], xsize, filterp, &bounds_horiz, &kk_horiz + ); if (!ksize_horiz) { return NULL; } ksize_vert = precompute_coeffs( - imIn->ysize, box[1], box[3], ysize, filterp, &bounds_vert, &kk_vert); + imIn->ysize, box[1], box[3], ysize, filterp, &bounds_vert, &kk_vert + ); if (!ksize_vert) { free(bounds_horiz); free(kk_horiz); @@ -662,7 +673,8 @@ ImagingResampleInner( imTemp = ImagingNewDirty(imIn->mode, xsize, ybox_last - ybox_first); if (imTemp) { ResampleHorizontal( - imTemp, imIn, ybox_first, ksize_horiz, bounds_horiz, kk_horiz); + imTemp, imIn, ybox_first, ksize_horiz, bounds_horiz, kk_horiz + ); } free(bounds_horiz); free(kk_horiz); diff --git a/src/libImaging/SgiRleDecode.c b/src/libImaging/SgiRleDecode.c index 89dedb525..a8db11740 100644 --- a/src/libImaging/SgiRleDecode.c +++ b/src/libImaging/SgiRleDecode.c @@ -114,7 +114,8 @@ expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer static int expandrow2( - UINT8 *dest, const UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer) { + UINT8 *dest, const UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer +) { UINT8 pixel, count; int x = 0; @@ -252,7 +253,8 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t c->rlelength, im->bands, im->xsize, - &ptr[c->bufsize - 1]); + &ptr[c->bufsize - 1] + ); } else { status = expandrow2( &state->buffer[c->channo * 2], @@ -260,7 +262,8 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t c->rlelength, im->bands, im->xsize, - &ptr[c->bufsize - 1]); + &ptr[c->bufsize - 1] + ); } if (status == -1) { state->errcode = IMAGING_CODEC_OVERRUN; diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c index b27195a35..9dc133b0f 100644 --- a/src/libImaging/Storage.c +++ b/src/libImaging/Storage.c @@ -110,9 +110,8 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { im->linesize = xsize * 4; im->type = IMAGING_TYPE_INT32; - } else if ( - strcmp(mode, "I;16") == 0 || strcmp(mode, "I;16L") == 0 || - strcmp(mode, "I;16B") == 0 || strcmp(mode, "I;16N") == 0) { + } else if (strcmp(mode, "I;16") == 0 || strcmp(mode, "I;16L") == 0 || + strcmp(mode, "I;16B") == 0 || strcmp(mode, "I;16N") == 0) { /* EXPERIMENTAL */ /* 16-bit raw integer images */ im->bands = 1; @@ -227,7 +226,8 @@ ImagingNewPrologueSubtype(const char *mode, int xsize, int ysize, int size) { Imaging ImagingNewPrologue(const char *mode, int xsize, int ysize) { return ImagingNewPrologueSubtype( - mode, xsize, ysize, sizeof(struct ImagingMemoryInstance)); + mode, xsize, ysize, sizeof(struct ImagingMemoryInstance) + ); } void diff --git a/src/libImaging/SunRleDecode.c b/src/libImaging/SunRleDecode.c index 9d8e1292a..d3231ad90 100644 --- a/src/libImaging/SunRleDecode.c +++ b/src/libImaging/SunRleDecode.c @@ -107,7 +107,8 @@ ImagingSunRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, state->buffer, - state->xsize); + state->xsize + ); state->x = 0; diff --git a/src/libImaging/TgaRleDecode.c b/src/libImaging/TgaRleDecode.c index 95ae9b622..fbf29452c 100644 --- a/src/libImaging/TgaRleDecode.c +++ b/src/libImaging/TgaRleDecode.c @@ -93,7 +93,8 @@ ImagingTgaRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, state->buffer, - state->xsize); + state->xsize + ); state->x = 0; diff --git a/src/libImaging/TgaRleEncode.c b/src/libImaging/TgaRleEncode.c index aa7e7b96d..dde476614 100644 --- a/src/libImaging/TgaRleEncode.c +++ b/src/libImaging/TgaRleEncode.c @@ -63,7 +63,8 @@ ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) state->buffer, (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, - state->xsize); + state->xsize + ); } row = state->buffer; @@ -146,7 +147,8 @@ ImagingTgaRleEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) } memcpy( - dst, state->buffer + (state->x * bytesPerPixel - state->count), flushCount); + dst, state->buffer + (state->x * bytesPerPixel - state->count), flushCount + ); dst += flushCount; bytes -= flushCount; diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index abffdeabc..18a54f633 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -44,7 +44,8 @@ dump_state(const TIFFSTATE *state) { (int)state->size, (uint)state->eof, state->data, - state->ifd)); + state->ifd) + ); } /* @@ -64,7 +65,8 @@ _tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) { "_tiffReadProc", "Invalid Read at loc %" PRIu64 ", eof: %" PRIu64, state->loc, - state->eof); + state->eof + ); return 0; } to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc); @@ -200,13 +202,15 @@ ImagingLibTiffInit(ImagingCodecState state, int fp, uint32_t offset) { state->state, state->x, state->y, - state->ystep)); + state->ystep) + ); TRACE( ("State: xsize %d, ysize %d, xoff %d, yoff %d \n", state->xsize, state->ysize, state->xoff, - state->yoff)); + state->yoff) + ); TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes)); TRACE(("State: context %p \n", state->context)); @@ -226,7 +230,8 @@ _pickUnpackers( ImagingCodecState state, TIFF *tiff, uint16_t planarconfig, - ImagingShuffler *unpackers) { + ImagingShuffler *unpackers +) { // if number of bands is 1, there is no difference with contig case if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1) { uint16_t bits_per_sample = 8; @@ -356,7 +361,8 @@ _decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) { (UINT8 *)im->image[state->y + state->yoff + current_row] + state->xoff * im->pixelsize, state->buffer + current_row * row_byte_size, - state->xsize); + state->xsize + ); } } @@ -374,7 +380,8 @@ _decodeTile( ImagingCodecState state, TIFF *tiff, int planes, - ImagingShuffler *unpackers) { + ImagingShuffler *unpackers +) { INT32 x, y, tile_y, current_tile_length, current_tile_width; UINT32 tile_width, tile_length; tsize_t tile_bytes_size, row_byte_size; @@ -453,7 +460,8 @@ _decodeTile( ("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, - current_tile_width)); + current_tile_width) + ); // UINT8 * bbb = state->buffer + tile_y * row_byte_size; // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], @@ -462,7 +470,8 @@ _decodeTile( shuffler( (UINT8 *)im->image[tile_y + y] + x * im->pixelsize, state->buffer + tile_y * row_byte_size, - current_tile_width); + current_tile_width + ); } } } @@ -477,7 +486,8 @@ _decodeStrip( ImagingCodecState state, TIFF *tiff, int planes, - ImagingShuffler *unpackers) { + ImagingShuffler *unpackers +) { INT32 strip_row = 0; UINT8 *new_data; UINT32 rows_per_strip; @@ -544,9 +554,10 @@ _decodeStrip( tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, - strip_size) == -1) { - TRACE( - ("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))); + strip_size + ) == -1) { + TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0)) + ); state->errcode = IMAGING_CODEC_BROKEN; return -1; } @@ -567,7 +578,8 @@ _decodeStrip( (UINT8 *)im->image[state->y + state->yoff + strip_row] + state->xoff * im->pixelsize, state->buffer + strip_row * row_byte_size, - state->xsize); + state->xsize + ); } } } @@ -577,7 +589,8 @@ _decodeStrip( int ImagingLibTiffDecode( - Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes) { + Imaging im, ImagingCodecState state, UINT8 *buffer, Py_ssize_t bytes +) { TIFFSTATE *clientstate = (TIFFSTATE *)state->context; char *filename = "tempfile.tif"; char *mode = "rC"; @@ -602,13 +615,15 @@ ImagingLibTiffDecode( state->state, state->x, state->y, - state->ystep)); + state->ystep) + ); TRACE( ("State: xsize %d, ysize %d, xoff %d, yoff %d \n", state->xsize, state->ysize, state->xoff, - state->yoff)); + state->yoff) + ); TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes)); TRACE( ("Buffer: %p: %c%c%c%c\n", @@ -616,26 +631,30 @@ ImagingLibTiffDecode( (char)buffer[0], (char)buffer[1], (char)buffer[2], - (char)buffer[3])); + (char)buffer[3]) + ); TRACE( ("State->Buffer: %c%c%c%c\n", (char)state->buffer[0], (char)state->buffer[1], (char)state->buffer[2], - (char)state->buffer[3])); + (char)state->buffer[3]) + ); TRACE( ("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n", im->mode, im->type, im->bands, im->xsize, - im->ysize)); + im->ysize) + ); TRACE( ("Image: image8 %p, image32 %p, image %p, block %p \n", im->image8, im->image32, im->image, - im->block)); + im->block) + ); TRACE(("Image: pixelsize: %d, linesize %d \n", im->pixelsize, im->linesize)); dump_state(clientstate); @@ -665,7 +684,8 @@ ImagingLibTiffDecode( _tiffCloseProc, _tiffSizeProc, _tiffMapProc, - _tiffUnmapProc); + _tiffUnmapProc + ); } if (!tiff) { @@ -694,7 +714,8 @@ ImagingLibTiffDecode( state->xsize, img_width, state->ysize, - img_height)); + img_height) + ); state->errcode = IMAGING_CODEC_BROKEN; goto decode_err; } @@ -739,7 +760,8 @@ ImagingLibTiffDecode( INT32 y; TIFFGetFieldDefaulted( - tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo); + tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo + ); if (extrasamples >= 1 && (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA)) { @@ -793,13 +815,15 @@ ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) { state->state, state->x, state->y, - state->ystep)); + state->ystep) + ); TRACE( ("State: xsize %d, ysize %d, xoff %d, yoff %d \n", state->xsize, state->ysize, state->xoff, - state->yoff)); + state->yoff) + ); TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes)); TRACE(("State: context %p \n", state->context)); @@ -839,7 +863,8 @@ ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) { _tiffCloseProc, _tiffSizeProc, _tiffNullMapProc, - _tiffUnmapProc); /*force no mmap*/ + _tiffUnmapProc + ); /*force no mmap*/ } if (!clientstate->tiff) { @@ -852,7 +877,8 @@ ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp) { int ImagingLibTiffMergeFieldInfo( - ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length) { + ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length +) { // Refer to libtiff docs (http://www.simplesystems.org/libtiff/addingtags.html) TIFFSTATE *clientstate = (TIFFSTATE *)state->context; uint32_t n; @@ -874,7 +900,8 @@ ImagingLibTiffMergeFieldInfo( FIELD_CUSTOM, 1, passcount, - "CustomField"}}; + "CustomField"} + }; n = sizeof(info) / sizeof(info[0]); @@ -922,13 +949,15 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt state->state, state->x, state->y, - state->ystep)); + state->ystep) + ); TRACE( ("State: xsize %d, ysize %d, xoff %d, yoff %d \n", state->xsize, state->ysize, state->xoff, - state->yoff)); + state->yoff) + ); TRACE(("State: bits %d, bytes %d \n", state->bits, state->bytes)); TRACE( ("Buffer: %p: %c%c%c%c\n", @@ -936,26 +965,30 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt (char)buffer[0], (char)buffer[1], (char)buffer[2], - (char)buffer[3])); + (char)buffer[3]) + ); TRACE( ("State->Buffer: %c%c%c%c\n", (char)state->buffer[0], (char)state->buffer[1], (char)state->buffer[2], - (char)state->buffer[3])); + (char)state->buffer[3]) + ); TRACE( ("Image: mode %s, type %d, bands: %d, xsize %d, ysize %d \n", im->mode, im->type, im->bands, im->xsize, - im->ysize)); + im->ysize) + ); TRACE( ("Image: image8 %p, image32 %p, image %p, block %p \n", im->image8, im->image32, im->image, - im->block)); + im->block) + ); TRACE(("Image: pixelsize: %d, linesize %d \n", im->pixelsize, im->linesize)); dump_state(clientstate); @@ -967,10 +1000,12 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt state->buffer, (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, - state->xsize); + state->xsize + ); if (TIFFWriteScanline( - tiff, (tdata_t)(state->buffer), (uint32_t)state->y, 0) == -1) { + tiff, (tdata_t)(state->buffer), (uint32_t)state->y, 0 + ) == -1) { TRACE(("Encode Error, row %d\n", state->y)); state->errcode = IMAGING_CODEC_BROKEN; TIFFClose(tiff); @@ -1013,7 +1048,8 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt (char)buffer[0], (char)buffer[1], (char)buffer[2], - (char)buffer[3])); + (char)buffer[3]) + ); if (clientstate->loc == clientstate->eof) { TRACE(("Hit EOF, calling an end, freeing data")); state->errcode = IMAGING_CODEC_END; diff --git a/src/libImaging/TiffDecode.h b/src/libImaging/TiffDecode.h index 212b7dee6..22361210d 100644 --- a/src/libImaging/TiffDecode.h +++ b/src/libImaging/TiffDecode.h @@ -41,7 +41,8 @@ extern int ImagingLibTiffEncodeInit(ImagingCodecState state, char *filename, int fp); extern int ImagingLibTiffMergeFieldInfo( - ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length); + ImagingCodecState state, TIFFDataType field_type, int key, int is_var_length +); extern int ImagingLibTiffSetField(ImagingCodecState state, ttag_t tag, ...); diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index eaa4374e3..c23d5d889 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -104,7 +104,8 @@ static UINT8 BITFLIP[] = { 3, 131, 67, 195, 35, 163, 99, 227, 19, 147, 83, 211, 51, 179, 115, 243, 11, 139, 75, 203, 43, 171, 107, 235, 27, 155, 91, 219, 59, 187, 123, 251, 7, 135, 71, 199, 39, 167, 103, 231, 23, 151, 87, 215, 55, 183, 119, 247, - 15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255}; + 15, 143, 79, 207, 47, 175, 111, 239, 31, 159, 95, 223, 63, 191, 127, 255 +}; /* Unpack to "1" image */ @@ -882,7 +883,8 @@ unpackRGBa16L(UINT8 *_out, const UINT8 *in, int pixels) { CLIP8(in[1] * 255 / a), CLIP8(in[3] * 255 / a), CLIP8(in[5] * 255 / a), - a); + a + ); } memcpy(_out, &iv, sizeof(iv)); in += 8; @@ -906,7 +908,8 @@ unpackRGBa16B(UINT8 *_out, const UINT8 *in, int pixels) { CLIP8(in[0] * 255 / a), CLIP8(in[2] * 255 / a), CLIP8(in[4] * 255 / a), - a); + a + ); } memcpy(_out, &iv, sizeof(iv)); in += 8; @@ -930,7 +933,8 @@ unpackRGBa(UINT8 *_out, const UINT8 *in, int pixels) { CLIP8(in[0] * 255 / a), CLIP8(in[1] * 255 / a), CLIP8(in[2] * 255 / a), - a); + a + ); } memcpy(_out, &iv, sizeof(iv)); in += 4; @@ -954,7 +958,8 @@ unpackRGBaskip1(UINT8 *_out, const UINT8 *in, int pixels) { CLIP8(in[0] * 255 / a), CLIP8(in[1] * 255 / a), CLIP8(in[2] * 255 / a), - a); + a + ); } in += 5; } @@ -976,7 +981,8 @@ unpackRGBaskip2(UINT8 *_out, const UINT8 *in, int pixels) { CLIP8(in[0] * 255 / a), CLIP8(in[1] * 255 / a), CLIP8(in[2] * 255 / a), - a); + a + ); } in += 6; } @@ -998,7 +1004,8 @@ unpackBGRa(UINT8 *_out, const UINT8 *in, int pixels) { CLIP8(in[2] * 255 / a), CLIP8(in[1] * 255 / a), CLIP8(in[0] * 255 / a), - a); + a + ); } memcpy(_out, &iv, sizeof(iv)); in += 4; @@ -1029,7 +1036,8 @@ unpackRGBAL(UINT8 *_out, const UINT8 *in, int pixels) { in[i], in[i + pixels], in[i + pixels + pixels], - in[i + pixels + pixels + pixels]); + in[i + pixels + pixels + pixels] + ); memcpy(_out, &iv, sizeof(iv)); } } diff --git a/src/libImaging/UnpackYCC.c b/src/libImaging/UnpackYCC.c index 0b177bdd4..35b0c3b69 100644 --- a/src/libImaging/UnpackYCC.c +++ b/src/libImaging/UnpackYCC.c @@ -34,7 +34,8 @@ static INT16 L[] = { 261, 262, 264, 265, 266, 268, 269, 270, 272, 273, 274, 276, 277, 278, 280, 281, 283, 284, 285, 287, 288, 289, 291, 292, 293, 295, 296, 297, 299, 300, 302, 303, 304, 306, 307, 308, 310, 311, 312, 314, 315, 317, 318, 319, 321, 322, 323, 325, - 326, 327, 329, 330, 331, 333, 334, 336, 337, 338, 340, 341, 342, 344, 345, 346}; + 326, 327, 329, 330, 331, 333, 334, 336, 337, 338, 340, 341, 342, 344, 345, 346 +}; static INT16 CB[] = { -345, -343, -341, -338, -336, -334, -332, -329, -327, -325, -323, -321, -318, -316, @@ -55,7 +56,8 @@ static INT16 CB[] = { 120, 122, 124, 126, 129, 131, 133, 135, 138, 140, 142, 144, 146, 149, 151, 153, 155, 157, 160, 162, 164, 166, 169, 171, 173, 175, 177, 180, 182, 184, 186, 189, 191, 193, 195, 197, 200, 202, 204, 206, 208, 211, - 213, 215, 217, 220}; + 213, 215, 217, 220 +}; static INT16 GB[] = { 67, 67, 66, 66, 65, 65, 65, 64, 64, 63, 63, 62, 62, 62, 61, 61, @@ -73,7 +75,8 @@ static INT16 GB[] = { -14, -15, -15, -16, -16, -17, -17, -18, -18, -18, -19, -19, -20, -20, -21, -21, -21, -22, -22, -23, -23, -24, -24, -24, -25, -25, -26, -26, -27, -27, -27, -28, -28, -29, -29, -30, -30, -30, -31, -31, -32, -32, -33, -33, -33, -34, -34, -35, - -35, -36, -36, -36, -37, -37, -38, -38, -39, -39, -39, -40, -40, -41, -41, -42}; + -35, -36, -36, -36, -37, -37, -38, -38, -39, -39, -39, -40, -40, -41, -41, -42 +}; static INT16 CR[] = { -249, -247, -245, -243, -241, -239, -238, -236, -234, -232, -230, -229, -227, -225, @@ -94,7 +97,8 @@ static INT16 CR[] = { 133, 135, 137, 138, 140, 142, 144, 146, 148, 149, 151, 153, 155, 157, 158, 160, 162, 164, 166, 168, 169, 171, 173, 175, 177, 179, 180, 182, 184, 186, 188, 189, 191, 193, 195, 197, 199, 200, 202, 204, 206, 208, - 209, 211, 213, 215}; + 209, 211, 213, 215 +}; static INT16 GR[] = { 127, 126, 125, 124, 123, 122, 121, 121, 120, 119, 118, 117, 116, 115, 114, @@ -114,7 +118,8 @@ static INT16 GR[] = { -67, -68, -69, -69, -70, -71, -72, -73, -74, -75, -76, -77, -78, -79, -80, -81, -82, -82, -83, -84, -85, -86, -87, -88, -89, -90, -91, -92, -93, -94, -94, -95, -96, -97, -98, -99, -100, -101, -102, -103, -104, -105, -106, -107, -107, - -108}; + -108 +}; #define R 0 #define G 1 diff --git a/src/libImaging/UnsharpMask.c b/src/libImaging/UnsharpMask.c index 2853ce903..e714749ef 100644 --- a/src/libImaging/UnsharpMask.c +++ b/src/libImaging/UnsharpMask.c @@ -23,7 +23,8 @@ clip8(int in) { Imaging ImagingUnsharpMask( - Imaging imOut, Imaging imIn, float radius, int percent, int threshold) { + Imaging imOut, Imaging imIn, float radius, int percent, int threshold +) { ImagingSectionCookie cookie; Imaging result; diff --git a/src/libImaging/XbmEncode.c b/src/libImaging/XbmEncode.c index eec4c0d84..65cc3c633 100644 --- a/src/libImaging/XbmEncode.c +++ b/src/libImaging/XbmEncode.c @@ -40,7 +40,8 @@ ImagingXbmEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { state->shuffle( state->buffer, (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, - state->xsize); + state->xsize + ); if (state->y < state->ysize - 1) { /* any line but the last */ diff --git a/src/libImaging/ZipDecode.c b/src/libImaging/ZipDecode.c index 874967834..d964ff2ca 100644 --- a/src/libImaging/ZipDecode.c +++ b/src/libImaging/ZipDecode.c @@ -217,7 +217,8 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt state->shuffle( (UINT8 *)im->image[state->y] + col * im->pixelsize, state->buffer + context->prefix + i, - 1); + 1 + ); col += COL_INCREMENT[context->pass]; } } else { @@ -229,7 +230,8 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt UINT8 byte = *(state->buffer + context->prefix + (i / 8)); byte <<= (i % 8); state->shuffle( - (UINT8 *)im->image[state->y] + col * im->pixelsize, &byte, 1); + (UINT8 *)im->image[state->y] + col * im->pixelsize, &byte, 1 + ); col += COL_INCREMENT[context->pass]; } } @@ -253,7 +255,8 @@ ImagingZipDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, state->buffer + context->prefix, - state->xsize); + state->xsize + ); state->y++; } diff --git a/src/libImaging/ZipEncode.c b/src/libImaging/ZipEncode.c index edbce3682..44f2629cc 100644 --- a/src/libImaging/ZipEncode.c +++ b/src/libImaging/ZipEncode.c @@ -98,7 +98,8 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { 15, 9, /* compression strategy (image data are filtered)*/ - compress_type); + compress_type + ); if (err < 0) { state->errcode = IMAGING_CODEC_CONFIG; return -1; @@ -108,7 +109,8 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { err = deflateSetDictionary( &context->z_stream, (unsigned char *)context->dictionary, - context->dictionary_size); + context->dictionary_size + ); if (err < 0) { state->errcode = IMAGING_CODEC_CONFIG; return -1; @@ -163,7 +165,8 @@ ImagingZipEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { state->buffer + 1, (UINT8 *)im->image[state->y + state->yoff] + state->xoff * im->pixelsize, - state->xsize); + state->xsize + ); state->y++; diff --git a/src/map.c b/src/map.c index c298bd148..c66702981 100644 --- a/src/map.c +++ b/src/map.c @@ -72,7 +72,8 @@ PyImaging_MapBuffer(PyObject *self, PyObject *args) { &offset, &mode, &stride, - &ystep)) { + &ystep + )) { return NULL; } diff --git a/src/path.c b/src/path.c index bd6ad2259..b96e8b78a 100644 --- a/src/path.c +++ b/src/path.c @@ -489,7 +489,8 @@ path_transform(PyPathObject *self, PyObject *args) { double wrap = 0.0; if (!PyArg_ParseTuple( - args, "(dddddd)|d:transform", &a, &b, &c, &d, &e, &f, &wrap)) { + args, "(dddddd)|d:transform", &a, &b, &c, &d, &e, &f, &wrap + )) { return NULL; } @@ -570,7 +571,8 @@ path_subscript(PyPathObject *self, PyObject *item) { PyErr_Format( PyExc_TypeError, "Path indices must be integers, not %.200s", - Py_TYPE(item)->tp_name); + Py_TYPE(item)->tp_name + ); return NULL; } } @@ -586,7 +588,8 @@ static PySequenceMethods path_as_sequence = { }; static PyMappingMethods path_as_mapping = { - (lenfunc)path_len, (binaryfunc)path_subscript, NULL}; + (lenfunc)path_len, (binaryfunc)path_subscript, NULL +}; static PyTypeObject PyPathType = { PyVarObject_HEAD_INIT(NULL, 0) "Path", /*tp_name*/ From 31469407166026ec6d74d2df07196ef2d4e32ab4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Jul 2024 08:26:05 +1000 Subject: [PATCH 28/61] Temporarily disable cifuzz --- .github/workflows/cifuzz.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml index eb73fc6a7..033ff98ce 100644 --- a/.github/workflows/cifuzz.yml +++ b/.github/workflows/cifuzz.yml @@ -24,6 +24,8 @@ concurrency: jobs: Fuzzing: + # Disabled until google/oss-fuzz#11419 upgrades Python to 3.9+ + if: false runs-on: ubuntu-latest steps: - name: Build Fuzzers From a3f93b3f68804d7c459faee165edf021e54f850a Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Jul 2024 16:23:29 +1000 Subject: [PATCH 29/61] Changed ContainerIO to subclass IO --- Tests/test_file_container.py | 91 +++++++++++++++++++++++++++++++----- pyproject.toml | 1 - src/PIL/ContainerIO.py | 72 ++++++++++++++++++++++++---- src/PIL/TarIO.py | 10 ---- 4 files changed, 142 insertions(+), 32 deletions(-) diff --git a/Tests/test_file_container.py b/Tests/test_file_container.py index 7f76fb47a..237045acc 100644 --- a/Tests/test_file_container.py +++ b/Tests/test_file_container.py @@ -1,7 +1,5 @@ from __future__ import annotations -from typing import Literal - import pytest from PIL import ContainerIO, Image @@ -23,6 +21,13 @@ def test_isatty() -> None: assert container.isatty() is False +def test_seekable() -> None: + with hopper() as im: + container = ContainerIO.ContainerIO(im, 0, 0) + + assert container.seekable() is True + + @pytest.mark.parametrize( "mode, expected_position", ( @@ -31,7 +36,7 @@ def test_isatty() -> None: (2, 100), ), ) -def test_seek_mode(mode: Literal[0, 1, 2], expected_position: int) -> None: +def test_seek_mode(mode: int, expected_position: int) -> None: # Arrange with open(TEST_FILE, "rb") as fh: container = ContainerIO.ContainerIO(fh, 22, 100) @@ -44,6 +49,14 @@ def test_seek_mode(mode: Literal[0, 1, 2], expected_position: int) -> None: assert container.tell() == expected_position +@pytest.mark.parametrize("bytesmode", (True, False)) +def test_readable(bytesmode: bool) -> None: + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 0, 120) + + assert container.readable() is True + + @pytest.mark.parametrize("bytesmode", (True, False)) def test_read_n0(bytesmode: bool) -> None: # Arrange @@ -51,7 +64,7 @@ def test_read_n0(bytesmode: bool) -> None: container = ContainerIO.ContainerIO(fh, 22, 100) # Act - container.seek(81) + assert container.seek(81) == 81 data = container.read() # Assert @@ -67,7 +80,7 @@ def test_read_n(bytesmode: bool) -> None: container = ContainerIO.ContainerIO(fh, 22, 100) # Act - container.seek(81) + assert container.seek(81) == 81 data = container.read(3) # Assert @@ -83,7 +96,7 @@ def test_read_eof(bytesmode: bool) -> None: container = ContainerIO.ContainerIO(fh, 22, 100) # Act - container.seek(100) + assert container.seek(100) == 100 data = container.read() # Assert @@ -94,21 +107,65 @@ def test_read_eof(bytesmode: bool) -> None: @pytest.mark.parametrize("bytesmode", (True, False)) def test_readline(bytesmode: bool) -> None: - # Arrange with open(TEST_FILE, "rb" if bytesmode else "r") as fh: container = ContainerIO.ContainerIO(fh, 0, 120) - # Act data = container.readline() - - # Assert if bytesmode: data = data.decode() assert data == "This is line 1\n" + data = container.readline(4) + if bytesmode: + data = data.decode() + assert data == "This" + @pytest.mark.parametrize("bytesmode", (True, False)) def test_readlines(bytesmode: bool) -> None: + expected = [ + "This is line 1\n", + "This is line 2\n", + "This is line 3\n", + "This is line 4\n", + "This is line 5\n", + "This is line 6\n", + "This is line 7\n", + "This is line 8\n", + ] + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 0, 120) + + data = container.readlines() + if bytesmode: + data = [line.decode() for line in data] + assert data == expected + + assert container.seek(0) == 0 + + data = container.readlines(2) + if bytesmode: + data = [line.decode() for line in data] + assert data == expected[:2] + + +@pytest.mark.parametrize("bytesmode", (True, False)) +def test_write(bytesmode: bool) -> None: + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 0, 120) + + assert container.writable() is False + + with pytest.raises(NotImplementedError): + container.write(b"" if bytesmode else "") + with pytest.raises(NotImplementedError): + container.writelines([]) + with pytest.raises(NotImplementedError): + container.truncate() + + +@pytest.mark.parametrize("bytesmode", (True, False)) +def test_iter(bytesmode: bool) -> None: # Arrange expected = [ "This is line 1\n", @@ -124,9 +181,21 @@ def test_readlines(bytesmode: bool) -> None: container = ContainerIO.ContainerIO(fh, 0, 120) # Act - data = container.readlines() + data = [] + for line in container: + data.append(line) # Assert if bytesmode: data = [line.decode() for line in data] assert data == expected + + +@pytest.mark.parametrize("bytesmode", (True, False)) +def test_file(bytesmode: bool) -> None: + with open(TEST_FILE, "rb" if bytesmode else "r") as fh: + container = ContainerIO.ContainerIO(fh, 0, 120) + + assert isinstance(container.fileno(), int) + container.flush() + container.close() diff --git a/pyproject.toml b/pyproject.toml index cd7248669..b76f3c24d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -161,5 +161,4 @@ exclude = [ '^Tests/test_qt_image_qapplication.py$', '^Tests/test_font_pcf_charsets.py$', '^Tests/test_font_pcf.py$', - '^Tests/test_file_tar.py$', ] diff --git a/src/PIL/ContainerIO.py b/src/PIL/ContainerIO.py index 0035296a4..ec9e66c71 100644 --- a/src/PIL/ContainerIO.py +++ b/src/PIL/ContainerIO.py @@ -16,10 +16,11 @@ from __future__ import annotations import io -from typing import IO, AnyStr, Generic, Literal +from collections.abc import Iterable +from typing import IO, AnyStr, NoReturn -class ContainerIO(Generic[AnyStr]): +class ContainerIO(IO[AnyStr]): """ A file object that provides read access to a part of an existing file (for example a TAR file). @@ -45,7 +46,10 @@ class ContainerIO(Generic[AnyStr]): def isatty(self) -> bool: return False - def seek(self, offset: int, mode: Literal[0, 1, 2] = io.SEEK_SET) -> None: + def seekable(self) -> bool: + return True + + def seek(self, offset: int, mode: int = io.SEEK_SET) -> int: """ Move file pointer. @@ -53,6 +57,7 @@ class ContainerIO(Generic[AnyStr]): :param mode: Starting position. Use 0 for beginning of region, 1 for current offset, and 2 for end of region. You cannot move the pointer outside the defined region. + :returns: Offset from start of region, in bytes. """ if mode == 1: self.pos = self.pos + offset @@ -63,6 +68,7 @@ class ContainerIO(Generic[AnyStr]): # clamp self.pos = max(0, min(self.pos, self.length)) self.fh.seek(self.offset + self.pos) + return self.pos def tell(self) -> int: """ @@ -72,27 +78,32 @@ class ContainerIO(Generic[AnyStr]): """ return self.pos - def read(self, n: int = 0) -> AnyStr: + def readable(self) -> bool: + return True + + def read(self, n: int = -1) -> AnyStr: """ Read data. - :param n: Number of bytes to read. If omitted or zero, + :param n: Number of bytes to read. If omitted, zero or negative, read until end of region. :returns: An 8-bit string. """ - if n: + if n > 0: n = min(n, self.length - self.pos) else: n = self.length - self.pos - if not n: # EOF + if n <= 0: # EOF return b"" if "b" in self.fh.mode else "" # type: ignore[return-value] self.pos = self.pos + n return self.fh.read(n) - def readline(self) -> AnyStr: + def readline(self, n: int = -1) -> AnyStr: """ Read a line of text. + :param n: Number of bytes to read. If omitted, zero or negative, + read until end of line. :returns: An 8-bit string. """ s: AnyStr = b"" if "b" in self.fh.mode else "" # type: ignore[assignment] @@ -102,14 +113,16 @@ class ContainerIO(Generic[AnyStr]): if not c: break s = s + c - if c == newline_character: + if c == newline_character or len(s) == n: break return s - def readlines(self) -> list[AnyStr]: + def readlines(self, n: int | None = -1) -> list[AnyStr]: """ Read multiple lines of text. + :param n: Number of lines to read. If omitted, zero, negative or None, + read until end of region. :returns: A list of 8-bit strings. """ lines = [] @@ -118,4 +131,43 @@ class ContainerIO(Generic[AnyStr]): if not s: break lines.append(s) + if len(lines) == n: + break return lines + + def writable(self) -> bool: + return False + + def write(self, b: AnyStr) -> NoReturn: + raise NotImplementedError() + + def writelines(self, lines: Iterable[AnyStr]) -> NoReturn: + raise NotImplementedError() + + def truncate(self, size: int | None = None) -> int: + raise NotImplementedError() + + def __enter__(self) -> ContainerIO[AnyStr]: + return self + + def __exit__(self, *args: object) -> None: + self.close() + + def __iter__(self) -> ContainerIO[AnyStr]: + return self + + def __next__(self) -> AnyStr: + line = self.readline() + if not line: + msg = "end of region" + raise StopIteration(msg) + return line + + def fileno(self) -> int: + return self.fh.fileno() + + def flush(self) -> None: + self.fh.flush() + + def close(self) -> None: + self.fh.close() diff --git a/src/PIL/TarIO.py b/src/PIL/TarIO.py index cba26d4b0..779288b1c 100644 --- a/src/PIL/TarIO.py +++ b/src/PIL/TarIO.py @@ -55,13 +55,3 @@ class TarIO(ContainerIO.ContainerIO[bytes]): # Open region super().__init__(self.fh, self.fh.tell(), size) - - # Context manager support - def __enter__(self) -> TarIO: - return self - - def __exit__(self, *args: object) -> None: - self.close() - - def close(self) -> None: - self.fh.close() From 10faa5df390c160570110b48bd4cd03fa4390dc6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Jul 2024 22:08:53 +1000 Subject: [PATCH 30/61] Deprecate lambda_eval and unsafe_eval options argument --- Tests/test_imagemath_lambda_eval.py | 65 ++++++++++++++++------------ Tests/test_imagemath_unsafe_eval.py | 67 ++++++++++++++++------------- docs/deprecations.rst | 9 ++++ docs/reference/ImageMath.rst | 16 +++---- docs/releasenotes/11.0.0.rst | 8 ++-- src/PIL/ImageMath.py | 24 ++++++++--- 6 files changed, 115 insertions(+), 74 deletions(-) diff --git a/Tests/test_imagemath_lambda_eval.py b/Tests/test_imagemath_lambda_eval.py index 5769c903e..360325780 100644 --- a/Tests/test_imagemath_lambda_eval.py +++ b/Tests/test_imagemath_lambda_eval.py @@ -1,5 +1,9 @@ from __future__ import annotations +from typing import Any + +import pytest + from PIL import Image, ImageMath @@ -19,7 +23,7 @@ I = Image.new("I", (1, 1), 4) # noqa: E741 A2 = A.resize((2, 2)) B2 = B.resize((2, 2)) -images = {"A": A, "B": B, "F": F, "I": I} +images: dict[str, Any] = {"A": A, "B": B, "F": F, "I": I} def test_sanity() -> None: @@ -30,13 +34,13 @@ def test_sanity() -> None: == "I 3" ) assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], **images)) == "I 3" ) assert ( pixel( ImageMath.lambda_eval( - lambda args: args["float"](args["A"]) + args["B"], images + lambda args: args["float"](args["A"]) + args["B"], **images ) ) == "F 3.0" @@ -44,42 +48,47 @@ def test_sanity() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["int"](args["float"](args["A"]) + args["B"]), images + lambda args: args["int"](args["float"](args["A"]) + args["B"]), **images ) ) == "I 3" ) +def test_options_deprecated() -> None: + with pytest.warns(DeprecationWarning): + assert ImageMath.lambda_eval(lambda args: 1, images) == 1 + + def test_ops() -> None: - assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, images)) == "I -1" + assert pixel(ImageMath.lambda_eval(lambda args: args["A"] * -1, **images)) == "I -1" assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] + args["B"], **images)) == "I 3" ) assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] - args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] - args["B"], **images)) == "I -1" ) assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] * args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] * args["B"], **images)) == "I 2" ) assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] / args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] / args["B"], **images)) == "I 0" ) - assert pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 2, images)) == "I 4" + assert pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 2, **images)) == "I 4" assert ( - pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, images)) + pixel(ImageMath.lambda_eval(lambda args: args["B"] ** 33, **images)) == "I 2147483647" ) assert ( pixel( ImageMath.lambda_eval( - lambda args: args["float"](args["A"]) + args["B"], images + lambda args: args["float"](args["A"]) + args["B"], **images ) ) == "F 3.0" @@ -87,7 +96,7 @@ def test_ops() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["float"](args["A"]) - args["B"], images + lambda args: args["float"](args["A"]) - args["B"], **images ) ) == "F -1.0" @@ -95,7 +104,7 @@ def test_ops() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["float"](args["A"]) * args["B"], images + lambda args: args["float"](args["A"]) * args["B"], **images ) ) == "F 2.0" @@ -103,31 +112,33 @@ def test_ops() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["float"](args["A"]) / args["B"], images + lambda args: args["float"](args["A"]) / args["B"], **images ) ) == "F 0.5" ) assert ( - pixel(ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 2, images)) + pixel( + ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 2, **images) + ) == "F 4.0" ) assert ( pixel( - ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 33, images) + ImageMath.lambda_eval(lambda args: args["float"](args["B"]) ** 33, **images) ) == "F 8589934592.0" ) def test_logical() -> None: - assert pixel(ImageMath.lambda_eval(lambda args: not args["A"], images)) == 0 + assert pixel(ImageMath.lambda_eval(lambda args: not args["A"], **images)) == 0 assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] and args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] and args["B"], **images)) == "L 2" ) assert ( - pixel(ImageMath.lambda_eval(lambda args: args["A"] or args["B"], images)) + pixel(ImageMath.lambda_eval(lambda args: args["A"] or args["B"], **images)) == "L 1" ) @@ -136,7 +147,7 @@ def test_convert() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["convert"](args["A"] + args["B"], "L"), images + lambda args: args["convert"](args["A"] + args["B"], "L"), **images ) ) == "L 3" @@ -144,7 +155,7 @@ def test_convert() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["convert"](args["A"] + args["B"], "1"), images + lambda args: args["convert"](args["A"] + args["B"], "1"), **images ) ) == "1 0" @@ -152,7 +163,7 @@ def test_convert() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["convert"](args["A"] + args["B"], "RGB"), images + lambda args: args["convert"](args["A"] + args["B"], "RGB"), **images ) ) == "RGB (3, 3, 3)" @@ -163,7 +174,7 @@ def test_compare() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["min"](args["A"], args["B"]), images + lambda args: args["min"](args["A"], args["B"]), **images ) ) == "I 1" @@ -171,13 +182,13 @@ def test_compare() -> None: assert ( pixel( ImageMath.lambda_eval( - lambda args: args["max"](args["A"], args["B"]), images + lambda args: args["max"](args["A"], args["B"]), **images ) ) == "I 2" ) - assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 1, images)) == "I 1" - assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, images)) == "I 0" + assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 1, **images)) == "I 1" + assert pixel(ImageMath.lambda_eval(lambda args: args["A"] == 2, **images)) == "I 0" def test_one_image_larger() -> None: diff --git a/Tests/test_imagemath_unsafe_eval.py b/Tests/test_imagemath_unsafe_eval.py index 7b8a562d7..b7ac84691 100644 --- a/Tests/test_imagemath_unsafe_eval.py +++ b/Tests/test_imagemath_unsafe_eval.py @@ -1,5 +1,7 @@ from __future__ import annotations +from typing import Any + import pytest from PIL import Image, ImageMath @@ -21,16 +23,16 @@ I = Image.new("I", (1, 1), 4) # noqa: E741 A2 = A.resize((2, 2)) B2 = B.resize((2, 2)) -images = {"A": A, "B": B, "F": F, "I": I} +images: dict[str, Any] = {"A": A, "B": B, "F": F, "I": I} def test_sanity() -> None: assert ImageMath.unsafe_eval("1") == 1 assert ImageMath.unsafe_eval("1+A", A=2) == 3 assert pixel(ImageMath.unsafe_eval("A+B", A=A, B=B)) == "I 3" - assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3" - assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0" - assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", images)) == "I 3" + assert pixel(ImageMath.unsafe_eval("A+B", **images)) == "I 3" + assert pixel(ImageMath.unsafe_eval("float(A)+B", **images)) == "F 3.0" + assert pixel(ImageMath.unsafe_eval("int(float(A)+B)", **images)) == "I 3" def test_eval_deprecated() -> None: @@ -38,23 +40,28 @@ def test_eval_deprecated() -> None: assert ImageMath.eval("1") == 1 +def test_options_deprecated() -> None: + with pytest.warns(DeprecationWarning): + assert ImageMath.unsafe_eval("1", images) == 1 + + def test_ops() -> None: - assert pixel(ImageMath.unsafe_eval("-A", images)) == "I -1" - assert pixel(ImageMath.unsafe_eval("+B", images)) == "L 2" + assert pixel(ImageMath.unsafe_eval("-A", **images)) == "I -1" + assert pixel(ImageMath.unsafe_eval("+B", **images)) == "L 2" - assert pixel(ImageMath.unsafe_eval("A+B", images)) == "I 3" - assert pixel(ImageMath.unsafe_eval("A-B", images)) == "I -1" - assert pixel(ImageMath.unsafe_eval("A*B", images)) == "I 2" - assert pixel(ImageMath.unsafe_eval("A/B", images)) == "I 0" - assert pixel(ImageMath.unsafe_eval("B**2", images)) == "I 4" - assert pixel(ImageMath.unsafe_eval("B**33", images)) == "I 2147483647" + assert pixel(ImageMath.unsafe_eval("A+B", **images)) == "I 3" + assert pixel(ImageMath.unsafe_eval("A-B", **images)) == "I -1" + assert pixel(ImageMath.unsafe_eval("A*B", **images)) == "I 2" + assert pixel(ImageMath.unsafe_eval("A/B", **images)) == "I 0" + assert pixel(ImageMath.unsafe_eval("B**2", **images)) == "I 4" + assert pixel(ImageMath.unsafe_eval("B**33", **images)) == "I 2147483647" - assert pixel(ImageMath.unsafe_eval("float(A)+B", images)) == "F 3.0" - assert pixel(ImageMath.unsafe_eval("float(A)-B", images)) == "F -1.0" - assert pixel(ImageMath.unsafe_eval("float(A)*B", images)) == "F 2.0" - assert pixel(ImageMath.unsafe_eval("float(A)/B", images)) == "F 0.5" - assert pixel(ImageMath.unsafe_eval("float(B)**2", images)) == "F 4.0" - assert pixel(ImageMath.unsafe_eval("float(B)**33", images)) == "F 8589934592.0" + assert pixel(ImageMath.unsafe_eval("float(A)+B", **images)) == "F 3.0" + assert pixel(ImageMath.unsafe_eval("float(A)-B", **images)) == "F -1.0" + assert pixel(ImageMath.unsafe_eval("float(A)*B", **images)) == "F 2.0" + assert pixel(ImageMath.unsafe_eval("float(A)/B", **images)) == "F 0.5" + assert pixel(ImageMath.unsafe_eval("float(B)**2", **images)) == "F 4.0" + assert pixel(ImageMath.unsafe_eval("float(B)**33", **images)) == "F 8589934592.0" @pytest.mark.parametrize( @@ -72,33 +79,33 @@ def test_prevent_exec(expression: str) -> None: def test_prevent_double_underscores() -> None: with pytest.raises(ValueError): - ImageMath.unsafe_eval("1", {"__": None}) + ImageMath.unsafe_eval("1", __=None) def test_prevent_builtins() -> None: with pytest.raises(ValueError): - ImageMath.unsafe_eval("(lambda: exec('exit()'))()", {"exec": None}) + ImageMath.unsafe_eval("(lambda: exec('exit()'))()", exec=None) def test_logical() -> None: - assert pixel(ImageMath.unsafe_eval("not A", images)) == 0 - assert pixel(ImageMath.unsafe_eval("A and B", images)) == "L 2" - assert pixel(ImageMath.unsafe_eval("A or B", images)) == "L 1" + assert pixel(ImageMath.unsafe_eval("not A", **images)) == 0 + assert pixel(ImageMath.unsafe_eval("A and B", **images)) == "L 2" + assert pixel(ImageMath.unsafe_eval("A or B", **images)) == "L 1" def test_convert() -> None: - assert pixel(ImageMath.unsafe_eval("convert(A+B, 'L')", images)) == "L 3" - assert pixel(ImageMath.unsafe_eval("convert(A+B, '1')", images)) == "1 0" + assert pixel(ImageMath.unsafe_eval("convert(A+B, 'L')", **images)) == "L 3" + assert pixel(ImageMath.unsafe_eval("convert(A+B, '1')", **images)) == "1 0" assert ( - pixel(ImageMath.unsafe_eval("convert(A+B, 'RGB')", images)) == "RGB (3, 3, 3)" + pixel(ImageMath.unsafe_eval("convert(A+B, 'RGB')", **images)) == "RGB (3, 3, 3)" ) def test_compare() -> None: - assert pixel(ImageMath.unsafe_eval("min(A, B)", images)) == "I 1" - assert pixel(ImageMath.unsafe_eval("max(A, B)", images)) == "I 2" - assert pixel(ImageMath.unsafe_eval("A == 1", images)) == "I 1" - assert pixel(ImageMath.unsafe_eval("A == 2", images)) == "I 0" + assert pixel(ImageMath.unsafe_eval("min(A, B)", **images)) == "I 1" + assert pixel(ImageMath.unsafe_eval("max(A, B)", **images)) == "I 2" + assert pixel(ImageMath.unsafe_eval("A == 1", **images)) == "I 1" + assert pixel(ImageMath.unsafe_eval("A == 2", **images)) == "I 0" def test_one_image_larger() -> None: diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 792fd1c70..2f5800e07 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -109,6 +109,15 @@ ImageDraw.getdraw hints parameter The ``hints`` parameter in :py:meth:`~PIL.ImageDraw.getdraw()` has been deprecated. +ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 11.0.0 + +The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and +:py:meth:`~PIL.ImageMath.unsafe_eval()` has been deprecated. One or more keyword +arguments can be used instead. + Removed features ---------------- diff --git a/docs/reference/ImageMath.rst b/docs/reference/ImageMath.rst index 2535db711..a72ca92d0 100644 --- a/docs/reference/ImageMath.rst +++ b/docs/reference/ImageMath.rst @@ -37,10 +37,10 @@ Example: Using the :py:mod:`~PIL.ImageMath` module :param expression: A function that receives a dictionary. :param options: Values to add to the function's dictionary, mapping image - names to Image instances. You can use one or more keyword - arguments instead of a dictionary, as shown in the above - example. Note that the names must be valid Python - identifiers. + names to Image instances. Deprecated. + You can instead use one or more keyword arguments, as + shown in the above example. Note that the names must be + valid Python identifiers. :return: An image, an integer value, a floating point value, or a pixel tuple, depending on the expression. @@ -62,10 +62,10 @@ Example: Using the :py:mod:`~PIL.ImageMath` module syntax. In addition to the standard operators, you can also use the functions described below. :param options: Values to add to the function's dictionary, mapping image - names to Image instances. You can use one or more keyword - arguments instead of a dictionary, as shown in the above - example. Note that the names must be valid Python - identifiers. + names to Image instances. Deprecated. + You can instead use one or more keyword arguments, as + shown in the above example. Note that the names must be + valid Python identifiers. :return: An image, an integer value, a floating point value, or a pixel tuple, depending on the expression. diff --git a/docs/releasenotes/11.0.0.rst b/docs/releasenotes/11.0.0.rst index 964423ae0..bb6179de8 100644 --- a/docs/releasenotes/11.0.0.rst +++ b/docs/releasenotes/11.0.0.rst @@ -43,10 +43,12 @@ similarly removed. Deprecations ============ -TODO -^^^^ +ImageMath.lambda_eval and ImageMath.unsafe_eval options parameter +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -TODO +The ``options`` parameter in :py:meth:`~PIL.ImageMath.lambda_eval()` and +:py:meth:`~PIL.ImageMath.unsafe_eval()` has been deprecated. One or more +keyword arguments can be used instead. API Changes =========== diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index 6664434ea..e79eb3ec9 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -249,14 +249,20 @@ def lambda_eval( :py:func:`~PIL.Image.merge` function. :param expression: A function that receives a dictionary. - :param options: Values to add to the function's dictionary. You - can either use a dictionary, or one or more keyword - arguments. + :param options: Values to add to the function's dictionary. Deprecated. + You can instead use one or more keyword arguments. :return: The expression result. This is usually an image object, but can also be an integer, a floating point value, or a pixel tuple, depending on the expression. """ + if options: + deprecate( + "ImageMath.lambda_eval options", + 12, + "ImageMath.lambda_eval keyword arguments", + ) + args: dict[str, Any] = ops.copy() args.update(options) args.update(kw) @@ -287,14 +293,20 @@ def unsafe_eval( :py:func:`~PIL.Image.merge` function. :param expression: A string containing a Python-style expression. - :param options: Values to add to the evaluation context. You - can either use a dictionary, or one or more keyword - arguments. + :param options: Values to add to the evaluation context. Deprecated. + You can instead use one or more keyword arguments. :return: The evaluated expression. This is usually an image object, but can also be an integer, a floating point value, or a pixel tuple, depending on the expression. """ + if options: + deprecate( + "ImageMath.unsafe_eval options", + 12, + "ImageMath.unsafe_eval keyword arguments", + ) + # build execution namespace args: dict[str, Any] = ops.copy() for k in list(options.keys()) + list(kw.keys()): From 47fc36a323892040b1945afd22d036a15fb99abd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 17 Jul 2024 22:28:56 +1000 Subject: [PATCH 31/61] Update CHANGES.rst [ci skip] --- CHANGES.rst | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 856458aa9..a2e02fe62 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,15 @@ Changelog (Pillow) 11.0.0 (unreleased) ------------------- +- Changed ContainerIO to subclass IO #8240 + [radarhere] + +- Move away from APIs that use borrowed references under the free-threaded build #8216 + [hugovk, lysnikolaou] + +- Allow size argument to resize() to be a NumPy array #8201 + [radarhere] + - Drop support for Python 3.8 #8183 [hugovk, radarhere] From f39ca5db5a39c2c82e62fbee1ebf95d5b61bdfba Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Wed, 17 Jul 2024 16:21:16 +0200 Subject: [PATCH 32/61] Skip QEMU-emulated wheels on workflow dispatch event --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index fa1825e45..10e37f343 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -41,7 +41,7 @@ env: jobs: build-1-QEMU-emulated-wheels: - if: github.event_name != 'schedule' + if: github.event_name != 'schedule' && github.event_name != 'workflow_dispatch' name: aarch64 ${{ matrix.python-version }} ${{ matrix.spec }} runs-on: ubuntu-latest strategy: From 09c817a2ec7c0dc4988235aa50b8073192b3e9e2 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Wed, 17 Jul 2024 23:05:48 +0200 Subject: [PATCH 33/61] Move uploading nightly wheels to different job This is needed cause the job does not support running on OS's other than Ubuntu. --- .github/workflows/wheels.yml | 31 +++++++++++++++++-------------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 10e37f343..087d2bd56 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -149,13 +149,6 @@ jobs: name: dist-${{ matrix.os }}-${{ matrix.cibw_arch }}${{ matrix.manylinux && format('-{0}', matrix.manylinux) }} path: ./wheelhouse/*.whl - - name: Upload wheels to scientific-python-nightly-wheels - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' - uses: scientific-python/upload-nightly-action@b67d7fcc0396e1128a474d1ab2b48aa94680f9fc # 0.5.0 - with: - artifacts_path: ./wheelhouse - anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} - windows: name: Windows ${{ matrix.cibw_arch }} runs-on: windows-latest @@ -242,13 +235,6 @@ jobs: name: fribidi-windows-${{ matrix.cibw_arch }} path: winbuild\build\bin\fribidi* - - name: Upload wheels to scientific-python-nightly-wheels - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' - uses: scientific-python/upload-nightly-action@b67d7fcc0396e1128a474d1ab2b48aa94680f9fc # 0.5.0 - with: - artifacts_path: ./wheelhouse - anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} - sdist: if: github.event_name != 'schedule' runs-on: ubuntu-latest @@ -269,6 +255,23 @@ jobs: name: dist-sdist path: dist/*.tar.gz + scientific-python-nightly-wheels-publish: + if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + needs: [build-2-native-wheels, windows] + runs-on: ubuntu-latest + name: Upload release to PyPI + steps: + - uses: actions/download-artifact@v4 + with: + pattern: dist-* + path: dist + merge-multiple: true + - name: Upload wheels to scientific-python-nightly-wheels + uses: scientific-python/upload-nightly-action@b67d7fcc0396e1128a474d1ab2b48aa94680f9fc # 0.5.0 + with: + artifacts_path: dist + anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} + pypi-publish: if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') needs: [build-1-QEMU-emulated-wheels, build-2-native-wheels, windows, sdist] From 7248cde50b985aba3198b70201fed462a15997d0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 18 Jul 2024 11:00:27 +1000 Subject: [PATCH 34/61] Documented keyword arguments --- docs/reference/ImageMath.rst | 22 ++++++++++++---------- src/PIL/ImageMath.py | 2 ++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/reference/ImageMath.rst b/docs/reference/ImageMath.rst index a72ca92d0..f4e1081e6 100644 --- a/docs/reference/ImageMath.rst +++ b/docs/reference/ImageMath.rst @@ -31,20 +31,21 @@ Example: Using the :py:mod:`~PIL.ImageMath` module b=im2 ) -.. py:function:: lambda_eval(expression, options) +.. py:function:: lambda_eval(expression, options, **kw) Returns the result of an image function. :param expression: A function that receives a dictionary. - :param options: Values to add to the function's dictionary, mapping image - names to Image instances. Deprecated. + :param options: Values to add to the function's dictionary. Note that the names + must be valid Python identifiers. Deprecated. You can instead use one or more keyword arguments, as - shown in the above example. Note that the names must be - valid Python identifiers. + shown in the above example. + :param \**kw: Values to add to the function's dictionary, mapping image names to + Image instances. :return: An image, an integer value, a floating point value, or a pixel tuple, depending on the expression. -.. py:function:: unsafe_eval(expression, options) +.. py:function:: unsafe_eval(expression, options, **kw) Evaluates an image expression. @@ -61,11 +62,12 @@ Example: Using the :py:mod:`~PIL.ImageMath` module :param expression: A string which uses the standard Python expression syntax. In addition to the standard operators, you can also use the functions described below. - :param options: Values to add to the function's dictionary, mapping image - names to Image instances. Deprecated. + :param options: Values to add to the evaluation context. Note that the names must + be valid Python identifiers. Deprecated. You can instead use one or more keyword arguments, as - shown in the above example. Note that the names must be - valid Python identifiers. + shown in the above example. + :param \**kw: Values to add to the evaluation context, mapping image names to Image + instances. :return: An image, an integer value, a floating point value, or a pixel tuple, depending on the expression. diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index e79eb3ec9..191cc2a5f 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -251,6 +251,7 @@ def lambda_eval( :param expression: A function that receives a dictionary. :param options: Values to add to the function's dictionary. Deprecated. You can instead use one or more keyword arguments. + :param **kw: Values to add to the function's dictionary. :return: The expression result. This is usually an image object, but can also be an integer, a floating point value, or a pixel tuple, depending on the expression. @@ -295,6 +296,7 @@ def unsafe_eval( :param expression: A string containing a Python-style expression. :param options: Values to add to the evaluation context. Deprecated. You can instead use one or more keyword arguments. + :param **kw: Values to add to the evaluation context. :return: The evaluated expression. This is usually an image object, but can also be an integer, a floating point value, or a pixel tuple, depending on the expression. From a6e93939566619aa1eb10fd5531aa59bb6e39bdc Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Thu, 18 Jul 2024 10:35:09 +0200 Subject: [PATCH 35/61] Update .github/workflows/wheels.yml Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/workflows/wheels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 087d2bd56..ffd1abd95 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -259,7 +259,7 @@ jobs: if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' needs: [build-2-native-wheels, windows] runs-on: ubuntu-latest - name: Upload release to PyPI + name: Upload wheels to scientific-python-nightly-wheels steps: - uses: actions/download-artifact@v4 with: From 73dfe67736565805f4c769cdfc92447f2b098187 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 18 Jul 2024 22:46:02 +1000 Subject: [PATCH 36/61] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index a2e02fe62..bd8d3af03 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 11.0.0 (unreleased) ------------------- +- Deprecate ImageMath lambda_eval and unsafe_eval options argument #8242 + [radarhere] + - Changed ContainerIO to subclass IO #8240 [radarhere] From 18d8020cab65a4aaca528f7848adfa06a9de5b85 Mon Sep 17 00:00:00 2001 From: "Jeffrey A. Clark (Alex)" Date: Thu, 18 Jul 2024 15:17:32 -0400 Subject: [PATCH 37/61] Add tutorial images --- docs/handbook/contrasted_hopper.jpg | Bin 0 -> 5572 bytes docs/handbook/cropped_hopper.jpg | Bin 0 -> 2017 bytes docs/handbook/enhanced_hopper.jpg | Bin 0 -> 5721 bytes docs/handbook/flip_left_right_hopper.jpg | Bin 0 -> 4637 bytes docs/handbook/flip_top_bottom_hopper.jpg | Bin 0 -> 4652 bytes docs/handbook/masked_hopper.jpg | Bin 0 -> 4904 bytes docs/handbook/merged_hopper.png | Bin 0 -> 35390 bytes docs/handbook/pasted_hopper.jpg | Bin 0 -> 4645 bytes docs/handbook/rebanded_hopper.jpg | Bin 0 -> 4743 bytes docs/handbook/rolled_hopper.jpg | Bin 0 -> 4651 bytes docs/handbook/rotated_hopper.jpg | Bin 0 -> 4895 bytes docs/handbook/rotated_hopper_180.jpg | Bin 0 -> 4636 bytes docs/handbook/rotated_hopper_270.jpg | Bin 0 -> 4895 bytes docs/handbook/rotated_hopper_90.jpg | Bin 0 -> 4903 bytes docs/handbook/show_hopper.png | Bin 0 -> 56122 bytes docs/handbook/thumbnail_hopper.jpg | Bin 0 -> 1984 bytes docs/handbook/transformed_hopper.jpg | Bin 0 -> 3608 bytes docs/handbook/tutorial.rst | 67 +++++++++++++++++++++++ 18 files changed, 67 insertions(+) create mode 100644 docs/handbook/contrasted_hopper.jpg create mode 100644 docs/handbook/cropped_hopper.jpg create mode 100644 docs/handbook/enhanced_hopper.jpg create mode 100644 docs/handbook/flip_left_right_hopper.jpg create mode 100644 docs/handbook/flip_top_bottom_hopper.jpg create mode 100644 docs/handbook/masked_hopper.jpg create mode 100644 docs/handbook/merged_hopper.png create mode 100644 docs/handbook/pasted_hopper.jpg create mode 100644 docs/handbook/rebanded_hopper.jpg create mode 100644 docs/handbook/rolled_hopper.jpg create mode 100644 docs/handbook/rotated_hopper.jpg create mode 100644 docs/handbook/rotated_hopper_180.jpg create mode 100644 docs/handbook/rotated_hopper_270.jpg create mode 100644 docs/handbook/rotated_hopper_90.jpg create mode 100644 docs/handbook/show_hopper.png create mode 100644 docs/handbook/thumbnail_hopper.jpg create mode 100644 docs/handbook/transformed_hopper.jpg diff --git a/docs/handbook/contrasted_hopper.jpg b/docs/handbook/contrasted_hopper.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b2d7cfacbb6b62fbcbbbc14aba4e955027bd201d GIT binary patch literal 5572 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 literal 0 HcmV?d00001 diff --git a/docs/handbook/cropped_hopper.jpg b/docs/handbook/cropped_hopper.jpg new file mode 100644 index 0000000000000000000000000000000000000000..912d0a29cb0d154e7b7062123863630e8a1ef3a8 GIT binary patch literal 2017 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 literal 0 HcmV?d00001 diff --git a/docs/handbook/flip_left_right_hopper.jpg b/docs/handbook/flip_left_right_hopper.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e1ef519e5cd8a34c65d681071b46318e3dd7f6a4 GIT binary patch literal 4637 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 literal 0 HcmV?d00001 diff --git a/docs/handbook/flip_top_bottom_hopper.jpg b/docs/handbook/flip_top_bottom_hopper.jpg new file mode 100644 index 0000000000000000000000000000000000000000..03a5020f3904067cb13d2ea918dd382dc4e29cc5 GIT binary patch literal 4652 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 D@-E{; literal 0 HcmV?d00001 diff --git a/docs/handbook/masked_hopper.jpg b/docs/handbook/masked_hopper.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f70a5ec4769e9c861fcfd4e1e90f812cd4369782 GIT binary patch literal 4904 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 literal 0 HcmV?d00001 diff --git a/docs/handbook/merged_hopper.png b/docs/handbook/merged_hopper.png new file mode 100644 index 0000000000000000000000000000000000000000..662816cbdad42a04dd522acbbd195c2d789fb87b GIT binary patch literal 35390 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 literal 0 HcmV?d00001 diff --git a/docs/handbook/rolled_hopper.jpg b/docs/handbook/rolled_hopper.jpg new file mode 100644 index 0000000000000000000000000000000000000000..b2220f39cbe11fa43e25b698a0dae49a4fadc39f GIT binary patch literal 4651 zcmbW(cQD-DzX$N|TD`LfqC{`mNOXb?9;*c*L|H8aLG&7;Cwh65L`V>AwXD8cl;{y< zWus-4gjJ%fPV~K=-|x=7Gxx81@B7R-|D4a9`Ml=LnK{I1;xB+v7p?;bKp+4BT@8RZ z184x0n9&S!9t{Z~4L~iik6yV|#l^45t z`;Lr^46lfyih`8#Eom94e+~grQBl#-(6ZCfu}krB@k#xkL;MUdQ34S_1Q^5*kT8M3 zOdw(}z11tk?V%~b)K5g-A9!6c+$GBQ%qtLo6JeSnmSjG6C_ z8ac%1F$KRji&SJ<0i}R?Z5ONYw;e%gd!HyOYPM_a9GpVJBBEmAGO}{=3W`b^e`&(C zv~_eJnwUN^Gqf&H=rAvnp;}CdwTo&2fhq`#eN^3n4Fsafm>L_FD# zNABHSYp2Lvi5^@>7JXJVqCF!HAKx+gsW1RX+2co=#KpzAR*r@pL3mh_S}gna!$eFQD)7NO;H*5!jfL9SJd67f&;eUp2UOYuIDfn1`WV+4-;t z@HO+%j6aeK39RJ5H@#SC|Gr2#qGh=>Ztzf6j0l*|4X|!r>!h_ajFGc^Q&H_1T8j9( zS5iaoM3#6%WCn(MJ`k&X*LzVBEeefB_p&8>bBbc#np6*+Ky37tY6G3i7Ke2Vglw&- z$H)8;$r2{}P{flA$&5B0Qv`tDDLW+4JQ#CoN;Q6|rd%7Z{062}?cURbEtIRDc73$w z&6NBlNb~2$-~mA6CM8@jSv4u@@?^{c3x3R*wy76a(|R(M3IC+#z-I0jQ>_lAv64zgT#>QDHWHJ2vY)k{YPj-V`?Pv87HY3J6|O2?lS2DY|(gP4<~ zT^bCu1mJ^pkh~aoHwQa)kqkWVlbd1uXK7%DO zik^xvpiuGPh4AF^U|LZU!?Ou@jfE7n3s{dT;g_U|&2?5xFkLRs*E+M>EN{Gu)5;`n z*jhQ@(eR5n=`?oC{XFf{youTF`?UgiP=xFf1^;ai)651f- zvCiHyd>(>1PFnrTjt{P_{i&=tM&SL%_{Ixc8ye1_?Ue?O+D9V-%j(Fj6R6FT0XED= zqz;7Vo-&v7tXY=0QC4ekg+4bd@i6qd&92!|b<)~f5Om0hlD=D#+;QdR_~U4t%{r2w?FOVr3EF6 zC(4~Ht~MySg%hW`>&ERNaaA^3fjQhAR>^!qz*_VSYY84g1XwkHRzYCai8-U+8!9g> zm((2G1+iRSMG>m#E`z`lnj`oGf^K9#w^?nRwio;Wzg+t1aEfHGpg%8(VKV2cfR~#?7JBkw;54m z)bDMxVrqHIfO2PFiTQyk2mO4*^HA5RH=<&ZHUtb~&r7jZ687L#JlmXPFk-Wt~pGE#zZ>f(io%rrkeGYWFaueIc$PDEHttf_nO z^D6XE`D$~@!rUNp-yidUfl~HG^lPorMMH7)h`Vbant?~3q{_GE?@&@VoA`w@6imBG zQg)=_{s2YDtB<0OBnbpPdx$?@imb%B#4?Gi=(*C7M{Bw~4&gogOzHN3!eMp(ssU5t zW~x0)aWQUv^!p{GQm}=o&-baBvQ&*C5g4dM)pa0#JEY%y{W!O~tqP+%<%#6XovdCC}m{W`)-lB^>J( z>JBO1JE`dn7!bP@A{YrcvZT4AunWu@BfSqjNqJk9Onu0M*f){_D!pCVn&kG_?JqWk z3Im@EsLrEJcvo$<^bKaetA@*0hFLyN(I*X8vei|mlHKCDft7~i#MF>%%2FC}+J`dB z%M{0xn1loE2B^6UqxSyr+xaE;*?|w>wS>V>k((-^lH9{D=m1gr<1A6f*)en=m4A#m3*{~p};*vLC9 zt*{Es(9T)9h3D$3J*FOdXMrv0tVK6QPP#>4Pu_Vc5b}t?h3mmYc8N8)zY%nC3z(8(Y)1N^^dQyd~e8=$dX`Fx)?PqcKXb@chu=ohUq< z&bv*!15@&=4RSV0!@4D!zA&H#V@^G{8HV_qHa_t&& zegYpyF3!W_&LA+=nK~i!nck;utzc9-`U_ov+Y{lhS||HVi*LlJW3#LfkNa zUMko0>yLV5@!vn{cnshZ2_iNUrj&>E3kUdD2h1RmpBM)+qVUb-@@21=9&$0n>UK~ zKfG~Ppp9t{X?l5RGX4VQadL5>@g#qn;SwG6^G(`4RyeR`Z5VY3aSw%l;+@l{Qowjg zpV!+sTDD<*R3q(7M1A~cx6x@U=A@pjPD{b(=}KxTJ0Hp4S#yi(bDq zA?)jWL|MSiwQy3e5q7VaTXU&H=cmS+C0~CJx;WVX28e)FG;fJ7Ze--#%`+64$uwF) zfP^F^J+fZyrI}N;iy0{jlb74-)SYKp4)?2eEkujd)kNV`K9It_^JdK~@RS)E$6952 zQkyLuOuZ2)Ugq9J;6;?1S(go|TUON7Z)Rnle2z=&(o!`z-!3FjU>{Eep3-^gl}`tS zQJg@h2#~V1=AEJ)jw@4TCxhnCJgw}+Ud_ubk%U!tRJ%y8B{GG@a<(ax;dbY6ewJr< zZNUCPd8Rw-Pb!%M<;jfM1C-<1BCqW1|M89s2g_8G43JFswDSEi5#>tQZ5LL& zscik2wfn?JBb2jQCC)piWf8+>8x;GfRl;9q=l-6hl(2$yi=)_XQNfCURiiD zxh#BNvZ#U2+G%repst{ zX7`dkUt!;2o$<_f02$V@FF^zttY-~`8WU_Yb2n<1wMp%!vW?%BO8+Rblzr?`%CWPt z2;Gi4bgyfuUz2V+)$H(5yF4WV=u@UHf)yrCHvU|#+Gc$Uw&77Cw;EniPt#v%- zxImCPGpzl#2_;l@*N+_eQIfm|=vBI^MSv9a`t&ZC7@=g&YIn{cU+| z>(J)hiOph)9CZp;Bkdp&+42ad*M=oRlHU5D1BTcRiRqxg1=-9xChv+~&dKZUbGmri z`2>*dvW;F@5E1C(EZ!URt@;U)U=w5MV+9t`wUzdF^7 zjk%Fwg?0BpQ*{QdEZ#no9-}?@T=4sDzS24l)n8wZ3H2Ayxrs_QKj5y&@SQ^40gW2! z=CCE{(pC&-|7NiCw6m<&(;SmLET=)t=uXy3y^GKEW`;!lT$QaR0+e>_PV(Rz`Jbkp zVP7Vq@p=ezhbHW%bA6LUUC`vO#m57-Q~Q+<&($LrTK>Y$$ydZ72)yg39)!`b@U!(o zx zJ)E=ETC0B~P$uTCg+Tm_chW2bQ?1)K=|43Ag`yI)6KU#c2b+B;JbMnySMpU*A52PO zj(ENq{@k>W&<_ZwE8Rn%EGS$YwhW9j5rL^HrB+_j`+U3pF^06(4CCbk8`dV=$W$q0 zt+URqYv>f`6W%#yMV|QA+&rA4Xu} AApigX literal 0 HcmV?d00001 diff --git a/docs/handbook/rotated_hopper.jpg b/docs/handbook/rotated_hopper.jpg new file mode 100644 index 0000000000000000000000000000000000000000..e4d22be70ce2bc4887a41799e1343fa0cc513cd9 GIT binary patch literal 4895 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 literal 0 HcmV?d00001 diff --git a/docs/handbook/show_hopper.png b/docs/handbook/show_hopper.png new file mode 100644 index 0000000000000000000000000000000000000000..1f2e6f243858d8a9944c4db0b1c0758111cc45ce GIT binary patch literal 56122 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 literal 0 HcmV?d00001 diff --git a/docs/handbook/transformed_hopper.jpg b/docs/handbook/transformed_hopper.jpg new file mode 100644 index 0000000000000000000000000000000000000000..33afe67e62be8f3e2922c5cfa5c0018774ac0b5c GIT binary patch literal 3608 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 im.show() +.. image:: show_hopper.png + :align: center + .. note:: The standard version of :py:meth:`~PIL.Image.Image.show` is not very @@ -79,6 +82,9 @@ Convert files to JPEG except OSError: print("cannot convert", infile) +.. image:: ../../Tests/images/hopper.jpg + :align: center + A second argument can be supplied to the :py:meth:`~PIL.Image.Image.save` method which explicitly specifies a file format. If you use a non-standard extension, you must always specify the format this way: @@ -103,6 +109,9 @@ Create JPEG thumbnails except OSError: print("cannot create thumbnail for", infile) +.. image:: thumbnail_hopper.jpg + :align: center + It is important to note that the library doesn’t decode or load the raster data unless it really has to. When you open a file, the file header is read to determine the file format and extract things like mode, size, and other @@ -150,6 +159,9 @@ pixels, so the region in the above example is exactly 300x300 pixels. The region could now be processed in a certain manner and pasted back. +.. image:: cropped_hopper.jpg + :align: center + Processing a subrectangle, and pasting it back ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -164,6 +176,9 @@ modes of the original image and the region do not need to match. If they don’t the region is automatically converted before being pasted (see the section on :ref:`color-transforms` below for details). +.. image:: pasted_hopper.jpg + :align: center + Here’s an additional example: Rolling an image @@ -186,6 +201,9 @@ Rolling an image return im +.. image:: rolled_hopper.jpg + :align: center + Or if you would like to merge two images into a wider image: Merging images @@ -203,6 +221,9 @@ Merging images return im +.. image:: merged_hopper.png + :align: center + For more advanced tricks, the paste method can also take a transparency mask as an optional argument. In this mask, the value 255 indicates that the pasted image is opaque in that position (that is, the pasted image should be used as @@ -229,6 +250,9 @@ Note that for a single-band image, :py:meth:`~PIL.Image.Image.split` returns the image itself. To work with individual color bands, you may want to convert the image to “RGB” first. +.. image:: rebanded_hopper.jpg + :align: center + Geometrical transforms ---------------------- @@ -245,6 +269,9 @@ Simple geometry transforms out = im.resize((128, 128)) out = im.rotate(45) # degrees counter-clockwise +.. image:: rotated_hopper_90.jpg + :align: center + To rotate the image in 90 degree steps, you can either use the :py:meth:`~PIL.Image.Image.rotate` method or the :py:meth:`~PIL.Image.Image.transpose` method. The latter can also be used to @@ -256,11 +283,38 @@ Transposing an image :: out = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) + +.. image:: flip_left_right_hopper.jpg + :align: center + +:: + out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM) + +.. image:: flip_top_bottom_hopper.jpg + :align: center + +:: + out = im.transpose(Image.Transpose.ROTATE_90) + +.. image:: rotated_hopper_90.jpg + :align: center + +:: + out = im.transpose(Image.Transpose.ROTATE_180) + +.. image:: rotated_hopper_180.jpg + :align: center + +:: + out = im.transpose(Image.Transpose.ROTATE_270) +.. image:: rotated_hopper_270.jpg + :align: center + ``transpose(ROTATE)`` operations can also be performed identically with :py:meth:`~PIL.Image.Image.rotate` operations, provided the ``expand`` flag is true, to provide for the same changes to the image's size. @@ -342,6 +396,9 @@ Applying filters from PIL import ImageFilter out = im.filter(ImageFilter.DETAIL) +.. image:: enhanced_hopper.jpg + :align: center + Point Operations ^^^^^^^^^^^^^^^^ @@ -358,6 +415,9 @@ Applying point transforms # multiply each pixel by 1.2 out = im.point(lambda i: i * 1.2) +.. image:: transformed_hopper.jpg + :align: center + Using the above technique, you can quickly apply any simple expression to an image. You can also combine the :py:meth:`~PIL.Image.Image.point` and :py:meth:`~PIL.Image.Image.paste` methods to selectively modify an image: @@ -388,6 +448,9 @@ Note the syntax used to create the mask:: imout = im.point(lambda i: expression and 255) +.. image:: masked_hopper.jpg + :align: center + Python only evaluates the portion of a logical expression as is necessary to determine the outcome, and returns the last value examined as the result of the expression. So if the expression above is false (0), Python does not look at @@ -412,6 +475,10 @@ Enhancing images enh = ImageEnhance.Contrast(im) enh.enhance(1.3).show("30% more contrast") + +.. image:: contrasted_hopper.jpg + :align: center + Image sequences --------------- From 96a1af9fa859ad36ab9041923dd6dcd420a86f16 Mon Sep 17 00:00:00 2001 From: "Jeffrey A. Clark (Alex)" Date: Thu, 18 Jul 2024 18:33:14 -0400 Subject: [PATCH 38/61] Add tutorial images Animated gif example --- docs/handbook/animated_hopper.gif | Bin 0 -> 58616 bytes docs/handbook/tutorial.rst | 37 ++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 docs/handbook/animated_hopper.gif diff --git a/docs/handbook/animated_hopper.gif b/docs/handbook/animated_hopper.gif new file mode 100644 index 0000000000000000000000000000000000000000..e6eeaffcc4e519810e323704e68d81953fad7e12 GIT binary patch literal 58616 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ƒ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 Date: Fri, 19 Jul 2024 14:32:59 +1000 Subject: [PATCH 39/61] Correct reference --- docs/handbook/tutorial.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index 2840377e3..6c4e3ad59 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -546,8 +546,8 @@ You can create animated GIFs with Pillow, e.g. The following class lets you use the for-statement to loop over the sequence: -Using the :py:meth:`ImageSequence.Iterator` class -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Using the :py:class:`~PIL.ImageSequence.Iterator` class +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ :: From 1daaef02cd2a6faeb0dfbc0a23360fdfb49a8621 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Jul 2024 18:27:00 +1000 Subject: [PATCH 40/61] Updated code to match image --- docs/handbook/tutorial.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index 6c4e3ad59..e19da47a5 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -149,13 +149,13 @@ Copying a subrectangle from an image :: - box = (100, 100, 400, 400) + box = (0, 0, 64, 64) region = im.crop(box) The region is defined by a 4-tuple, where coordinates are (left, upper, right, lower). The Python Imaging Library uses a coordinate system with (0, 0) in the upper left corner. Also note that coordinates refer to positions between the -pixels, so the region in the above example is exactly 300x300 pixels. +pixels, so the region in the above example is exactly 64x64 pixels. The region could now be processed in a certain manner and pasted back. From 54055c76c4fe3ffe6293f5ed1ab8ded2c0f7d669 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Jul 2024 18:45:00 +1000 Subject: [PATCH 41/61] Converted images to WebP --- docs/handbook/cropped_hopper.jpg | Bin 2017 -> 0 bytes docs/handbook/cropped_hopper.webp | Bin 0 -> 1656 bytes docs/handbook/enhanced_hopper.jpg | Bin 5721 -> 0 bytes docs/handbook/enhanced_hopper.webp | Bin 0 -> 4684 bytes docs/handbook/flip_left_right_hopper.jpg | Bin 4637 -> 0 bytes docs/handbook/flip_left_right_hopper.webp | Bin 0 -> 3976 bytes docs/handbook/flip_top_bottom_hopper.jpg | Bin 4652 -> 0 bytes docs/handbook/flip_top_bottom_hopper.webp | Bin 0 -> 3966 bytes docs/handbook/masked_hopper.jpg | Bin 4904 -> 0 bytes docs/handbook/masked_hopper.webp | Bin 0 -> 3954 bytes docs/handbook/merged_hopper.png | Bin 35390 -> 0 bytes docs/handbook/merged_hopper.webp | Bin 0 -> 7536 bytes docs/handbook/pasted_hopper.jpg | Bin 4645 -> 0 bytes docs/handbook/pasted_hopper.webp | Bin 0 -> 3922 bytes docs/handbook/rebanded_hopper.jpg | Bin 4743 -> 0 bytes docs/handbook/rebanded_hopper.webp | Bin 0 -> 3866 bytes docs/handbook/rolled_hopper.jpg | Bin 4651 -> 0 bytes docs/handbook/rolled_hopper.webp | Bin 0 -> 3912 bytes docs/handbook/rotated_hopper_180.jpg | Bin 4636 -> 0 bytes docs/handbook/rotated_hopper_180.webp | Bin 0 -> 3946 bytes docs/handbook/rotated_hopper_270.jpg | Bin 4895 -> 0 bytes docs/handbook/rotated_hopper_270.webp | Bin 0 -> 3922 bytes docs/handbook/rotated_hopper_90.jpg | Bin 4903 -> 0 bytes docs/handbook/rotated_hopper_90.webp | Bin 0 -> 3888 bytes docs/handbook/show_hopper.png | Bin 56122 -> 0 bytes docs/handbook/show_hopper.webp | Bin 0 -> 6548 bytes docs/handbook/tutorial.rst | 28 +++++++++++----------- 27 files changed, 14 insertions(+), 14 deletions(-) delete mode 100644 docs/handbook/cropped_hopper.jpg create mode 100644 docs/handbook/cropped_hopper.webp delete mode 100644 docs/handbook/enhanced_hopper.jpg create mode 100644 docs/handbook/enhanced_hopper.webp delete mode 100644 docs/handbook/flip_left_right_hopper.jpg create mode 100644 docs/handbook/flip_left_right_hopper.webp delete mode 100644 docs/handbook/flip_top_bottom_hopper.jpg create mode 100644 docs/handbook/flip_top_bottom_hopper.webp delete mode 100644 docs/handbook/masked_hopper.jpg create mode 100644 docs/handbook/masked_hopper.webp delete mode 100644 docs/handbook/merged_hopper.png create mode 100644 docs/handbook/merged_hopper.webp delete mode 100644 docs/handbook/pasted_hopper.jpg create mode 100644 docs/handbook/pasted_hopper.webp delete mode 100644 docs/handbook/rebanded_hopper.jpg create mode 100644 docs/handbook/rebanded_hopper.webp delete mode 100644 docs/handbook/rolled_hopper.jpg create mode 100644 docs/handbook/rolled_hopper.webp delete mode 100644 docs/handbook/rotated_hopper_180.jpg create mode 100644 docs/handbook/rotated_hopper_180.webp delete mode 100644 docs/handbook/rotated_hopper_270.jpg create mode 100644 docs/handbook/rotated_hopper_270.webp delete mode 100644 docs/handbook/rotated_hopper_90.jpg create mode 100644 docs/handbook/rotated_hopper_90.webp delete mode 100644 docs/handbook/show_hopper.png create mode 100644 docs/handbook/show_hopper.webp diff --git a/docs/handbook/cropped_hopper.jpg b/docs/handbook/cropped_hopper.jpg deleted file mode 100644 index 912d0a29cb0d154e7b7062123863630e8a1ef3a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2017 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 C4MrUR literal 0 HcmV?d00001 diff --git a/docs/handbook/enhanced_hopper.jpg b/docs/handbook/enhanced_hopper.jpg deleted file mode 100644 index a87903eba41acc0c4b2873f45b0aec0290c0adc5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5721 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 diff --git a/docs/handbook/enhanced_hopper.webp b/docs/handbook/enhanced_hopper.webp new file mode 100644 index 0000000000000000000000000000000000000000..a582ac0c216f10b4371d363683f2605be432b7fb GIT binary patch literal 4684 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 OQUa_$C79z|Jb(aTVNbaL literal 0 HcmV?d00001 diff --git a/docs/handbook/flip_left_right_hopper.jpg b/docs/handbook/flip_left_right_hopper.jpg deleted file mode 100644 index e1ef519e5cd8a34c65d681071b46318e3dd7f6a4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4637 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 diff --git a/docs/handbook/flip_left_right_hopper.webp b/docs/handbook/flip_left_right_hopper.webp new file mode 100644 index 0000000000000000000000000000000000000000..59452268dfe48ec4c1646c3202b3b43a0fe02a35 GIT binary patch literal 3976 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 D@-E{; diff --git a/docs/handbook/flip_top_bottom_hopper.webp b/docs/handbook/flip_top_bottom_hopper.webp new file mode 100644 index 0000000000000000000000000000000000000000..28af4bf36591c0fc44ad2bc03bdabddc920ee3b6 GIT binary patch literal 3966 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 YSj>lQ@V5l_3oj|F$BFYaXz>q#0Q=j;w*UYD literal 0 HcmV?d00001 diff --git a/docs/handbook/masked_hopper.jpg b/docs/handbook/masked_hopper.jpg deleted file mode 100644 index f70a5ec4769e9c861fcfd4e1e90f812cd4369782..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4904 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 diff --git a/docs/handbook/masked_hopper.webp b/docs/handbook/masked_hopper.webp new file mode 100644 index 0000000000000000000000000000000000000000..ef892cb94f7985b7ef38584d78d479e2c46c0e98 GIT binary patch literal 3954 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 Me`MlElL5m302v_I#{d8T literal 0 HcmV?d00001 diff --git a/docs/handbook/merged_hopper.png b/docs/handbook/merged_hopper.png deleted file mode 100644 index 662816cbdad42a04dd522acbbd195c2d789fb87b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35390 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 E0m`+u%m4rY literal 0 HcmV?d00001 diff --git a/docs/handbook/pasted_hopper.jpg b/docs/handbook/pasted_hopper.jpg deleted file mode 100644 index 36e3492a40f92e1c37d91722e1a993793fffbdec..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4645 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 diff --git a/docs/handbook/rebanded_hopper.webp b/docs/handbook/rebanded_hopper.webp new file mode 100644 index 0000000000000000000000000000000000000000..7a9069c9f3d18b5ec4b705144cf9460cdb870093 GIT binary patch literal 3866 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 AApigX diff --git a/docs/handbook/rolled_hopper.webp b/docs/handbook/rolled_hopper.webp new file mode 100644 index 0000000000000000000000000000000000000000..7fe8024636331b408b5ba490068f5ef0e6d47b7c GIT binary patch literal 3912 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 E0O35#SpWb4 literal 0 HcmV?d00001 diff --git a/docs/handbook/rotated_hopper_270.jpg b/docs/handbook/rotated_hopper_270.jpg deleted file mode 100644 index e4d22be70ce2bc4887a41799e1343fa0cc513cd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4895 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 oaT?m_M8uk0LZ?++fwj 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 literal 0 HcmV?d00001 diff --git a/docs/handbook/rotated_hopper_90.jpg b/docs/handbook/rotated_hopper_90.jpg deleted file mode 100644 index 763785c7a1d5b2d45df93884dc63f9a8217ba686..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4903 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 diff --git a/docs/handbook/rotated_hopper_90.webp b/docs/handbook/rotated_hopper_90.webp new file mode 100644 index 0000000000000000000000000000000000000000..9a5f70b2030835a5feeaf54665ddae2ad49cbb53 GIT binary patch literal 3888 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 literal 0 HcmV?d00001 diff --git a/docs/handbook/show_hopper.png b/docs/handbook/show_hopper.png deleted file mode 100644 index 1f2e6f243858d8a9944c4db0b1c0758111cc45ce..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 56122 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

    ViV@m zMy|x4r;}FhOSn>e$X6W};h$iqZ&67r=TNKRV{_Y%-XiLv!>t{ z-LQDV=<&LB;<)r+*N;7;_+N$c%EfeYkzEX#+T&=Go4bWhD)XuD9<6dSYjh|c$XjA7 zVeP7Ef;2Zv7FQ;tHH%R7NaVayVi$NPfvbUG0}pg}<2Y$WxMI>TO^hXI!10Ru=o(7B zG(lXviP}v1e#1!7gQ4L?>Nuwcu;UPkzn++`0G6ksq?1QU{AXyRcbZ2tA^Wiq5(Q?|t*_@(o{nV-`8hV4HE#LM!WH%WH4cFrCor@8V z%k=CQGbO9By}jow`zs5yY}j^?6182&e35fE6Q6L6uk${Q7Jsx4v=EV+D?8F04ohGc=*&h>g8ku7;Sp@OnJOAk2cIY{%4dS zeupl9>9K8})JuJ)7n6I%!PF4ui=s1u^HZGVjX{fJ>H{nOeH`&8%z8!d%61;%4oq^S zQ#bj7lZyw{oaDIUXdKk{<%TZd_s&R*nxd)lDELw5Z2^rVczD9St$drJ7V3BS58&tqHTS`s%yE z*fTJHbNDsSSy;mrbgs6@$|7n`G01xzo*kR;ORQj}1eoV<@-a4Yq)E*{}5_GzB0HM=# z%^RUO`@pMs&61?AY4adANTbxD?=>dNGn$gJM9+}>SX^l|WQJ9!>CYY0Sk4>l&e2}`lau-u^4Pm66>3rR zE7nP$_!xepi^8A@Cly$COXVYHLd=rz@|v=~hVs814~#S3O_o7;&Ype9uie2b zVq=};q3~>Ndw+%q?vVBadrP;5$EUFT={!FG#Sy2W5RxE#zAs=Yf~S?I`vNq!<-uY3 z8s_wNV3B;Fja}vYHFFH{V={9GzF76==CZees%7`yi+ zI-0AXtUhl%rejodQfHHERrIx{z(fH62LWxAf3*gIh)q4*s+ZtgQuSWF=s5+FA80qd&kCY(jslxOKRq*5V*J2n9!;v?pJ3mKva{*;{QEFlA5EPqR#m-Pg9J0i zXhzDl?n!i~l1WMt!awVwqqxyqVv*%V);KZ5Mmr$SdfgHJsq5^Kl&abZYB7+C8!{Gp zr|+!Uo>Ce5iA&angQlNK!dQD~q?bTwyRYk+PtZ$Ql9-K!OIzjeN@e9;zZt_?;fA}{ z?NL=F$_a*r<aJfc_|Pyoc~#4EpSe9+goZTW)aXBN#60Z5Vo<_5wJwjJPMRub$LFC4yn~~SM(d% z)St4Ja*>O4lbU!*nT_7Rv9v4oGerXoEmu=Lp%+EPYpugK|Hr{5ZhI-Htc? zv=B7AvZ^_8J|<3Mj<5;1ee$J`3kxK?qjA3&fbC9>A zgOj_kw-}wdrLzT07>RGy5IWFr7r4C`-7QJR*$oEb=iubvq!Y&hfk2{emR7=Avhx3; zBPB698#vrW7y|L~^5XE~;c#}dhHwc92|+lyA>7>TNDp>*A1AoEH@lNN{huKJjw6eF zPj|C*f!jJefo|iPTR3~b#pvj66aDY_Qzy*Z_HQO9caGmhb3mOPA-5I?7Y8TgKhfZ} zR{w=|YxzU_y{|v%L~nx$YuI|j91LY`9br!H$kN2Qx%owZr}=-DzlHu`>bk?+WSkw5 zM7X#%)D4CVVB-w`7x|yY|4aOx(%^4O0ZyTRru>KHAI#e=2&=o>> im.show() -.. image:: show_hopper.png +.. image:: show_hopper.webp :align: center .. note:: @@ -159,7 +159,7 @@ pixels, so the region in the above example is exactly 64x64 pixels. The region could now be processed in a certain manner and pasted back. -.. image:: cropped_hopper.jpg +.. image:: cropped_hopper.webp :align: center Processing a subrectangle, and pasting it back @@ -176,7 +176,7 @@ modes of the original image and the region do not need to match. If they don’t the region is automatically converted before being pasted (see the section on :ref:`color-transforms` below for details). -.. image:: pasted_hopper.jpg +.. image:: pasted_hopper.webp :align: center Here’s an additional example: @@ -201,7 +201,7 @@ Rolling an image return im -.. image:: rolled_hopper.jpg +.. image:: rolled_hopper.webp :align: center Or if you would like to merge two images into a wider image: @@ -221,7 +221,7 @@ Merging images return im -.. image:: merged_hopper.png +.. image:: merged_hopper.webp :align: center For more advanced tricks, the paste method can also take a transparency mask as @@ -250,7 +250,7 @@ Note that for a single-band image, :py:meth:`~PIL.Image.Image.split` returns the image itself. To work with individual color bands, you may want to convert the image to “RGB” first. -.. image:: rebanded_hopper.jpg +.. image:: rebanded_hopper.webp :align: center Geometrical transforms @@ -269,7 +269,7 @@ Simple geometry transforms out = im.resize((128, 128)) out = im.rotate(45) # degrees counter-clockwise -.. image:: rotated_hopper_90.jpg +.. image:: rotated_hopper_90.webp :align: center To rotate the image in 90 degree steps, you can either use the @@ -284,35 +284,35 @@ Transposing an image out = im.transpose(Image.Transpose.FLIP_LEFT_RIGHT) -.. image:: flip_left_right_hopper.jpg +.. image:: flip_left_right_hopper.webp :align: center :: out = im.transpose(Image.Transpose.FLIP_TOP_BOTTOM) -.. image:: flip_top_bottom_hopper.jpg +.. image:: flip_top_bottom_hopper.webp :align: center :: out = im.transpose(Image.Transpose.ROTATE_90) -.. image:: rotated_hopper_90.jpg +.. image:: rotated_hopper_90.webp :align: center :: out = im.transpose(Image.Transpose.ROTATE_180) -.. image:: rotated_hopper_180.jpg +.. image:: rotated_hopper_180.webp :align: center :: out = im.transpose(Image.Transpose.ROTATE_270) -.. image:: rotated_hopper_270.jpg +.. image:: rotated_hopper_270.webp :align: center ``transpose(ROTATE)`` operations can also be performed identically with @@ -396,7 +396,7 @@ Applying filters from PIL import ImageFilter out = im.filter(ImageFilter.DETAIL) -.. image:: enhanced_hopper.jpg +.. image:: enhanced_hopper.webp :align: center Point Operations @@ -448,7 +448,7 @@ Note the syntax used to create the mask:: imout = im.point(lambda i: expression and 255) -.. image:: masked_hopper.jpg +.. image:: masked_hopper.webp :align: center Python only evaluates the portion of a logical expression as is necessary to From 4db814042615a45aec0847ee55ef35f0f32521bb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 19 Jul 2024 18:49:04 +1000 Subject: [PATCH 42/61] Removed unused image --- docs/handbook/rotated_hopper.jpg | Bin 4895 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 docs/handbook/rotated_hopper.jpg diff --git a/docs/handbook/rotated_hopper.jpg b/docs/handbook/rotated_hopper.jpg deleted file mode 100644 index e4d22be70ce2bc4887a41799e1343fa0cc513cd9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4895 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 oaT?m_M8uk0LZ?+ Date: Fri, 19 Jul 2024 09:00:26 -0400 Subject: [PATCH 43/61] Update docs/handbook/tutorial.rst Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- docs/handbook/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index 402f57f69..ffa8fa019 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -528,7 +528,7 @@ You can create animated GIFs with Pillow, e.g. "rotated_hopper_90.jpg", ] - # Open images and append them to a list + # Open images and create a list images = [Image.open(filename) for filename in image_filenames] # Save the images as an animated GIF From 204ec11f64e2ec6a322a39dc525bd97e31113d12 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Fri, 19 Jul 2024 08:28:59 -0500 Subject: [PATCH 44/61] use local variable instead of casting every line --- src/encode.c | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/src/encode.c b/src/encode.c index f711865d5..529982dad 100644 --- a/src/encode.c +++ b/src/encode.c @@ -1186,29 +1186,27 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { encoder->encode = ImagingJpegEncode; - strncpy(((JPEGENCODERSTATE *)encoder->state.context)->rawmode, rawmode, 8); - - ((JPEGENCODERSTATE *)encoder->state.context)->keep_rgb = keep_rgb; - ((JPEGENCODERSTATE *)encoder->state.context)->quality = quality; - ((JPEGENCODERSTATE *)encoder->state.context)->qtables = qarrays; - ((JPEGENCODERSTATE *)encoder->state.context)->qtablesLen = qtablesLen; - ((JPEGENCODERSTATE *)encoder->state.context)->subsampling = subsampling; - ((JPEGENCODERSTATE *)encoder->state.context)->progressive = progressive; - ((JPEGENCODERSTATE *)encoder->state.context)->smooth = smooth; - ((JPEGENCODERSTATE *)encoder->state.context)->optimize = optimize; - ((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype; - ((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi; - ((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi; - ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_blocks = - restart_marker_blocks; - ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_rows = - restart_marker_rows; - ((JPEGENCODERSTATE *)encoder->state.context)->comment = comment; - ((JPEGENCODERSTATE *)encoder->state.context)->comment_size = comment_size; - ((JPEGENCODERSTATE *)encoder->state.context)->extra = extra; - ((JPEGENCODERSTATE *)encoder->state.context)->extra_size = extra_size; - ((JPEGENCODERSTATE *)encoder->state.context)->rawExif = rawExif; - ((JPEGENCODERSTATE *)encoder->state.context)->rawExifLen = rawExifLen; + JPEGENCODERSTATE *jpeg_encoder_state = (JPEGENCODERSTATE *)encoder->state.context; + strncpy(jpeg_encoder_state->rawmode, rawmode, 8); + jpeg_encoder_state->keep_rgb = keep_rgb; + jpeg_encoder_state->quality = quality; + jpeg_encoder_state->qtables = qarrays; + jpeg_encoder_state->qtablesLen = qtablesLen; + jpeg_encoder_state->subsampling = subsampling; + jpeg_encoder_state->progressive = progressive; + jpeg_encoder_state->smooth = smooth; + jpeg_encoder_state->optimize = optimize; + jpeg_encoder_state->streamtype = streamtype; + jpeg_encoder_state->xdpi = xdpi; + jpeg_encoder_state->ydpi = ydpi; + jpeg_encoder_state->restart_marker_blocks = restart_marker_blocks; + jpeg_encoder_state->restart_marker_rows = restart_marker_rows; + jpeg_encoder_state->comment = comment; + jpeg_encoder_state->comment_size = comment_size; + jpeg_encoder_state->extra = extra; + jpeg_encoder_state->extra_size = extra_size; + jpeg_encoder_state->rawExif = rawExif; + jpeg_encoder_state->rawExifLen = rawExifLen; return (PyObject *)encoder; } From f62446032166827cd0e20c711bca07650019d43d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Jul 2024 13:14:18 +1000 Subject: [PATCH 45/61] Added type hints --- Tests/test_file_libtiff.py | 7 +++--- docs/reference/ImageFile.rst | 5 ++++ src/PIL/ImageWin.py | 38 ++++++++++++++-------------- src/PIL/PsdImagePlugin.py | 48 +++++++++++++++++++++--------------- src/PIL/TiffImagePlugin.py | 28 ++++++++++----------- src/PIL/TiffTags.py | 27 ++++++++++++-------- 6 files changed, 86 insertions(+), 67 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index d5dbeeb6f..58ac705c9 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -240,10 +240,11 @@ class TestFileLibTiff(LibTiffTestCase): new_ifd = TiffImagePlugin.ImageFileDirectory_v2() for tag, info in core_items.items(): - if info.length == 1: - new_ifd[tag] = values[info.type] - if info.length == 0: + assert info.type is not None + if not info.length: new_ifd[tag] = tuple(values[info.type] for _ in range(3)) + elif info.length == 1: + new_ifd[tag] = values[info.type] else: new_ifd[tag] = tuple(values[info.type] for _ in range(info.length)) diff --git a/docs/reference/ImageFile.rst b/docs/reference/ImageFile.rst index e59c7311a..fdfeb60f9 100644 --- a/docs/reference/ImageFile.rst +++ b/docs/reference/ImageFile.rst @@ -37,6 +37,11 @@ Example: Parse an image Classes ------- +.. autoclass:: PIL.ImageFile._Tile() + :member-order: bysource + :members: + :show-inheritance: + .. autoclass:: PIL.ImageFile.Parser() :members: diff --git a/src/PIL/ImageWin.py b/src/PIL/ImageWin.py index 4f9956087..6fc7cfaf5 100644 --- a/src/PIL/ImageWin.py +++ b/src/PIL/ImageWin.py @@ -90,7 +90,7 @@ class Dib: assert not isinstance(image, str) self.paste(image) - def expose(self, handle): + def expose(self, handle: int | HDC | HWND) -> None: """ Copy the bitmap contents to a device context. @@ -101,19 +101,18 @@ class Dib: if isinstance(handle, HWND): dc = self.image.getdc(handle) try: - result = self.image.expose(dc) + self.image.expose(dc) finally: self.image.releasedc(handle, dc) else: - result = self.image.expose(handle) - return result + self.image.expose(handle) def draw( self, - handle, + handle: int | HDC | HWND, dst: tuple[int, int, int, int], src: tuple[int, int, int, int] | None = None, - ): + ) -> None: """ Same as expose, but allows you to specify where to draw the image, and what part of it to draw. @@ -128,14 +127,13 @@ class Dib: if isinstance(handle, HWND): dc = self.image.getdc(handle) try: - result = self.image.draw(dc, dst, src) + self.image.draw(dc, dst, src) finally: self.image.releasedc(handle, dc) else: - result = self.image.draw(handle, dst, src) - return result + self.image.draw(handle, dst, src) - def query_palette(self, handle): + def query_palette(self, handle: int | HDC | HWND) -> int: """ Installs the palette associated with the image in the given device context. @@ -147,8 +145,8 @@ class Dib: :param handle: Device context (HDC), cast to a Python integer, or an HDC or HWND instance. - :return: A true value if one or more entries were changed (this - indicates that the image should be redrawn). + :return: The number of entries that were changed (if one or more entries, + this indicates that the image should be redrawn). """ if isinstance(handle, HWND): handle = self.image.getdc(handle) @@ -210,22 +208,22 @@ class Window: title, self.__dispatcher, width or 0, height or 0 ) - def __dispatcher(self, action: str, *args): - return getattr(self, f"ui_handle_{action}")(*args) + def __dispatcher(self, action: str, *args: int) -> None: + getattr(self, f"ui_handle_{action}")(*args) - def ui_handle_clear(self, dc, x0, y0, x1, y1) -> None: + def ui_handle_clear(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None: pass - def ui_handle_damage(self, x0, y0, x1, y1) -> None: + def ui_handle_damage(self, x0: int, y0: int, x1: int, y1: int) -> None: pass def ui_handle_destroy(self) -> None: pass - def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None: + def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None: pass - def ui_handle_resize(self, width, height) -> None: + def ui_handle_resize(self, width: int, height: int) -> None: pass def mainloop(self) -> None: @@ -235,12 +233,12 @@ class Window: class ImageWindow(Window): """Create an image window which displays the given image.""" - def __init__(self, image, title: str = "PIL") -> None: + def __init__(self, image: Image.Image | Dib, title: str = "PIL") -> None: if not isinstance(image, Dib): image = Dib(image) self.image = image width, height = image.size super().__init__(title, width=width, height=height) - def ui_handle_repair(self, dc, x0, y0, x1, y1) -> None: + def ui_handle_repair(self, dc: int, x0: int, y0: int, x1: int, y1: int) -> None: self.image.draw(dc, (x0, y0, x1, y1)) diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index 31dfd4d12..cc99cd6d8 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -19,6 +19,7 @@ from __future__ import annotations import io from functools import cached_property +from typing import IO from . import Image, ImageFile, ImagePalette from ._binary import i8 @@ -142,7 +143,9 @@ class PsdImageFile(ImageFile.ImageFile): self._min_frame = 1 @cached_property - def layers(self): + def layers( + self, + ) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]: layers = [] if self._layers_position is not None: self._fp.seek(self._layers_position) @@ -181,7 +184,9 @@ class PsdImageFile(ImageFile.ImageFile): return self.frame -def _layerinfo(fp, ct_bytes): +def _layerinfo( + fp: IO[bytes], ct_bytes: int +) -> list[tuple[str, str, tuple[int, int, int, int], list[ImageFile._Tile]]]: # read layerinfo block layers = [] @@ -203,7 +208,7 @@ def _layerinfo(fp, ct_bytes): x1 = si32(read(4)) # image info - mode = [] + bands = [] ct_types = i16(read(2)) if ct_types > 4: fp.seek(ct_types * 6 + 12, io.SEEK_CUR) @@ -215,23 +220,23 @@ def _layerinfo(fp, ct_bytes): type = i16(read(2)) if type == 65535: - m = "A" + b = "A" else: - m = "RGBA"[type] + b = "RGBA"[type] - mode.append(m) + bands.append(b) read(4) # size # figure out the image mode - mode.sort() - if mode == ["R"]: + bands.sort() + if bands == ["R"]: mode = "L" - elif mode == ["B", "G", "R"]: + elif bands == ["B", "G", "R"]: mode = "RGB" - elif mode == ["A", "B", "G", "R"]: + elif bands == ["A", "B", "G", "R"]: mode = "RGBA" else: - mode = None # unknown + mode = "" # unknown # skip over blend flags and extra information read(12) # filler @@ -258,19 +263,22 @@ def _layerinfo(fp, ct_bytes): layers.append((name, mode, (x0, y0, x1, y1))) # get tiles + layerinfo = [] for i, (name, mode, bbox) in enumerate(layers): tile = [] for m in mode: t = _maketile(fp, m, bbox, 1) if t: tile.extend(t) - layers[i] = name, mode, bbox, tile + layerinfo.append((name, mode, bbox, tile)) - return layers + return layerinfo -def _maketile(file, mode, bbox, channels): - tile = None +def _maketile( + file: IO[bytes], mode: str, bbox: tuple[int, int, int, int], channels: int +) -> list[ImageFile._Tile] | None: + tiles = None read = file.read compression = i16(read(2)) @@ -283,26 +291,26 @@ def _maketile(file, mode, bbox, channels): if compression == 0: # # raw compression - tile = [] + tiles = [] for channel in range(channels): layer = mode[channel] if mode == "CMYK": layer += ";I" - tile.append(("raw", bbox, offset, layer)) + tiles.append(ImageFile._Tile("raw", bbox, offset, layer)) offset = offset + xsize * ysize elif compression == 1: # # packbits compression i = 0 - tile = [] + tiles = [] bytecount = read(channels * ysize * 2) offset = file.tell() for channel in range(channels): layer = mode[channel] if mode == "CMYK": layer += ";I" - tile.append(("packbits", bbox, offset, layer)) + tiles.append(ImageFile._Tile("packbits", bbox, offset, layer)) for y in range(ysize): offset = offset + i16(bytecount, i) i += 2 @@ -312,7 +320,7 @@ def _maketile(file, mode, bbox, channels): if offset & 1: read(1) # padding - return tile + return tiles # -------------------------------------------------------------------- diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 253f64852..1dab0d50b 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -445,7 +445,7 @@ class IFDRational(Rational): __int__ = _delegate("__int__") -def _register_loader(idx, size): +def _register_loader(idx: int, size: int): def decorator(func): from .TiffTags import TYPES @@ -457,7 +457,7 @@ def _register_loader(idx, size): return decorator -def _register_writer(idx): +def _register_writer(idx: int): def decorator(func): _write_dispatch[idx] = func # noqa: F821 return func @@ -465,7 +465,7 @@ def _register_writer(idx): return decorator -def _register_basic(idx_fmt_name): +def _register_basic(idx_fmt_name: tuple[int, str, str]) -> None: from .TiffTags import TYPES idx, fmt, name = idx_fmt_name @@ -640,7 +640,7 @@ class ImageFileDirectory_v2(_IFDv2Base): def __contains__(self, tag: object) -> bool: return tag in self._tags_v2 or tag in self._tagdata - def __setitem__(self, tag, value) -> None: + def __setitem__(self, tag: int, value) -> None: self._setitem(tag, value, self.legacy_api) def _setitem(self, tag, value, legacy_api) -> None: @@ -731,10 +731,10 @@ class ImageFileDirectory_v2(_IFDv2Base): def __iter__(self): return iter(set(self._tagdata) | set(self._tags_v2)) - def _unpack(self, fmt, data): + def _unpack(self, fmt: str, data): return struct.unpack(self._endian + fmt, data) - def _pack(self, fmt, *values): + def _pack(self, fmt: str, *values): return struct.pack(self._endian + fmt, *values) list( @@ -755,7 +755,7 @@ class ImageFileDirectory_v2(_IFDv2Base): ) @_register_loader(1, 1) # Basic type, except for the legacy API. - def load_byte(self, data, legacy_api=True): + def load_byte(self, data, legacy_api: bool = True): return data @_register_writer(1) # Basic type, except for the legacy API. @@ -767,7 +767,7 @@ class ImageFileDirectory_v2(_IFDv2Base): return data @_register_loader(2, 1) - def load_string(self, data, legacy_api=True): + def load_string(self, data: bytes, legacy_api: bool = True) -> str: if data.endswith(b"\0"): data = data[:-1] return data.decode("latin-1", "replace") @@ -797,7 +797,7 @@ class ImageFileDirectory_v2(_IFDv2Base): ) @_register_loader(7, 1) - def load_undefined(self, data, legacy_api=True): + def load_undefined(self, data, legacy_api: bool = True): return data @_register_writer(7) @@ -809,7 +809,7 @@ class ImageFileDirectory_v2(_IFDv2Base): return value @_register_loader(10, 8) - def load_signed_rational(self, data, legacy_api=True): + def load_signed_rational(self, data, legacy_api: bool = True): vals = self._unpack(f"{len(data) // 4}l", data) def combine(a, b): @@ -1030,7 +1030,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): """Dictionary of tag types""" @classmethod - def from_v2(cls, original) -> ImageFileDirectory_v1: + def from_v2(cls, original: ImageFileDirectory_v2) -> ImageFileDirectory_v1: """Returns an :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v1` instance with the same data as is contained in the original @@ -1073,7 +1073,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): def __iter__(self): return iter(set(self._tagdata) | set(self._tags_v1)) - def __setitem__(self, tag, value) -> None: + def __setitem__(self, tag: int, value) -> None: for legacy_api in (False, True): self._setitem(tag, value, legacy_api) @@ -1212,7 +1212,7 @@ class TiffImageFile(ImageFile.ImageFile): """Return the current frame number""" return self.__frame - def get_photoshop_blocks(self): + def get_photoshop_blocks(self) -> dict[int, dict[str, bytes]]: """ Returns a dictionary of Photoshop "Image Resource Blocks". The keys are the image resource ID. For more information, see @@ -1259,7 +1259,7 @@ class TiffImageFile(ImageFile.ImageFile): if ExifTags.Base.Orientation in self.tag_v2: del self.tag_v2[ExifTags.Base.Orientation] - def _load_libtiff(self): + def _load_libtiff(self) -> Image.core.PixelAccess | None: """Overload method triggered when we detect a compressed tiff Calls out to libtiff""" diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index e318c8739..86adaa458 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -32,17 +32,24 @@ class _TagInfo(NamedTuple): class TagInfo(_TagInfo): __slots__: list[str] = [] - def __new__(cls, value=None, name="unknown", type=None, length=None, enum=None): + def __new__( + cls, + value: int | None = None, + name: str = "unknown", + type: int | None = None, + length: int | None = None, + enum: dict[str, int] | None = None, + ) -> TagInfo: return super().__new__(cls, value, name, type, length, enum or {}) - def cvt_enum(self, value): + def cvt_enum(self, value: str) -> int | str: # Using get will call hash(value), which can be expensive # for some types (e.g. Fraction). Since self.enum is rarely # used, it's usually better to test it first. return self.enum.get(value, value) if self.enum else value -def lookup(tag, group=None): +def lookup(tag: int, group: int | None = None) -> TagInfo: """ :param tag: Integer tag number :param group: Which :py:data:`~PIL.TiffTags.TAGS_V2_GROUPS` to look in @@ -89,7 +96,7 @@ DOUBLE = 12 IFD = 13 LONG8 = 16 -_tags_v2 = { +_tags_v2: dict[int, tuple[str, int, int] | tuple[str, int, int, dict[str, int]]] = { 254: ("NewSubfileType", LONG, 1), 255: ("SubfileType", SHORT, 1), 256: ("ImageWidth", LONG, 1), @@ -233,7 +240,7 @@ _tags_v2 = { 50838: ("ImageJMetaDataByteCounts", LONG, 0), # Can be more than one 50839: ("ImageJMetaData", UNDEFINED, 1), # see Issue #2006 } -TAGS_V2_GROUPS = { +_tags_v2_groups = { # ExifIFD 34665: { 36864: ("ExifVersion", UNDEFINED, 1), @@ -281,7 +288,7 @@ TAGS_V2_GROUPS = { # Legacy Tags structure # these tags aren't included above, but were in the previous versions -TAGS = { +TAGS: dict[int | tuple[int, int], str] = { 347: "JPEGTables", 700: "XMP", # Additional Exif Info @@ -426,9 +433,10 @@ TAGS = { } TAGS_V2: dict[int, TagInfo] = {} +TAGS_V2_GROUPS: dict[int, dict[int, TagInfo]] = {} -def _populate(): +def _populate() -> None: for k, v in _tags_v2.items(): # Populate legacy structure. TAGS[k] = v[0] @@ -438,9 +446,8 @@ def _populate(): TAGS_V2[k] = TagInfo(k, *v) - for tags in TAGS_V2_GROUPS.values(): - for k, v in tags.items(): - tags[k] = TagInfo(k, *v) + for group, tags in _tags_v2_groups.items(): + TAGS_V2_GROUPS[group] = {k: TagInfo(k, *v) for k, v in tags.items()} _populate() From b976a8496df8ad741595decac321f5469a100d77 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 20 Jul 2024 08:07:12 +0000 Subject: [PATCH 46/61] Update dependency mypy to v1.11.0 --- .ci/requirements-mypy.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-mypy.txt b/.ci/requirements-mypy.txt index 6dd432488..776bb0dbb 100644 --- a/.ci/requirements-mypy.txt +++ b/.ci/requirements-mypy.txt @@ -1 +1 @@ -mypy==1.10.1 +mypy==1.11.0 From 882a196a8f04f446c9ea6eaaac900fdecf220429 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Jul 2024 18:59:27 +1000 Subject: [PATCH 47/61] Removed unused ignores --- Tests/test_file_png.py | 2 +- Tests/test_file_ppm.py | 2 +- Tests/test_psdraw.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index e2913e944..4958b2222 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -776,7 +776,7 @@ class TestFilePng: mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO() - sys.stdout = mystdout # type: ignore[assignment] + sys.stdout = mystdout with Image.open(TEST_PNG_FILE) as im: im.save(sys.stdout, "PNG") diff --git a/Tests/test_file_ppm.py b/Tests/test_file_ppm.py index d6451ec18..0a61830a4 100644 --- a/Tests/test_file_ppm.py +++ b/Tests/test_file_ppm.py @@ -373,7 +373,7 @@ def test_save_stdout(buffer: bool) -> None: mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO() - sys.stdout = mystdout # type: ignore[assignment] + sys.stdout = mystdout with Image.open(TEST_FILE) as im: im.save(sys.stdout, "PPM") diff --git a/Tests/test_psdraw.py b/Tests/test_psdraw.py index 130ffa863..c3afa9089 100644 --- a/Tests/test_psdraw.py +++ b/Tests/test_psdraw.py @@ -59,7 +59,7 @@ def test_stdout(buffer: bool) -> None: mystdout: MyStdOut | BytesIO = MyStdOut() if buffer else BytesIO() - sys.stdout = mystdout # type: ignore[assignment] + sys.stdout = mystdout ps = PSDraw.PSDraw() _create_document(ps) From 38458a204cbbe1a83b458dd8d6be8762bc73ca99 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 20 Jul 2024 19:07:42 +1000 Subject: [PATCH 48/61] Corrected type hints --- Tests/test_file_mpo.py | 2 ++ src/PIL/JpegImagePlugin.py | 4 ++-- src/PIL/PngImagePlugin.py | 2 +- src/PIL/WebPImagePlugin.py | 2 +- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index 5402fcb44..e0f42a266 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -85,7 +85,9 @@ def test_exif(test_file: str) -> None: im_reloaded = roundtrip(im_original, save_all=True, exif=im_original.getexif()) for im in (im_original, im_reloaded): + assert isinstance(im, MpoImagePlugin.MpoImageFile) info = im._getexif() + assert info is not None assert info[272] == "Nintendo 3DS" assert info[296] == 2 assert info[34665] == 188 diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index af24faa5d..d83d60b7b 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -468,7 +468,7 @@ class JpegImageFile(ImageFile.ImageFile): self.tile = [] - def _getexif(self) -> dict[str, Any] | None: + def _getexif(self) -> dict[int, Any] | None: return _getexif(self) def _read_dpi_from_exif(self) -> None: @@ -504,7 +504,7 @@ class JpegImageFile(ImageFile.ImageFile): return _getmp(self) -def _getexif(self: JpegImageFile) -> dict[str, Any] | None: +def _getexif(self: JpegImageFile) -> dict[int, Any] | None: if "exif" not in self.info: return None return self.getexif()._get_merged_dict() diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 247f908ed..58db7777c 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1054,7 +1054,7 @@ class PngImageFile(ImageFile.ImageFile): self._prev_im.paste(updated, self.dispose_extent, mask) self.im = self._prev_im - def _getexif(self) -> dict[str, Any] | None: + def _getexif(self) -> dict[int, Any] | None: if "exif" not in self.info: self.load() if "exif" not in self.info and "Raw profile type exif" not in self.info: diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 011de9c6a..cec796340 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -96,7 +96,7 @@ class WebPImageFile(ImageFile.ImageFile): # Initialize seek state self._reset(reset=False) - def _getexif(self) -> dict[str, Any] | None: + def _getexif(self) -> dict[int, Any] | None: if "exif" not in self.info: return None return self.getexif()._get_merged_dict() From ddc02bb78c327a1025a772619dd3114752cbb68b Mon Sep 17 00:00:00 2001 From: "Jeffrey A. Clark (Alex)" Date: Sat, 20 Jul 2024 14:41:47 -0400 Subject: [PATCH 49/61] Update and add image to PostScript printing example --- docs/handbook/hopper_ps.png | Bin 0 -> 76966 bytes docs/handbook/tutorial.rst | 65 ++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 14 deletions(-) create mode 100644 docs/handbook/hopper_ps.png diff --git a/docs/handbook/hopper_ps.png b/docs/handbook/hopper_ps.png new file mode 100644 index 0000000000000000000000000000000000000000..fe44e79843f1f46eb2db109d96248670bb645eed GIT binary patch literal 76966 zcmdqJWn5HS^fyjQg9ri&k}BN-Lk^7y5+XG;NJ$Lc-5n|=4H8OsmvooJ(A`}_^FMm; z{axZB#ASZ=``4kfY0Rcz)wfH*(1f)&`1ms@y z2f&juvFD2j2#C6-Vqyx?Vq%mEHc%r|b3+6Kx@h|-@M|P%!pB|!v=daEjvgU|m>&jan@^lQ^b~OgsJzfcN&M&8%$UCac=D#D>Wmyt^l2y& zV|ZJCyXKJ1-ixyVRxf~Cb-M;nYVlF#l%A&}{0zgTY!4d43B<5`C}sXM#7VNN>Ql^in!k1Q ziqx5g{Lejd@9&BtN@CK|z_*ftjiI5XtqIglD>*I`=xW^by_%icTUkB>s0EAO2dKUw zi<5=b?o|MU0PI1Qal z|D|MU`}eef39|k!VP$7wWBs4Lfu;h#pYkb~IvJX)ii>;Y&9@$#?>{Hgzcl>AHa zUQ4xqTk^2~r{%qp|J_pA*3d=_Y5|b66Z%&&f0Mmm_;*7A*55O~$BDn#{O2jaXCX`h z*8h>25av^X)#nHZ!U)phBJZ6LcN#F9G*_L^?)tK|Dw+1L8uxc}(Q&ErG1M{ok-nLJ zdYNweHE(w;Zxej;DO+b`TShw3Z8xsK6v@vozz+fADgu#|loa`adA*)S<*M>{;joF( zE&PT&c{o~tCOrH@Wd2o!l(Fkz-Sq$y+!43B0`hztzCk z+q3XC?V@GxJY#r}d_x=kYI1aeid;zOG`Tg|*IZlf_N~Sj{+l;$+4*bNXE|#_-Vd4VDh}tle6B56H}ZSAJ?}yR3T;0=rQVxH#6vnsMy+K zU}RvZ92t4Hg2$WQx0R4oz4tM&inI4NW&Prb@0IVicII`&WsQ33);IX>WBGFzQU~JHrKB z?lzAn!1LgOf`ak6IZLTX@(N!xJXnfgT|wbqPz+%lL|R&1^7+;N>Z>0(;iUXE$HrV1 zhfbuJK^hW@ZM7>pNLJ0kbovL_YgcE-o$&F)?3)wibeN79?uQ ze=px?gjq(qsJuL>$+KPl-2!H)J#kkt8Gj(wx(2qMfbBRoDcB?F8L!Q+uZf9h2B~94 z_zw~Py}I6bX+N|p3F=(tcGw|Ve{ndj0af=C>W+>-&drSD;3$VkNF=;X7wA5BF z3DSfuO-#y<2b-Er^&m0yQLUO!*!5Aane_)c&t(JB8iVigDacFSYk7NQlh7?HFZ_W) zwiS|fSSR&+(y_9f_?sO_$o;C#2{K+&aoPj~w6fwKDjg<<*=Wy6cx}G0<0gIjm-B!W zuwNjNCGptⅆ)>Y?(Ch^Y`ZumX*6c3VfME-O9UPlP1b5y}G)Zq)ddi!Q^ogZf$ig z`ly6K;Q#853Mqw%g_~v8OMs7x_%U$}2r3rjTg&jTi~tVE5xPR+mp~s_2H4j>pZzLD z|IaL&hp2|MkZHb)F9hkC3`}2b#wQVUDW7#ae(NL}T}g4h zfF|}4VI%c+GnJZJwnFfr8To82F_HOE@FW`e95ImxnqnORhq`a4o~{@z%(*bA|1G`C z=TUp*gi4Ra$Q@{4Ph*_GQK+q1e3j6I{#``ShCm}2)jHbO#>`Osk4RnxmC;0gj*&nSUr5G}2YWU$>b3^N?atrb z%s-}IOJ4j1Q%7fet_UKN6uuG{nGGWPSm%S@+tafXKx)}Mni9?Z@fV%z8O+!>5bfr& zZvMuSe?7_8e1EP%?%+_+rquNOU{OU!$Fnqt_{2FTCdOg6Vq$8g`}1sgTebD_&c<-& z_}t#xM~Xw?s>^bpgyTvk)gxAI-9C}Yx0sfw^$7-m6MjrhP06j5BZAjubS@w5b+B4B z?&YZnn%x~z+-=Ta0_tf|la+``Er{YQs;qR*d7X9E=Jwk8jK}l1%0y#I+jW<#ZX+W! zzkt~?Pqj%Ge9nhK zXZ0uXj-KsK8?)9IPg&OOa;r9Z-d0*@SSCGu~MW zRZU}#sdU&44N6w~Lch#Ee^JS-QQ2oPn9SeVczb#`Xj!w$%F$WzP7Q@_?OZ#7O%JKi znAgu}CZ9p-rR#Hu>(70SxWuv+UXxk4xDcvMhKqzhVZ zi>dc;Z+#*1^>9>*;KRcWWAGKP>%Qy!c8@-lMwMk5z0ylkEwY~8-XvkXltGT|txEEV z8{HIh`x&d&&V%Jkc1Lo5jBV_fkY^xh=eYER7B%4d1puO3N10t$7S=G@mVgwoPi*1>l*S3Ac%+duLOZFop>elUxrlRu3m zVupM$PPUH?f5Q24)DW_58s>7b8kfw{MMkplSS@1Fo~bDWvBPbty=h%=-`ck##pBWx z-o6tj9Qsn?#XMcZ3vDGx)H%Avbfv}k(9n>^nUc%Sw$7c@bhUNntjCpwsJ)L>p>gs&m^QH(&0!2C(I}HS154$fK;eO1bL_cuvN@Vd6 zfzH*#+{e$qw7>_y1$`BAuO)bToxajPkr^rEGL;X#X0KWKB$MQSe%MLj_!C4D8)S*} z`Q=-iRJ6tk`Ur=eN%n<&Kw33?ay*2PNmGS|a1;V42VK;~Yj-xZc=P~yqFE2S!;*7Hb zE{=Y{A0y9x-tcD5O^2JC`=^!iUdoJOrxgN!s?C9qJ zn5yD2mL+Tmj5?^Dkbg8Ondg25Swgwt)yFT3OwswE${Ie^$Ic>NW&w^b4bRkiNukv9U3G{+hsG$GR{wDq+C-` z^DO2ZxeJe-!i?w>Xw85gQeoPVWgm}Ey78{1+-!nH&3t>N)-!2|mo6gJ-64j6+U|QA zC_5$@Z0?bfhtiJdxpMu=bmRk^YMGna^zKA{{sfQL%yvk0kn~d{Q*F)G!m?vFmzhdk z-OO0t1gO{H`N&wKY&(P=gj9c6e|xz-YaEtgvObX9SrILdZQ{Nf$|H7*Gu(;?cGH~m zo@>dW%}*^P@H}s&n8AB9$ai7zlk|g7$~PH&=u@+gA1~b^;Y{n|m9`b3JiE91{hKDL zQ1=DLN8l?#0%+Lz)BIo_$@<{T{c><~6!MYK<#1#zX_oo^VawD6fd!AJ>m>*7Q5EO; zRX5zi!lK}6HeFe1GnpU_$`g)#jhi+7flU$la4o{wr2PD}$Wu@^0DVv{Y@hDhHl(b8^P3>BXRyrlxtQ6!A1#K+Lqx9@qqJu{BQqDJPRTxVoloEh119 z_gnjCnvMyyfru%cxp5;jQc#`rJiYqkl3G^mstD;6y0bx()BvemrwVN~eHxGV&}f5; zia~HwQ|$H7yR_kub{-s01-TRJT-F0M6{;+jJe7%lGzwJC!_Xwh&~nRRe{a*ll&4iE zcT75sGlJyVr!kvD>0+}&OudWHKD-3aBX_#!kDpgQ8kY28}-{$Dg3`K9soJ^G>cYa>xnVWymjZnm&`r{<#oc~Sgaawu^ z&%5iHAtlNheU`lG!2p#b&jn>+;!g9bj zrhWpXxWvH}b+<<)-0qP&Zif<%pBLaafYRmj6aGAW+0ypVw53gojc};Em~*(vvNIyN z_&IoAyd^76d+Pzy@K@Qp4-kKq0v8rFKaCunP4L}u)eIjR?85i5nnv~6_0d9Lvi>Gd z(**_Xi5B9e(g6zHbHLW|68U0Qhhe~-1ok7N{5fbaDBjX-CO7e*R?lws`m_&rH$9sD zwhVBZrlM)2F7x9=yQ)Zz`3))CtqUC#No|6`f7-98i-p zH}kL8vg**GKD?7vQJ*%w?us9_hYguvgYX+jNsa=g?hR zRHE1z+Pl;qiY7~DjNKmf4O#!)byso7GQfP@0dL`3D?|TT<@xwsE2DX-0cO4d{Ulxz zwFFKfa|$6^BgcYG!C!BVC0=x@hatCrP$3}5?5JNI{Mo+r>O~{(#d0TMxyhNME!~?0 zmw?soiaYUb__W;OLMU3qweb@BT-NO}_?A`N^8B5VA6xr|=govCuM7^eoCk2);#j5; zHszjs>(8v{E@g_%1;11KJP&V#L-)kqno%-)h4J?xzL6#5yt^!_s-*zON+GV$dg$Iz z^u)&Q?R#t5UoR0?@#-kY%40pJy@}_67P{IIN4gG+xe?ut6v_9F%6Q#gyFraeo!Uhp zU_g!8)KVWjh!C<>%9iEL;wd2%Cj97;pU~w7vCd|;q&H#tkF!28>m4v`i5-&QtW6ba;1M}27!>AOdo`rbr}tC*YOKhE zEA2K>ke6tLdp_`bH*H~;@h}*B_V#?bWgqe7Wn0Sk;3kXu;F~TfA$`YL`#4v;L;f{* z(_O=`YI5uefqTd-DPt}5$Zl^F;x03*mc+&Lzj-PE~|tt9(Je> z;H#`&@O*Z@denJ%{N~6ag?)dMu+!~YjJ=cR=Wf+}iesJptL1*4`5n2%z!u`=@|oN2 z)azi^lMa)I`_G3x&jG(@N2QIY@i5$S`nq&USHJ^<-OyE578fu_z@Tk{3HOO;T@lCm z8x2~kbHtHx=Afi0oXB0OZrykr@->lYz=H7BZ*;aq}`oO2>ZzHhgiNZT- zWA;<;;GTC}p31#Q)WR(tEnfwTxDsD4%5Jl}yYlLOGtQWQ=}+4Wijj9Z_rQYaUC^|O zd=Q~@obn=e$ZkK zk7M8Y+74P~LC9{t?hc=wcq@JoOw>9$3>_tQ$@$S>r>C2E&U;8OABr8<1>g8CCqng0 zWAjsmJ<0~h zB3Kf8{0+O5BbFR`3kAmAF@9ew97(Ell-izw0F|~NzL6yRMQo9IFqpy4bb%n;yVB*% zJZ`_~b%!}Jv+!-?_IGntZWiMyp@_URt_Y#hmVLSoRbwa70IOph%AC@DHzs+J+h%0E zYKKGHb`cdf@a+gm(HmUzsJRVnJ5U{etx~tJ?PQI`Fez zgvHKy%L03rLMdb^ZS3fMr5WXwCY|B&(_{WbBK?R4-K8sEs%dh!gO(&7dE{9S$zI)W zlpUY4J;8A^LZe2MWF`?grEay63SpPX@6>sk&@OI7ns#qr*C*smW1dPvgXas}D9}0) zPGavqMA&x^4>g~YK?Ayz73q(pv3JK+r|MZZso$N%M0P%zDkysY{`S?x5vp!s4gTb0aJpcjJe)y*DUtOf{A_#=flnSzt|t{swB7*q@gZq`h9~Z0j!nWfF?6;qwcsctzAA5ME zoJdr-0d$@4Cp%;uH2KM+!%is^AuDmJW<6y);{^NLVp`Gd=#~>;mGCK^2Ftts#Gzhw z@KmF?`k~P&AAx|%7XEq%K`B;SpQB;n-_@^qU* z6k&(kj2ynar)%SpJu}}hH@jzgCF6V}n;qdzFPg>@2w!M3jh`g$WP5EvR}Rt!@7R>P z4!b#uYZ934SGaM0m`1Yd_`p5l!u@BP_a50$cQ>dsyrC;|a=G-J>g6Wh;J$VM1m}|S zIC!$1SSRU;-+t9dny5?uVh_(`(Q*PoAV%E{P2mB;#igt#?WIg75qk@L+%Uzw`ODo zL~bWM7GHU+ST_c{53~vWkar8C3JBb4PXH?f%;LeGSvyqp1^%M^q zv4eiy@oA^(Ig3JlcXlKQ+HrN&P0*@$WR!vgb4*?s)vpa>M|wCvdM#%_NF;=_2%l}9 zopDX~Pv+Y>D%+qK3e&hYlsy>M$X~8vQww&!46*JbVz)eugPQGfjUQ@UDY3|>vPY;> z_T*jaxfhj07`XCBp((M@6urB9xIoNf{Ba2{G9T@x^?lX}1tO*fM2w^;Uf%55JDspy zRm)!4=o5OOlGV9rynX@^5w7lGYVCTR#g|HO7NimPc_OP^N7YcS6`dP&dhMVzzBO(KL=i@Swo^BHZCyA-gqe_qE$$ zi81M7uj4DEj(k)~2aECOWT1tb|1fvmJHV*!3MD6Ut>;tybLE|qAK3F8ifCH;5a}-HNF~{P3U366a zZ{1`?uvB%{v42^6RG%C4;c4g~2R`_L!=Q?CNiCdOCGTV@<9!<8`6AyQhGR2oG47b z9QOd<7aZ*0)U^JI!UPD3nAoRzp1X`ETO{7-qElcR2%YuEg0P;U?O1)`+JD50?nNPa zX87U(*)U&c8-I6Bs>hVNIOu6&q*s^U74PLm1pl7ZMSQCL;V?Ec-Stp%F6(#3*Pa2` zh;#Mq+)Ou)7bY%1ENM@0^4Pr<&gsc^o)Hs4G$~q*vAZpLc;$tZkSZZWoh59f!wVbO zl+DT0qBZbK8>T)_cH^r--CkQ>UwAIrV&Gm-2EP3q)p(SG(Ro4Wzo#VYu0GCt9=0!k zCq0rdbT@a|?2O=&eYFovt8lcmf18M~i_GkBnc}W`e(F&@{+3GC=?4i&vGO9uGz+27W%$tj@mC9*LTWh|H^RJPc;38klOuzlu7O4ua06}+&_cmOV$9ZC z+PKu|`}oX1?K;yC^w7EHT1NGybl!yMVwwsSp8E3)uHvB(xW}IRcIuwH4U2XJlRb|J zoZ{bFx$GUJxLLYLN#u7dbfso@zr?0Z11b2?-0?sdf6y6Y|9zk zi+B?s`7_fZE~b5ck2$dZ`Xu=SN?0#TU0=A>Dh&siJ>_LP=P{=PCk1KB4vkckEVINP ze5n>4yTi&+t6<_Qx*e@m(kf_BBym<|I2Iin0kKwe#-^89uFpo8oSC1nlI$Ct*z=H> z<;+A$sVpy{Q7Vj1*bxKM^b(N+1&6>Lqo#lOoar+@+D2mj^M64ZL5vSfouHvjm0`}% zN(o(EbheKuIyH}4+Xc=Sd@b#Y_w_|oF)xX&zkrNAjtBr*hvFdzDbaCu(4x&>1bg%( zef_iqa!T>kl=&A;+mNFq=qb3ev7PskiOrVyMn(UZoL)#JX>@p-EYLy=5z@ zPgzHeG}HPAMiWNy(IzY<+rCj?L$P^VKBR&CBruls5)+6-pEyKB{EHL`Mn%4hsPMNZ62GQ8Ip+_F7bK zxev(s1`DHy$7r)Mcb5mH)Q!B3Mumc%J*gm^XH?q*KiEFVX)6 zFb1$82I9Pe|B zdWaS=cv%Ra-uGUkW(CTq1R)nXI~8}r{}7u)XPYWv>TJLtVZYA~Dhc5wr`KAR_A37_ z0P!>)BsnrPgSyjL_0E3*R$wPA$O0lQd5OsTnYHBd<-g09ItmXFr>i&c)6sog8T$#+ z5idDM9OT*GE8@?9HbT7;sfvnZX>Bxbduc^wobU5lF1A^?`axC7^)|-2LpZcY* zFpHo|X-0x|guEKr`bTk3I|al0^n#lCUo32oea7#04mD6&QazC)%|E{1ySQ!|hC-dl zr}_KKq3x65oLcrD@+jiE2lybnji?*IN!}Pg=w)r1F&apM<$;78cH#vA#Lxk7ri=!d zSy`*FSgM<|WfQ2alhQLD|CztHl7tuX(enuT#};EWNXB&DXJ-zXr=a)V46@jS1}LH%N~EGvhaaGD( zCJ0$}mi0(z5m>aEj7cnD^3-8#YGPG0Vu(3%G@ja^7$n;;e4$Zy-FT)fr(<#4Ys9;g z_a`9$+a4e$cJnOlHnf?NMmYUqEfG5PSzgE#2M1?-YHAB*9e^<{E{;|m7X69WcJR>< zIchs9Ev7k~LFiG@Tmg6#mag!eIoB^>0uWQHhR7tlpqF7j100XeDA7h2p zZ4t}zNm$d&@bIGF<^1jLJRf_%D7_1`{!o&3SeYfs{EkzqjYp5`c1n9|)!pcg=zjo5&ZkX&=I?}2z@P&JuMb-H zoYr*LF}E{yZvhv4r{^Sc)MjR6WQnOLKQVYIW_+@0&ZXA_h}H9(m8Z_@l!6JD%UzW0 z;If1rkwTZbH#^4QGC=Zkr&Y$}mJ}eX=HIxWw$9C#YBJk?0_&s>B&jo;)VQeO>?^@d z80Xu?0rAlCzt*8MQhHR|VU#BUx)(Yj2r*=k%WQm7-t$t)cx;#1gTtS`ol=5gLMr^J zp>e8vWlU`B+Rh8+(x$tc{p4xn!Twm1$OKC=yEqa~qcCPjIh_V~Rpf5gX@dS|797|k ze{Lhn}_yeZP+=n6eHsTI% z>WV+fFk&fQOr2u4%#;Y!Lw6_}G_Dg`j=YGmd3F#%(}G<_cD!~*R09Q)Q3>6nm2SyY zJxooA0ljR(#>bM6V+@Yu?$Mh{NsMFT<5Sbnrrn_!sr$|p5TvN_Rmpq5ohHQU&*S)QC-EhY8(vD6S-!+P3?z5SO=QcS%! z1L87AK%C!weY@8|(Q5RQ3>KTjc@nQ}QhOW^GU1}fkNo^{M<9{o%$MT2S-tya-wZN( zw>y8^k72o%wQx$~RD5f$2b)Bj&k!`|z^?lZ%5#0as44i%!jD=%8Af{XTaD%TA~5HQ zV7)5HEl09Fc+&ozBHSnT9mJIW>m~u2EMaGz<38>lpXJR_FKgBBQ&NuPm~uP)MT~xI z2G@qD2v698SK~5fD=x$v+IX=R#%4s0set>aekp_zz_ZC`83B>3`*EOhw*DHHXp=Wb zyON0B!7?DPI~kElu0DM-Df+f!?k`cS3cpGqjvn zV_KLX#DJydO(jDq@WHh(0CpG$g=)u%eD#Vph$dtlw|EN|LuO6bK_1NrixiePr0<`whLE=~6a1$z;m~ zMrmuR#NpVIm0htUZ>dQ_d3%s6-mtX`7sL^V?`*BSe2qhUcG`FBH;D|8hZt zpS5-)BZ+I&-D?k};RVFacPSou^*RU#Hjo*LxQ+V~V#NE9SykVe>>yUg3QpE1g$0GK zvV)l?8|%adUFd)M5@}#UDx-sCEYS!G)_)^oK6M=1cHJBF*}PWK7CX$tcYG5F?Q;X8 ze=K-PHUr>in` zHD70!!qKTDnJx}ii4QD`Gi6Mcw#}4)yIC;bFg9^45TfNNtUqW#Tj!qP&Ifa``{P+* zO9BRk@8OT{{lAZ69|Vb4P->eRw-di+Qe;Sryto^jNo@KLJHBA}4LJudr(feL%uA#- zV^}R=L3nRSI?JhT*)&k^Z4MxVL4+PM zbONrpv6*I4R>H}t6FMAVG}!Pw4@qf1c%Yl#XDPv_fyWU)R)Pl|x4d^vyM@i7#q@{i zYF0(u&sar}PvOgYjD>}UL65UC_35=L#lO61?^~KQ119Mt87zT$-Xpu9@Tj6CR|MA%F#&C#r+(F^|qHSPYz79*7DtMg+(Q6b)R`;kajOe zf;}|V%QL_yLtlu2%=2bFB+j3EB0$i1TC* zc!vdy&9I8qT6Q8*2QW;|0JZ0sc?M(*Otn)PQCy3sehe`HQ+H%Ygx0F}ZjQHn&r~-m zenxnbzdS5OQL&!lI?$j}q`mv{K{^*i`j3OQu!3$PJlAbKst&A=94|oI66?5?5PV6R z@d@^*EluI`V=J!o8$gm}8b&jZN0?RM&xIUwegz z$MxHVbpvnnMa?b&ds;Kh1d`^y&5O3^z9B|oGirQgcsefm?5{vTmz?kth@W^DaF6K_ zknFh9nzIs-+&$f8*mI<^C%@UpiAKdvrg6o-%~7l0%uY-?KPf1!sTG%D99%bdIl)+4 z09nGr7+Oq|cuOz}8X8{U?ieJOU%taGqhJax(*1^W`QG!umtu!oH|lecrgi&cIfSx7 z!loTo=1KUBZ6xW6REr$gWZuGeb(HvP;o~smzt1bHBS53brz8&m(gvZTkd!P38NOlV)i$w6rTvh12UXC z>fK*JQUOy$S6=Y+m$fDNZH<6OKi9-0n$Q8FXPgEH1Qo7yJt_rDMew8MolX-;X%h+M znX&RM`)C|mJzRif@t1>t@6T2Cd3*WJNaTdPoobrZj_J*} z>DG}fnQ%*V!k+T3kr5dg2IdBVNYUtMdX;>&=n@k6s@&oPzw4R#x<|`-JCKOzQ#Lg+ z5l3WF%UGODn3fMt6@N)MlAo%dviI2TyDr!S!+3)m)8Fs*w!gnP5?*xE=SqDcawL2sxs9#Hkz*igS`ep)DZNUO%}C4PN6~t^;VR4C`_L-M zI%09f1@o+NYk@cQdWCjZaNjvHG+*S-WDdPJ+}unEDdu8>=WWvmO!LanjAZIWd`_eO zuVc(R8kO9NlgC+qn@1Et80#8D55&HNy&o><@E_t7$dESSNhZd-!{|L zmTJ?~uDKkN4M(uvI4E&;T?o#LHK5DmetGhlUT*!bg-?h8FbAm`{i6?X7<{K{PB;N! zpy}L1gOJG!29j{(|5kf_l$5EAC6 z{|Y?Zi<5o;OeA9k6Ck(#Pdxo^i$4lPzO|2Vs&nrtr5Kw2dGlsqum2^k%drQO%-R@Bo zXk0_&-f)Cb_%L3RZ5RAA>Awb$4Cu#GmH$~7zi+&M%h-kSKI4Lff%vRJ!{h(ZP5bQC zRx*cqe}pFbuSoX&CmoZ=APM;w&kEbw|u#Xv-pZ)sIZMyWFF^{e+BSS?d z=To7@v)rbVtl3KQX#aTVK9pve7}I_H{#^UJ{`Tui zHAQTTx1EQgU>F{`l(zZeV;fd9YH=0%&~KSUaxvAV?-o^5S)MK#V(=;yX1~&_Iey}W zzT5oM3^jIAxGZXhLPn2f<;Yg&mfjXOL^?;E4-$r|qjw@0c4iek#m@-WSQy*+y35s) z9hO&YX#3;1NR#)slo#GVY9>x>JN%APSS!AOO>4Cc_b}C{vZzqYC($n^>7hsTp4L5d zDwFuxT9EO9p$+2SKAp%M{iEvjbHia1Y#02cht!~_zWK6|{#~!ow^YoaP@CTJ7gV)B z&vj6|wY-ntFk>h_m8lXHCNGmXemQwn)YxStOKQk9tzDUy^L!VU+-dIV6lU?=w7g=w z5_XIHjEMewrIl1>3{T@n#%yj_E~{JLo-+`<@@&P=i&TZpbldJ^ya=^eymsc+@1rAS z-$`SR=b2^*P*&q0tGF~MsMNd3bC`gp7T0FyhCc;QPvs6$@qHe*lz8#5{7d#`ar@7# z4OI+r$M0D`ndxXk9F)#%)N#|bu^*%q@4Xhe%{ixxT&lnssqN3G4i1_^|AJ=ytk9N$ zGyM8X8sS6=ACB0UOj);toWn?|WIjo0ITA7V<=a-#e6+w%^-uJ)e<;U(87`iytxA<} zdNWSke*0;?7`i9ffniZbVu`L_611)1hx9~UvnC(yLS5z2t}wARrFmQ`kH``U-4{R) zV8#K4^P)Csg)GVgD#ZDW*JG%9C=y@%*kl$To6Vdyug}@RMt6*EiDdkIcz6E4;%_+`C$%0j! zmZ!Uh#WS&sGYm40U(^VZm>>NpK6)4QOQEKsDppO(&F{rj!1Odf=}z-m&Q3}lMb%OW zW?tc>;oa5rJOFIOiVk%$&rIoY=1EP>L_a?3+h&EeOjRj5{`yobDV0H=N5)IGBxr6? zP!PNEI>S>@CN1@aax2p;Ux#Rn5|bteOjIH!Hsp(lLZ)mIu_#PJ*UJ?(iJ-L~(ego% zU%sxpBNM$1Rft%(Al%eUk5T_~9Ad^GG%Y59 z&YB)#Riyuh)S4G4quwZXy(4ng@3jz~<}DGAmO4elDiNWpi|bv$qE8W%G4Wx*6^#@n zy=ZkxN5M4Rr+^#abkjVC(PZPxzz zM%zM-ba^G00mYV|KR1$I+?yD6{v+bnTGC+2f&#%0r>6zu<3G=0Cd=f(7E-um zcl*P)^XXt83_*fOB9n;3(WX}IYQ?${;ZL-HD$k1hp^cV-z{{RXOK(iN9+(M}lETd^ zL!xahCUgB&Gwaf{TAGehntFLh@v87g_ttw&Hb?D;#$j*~| z#K14Q%`;X>Fq=<-rp|R&O=MpIW0m>i3)8(qx<0d60!P%?4%T%PP#%}W&eX!Ayz61du0Y6iJWGKTZHScOTF@hy7Xq|=A}#2)r`#{ zvWIGpWs^}L04XEP_ok0jtdOTaWbin)aI6c9&9 zECAkk+ny*2%*CDdC)Pobu}K^whZ$UL<4&%sy7jCvf7QhkI3k^{v`^$BgFvMscgnVg zu`Fg*UZ`ks_dH!XC<;IocpUuGjA|{ve<)3b%zxgtybIELiYtuHf;2DEZ*jqzIL zyBw~kV-wi^8uq77bq8CFyPx1X2C7;Y?2OEjQ+0C{UE11J9k=TPuDkscaP=dFuBW>0 zJkUQM%(I;&g&ckzd zE*P#fH*|=}=bv5Brp< zlTcRRfhriM=%V%s2x-z%G}brGbl7j6Ft5e3 zz7ss`5oDUHlhbsZ=-phCW3*bgF487n@5dR`vrL&#e#t_0RyI4iKed9nk~%FGa%2nr?tKXTAaU%cf#l5rFTd6c zCq-e~_fJK?D!vEeX5hOQ3$i;B=3eFgU0F_HRCZ)EJkJ+=Wig2KUIn2Hyd_=^BRNWM z`xeLQgZ`vd?`D;<&PgERz%uNs_mpmyzf0xO(Ho8OYjfZQ%A6^mtmB!qrkoUY!#en0 zNeMwm^N$&mNWlxVYHL69>4@F4k=ij8F8xn$6L|--2U84MCEu$tQd`tlIOjccx37Bk zecFtsQ{^Xd?aaqBvN90Uk5)+)Sb8mFx@;ZiYO=GaVpS$|=e5{~!>{tmtqJ(l8ASZV zYn@bG+$mEyy_*#0q$9dN=tC*B?9tMQyoWX=B@e`sACGrlH~;wS`9x@-fXI#(qi1qw zLcN=10cWbQphMs+#rW>3{o-nIPo+)d)wDXUjKXh^%Ukp%C7>cyr7Q@%MI^n5T5D{L z)ol&Xu0N745)EDCQrx^2i>H2%!c_aPK6!ePL^MSt_hnT0Pnh~wDt)nsZDHmjdOHt> zTKIaRjR@^uQPxei66y$`6{68&PuW87V-;yL7gM6%5K)Oe@v}wkvE_JHVWUf)A=3PF zC%=!0Aa<4H!GZ+U9Cb0_U_AFQi|62DhqH^gsUzEtR+)@PouS2kT*T3|z`I$Kq={e6 z_mZqPwo&R8TGJNt!!JF>@p_;&})*2!ag9hyOpS-YTrkw&@ziin|7PcXxM} zwiI`FcMI-X9EwYE4Q_?t?hxE5QYeMeVt;x+@3-+EvzN^?bB(N7vj&#YX&W~%cz7eM0gY#S+!&^C<$q%4w7v%4II)T7*oFw@#U7Hj#ZqkKgd&aplneRTa6=+Kt+j zyj>ju3K|4-nFLIr)@}sP3plM?DJBcZ^wMm?F^;*JuW{|6!*Rt`j+bkhwye%?!%v~_*kcx941bZ6`7^3cGGNWX# zGJpJ8$%UMvdxR-hhP*UJ^h9i3Miw$YMa0>JW$fKx{AY*N`PZW|VKTokQ5-hX`A~Ac zRAd4?|Gh4bbNemPG{}Exbfch)mm9l&9CR(@P&PUvQ)j7Hj#n=bwC+Y~A~t6*b+S=F ze~7(v<&2K+)F#;BGz8NxBKK`jjwv?Wy{=?fz;)Y__hcsK2220r$K_dLk%j1NBFKg7 z@;ZcHvHrvktyg-FGbuj`Kx0_^MKkbtUwe5s_+6GEYXRQ5_!iW$@}l zE%0{^URNRH3-&`BM<#nmUt|Crqyd9%`=u%=ho77C;&%21(M+dqX{FM$%2(+>_Eqs7 z6gD8*qSuaqvORE>WCz)ydL<{*1&-|MGa#22*vFsW-ob^mdRZ4&t9<)Df!&1;R4DnZ zLbKD{8vgBX924$7*8y%1U>5SJ zz0{~vVOEx`1_`Uu;zY2}6Ip#dbhwYPkq({iCsBr`^!+}N*EI>hajY(*1x^U<^=whN z4j)k$mAQ3<4Y;)p*I=L1hy)~p zQ=CCrAjCEl_;=h?P3Tt~id{3Y$;$)7b27et*E(P*t(vm`r^HZvdS$_^4UzeHQ+7@C@% zzD6&mPw8&c+`+3Bid)6i57@X zfH+N#v?W#NnPpm5t{-Z8uRj=fZtNkje9lDQ$`2D`wqb z3NKs?)D|cq5@~j)7Y&@8-ekRoaQyiRZbu`}7DhVNiE8CDLF!0;CurRKoUw6c0tEd7 zg^Hz+Zw%EAI;A+G^Qhr$;ebrjHO|iW*o`QL{Xtpe;y&CX+N95(wi^*x60VMzS7A05 z2x!Qbu^=v+Zm0+f{6^($Q`MJ%EfSIxsy2hJ1RvP@(@0L+)r&AGjX?r#47o1ZaK92-JadGQr5_(MZ|s2<-StRw{#jW&m_dWgel7S! z7W8YY%k9`M%uu1MU0~Fl*rmg{u@^b?j8F|v92fFbPtZT4J?*p>9w+Vau(BbWQr`lF z&Lebp!=!@_YwDcVKYWO=BdBJ9y`9aOu*Oj%OsBB+PAYTyoouYz`qq?hRNjt~^%MEI z#r$|@;NIGCD-;&-NQK7LAI9z3F-Or8EGO0$E=bR8GTm7#OSvj67lK6u>I=(@pU zJoBwO$~|i9T%*czz|aG$ezQ*NKmSLBg+iXvZfVPMH~=<;cxoS6d%buZ+^cVB*_#LS zy!HL(seKQdtgGv8fN!`yM(8qVLqBpNZn-dLN0Z3SPYZY{Gu)lp?@1pl6}roGb!67! zh1e!g(4$q$#HL-4)_{m=ui7REs`lX_({5F6&^(_(!};!a3??IC8TPU~_E-s*OgsI# zCp!1(GgQG2sDEbm(Zx}Aqgm%^sxfFHdalUcS{`LOV9nvlx7XgWXxl6XlfDkN0gu!) zaN&V$%Ni+}zP{56fzL$Px+haN8I-`$M|M3!|8N3Yt7kWkvLh&~cM@V?lK%aU$xPKmIZm;EWZl6;M$DlDR~sEH*p?FVz>+^$X+jJ=B+0YMB_$B zP@`EyM8X-}%j2FPT*wuQW;^xCoK+(f1c*Y&m_MK|8hFR z{|{38KNu1SM-Q!Riis3vaDhWg#pQHx&1jN_aurHd5Gs!h-Q9Hy{y<-vNa8K*^x@mb z0kQzBaE@hE*EpXeb!qJ6=40Erb|TY&5f#;`eO;W6cXwRE3f`Gl0Z-15_}t@kt_aLp zHt!7od3uYZ>V;LklqNfJ0NdIhK+$bFGs0JlGA(QT@65|M@t?JDyQRZY9cDQ@+ux;8 zGt4wM1N^c%z-(UZV@`+Br%o>!Df%6>mu_6dDqA-USb100& z4trxfOjfSdmG}cTB~h=KqY~uF7&r7oXG++9Yp*mGZv$?KYSbXOW zuYUjhL$h}7eEAdc)jMmv<#spD=H`ImIzq(Q#BUmJV*6q(>mR0!PYC@$zI9VLBq5_a zz!g1@U6t_6bPTjia9jkBZR;jY-Pw@L|*zxSiue8K$RBX z%HqhiL?fD!`y7|{ursym$ZIYLwXeFyodT@*CEbHfqV+qH;E9t6W*lT$wmk8y2#Az2V}8V3k4 zJPls@)O&+$FG6ao^TL&7PxO`jh1oXLeQxHdYXNCZ8}At`4Zb(kCpmu`l=zG6J6KUV zgX;!c^7iY1{tk!e4~Ag^6Q#QI0MOdMXvVa2nL(q_>Ch%oRzC?@@RP%7Ve9$>g>lS; z3>PgG+hxxB-2D`>tNq0B3~ayz5jg|X2OxTAS4v>tQG!vZ=xznx8WZ9o(S1u0!zD7Z zsRE(K^o~5VvyTLR^mSvMERqx;oxz*&Ra!yzOBD8@?E@st^ z_r^La$b9%_87t2oscH{$oX zhBzhN+C=K8>REqm|M)o>EbKO;v<7MJhF?XzEsFGWb;Y2Clkegu-*TCA_Q${r;G=So zk5gx&e3xyAz8SI7)rk;+vw);fQ$2`40|<=0aU<6i_fJ$eY#S7~s&1~?$6IJL5N*2S z2=x5U3vxS`5xw}CDPZ}43y7l?aDQeZ75C^Qua^9Npu}s^n!ikYs3Q%W?!Y5psqgs0 z_AuhG24Q2;VfKiC7X$ZgPRME}tks^eC)`VU+$ zZu(c9uHI`o%9&~5`CZ=fgKXardc2NNNkv`6ES|oJc34KL7q*dw`xfz&HAKZaN366D zDI}v?9wfwpf)?m%6^_~#TWCmwcWBhIxF<=HE3V>V1U6UMd+Le5z<+gPE^RfxA%0Fx zNRZXl&uiOQ+B48zuvsCP5kPz|s)*B7a?P~GgpHQ7z*Op{RLSNPV}>Rx6wN(Y58xm) zM^AT*DY}uUZ6RmR(K<}pet3$ZXe0U(h!Zk5CL|x5L?ODf6TGsb%TtQddY}#(U8Sv- zv|6DZ-Kb7GENpbMJe1DoYS=Ofu*dt0VtPyxX{q{|CQkvsw0{uC6wv47FQ5PW9gG9 zI%48a*v#)P6a~B7bw7R)*-Ro^{F=(H`yzExkNB`Brg`>LVr*)t0+OKK(DSmjpYBS4 z=$coP_ME(Q{TOebq!}7)^(|;w?YF_@0BnPjWD!VJ}sO~7*enLLrrfh}sFgIT#nxuwhoNpvuBBdl2*Ue6+ zIa}Ow)-6wGQ)lu8hes{H+o65~)a^8v%45fC8@?^pVwWpF;`pwEIvxfWw{4P=c52(m zEQnvho+q;)Zfm^yYNzm-e37ka3|6qZ6hdrkfEd_O*XgtKPHxW?D*}<9UPS{t#E0gcB zo;lZN9QR~&Q0m!!QN8+KS^WR|uJ)!q6;oCXd8hlJ_$H#p z3CV4>p+?wp>r@?adfhtCnI5jI^Q$9}--%4!N%)IJ6*{hvG$==rp|B}G#n~cn%igYo z{-KU3rYRa(vSomf@TAYJU5XFY=$;PFvCg<1#fAA8kl49CYvNW?Z~T;gwHY#kDG-Sx zaIH-m=Qoza05*a2f=vP)Yp-N`kJfK)1wux-Y5~Hi&~46Nnf)t?J89|(I^%y2*6~{U z`A!Vyh&nV#;L*hs{5==!39i|=T4&yiykG0T{+|7m`+V5X-*v~w{@-6xjC@0?deB+S z5l2D`7m+U|`mL>9T24Ta@$xLq7-YX>qjv2MST&8E^Bak${g|UiYo6cX)VjZtlS;hA zHkZ#k_XiNe6_%W`wLj~qN?7FVxa!;PswvsBiHbqS zUj3VOMfJ1=iqh*DX1*amLH|a@85*OAd$TZ=614}h4(40&xpa0wcGlrF+-k>jfJjzX z0P>~=aB5q+0~vK^x~uSX??~6JZqq;?WFzpg>Yl^lSzjlsMjh*Ovm|?QAkDbfu@A{lC4^_o&r9y z23X8Br!e%D1A+k?P-iYlBZ&`*RHg^n?E?&3%#!PS-RRls2y{PXw+RXk*f%-_k-v`S z01(XQ3=9oQuy9a@#ec-;MXA0sJ*Rt4!*LbDnDRuuaL$?S!e3JGwCRF^cqp@NNRr#Q zk1S*H3EWL{b=5(U?2}&==SqGlE-K-v3GZ15j+u#b$ap1`>LG+v!as~Af&Qp+L~3z1Cw%Ik3kjX; zdl*yvw82|z+6*0MM(C%BzaEf48yVyTL76r4L32RK%^qdA9T8MjRS(J0BT2n*+f3dp zJ@N))ND`9qO>0Es)PmAx@y-Y+8s#oUCF;mCdnOis_s4}!>hdnC8S?#gtV61&Pbj8J zTkI)(W3G^SxN%Uj_&;4$d@5*$f$L>*NJmE3g=;?Wcz5tOx+8sH+b?_f@hmLef zXwB2(`~o$9edmJCjc()!^akxrS*EL&&7XE9QFb@EDUi92@+Lf`6ig+IvsLivPSRd-!{egj0jTopp;E zgHubq&kj)GNQCvh(He?maZ+5jAh=rfyX2}YYw*D<;NmODua3~QdM^kZQc$mPEWaWDW@g;R;7d<}jVZFIFUqHP;I2<8t%R0kwuRy~i0$lLcI>R%4F=@luj8tpaakYM0$ph{ zk6ytkq`V$uwez^CH4kL2(2Y4;heUm`g|??7ZF<5N=}iJHW`5_?+sC-EOK=h;`4H*gEX>ES9iy+>w`ro{MO)doqWGz9KzP6Sm}Ul0ZY3 zx{H=M0-Ojw(7FS@^^m&X!_kqqR2zS&o(^?NC3Vz@NY=W}2*p<(MSzadP*&G)w# z&Zg+Kp|w9@+%>Gm^9v2Ve8#B7$VaL!H5PFRJ>cwfp z$}!V%Cu@^gdf+oUqzO{#io>c=s2S6&67}{=J=|%-#FztT^WeVaH;|5D99d!jyIZqL z3jga;VE%O}do2MD48Zet;==r`CY`BNH&@=}XH$?i_qR!l2}17CkEwrd}B7VuB!DI zkx|&Jy|@n@;gTAUBIc_8(?>7&t4vMI)wwD1UMO}HzvXz>x*1Q#w0qqoG>`H+c=dWA zjgtR8xDvwSJ-xe;(kD4*9H6vD7*DM3GX97;%7`mEp{{{Gvj<*II5AH2{&m=d5EbTm zo)j$USXfI3=&E_e(GY?k*wx@B!uP-2 z^Eqn-iB5qQxckQd-VObXw3uTLFY2kY;_u$cGvkOC&PabxY2>sxX$=tHgz>l!INs~) zJo2=WfJ+%Y(y%Vb$$;aBH~QC7fAB{VXQXT0khjvYhwNh=yWeYBEnU&V0cU;OA9_AJ z;<<>-`^5Y1s?13UeL|hIXG}Mjw(%2e%Z#(gbiS3Z9gwqZN^u^h{4JS^1?gY+j@o;6 zffOG91n76d=_%P-kQ*Dmn*6u!U+e}gc61Ip-5O#HXy78ggrU(Q{uv&!V}2xhU)`nn z_k&!}^DeQ&bR37oml*jrn;)=p9qVRDmUsp_bR5U+*rf*JckI>8CqdI`56Tv(xd&;6iHeEW!P``2y~O(R|zHpGi<@6rc9{SqyTioI)jX-j=*Tx6JI z77;A82?NxyR{~xlu#JH5zB?hk4Yd>?^0K^--{h(X>oIJE>V*=Q<+$M1@rHxu;dbBIvA-8P90~74Q>N_ zxaB>?JjXHB&D$YWP_uV>VU%#P2S@26z7vplAi1i@jr$4{S29o^1AWq;*YV&E8Phirv_9=lsXgF6a*iL%}^O;HMU z^(gxrZUxQh%(~~_w2h|kn9d)G((z+E(_aN#!Ruj9H zPmOp@Uj~owheA6jL*3J-CI_xJyEapYM88?$cZtQYW6|j@be&ULQoeYvK{291VS|4P z0zf!AC@>sGB(DwyhE>AMmzekpVU}IY5wK31~6)AytL;lB6UOCzJZ4)xg^@3o9pOl6MxOp#yTru%2cGB$ZN| z2m7SdLaqzX@eAe=_cFwu*k+rIN^yZy0V3p<9L|rimdG{Y#BEZs&Y`%BrqEjKlRU(8 zwMFd5VNAC!j6ZwWP+~_h<+*B)DsS5k?$?k;RZqC%R>C^HK?Gjl4>(cImA&!0qE`bA zu?4#d!vSwP<9LcYbUX?R8V1RQ^?(KfdotBx_bUM3`qtuQq&x0YbqRB^HjnNZEk**r zJRjRn<1F(NDzi&lnLq0u6L@V~KO@~vuU*hi&T;#PWP2NSK(6wb9Kp5zH|X{MmWZUN zaZ-gi%4_`~*^vd`ieTG zscl^4H}9I6RvS>q822%6rH4Y$LF%2MRN8W;9@bJ`5;FEm(@9pA84p%u1}i7)ut=$_ znHHHm&y<5=!M;$bo?95@I~Ou*og?T5^58GL7HH6`>&?N{D+5k5jw;nqX)ogM9OI0N=Buh4ycM&dGZX7cw-OX z`@1v|M}1)|E^v_#5xl{e;(JMKUW&6qWJHs#lYt39Ayk!Xv(vOV5k_9}i3};*WoJAK zV;Byi^lIBn^v+?%g3FEAU&QWe&@QyV7~UvYSC{?3NEK-*#0~dm5j`9$Bl{^+!8z#I zPSo*0N$vi!Pl(nOr_^+wE8$Dm12-9|Y@;^9V)%6Dczw=hu1u~>s_qf?0dA{!VviB3 zv=O4Y5#o?hc;_VkCVr>88tS)dQmx6R@)#%wKqn}8$_D}p?zj@salLP_tR|Hq+0Z6p zzsBzI`V*$4A;Z>De2vMbjirK=!eP)YT;R2WI?;qV$Y$PpW z@{1?KG+eZ3WpDHExAi!yBe#;hb=d#$fj+=JVty9lSWYPOi5sM%gV~TniOjll7sJP; zJhUu~n0QPnnFolj|GCf^_p#SyEN5b|Cq9PZ0f@L$RE3_Pxe(^i<(B4x8Q;ggLpPrs z3a|!eVu$CvU}mJ0?*EJ=Zy-X3RjAU>v95v=f=!Lr7(h|04l0vvA3uYg z7ISbwUME3@B93Y54N`12u^T!g6$vy>h5#DUZoC7E;L>qfLqqec`_Fa&ydWtu%XBQC z2z(~g1nvsHLUun2OV(_BMN!*LPvLo@&i7`~Qr#cu1{1hBqCcs(yC2p#ttK`<{B3x7 zxP-*i?E{``-FXMYxAqi}ZMqBHQmN{fmG#!XHl7D#O~`!bnUuBFE|(e=F~ttUx5QT?!$*=okt2~OHC67!nI|Dpv1AZ`B&NMa z7-0o*3(a*&+!$p=I%{;}sAS4w4|C?wkQ{KrRUPwL)Da9vQAZ7h{JN+BSYD}#WpiMs zuIc&c(HoO;{cJ$+PNDed(rTOB5!G6?QGoK^*$J$~njj!_tFmCKi<02VyiLlau;J3J zV|I`vpth^_J5evK6=n$>A(Qc9?k70zuzZa1P*&0jfXlVURzNZm+~oFasyKeL{V|hA z*EP%6$N5eCfuGl7u12PQT^fcijDh4RRlL0OSKD&;e3TW#(#qCZsM4F1q`3>-5_sGy zU4gDApWTHD1$(5QP)k);_Zo-zi6UU<@cg6xyD)Jg5?MO0UTk8D>#-EdH!=2`$Lt^e zk~}?UDhOl4C_k}ZSUC*8=C%Jk>5j@qd>9I(+?biWaYy`1zXrf_{6wD8a70`G31%?k z0|Q9AIqmEl0lWb29Y9a`kN8amYX_4?yjpKT^rm0_&H#F%Woq(o#qBR%A&Wo=QPGW8 zRmisDAk@r~Y4-O0l3k)H<5LFpQwG5^^CW@HE#1N}Nxp-+`YR{~d)$Lr2o^Z^KW9H- zIo+)vtGjHEjEvLgwI5yx%VhPDQjx-RX@6Ay5iR~he^#w=9u8s)}N2H znP`ueFfA~kSnZ4pk3q-exX{_JGGNIt?Fs(yUxU&$b&EW95%2X0Zjk+9&j$O;NmAj2@Qy&vB!mZulnTM?FFa#Z#CAkWPAQElTB zvS3XJV`t`QPF;;>pc#faoTSu>ezpz%-+4JcX}U2@x=bq3I<{8Nq&-9DkynaS)G7a# z>d9i2$)Q}nbIc8Sg4h)?O~Fh0}dr=XzvmS7IEEvs=)t2{weVpiABS2E^H68*Wbyh{R`j{1fD<>Ds? z)ChVH2X4je9NwP+-j@F7w68S9jT}g0uV{|1F3FT>P|r;q#0t3~f0BGl5xgu8 zaY9480Nsu=qw_+4!9kf^hS!nat{&qyky%q(J6*E7!YK}pUUy83~UL=O3oaR)dBq-w&PF^Bo0 zGM%E85O^ktc_FOk5!6d`L30wkZ@(HUqaBq2|MO?|RmncaxHJ|FmaNGdJaj}^lN{gd z`C}d<^o^5O$znfyNzRVyzN|;ym$RZQq_=?*XGUjz&#^mgX9}mPki)>q8q*-CcLpJx zaKV}Aa>odQu_hGv>J-d^or!&8<{Rj19%hJ4+wO=O_quO5RxWe66me2jm>toMt!sbW zGZ{#O0P}dg-IpimOmxS>aLLLc3iBwz3QY7jxN3@xk(F~3iuxEUFpC+O>-LWPKu#*p zt3f;=w9+~csVCelr`m)B-CBINSv%Nba#ps_b@7ZIvH2qayFzqXRWw4_t+=qP>yKfU zVa^aQFG@05zB#Z3PN)sRJJ_5FwOYBhCAF!^WLT8Xq*sKS$B7lGVTpz#z!1K;#vK;} z&-Hd-3xupuq6VB^k*33xP|IOsl?i{qtBUE?W2QFDym=O1ulWg4>B8ce=E44W#dw2i)y#NxkA0@R9V>A9Kn|ApWDjGgpjwN%O z)kJoh=>}8ho^poInv}3m1O7<~i#hx=pcoP<0cXnyXQeRka#iJg^<3t2b?dr$;o9r! z`tk?MN&_hiUZT-xt7#zMmf#5-`q=L5+74v&nt*9$nR&;Yc~-ng&in~>T}`XnS4typ zia3nOI`I}!VUz2<4Ze!!PsG1k@A`-rfy6qIbluj%s&?s{IfsZ+F46r~)Y*i*$vuQ> z$EJzS5Q`lhe>zMhS{wLL*n(&&4=#lsqh06={=!Uk@Cvh>;Lq{P&ivtyV(d{GZ2}PI zgApOl=zvC_&=Nkd+jE;_8;Aa(6tNN8N%Razz1hf7&~ViS9%R+Yc$S@V%fykTsylr| zT$a3YpB7}ZB^xb@0+8G|HrJHFprR}`-8E_*w!sTMG!}e7@4gOm)J;o8$-o^lJ^P&S zv656zeH;@$Hd*@7-0fq*VesFmr$z7Cx2S~crHSOn^|?OD3hk}2RAE6cm#<_8O zCfJ1u4WI-zzDF2+`T8RGb{}dTY=zWueYp|%qu-?{_!*XX4QO4mXvAP1`nzMdwXYts z>Nwy!W3#3ZOhw2no9pD#BiYo?uu&q0$PO#llBywy-dUKMU98RZ{nd*Tf2CZ2%2Q;C zj!y!9BEnp>#RM()Oi>aREYlL^9F<`$owQB}vn9-M8)d!<^du=} zuWT7~CPIni!>w6Y^vQBnPj9n}R9tnXXfm-0T(u6U2-J`Q$5HuWEhkj;VuT$~fW>ju z`q5zjm=8F{^aSeu068HnvjlEAcstFLZyb~wu{dP0XR=So4&UCUVZfYasttL7iQTt5 zT*A?c!<0(2K;X&wO{;WI;nC!_Z#KkMq-q9q;V8lZZrkaeLS4-Vci0GH1EJjG-#vR9 zP`t3%yhw!8&&=t&sRL4eykouA`ve27=r1cX*2qO7z9$mfI)!Jvq5NMw=>LoCC-nOL z2apn&GPuS72Y!YNgHRm`gM4=bmR_I#I!Ybm*EUoG7g-TRIA5T}6Yvc| zK&}sl8Sh?5&IVVVp~F5DRXKv8lG(EE&a|t^h3E!bLtBpy9x#RJtRNsXoe3pX`w^58 zD_p1M+FS~~D*z$tHVQTy4rhiR)yC)0*=Lu>G{o*TZ z_4H@^-Z@JFBWWSCkB{L+qbyN?-dWr)$6(m)xKgX3^j3$RUY~6x+k(B!;DYlw8b&7D zRIGdTBUnOT@Cjnq&8cvj-6??)R&d*6dhT$?m18HbUp4iRf^H zyYF;T+%ttJs_J6I!p+Ujvzlq7$kH=+HtT88lnv@@4k*UOVTO*7nJCiCHDDXsi04FL z#PDtc)J?{j!wwKr1NkUCo;mW48ZxIo{u=G|qx9UqUzmS@Ig8L(LQP=xxnCHoH{NZ> zlK8XyZ8M!d8_g>Du7FnaB#(yvPk5$kxV070qB<)Lg5_jHAk5Ez8&g{V6CeiO-y&Uxtd106% znWuBjepy|@MAfXT7c3XZ52#w~h41()jU#rd@H|0*bmt;=h9*5;-FHuxm&Af%6;rhx zPeK>DnU^j~_j;8NQzpVmE`qWr{IFCu!^HZBb@d-SrI=Rj)m|@#J&kKN_&47}9GP1a zo@DRl8Nz}nb|@&YoRH=$PQ_rY=G;=0(r0EDZH4DXzFEv>Exdl)&^`8SF8#T<@TrF3 zoL<>92F4yUcbsX$8*YRh=FiEtGsKloxw_Knu6gVVg#sP1P~xygPk3rXif4{|^X>j5 zoj=v<`QTG%)7sl}KRaw?VI|AU{disnvr4+h%5N)GqO2*ZaP~-ayNb1J zzcD#>ClttXLQo1&O^o+(XYo`Su3vaWnEHR8Q#_9vA_LcR0?Us>Z2le{DnBualD~Kb zK4bkaHFhP0^^b{>&22m1!vDk(>cLsxdBG{Fv+^xZU`L%2oAg|8a6nu z|L%Vx9|Tk(!*qc@@GTQQ>^wmpo<5nj_ZY^gZB3=YX3fF5F`~fl>ZG&cdzP>lgPnab z%^L@y=a=XW_4}RPZZrp4FN(+qgJ7DAHR~|MhKD)OSWaX3!T>T&7gO>*P0h&b(W}BP z)H5)N`N2J{yX|t(iTr=J_V?CDlVn*f*?<447`u#Pq^$UEyt{b@47AF7uY`{}&RjX% z&z}Ni>#et^ko07IY0DY(O?EV~ZlNHy;{o@%K)OD9ZN{t$Ijs-PCSwH9LgeDL_4PXx zQoj4C74)?F+Ob=aMm1qt5!HQQ(sOUOs4$w;rR+l1!nzE7J?dCx$mr|uwr4*ghCc!* zo3!gJthTD{OdYfq*3f^Q}7(DvV6IEL&&cbc7_r%Yz3-S%V{MM`F@gi5Ai1TPN{cKwJIP9K~Z3I6df7s zTw)*%af*5JMlJC=msBK+xBHyt(DwUs_NoVjB=qCz(vV+q+K(%>b}YaVY!CrI#hEo8 z&ghikNpIjRSkk}k9Xo5@P;&<$5I{0iHAN|TwI0QNM_`ot`;UiXA`#p2~T1eYgl}D|CeD; z=>Em~BZWtEXuKzQyFfrpv{@FG9H3^v1P z6f+Q^0RhJqUn7ZNHWQSDXI+;s4;$rq+0P!b>p$UYi7n}^SBVzY&-sVqC?%|xE zVjv^>eQY54OCR!lK)VMuE>~zdDdy@sk}{^ahpt$>t*#{!V21BP>lt80X}Sx$>6QUl zn+Qn0dVAfFgeh{AIzy$4sxY8-P7#025r3->=4oq$B*GEH8UDc$-L)_xv|1Fk`DsA; z#0$K0KOA%k@)?tO-jlV$#>d2ITVRv=k-zL7lmpwtGM}bEk;>2-)S%mp)@g19^O0aL z2R%lKjAW6wiCjg0m_2X>%SS&jK=%PgFNCtfV~%3JDf z^%ZAKCo9jrX4;#u&m%e}oTmHANW$5F^h;&k{VmSM8AvSu?!&Yy7fO?pqxE=450sB2 z(KNlE#x7>q>d|61R?wIt+3Iwm^Rv)0GWg@x()Wp<^2A2mH}G7lhQLNyGuS4>wW_75 z;i9r&tO)nEk74!6|L?g6{yq1=@6nWL#l30yJ+n{+_XKn*WrpnOdio{~*;Y{)3#5oI zFF%VytwVpihRDcwqdwTh3>w%?v62qn-aX6>_)qyd@2437{Lp48tQ?CX2g@y9y@2HT(jc_%Y2uXpOU5IYA zB+8Y>|8B;M$KhLqAqL3Q@r3zcBy`DBkg5355joJK>Hb``!L^j|UHGo3xGJJB$!)h0 zrW}D6A5XT^ZC#k}dzqU#)>PdyDhEbBdT$l|{t<&$GEJ?sE=|)hPV()-n

    5#|r#- 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 literal 0 HcmV?d00001 diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index ffa8fa019..bc4339928 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -567,25 +567,62 @@ Drawing PostScript :: - from PIL import Image - from PIL import PSDraw + from PIL import Image, PSDraw + import os - with Image.open("hopper.ppm") as im: - title = "hopper" - box = (1 * 72, 2 * 72, 7 * 72, 10 * 72) # in points + # Define the PostScript file + ps_file = open("hopper.ps", "wb") - ps = PSDraw.PSDraw() # default is sys.stdout or sys.stdout.buffer - ps.begin_document(title) + # Create a PSDraw object + ps = PSDraw.PSDraw(ps_file) - # draw the image (75 dpi) - ps.image(box, im, 75) - ps.rectangle(box) + # Start the document + ps.begin_document() - # draw title - ps.setfont("HelveticaNarrow-Bold", 36) - ps.text((3 * 72, 4 * 72), title) + # Set the text to be drawn + text = "Hopper" - ps.end_document() + # Define the PostScript font + font_name = "Helvetica-Narrow-Bold" + font_size = 36 + + # Calculate text size (approximation as PSDraw doesn't provide direct method) + # Assuming average character width as 0.6 of the font size + text_width = len(text) * font_size * 0.6 + text_height = font_size + + # Set the position (top-center) + page_width, page_height = 595, 842 # A4 size in points + text_x = (page_width - text_width) // 2 + text_y = page_height - text_height - 50 # Distance from the top of the page + + # Load the image + image_path = os.path.join("img", "hopper.ppm") # Update this with your image path + with Image.open(image_path) as im: + # Resize the image if it's too large + im.thumbnail((page_width - 100, page_height // 2)) + + # Define the box where the image will be placed + img_width, img_height = im.size + img_x = (page_width - img_width) // 2 + img_y = text_y + text_height - 200 # 200 points below the text + + # Draw the image (75 dpi) + ps.image((img_x, img_y, img_x + img_width, img_y + img_height), im, 75) + + # Draw the text + ps.setfont(font_name, font_size) + ps.text((text_x, text_y), text) + + # End the document + ps.end_document() + ps_file.close() + +.. image:: hopper_ps.png + +.. note:: + + PostScript converted to PDF for display purposes More on reading images ---------------------- From 1cf887dbec2387d1e3228497cc12329bf84d18e5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 21 Jul 2024 05:22:13 +1000 Subject: [PATCH 50/61] Rearranged code --- Tests/test_file_libtiff.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 58ac705c9..62f8719af 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -241,10 +241,10 @@ class TestFileLibTiff(LibTiffTestCase): new_ifd = TiffImagePlugin.ImageFileDirectory_v2() for tag, info in core_items.items(): assert info.type is not None - if not info.length: - new_ifd[tag] = tuple(values[info.type] for _ in range(3)) - elif info.length == 1: + if info.length == 1: new_ifd[tag] = values[info.type] + elif not info.length: + new_ifd[tag] = tuple(values[info.type] for _ in range(3)) else: new_ifd[tag] = tuple(values[info.type] for _ in range(info.length)) From 22ef8df59abf461824e4672bba8c47137730ef57 Mon Sep 17 00:00:00 2001 From: Yay295 Date: Mon, 22 Jul 2024 00:29:55 -0500 Subject: [PATCH 51/61] Do not run scheduled wheel jobs on forks --- .github/workflows/wheels.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index ffd1abd95..3ed1b5926 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -97,6 +97,7 @@ jobs: path: ./wheelhouse/*.whl build-2-native-wheels: + if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow' name: ${{ matrix.name }} runs-on: ${{ matrix.os }} strategy: @@ -150,6 +151,7 @@ jobs: path: ./wheelhouse/*.whl windows: + if: github.event_name != 'schedule' || github.repository_owner == 'python-pillow' name: Windows ${{ matrix.cibw_arch }} runs-on: windows-latest strategy: @@ -256,7 +258,7 @@ jobs: path: dist/*.tar.gz scientific-python-nightly-wheels-publish: - if: github.event_name == 'schedule' || github.event_name == 'workflow_dispatch' + if: github.repository_owner == 'python-pillow' && (github.event_name == 'schedule' || github.event_name == 'workflow_dispatch') needs: [build-2-native-wheels, windows] runs-on: ubuntu-latest name: Upload wheels to scientific-python-nightly-wheels @@ -273,7 +275,7 @@ jobs: anaconda_nightly_upload_token: ${{ secrets.ANACONDA_ORG_UPLOAD_TOKEN }} pypi-publish: - if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags') + if: github.repository_owner == 'python-pillow' && github.event_name == 'push' && startsWith(github.ref, 'refs/tags') needs: [build-1-QEMU-emulated-wheels, build-2-native-wheels, windows, sdist] runs-on: ubuntu-latest name: Upload release to PyPI From 96fa1f5dbf43fe26a9b7abd98697ae46e8165435 Mon Sep 17 00:00:00 2001 From: "Jeffrey A. Clark (Alex)" Date: Tue, 23 Jul 2024 14:55:04 -0400 Subject: [PATCH 52/61] multiply each pixel by 20 --- docs/handbook/tutorial.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index bc4339928..4e7477143 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -412,8 +412,8 @@ Applying point transforms :: - # multiply each pixel by 1.2 - out = im.point(lambda i: i * 1.2) + # multiply each pixel by 20 + out = im.point(lambda i: i * 20) .. image:: transformed_hopper.jpg :align: center From cf6ec5e065e910d8ce5951de0d19a84879ea77c9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 24 Jul 2024 06:44:06 +1000 Subject: [PATCH 53/61] Converted images to WebP --- docs/handbook/hopper_ps.png | Bin 76966 -> 0 bytes docs/handbook/hopper_ps.webp | Bin 0 -> 8322 bytes docs/handbook/transformed_hopper.jpg | Bin 3608 -> 0 bytes docs/handbook/transformed_hopper.webp | Bin 0 -> 3054 bytes docs/handbook/tutorial.rst | 4 ++-- 5 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 docs/handbook/hopper_ps.png create mode 100644 docs/handbook/hopper_ps.webp delete mode 100644 docs/handbook/transformed_hopper.jpg create mode 100644 docs/handbook/transformed_hopper.webp diff --git a/docs/handbook/hopper_ps.png b/docs/handbook/hopper_ps.png deleted file mode 100644 index fe44e79843f1f46eb2db109d96248670bb645eed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 76966 zcmdqJWn5HS^fyjQg9ri&k}BN-Lk^7y5+XG;NJ$Lc-5n|=4H8OsmvooJ(A`}_^FMm; z{axZB#ASZ=``4kfY0Rcz)wfH*(1f)&`1ms@y z2f&juvFD2j2#C6-Vqyx?Vq%mEHc%r|b3+6Kx@h|-@M|P%!pB|!v=daEjvgU|m>&jan@^lQ^b~OgsJzfcN&M&8%$UCac=D#D>Wmyt^l2y& zV|ZJCyXKJ1-ixyVRxf~Cb-M;nYVlF#l%A&}{0zgTY!4d43B<5`C}sXM#7VNN>Ql^in!k1Q ziqx5g{Lejd@9&BtN@CK|z_*ftjiI5XtqIglD>*I`=xW^by_%icTUkB>s0EAO2dKUw zi<5=b?o|MU0PI1Qal z|D|MU`}eef39|k!VP$7wWBs4Lfu;h#pYkb~IvJX)ii>;Y&9@$#?>{Hgzcl>AHa zUQ4xqTk^2~r{%qp|J_pA*3d=_Y5|b66Z%&&f0Mmm_;*7A*55O~$BDn#{O2jaXCX`h z*8h>25av^X)#nHZ!U)phBJZ6LcN#F9G*_L^?)tK|Dw+1L8uxc}(Q&ErG1M{ok-nLJ zdYNweHE(w;Zxej;DO+b`TShw3Z8xsK6v@vozz+fADgu#|loa`adA*)S<*M>{;joF( zE&PT&c{o~tCOrH@Wd2o!l(Fkz-Sq$y+!43B0`hztzCk z+q3XC?V@GxJY#r}d_x=kYI1aeid;zOG`Tg|*IZlf_N~Sj{+l;$+4*bNXE|#_-Vd4VDh}tle6B56H}ZSAJ?}yR3T;0=rQVxH#6vnsMy+K zU}RvZ92t4Hg2$WQx0R4oz4tM&inI4NW&Prb@0IVicII`&WsQ33);IX>WBGFzQU~JHrKB z?lzAn!1LgOf`ak6IZLTX@(N!xJXnfgT|wbqPz+%lL|R&1^7+;N>Z>0(;iUXE$HrV1 zhfbuJK^hW@ZM7>pNLJ0kbovL_YgcE-o$&F)?3)wibeN79?uQ ze=px?gjq(qsJuL>$+KPl-2!H)J#kkt8Gj(wx(2qMfbBRoDcB?F8L!Q+uZf9h2B~94 z_zw~Py}I6bX+N|p3F=(tcGw|Ve{ndj0af=C>W+>-&drSD;3$VkNF=;X7wA5BF z3DSfuO-#y<2b-Er^&m0yQLUO!*!5Aane_)c&t(JB8iVigDacFSYk7NQlh7?HFZ_W) zwiS|fSSR&+(y_9f_?sO_$o;C#2{K+&aoPj~w6fwKDjg<<*=Wy6cx}G0<0gIjm-B!W zuwNjNCGptⅆ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

    5#|r#- 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 diff --git a/docs/handbook/hopper_ps.webp b/docs/handbook/hopper_ps.webp new file mode 100644 index 0000000000000000000000000000000000000000..3dd2943d68262e42c96df88647fb958413560f20 GIT binary patch literal 8322 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 T%)hKKorTfBZ2#<;FxvkBGmtjo literal 0 HcmV?d00001 diff --git a/docs/handbook/transformed_hopper.jpg b/docs/handbook/transformed_hopper.jpg deleted file mode 100644 index 33afe67e62be8f3e2922c5cfa5c0018774ac0b5c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3608 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 literal 0 HcmV?d00001 diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index 4e7477143..d8a1ae9c9 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -415,7 +415,7 @@ Applying point transforms # multiply each pixel by 20 out = im.point(lambda i: i * 20) -.. image:: transformed_hopper.jpg +.. image:: transformed_hopper.webp :align: center Using the above technique, you can quickly apply any simple expression to an @@ -618,7 +618,7 @@ Drawing PostScript ps.end_document() ps_file.close() -.. image:: hopper_ps.png +.. image:: hopper_ps.webp .. note:: From 888b2f716e0873bd7692fd1055c0b588de648352 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 24 Jul 2024 06:56:42 +1000 Subject: [PATCH 54/61] Improved consistency of example paths --- docs/handbook/tutorial.rst | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index d8a1ae9c9..fead6fb21 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -332,7 +332,7 @@ choose to resize relative to a given size. from PIL import Image, ImageOps size = (100, 150) - with Image.open("Tests/images/hopper.webp") as im: + with Image.open("hopper.webp") as im: ImageOps.contain(im, size).save("imageops_contain.webp") ImageOps.cover(im, size).save("imageops_cover.webp") ImageOps.fit(im, size).save("imageops_fit.webp") @@ -597,7 +597,7 @@ Drawing PostScript text_y = page_height - text_height - 50 # Distance from the top of the page # Load the image - image_path = os.path.join("img", "hopper.ppm") # Update this with your image path + image_path = "hopper.ppm" # Update this with your image path with Image.open(image_path) as im: # Resize the image if it's too large im.thumbnail((page_width - 100, page_height // 2)) @@ -690,7 +690,7 @@ Reading from a tar archive from PIL import Image, TarIO - fp = TarIO.TarIO("Tests/images/hopper.tar", "hopper.jpg") + fp = TarIO.TarIO("hopper.tar", "hopper.jpg") im = Image.open(fp) @@ -705,7 +705,6 @@ in the current directory can be saved as JPEGs at reduced quality. import glob from PIL import Image - def compress_image(source_path, dest_path): with Image.open(source_path) as img: if img.mode != "RGB": From 8f4dbfe6a64240f613372a6a66f2e7cc563b4251 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 24 Jul 2024 06:56:59 +1000 Subject: [PATCH 55/61] Simplified code --- docs/handbook/tutorial.rst | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index fead6fb21..c36011362 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -603,12 +603,11 @@ Drawing PostScript im.thumbnail((page_width - 100, page_height // 2)) # Define the box where the image will be placed - img_width, img_height = im.size - img_x = (page_width - img_width) // 2 + img_x = (page_width - im.width) // 2 img_y = text_y + text_height - 200 # 200 points below the text # Draw the image (75 dpi) - ps.image((img_x, img_y, img_x + img_width, img_y + img_height), im, 75) + ps.image((img_x, img_y, img_x + im.width, img_y + im.height), im, 75) # Draw the text ps.setfont(font_name, font_size) From ab635be11bfbf1ac67fdf13f50c66781a7849ca9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 24 Jul 2024 22:08:15 +1000 Subject: [PATCH 56/61] Removed unused argument --- src/PIL/ImageDraw.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 2b3620e71..ce8e66571 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -623,7 +623,7 @@ class ImageDraw: return fill_ink return ink - def draw_text(ink, stroke_width=0, stroke_offset=None) -> None: + def draw_text(ink, stroke_width=0) -> None: mode = self.fontmode if stroke_width == 0 and embedded_color: mode = "RGBA" @@ -664,8 +664,6 @@ class ImageDraw: ) except TypeError: mask = font.getmask(text) - if stroke_offset: - coord = [coord[0] + stroke_offset[0], coord[1] + stroke_offset[1]] if mode == "RGBA": # font.getmask2(mode="RGBA") returns color in RGB bands and mask in A # extract mask and set text alpha From 7a570d67bf518684a63721ae40d151d40cf2874d Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 24 Jul 2024 20:32:20 +0300 Subject: [PATCH 57/61] Remove unused _util.is_directory --- Tests/test_util.py | 22 ---------------------- src/PIL/_util.py | 5 ----- 2 files changed, 27 deletions(-) diff --git a/Tests/test_util.py b/Tests/test_util.py index 197ef79ee..9eaabf18a 100644 --- a/Tests/test_util.py +++ b/Tests/test_util.py @@ -30,28 +30,6 @@ def test_is_not_path(tmp_path: Path) -> None: assert not it_is_not -def test_is_directory() -> None: - # Arrange - directory = "Tests" - - # Act - it_is = _util.is_directory(directory) - - # Assert - assert it_is - - -def test_is_not_directory() -> None: - # Arrange - text = "abc" - - # Act - it_is_not = _util.is_directory(text) - - # Assert - assert not it_is_not - - def test_deferred_error() -> None: # Arrange diff --git a/src/PIL/_util.py b/src/PIL/_util.py index 6bc762816..8ef0d36f7 100644 --- a/src/PIL/_util.py +++ b/src/PIL/_util.py @@ -10,11 +10,6 @@ def is_path(f: Any) -> TypeGuard[StrOrBytesPath]: return isinstance(f, (bytes, str, os.PathLike)) -def is_directory(f: Any) -> TypeGuard[StrOrBytesPath]: - """Checks if an object is a string, and that it points to a directory.""" - return is_path(f) and os.path.isdir(f) - - class DeferredError: def __init__(self, ex: BaseException): self.ex = ex From 72a243c49815b95df5a64a950db6dc815c91cb88 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 24 Jul 2024 11:44:44 -0600 Subject: [PATCH 58/61] Revert "Corrected exiv2.org links" --- Tests/test_file_jpeg.py | 2 +- src/PIL/JpegPresets.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 68705094b..9660e2828 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -829,7 +829,7 @@ class TestFileJpeg: with Image.open("Tests/images/no-dpi-in-exif.jpg") as im: # Act / Assert # "When the image resolution is unknown, 72 [dpi] is designated." - # https://web.archive.org/web/20240227115053/https://exiv2.org/tags.html + # https://exiv2.org/tags.html assert im.info.get("dpi") == (72, 72) def test_invalid_exif(self) -> None: diff --git a/src/PIL/JpegPresets.py b/src/PIL/JpegPresets.py index 3aefa073c..d0e64a35e 100644 --- a/src/PIL/JpegPresets.py +++ b/src/PIL/JpegPresets.py @@ -37,7 +37,7 @@ You can get the subsampling of a JPEG with the :func:`.JpegImagePlugin.get_sampling` function. In JPEG compressed data a JPEG marker is used instead of an EXIF tag. -(ref.: https://web.archive.org/web/20240227115053/https://exiv2.org/tags.html) +(ref.: https://exiv2.org/tags.html) Quantization tables From 726cdf5eed05b2494f2948878c3f2a8c1bc9bfab Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 25 Jul 2024 22:55:49 +1000 Subject: [PATCH 59/61] Added type hints --- Tests/test_font_pcf.py | 3 +- Tests/test_imagefont.py | 4 +- docs/reference/ImageFont.rst | 8 ++ docs/reference/internal_modules.rst | 4 + pyproject.toml | 2 - src/PIL/Image.py | 68 ++++++++------ src/PIL/ImageDraw.py | 140 +++++++++++++++------------- src/PIL/ImageFont.py | 93 +++++++++++------- src/PIL/_imaging.pyi | 1 + src/PIL/_imagingft.pyi | 58 ++++++------ 10 files changed, 223 insertions(+), 158 deletions(-) diff --git a/Tests/test_font_pcf.py b/Tests/test_font_pcf.py index 997809e46..567ddaf13 100644 --- a/Tests/test_font_pcf.py +++ b/Tests/test_font_pcf.py @@ -2,6 +2,7 @@ from __future__ import annotations import os from pathlib import Path +from typing import AnyStr import pytest @@ -92,7 +93,7 @@ def test_textsize(request: pytest.FixtureRequest, tmp_path: Path) -> None: def _test_high_characters( - request: pytest.FixtureRequest, tmp_path: Path, message: str | bytes + request: pytest.FixtureRequest, tmp_path: Path, message: AnyStr ) -> None: tempname = save_font(request, tmp_path) font = ImageFont.load(tempname) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 9cb420371..340cc4742 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -717,14 +717,14 @@ def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None: font = ImageFont.truetype("Tests/fonts/AdobeVFPrototype.ttf", 36) _check_text(font, "Tests/images/variation_adobe.png", 11) - for name in ["Bold", b"Bold"]: + for name in ("Bold", b"Bold"): font.set_variation_by_name(name) assert font.getname()[1] == "Bold" _check_text(font, "Tests/images/variation_adobe_name.png", 16) font = ImageFont.truetype("Tests/fonts/TINY5x3GX.ttf", 36) _check_text(font, "Tests/images/variation_tiny.png", 40) - for name in ["200", b"200"]: + for name in ("200", b"200"): font.set_variation_by_name(name) assert font.getname()[1] == "200" _check_text(font, "Tests/images/variation_tiny_name.png", 40) diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index edbdd9a32..d9d9cac6e 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -91,3 +91,11 @@ Constants Set to 1,000,000, to protect against potential DOS attacks. Pillow will raise a :py:exc:`ValueError` if the number of characters is over this limit. The check can be disabled by setting ``ImageFont.MAX_STRING_LENGTH = None``. + +Dictionaries +------------ + +.. autoclass:: Axis + :members: + :undoc-members: + :show-inheritance: diff --git a/docs/reference/internal_modules.rst b/docs/reference/internal_modules.rst index e4cb17c4d..2fb4ff8c0 100644 --- a/docs/reference/internal_modules.rst +++ b/docs/reference/internal_modules.rst @@ -78,3 +78,7 @@ on some Python versions. An internal interface module previously known as :mod:`~PIL._imaging`, implemented in :file:`_imaging.c`. + +.. py:class:: ImagingCore + + A representation of the image data. diff --git a/pyproject.toml b/pyproject.toml index b76f3c24d..0940664f3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -159,6 +159,4 @@ exclude = [ '^Tests/oss-fuzz/fuzz_font.py$', '^Tests/oss-fuzz/fuzz_pillow.py$', '^Tests/test_qt_image_qapplication.py$', - '^Tests/test_font_pcf_charsets.py$', - '^Tests/test_font_pcf.py$', ] diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 9d901e028..aa5eeabb2 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -218,6 +218,8 @@ if hasattr(core, "DEFAULT_STRATEGY"): # Registries if TYPE_CHECKING: + from xml.etree.ElementTree import Element + from . import ImageFile, ImagePalette from ._typing import NumpyArray, StrOrBytesPath, TypeGuard ID: list[str] = [] @@ -241,9 +243,9 @@ ENCODERS: dict[str, type[ImageFile.PyEncoder]] = {} _ENDIAN = "<" if sys.byteorder == "little" else ">" -def _conv_type_shape(im): +def _conv_type_shape(im: Image) -> tuple[tuple[int, ...], str]: m = ImageMode.getmode(im.mode) - shape = (im.height, im.width) + shape: tuple[int, ...] = (im.height, im.width) extra = len(m.bands) if extra != 1: shape += (extra,) @@ -470,10 +472,10 @@ class _E: self.scale = scale self.offset = offset - def __neg__(self): + def __neg__(self) -> _E: return _E(-self.scale, -self.offset) - def __add__(self, other): + def __add__(self, other) -> _E: if isinstance(other, _E): return _E(self.scale + other.scale, self.offset + other.offset) return _E(self.scale, self.offset + other) @@ -486,14 +488,14 @@ class _E: def __rsub__(self, other): return other + -self - def __mul__(self, other): + def __mul__(self, other) -> _E: if isinstance(other, _E): return NotImplemented return _E(self.scale * other, self.offset * other) __rmul__ = __mul__ - def __truediv__(self, other): + def __truediv__(self, other) -> _E: if isinstance(other, _E): return NotImplemented return _E(self.scale / other, self.offset / other) @@ -718,9 +720,9 @@ class Image: return self._repr_image("JPEG") @property - def __array_interface__(self): + def __array_interface__(self) -> dict[str, str | bytes | int | tuple[int, ...]]: # numpy array interface support - new = {"version": 3} + new: dict[str, str | bytes | int | tuple[int, ...]] = {"version": 3} try: if self.mode == "1": # Binary images need to be extended from bits to bytes @@ -1418,7 +1420,7 @@ class Image: return out return self.im.getcolors(maxcolors) - def getdata(self, band: int | None = None): + def getdata(self, band: int | None = None) -> core.ImagingCore: """ Returns the contents of this image as a sequence object containing pixel values. The sequence object is flattened, so @@ -1467,8 +1469,8 @@ class Image: def get_name(tag: str) -> str: return re.sub("^{[^}]+}", "", tag) - def get_value(element): - value = {get_name(k): v for k, v in element.attrib.items()} + def get_value(element: Element) -> str | dict[str, Any] | None: + value: dict[str, Any] = {get_name(k): v for k, v in element.attrib.items()} children = list(element) if children: for child in children: @@ -1712,7 +1714,7 @@ class Image: return self.im.histogram(extrema) return self.im.histogram() - def entropy(self, mask=None, extrema=None): + def entropy(self, mask: Image | None = None, extrema=None): """ Calculates and returns the entropy for the image. @@ -1996,7 +1998,7 @@ class Image: def putdata( self, - data: Sequence[float] | Sequence[Sequence[int]] | NumpyArray, + data: Sequence[float] | Sequence[Sequence[int]] | core.ImagingCore | NumpyArray, scale: float = 1.0, offset: float = 0.0, ) -> None: @@ -2184,7 +2186,12 @@ class Image: return m_im - def _get_safe_box(self, size, resample, box): + def _get_safe_box( + self, + size: tuple[int, int], + resample: Resampling, + box: tuple[float, float, float, float], + ) -> tuple[int, int, int, int]: """Expands the box so it includes adjacent pixels that may be used by resampling with the given resampling filter. """ @@ -2294,7 +2301,7 @@ class Image: factor_x = int((box[2] - box[0]) / size[0] / reducing_gap) or 1 factor_y = int((box[3] - box[1]) / size[1] / reducing_gap) or 1 if factor_x > 1 or factor_y > 1: - reduce_box = self._get_safe_box(size, resample, box) + reduce_box = self._get_safe_box(size, cast(Resampling, resample), box) factor = (factor_x, factor_y) self = ( self.reduce(factor, box=reduce_box) @@ -2430,7 +2437,7 @@ class Image: 0.0, ] - def transform(x, y, matrix): + def transform(x: float, y: float, matrix: list[float]) -> tuple[float, float]: (a, b, c, d, e, f) = matrix return a * x + b * y + c, d * x + e * y + f @@ -2445,9 +2452,9 @@ class Image: xx = [] yy = [] for x, y in ((0, 0), (w, 0), (w, h), (0, h)): - x, y = transform(x, y, matrix) - xx.append(x) - yy.append(y) + transformed_x, transformed_y = transform(x, y, matrix) + xx.append(transformed_x) + yy.append(transformed_y) nw = math.ceil(max(xx)) - math.floor(min(xx)) nh = math.ceil(max(yy)) - math.floor(min(yy)) @@ -2705,7 +2712,7 @@ class Image: provided_size = tuple(map(math.floor, size)) def preserve_aspect_ratio() -> tuple[int, int] | None: - def round_aspect(number, key): + def round_aspect(number: float, key: Callable[[int], float]) -> int: return max(min(math.floor(number), math.ceil(number), key=key), 1) x, y = provided_size @@ -2849,7 +2856,13 @@ class Image: return im def __transformer( - self, box, image, method, data, resample=Resampling.NEAREST, fill=1 + self, + box: tuple[int, int, int, int], + image: Image, + method, + data, + resample: int = Resampling.NEAREST, + fill: bool = True, ): w = box[2] - box[0] h = box[3] - box[1] @@ -2899,11 +2912,12 @@ class Image: Resampling.BICUBIC, ): if resample in (Resampling.BOX, Resampling.HAMMING, Resampling.LANCZOS): - msg = { + unusable: dict[int, str] = { Resampling.BOX: "Image.Resampling.BOX", Resampling.HAMMING: "Image.Resampling.HAMMING", Resampling.LANCZOS: "Image.Resampling.LANCZOS", - }[resample] + f" ({resample}) cannot be used." + } + msg = unusable[resample] + f" ({resample}) cannot be used." else: msg = f"Unknown resampling filter ({resample})." @@ -3843,7 +3857,7 @@ class Exif(_ExifBase): print(gps_ifd[ExifTags.GPS.GPSDateStamp]) # 1999:99:99 99:99:99 """ - endian = None + endian: str | None = None bigtiff = False _loaded = False @@ -3892,7 +3906,7 @@ class Exif(_ExifBase): head += b"\x00\x00\x00\x00" return head - def load(self, data): + def load(self, data: bytes) -> None: # Extract EXIF information. This is highly experimental, # and is likely to be replaced with something better in a future # version. @@ -3911,7 +3925,7 @@ class Exif(_ExifBase): self._info = None return - self.fp = io.BytesIO(data) + self.fp: IO[bytes] = io.BytesIO(data) self.head = self.fp.read(8) # process dictionary from . import TiffImagePlugin @@ -3921,7 +3935,7 @@ class Exif(_ExifBase): self.fp.seek(self._info.next) self._info.load(self.fp) - def load_from_fp(self, fp, offset=None): + def load_from_fp(self, fp: IO[bytes], offset: int | None = None) -> None: self._loaded_exif = None self._data.clear() self._hidden_data.clear() diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index ce8e66571..6f56d0236 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -36,7 +36,7 @@ import numbers import struct from collections.abc import Sequence from types import ModuleType -from typing import TYPE_CHECKING, AnyStr, Callable, Union, cast +from typing import TYPE_CHECKING, Any, AnyStr, Callable, Union, cast from . import Image, ImageColor from ._deprecate import deprecate @@ -561,7 +561,12 @@ class ImageDraw: def _multiline_split(self, text: AnyStr) -> list[AnyStr]: return text.split("\n" if isinstance(text, str) else b"\n") - def _multiline_spacing(self, font, spacing, stroke_width): + def _multiline_spacing( + self, + font: ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont, + spacing: float, + stroke_width: float, + ) -> float: return ( self.textbbox((0, 0), "A", font, stroke_width=stroke_width)[3] + stroke_width @@ -571,25 +576,25 @@ class ImageDraw: def text( self, xy: tuple[float, float], - text: str, - fill=None, + text: AnyStr, + fill: _Ink | None = None, font: ( ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont | None ) = None, - anchor=None, - spacing=4, - align="left", - direction=None, - features=None, - language=None, - stroke_width=0, - stroke_fill=None, - embedded_color=False, - *args, - **kwargs, + anchor: str | None = None, + spacing: float = 4, + align: str = "left", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + stroke_fill: _Ink | None = None, + embedded_color: bool = False, + *args: Any, + **kwargs: Any, ) -> None: """Draw text.""" if embedded_color and self.mode not in ("RGB", "RGBA"): @@ -623,15 +628,14 @@ class ImageDraw: return fill_ink return ink - def draw_text(ink, stroke_width=0) -> None: + def draw_text(ink: int, stroke_width: float = 0) -> None: mode = self.fontmode if stroke_width == 0 and embedded_color: mode = "RGBA" coord = [] - start = [] for i in range(2): coord.append(int(xy[i])) - start.append(math.modf(xy[i])[0]) + start = (math.modf(xy[0])[0], math.modf(xy[1])[0]) try: mask, offset = font.getmask2( # type: ignore[union-attr,misc] text, @@ -697,25 +701,25 @@ class ImageDraw: def multiline_text( self, xy: tuple[float, float], - text: str, - fill=None, + text: AnyStr, + fill: _Ink | None = None, font: ( ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont | None ) = None, - anchor=None, - spacing=4, - align="left", - direction=None, - features=None, - language=None, - stroke_width=0, - stroke_fill=None, - embedded_color=False, + anchor: str | None = None, + spacing: float = 4, + align: str = "left", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + stroke_fill: _Ink | None = None, + embedded_color: bool = False, *, - font_size=None, + font_size: float | None = None, ) -> None: if direction == "ttb": msg = "ttb direction is unsupported for multiline text" @@ -788,19 +792,19 @@ class ImageDraw: def textlength( self, - text: str, + text: AnyStr, font: ( ImageFont.ImageFont | ImageFont.FreeTypeFont | ImageFont.TransposedFont | None ) = None, - direction=None, - features=None, - language=None, - embedded_color=False, + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + embedded_color: bool = False, *, - font_size=None, + font_size: float | None = None, ) -> float: """Get the length of a given string, in pixels with 1/64 precision.""" if self._multiline_check(text): @@ -817,20 +821,25 @@ class ImageDraw: def textbbox( self, - xy, - text, - font=None, - anchor=None, - spacing=4, - align="left", - direction=None, - features=None, - language=None, - stroke_width=0, - embedded_color=False, + xy: tuple[float, float], + text: AnyStr, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, + anchor: str | None = None, + spacing: float = 4, + align: str = "left", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + embedded_color: bool = False, *, - font_size=None, - ) -> tuple[int, int, int, int]: + font_size: float | None = None, + ) -> tuple[float, float, float, float]: """Get the bounding box of a given string, in pixels.""" if embedded_color and self.mode not in ("RGB", "RGBA"): msg = "Embedded color supported only in RGB and RGBA modes" @@ -862,20 +871,25 @@ class ImageDraw: def multiline_textbbox( self, - xy, - text, - font=None, - anchor=None, - spacing=4, - align="left", - direction=None, - features=None, - language=None, - stroke_width=0, - embedded_color=False, + xy: tuple[float, float], + text: AnyStr, + font: ( + ImageFont.ImageFont + | ImageFont.FreeTypeFont + | ImageFont.TransposedFont + | None + ) = None, + anchor: str | None = None, + spacing: float = 4, + align: str = "left", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + embedded_color: bool = False, *, - font_size=None, - ) -> tuple[int, int, int, int]: + font_size: float | None = None, + ) -> tuple[float, float, float, float]: if direction == "ttb": msg = "ttb direction is unsupported for multiline text" raise ValueError(msg) @@ -914,7 +928,7 @@ class ImageDraw: elif anchor[1] == "d": top -= (len(lines) - 1) * line_spacing - bbox: tuple[int, int, int, int] | None = None + bbox: tuple[float, float, float, float] | None = None for idx, line in enumerate(lines): left = xy[0] diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index d260eef69..24490348c 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -34,7 +34,7 @@ import warnings from enum import IntEnum from io import BytesIO from types import ModuleType -from typing import IO, TYPE_CHECKING, Any, BinaryIO +from typing import IO, TYPE_CHECKING, Any, BinaryIO, TypedDict from . import Image from ._typing import StrOrBytesPath @@ -46,6 +46,13 @@ if TYPE_CHECKING: from ._imagingft import Font +class Axis(TypedDict): + minimum: int | None + default: int | None + maximum: int | None + name: bytes | None + + class Layout(IntEnum): BASIC = 0 RAQM = 1 @@ -138,7 +145,9 @@ class ImageFont: self.font = Image.core.font(image.im, data) - def getmask(self, text, mode="", *args, **kwargs): + def getmask( + self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any + ) -> Image.core.ImagingCore: """ Create a bitmap for the text. @@ -236,7 +245,7 @@ class FreeTypeFont: self.layout_engine = layout_engine - def load_from_bytes(f): + def load_from_bytes(f) -> None: self.font_bytes = f.read() self.font = core.getfont( "", size, index, encoding, self.font_bytes, layout_engine @@ -283,7 +292,12 @@ class FreeTypeFont: return self.font.ascent, self.font.descent def getlength( - self, text: str | bytes, mode="", direction=None, features=None, language=None + self, + text: str | bytes, + mode: str = "", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, ) -> float: """ Returns length (in pixels with 1/64 precision) of given text when rendered @@ -424,16 +438,16 @@ class FreeTypeFont: def getmask( self, - text, - mode="", - direction=None, - features=None, - language=None, - stroke_width=0, - anchor=None, - ink=0, - start=None, - ): + text: str | bytes, + mode: str = "", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + anchor: str | None = None, + ink: int = 0, + start: tuple[float, float] | None = None, + ) -> Image.core.ImagingCore: """ Create a bitmap for the text. @@ -516,17 +530,17 @@ class FreeTypeFont: def getmask2( self, text: str | bytes, - mode="", - direction=None, - features=None, - language=None, - stroke_width=0, - anchor=None, - ink=0, - start=None, - *args, - **kwargs, - ): + mode: str = "", + direction: str | None = None, + features: list[str] | None = None, + language: str | None = None, + stroke_width: float = 0, + anchor: str | None = None, + ink: int = 0, + start: tuple[float, float] | None = None, + *args: Any, + **kwargs: Any, + ) -> tuple[Image.core.ImagingCore, tuple[int, int]]: """ Create a bitmap for the text. @@ -599,7 +613,7 @@ class FreeTypeFont: if start is None: start = (0, 0) - def fill(width, height): + def fill(width: int, height: int) -> Image.core.ImagingCore: size = (width, height) Image._decompression_bomb_check(size) return Image.core.fill("RGBA" if mode == "RGBA" else "L", size) @@ -619,8 +633,13 @@ class FreeTypeFont: ) def font_variant( - self, font=None, size=None, index=None, encoding=None, layout_engine=None - ): + self, + font: StrOrBytesPath | BinaryIO | None = None, + size: float | None = None, + index: int | None = None, + encoding: str | None = None, + layout_engine: Layout | None = None, + ) -> FreeTypeFont: """ Create a copy of this FreeTypeFont object, using any specified arguments to override the settings. @@ -655,7 +674,7 @@ class FreeTypeFont: raise NotImplementedError(msg) from e return [name.replace(b"\x00", b"") for name in names] - def set_variation_by_name(self, name): + def set_variation_by_name(self, name: str | bytes) -> None: """ :param name: The name of the style. :exception OSError: If the font is not a variation font. @@ -674,7 +693,7 @@ class FreeTypeFont: self.font.setvarname(index) - def get_variation_axes(self): + def get_variation_axes(self) -> list[Axis]: """ :returns: A list of the axes in a variation font. :exception OSError: If the font is not a variation font. @@ -704,7 +723,9 @@ class FreeTypeFont: class TransposedFont: """Wrapper for writing rotated or mirrored text""" - def __init__(self, font, orientation=None): + def __init__( + self, font: ImageFont | FreeTypeFont, orientation: Image.Transpose | None = None + ): """ Wrapper that creates a transposed font from any existing font object. @@ -718,13 +739,17 @@ class TransposedFont: self.font = font self.orientation = orientation # any 'transpose' argument, or None - def getmask(self, text, mode="", *args, **kwargs): + def getmask( + self, text: str | bytes, mode: str = "", *args: Any, **kwargs: Any + ) -> Image.core.ImagingCore: im = self.font.getmask(text, mode, *args, **kwargs) if self.orientation is not None: return im.transpose(self.orientation) return im - def getbbox(self, text, *args, **kwargs): + def getbbox( + self, text: str | bytes, *args: Any, **kwargs: Any + ) -> tuple[int, int, float, float]: # TransposedFont doesn't support getmask2, move top-left point to (0, 0) # this has no effect on ImageFont and simulates anchor="lt" for FreeTypeFont left, top, right, bottom = self.font.getbbox(text, *args, **kwargs) @@ -734,7 +759,7 @@ class TransposedFont: return 0, 0, height, width return 0, 0, width, height - def getlength(self, text: str | bytes, *args, **kwargs) -> float: + def getlength(self, text: str | bytes, *args: Any, **kwargs: Any) -> float: if self.orientation in (Image.Transpose.ROTATE_90, Image.Transpose.ROTATE_270): msg = "text length is undefined for text rotated by 90 or 270 degrees" raise ValueError(msg) diff --git a/src/PIL/_imaging.pyi b/src/PIL/_imaging.pyi index 8cccd3ac7..998bc52eb 100644 --- a/src/PIL/_imaging.pyi +++ b/src/PIL/_imaging.pyi @@ -1,6 +1,7 @@ from typing import Any class ImagingCore: + def __getitem__(self, index: int) -> float: ... def __getattr__(self, name: str) -> Any: ... class ImagingFont: diff --git a/src/PIL/_imagingft.pyi b/src/PIL/_imagingft.pyi index 5e97b40b2..9cc9822f5 100644 --- a/src/PIL/_imagingft.pyi +++ b/src/PIL/_imagingft.pyi @@ -1,12 +1,6 @@ -from typing import Any, TypedDict +from typing import Any, Callable -from . import _imaging - -class _Axis(TypedDict): - minimum: int | None - default: int | None - maximum: int | None - name: bytes | None +from . import ImageFont, _imaging class Font: @property @@ -28,42 +22,48 @@ class Font: def render( self, string: str | bytes, - fill, - mode=..., - dir=..., - features=..., - lang=..., - stroke_width=..., - anchor=..., - foreground_ink_long=..., - x_start=..., - y_start=..., + fill: Callable[[int, int], _imaging.ImagingCore], + mode: str, + dir: str | None, + features: list[str] | None, + lang: str | None, + stroke_width: float, + anchor: str | None, + foreground_ink_long: int, + x_start: float, + y_start: float, /, ) -> tuple[_imaging.ImagingCore, tuple[int, int]]: ... def getsize( self, string: str | bytes | bytearray, - mode=..., - dir=..., - features=..., - lang=..., - anchor=..., + mode: str, + dir: str | None, + features: list[str] | None, + lang: str | None, + anchor: str | None, /, ) -> tuple[tuple[int, int], tuple[int, int]]: ... def getlength( - self, string: str | bytes, mode=..., dir=..., features=..., lang=..., / + self, + string: str | bytes, + mode: str, + dir: str | None, + features: list[str] | None, + lang: str | None, + /, ) -> float: ... def getvarnames(self) -> list[bytes]: ... - def getvaraxes(self) -> list[_Axis] | None: ... + def getvaraxes(self) -> list[ImageFont.Axis]: ... def setvarname(self, instance_index: int, /) -> None: ... def setvaraxes(self, axes: list[float], /) -> None: ... def getfont( filename: str | bytes, size: float, - index=..., - encoding=..., - font_bytes=..., - layout_engine=..., + index: int, + encoding: str, + font_bytes: bytes, + layout_engine: int, ) -> Font: ... def __getattr__(name: str) -> Any: ... From 046285ac5d0998ec8c80e4cf70192dcdf4753ec4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 26 Jul 2024 16:42:28 +1000 Subject: [PATCH 60/61] Added type hints --- Tests/test_file_iptc.py | 1 + src/PIL/Image.py | 14 +++++------ src/PIL/ImageDraw2.py | 49 ++++++++++++++++++++++---------------- src/PIL/ImageQt.py | 2 +- src/PIL/IptcImagePlugin.py | 4 +++- src/PIL/TiffImagePlugin.py | 24 ++++++++++--------- 6 files changed, 53 insertions(+), 41 deletions(-) diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index 88c30d468..b0ea2bf42 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -57,6 +57,7 @@ def test_getiptcinfo_fotostation() -> None: iptc = IptcImagePlugin.getiptcinfo(im) # Assert + assert iptc is not None for tag in iptc.keys(): if tag[0] == 240: return diff --git a/src/PIL/Image.py b/src/PIL/Image.py index aa5eeabb2..72d167a8a 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -468,40 +468,40 @@ def _getencoder( class _E: - def __init__(self, scale, offset) -> None: + def __init__(self, scale: float, offset: float) -> None: self.scale = scale self.offset = offset def __neg__(self) -> _E: return _E(-self.scale, -self.offset) - def __add__(self, other) -> _E: + def __add__(self, other: _E | float) -> _E: if isinstance(other, _E): return _E(self.scale + other.scale, self.offset + other.offset) return _E(self.scale, self.offset + other) __radd__ = __add__ - def __sub__(self, other): + def __sub__(self, other: _E | float) -> _E: return self + -other - def __rsub__(self, other): + def __rsub__(self, other: _E | float) -> _E: return other + -self - def __mul__(self, other) -> _E: + def __mul__(self, other: _E | float) -> _E: if isinstance(other, _E): return NotImplemented return _E(self.scale * other, self.offset * other) __rmul__ = __mul__ - def __truediv__(self, other) -> _E: + def __truediv__(self, other: _E | float) -> _E: if isinstance(other, _E): return NotImplemented return _E(self.scale / other, self.offset / other) -def _getscaleoffset(expr): +def _getscaleoffset(expr) -> tuple[float, float]: a = expr(_E(1, 0)) return (a.scale, a.offset) if isinstance(a, _E) else (0, a) diff --git a/src/PIL/ImageDraw2.py b/src/PIL/ImageDraw2.py index e89a78be4..c0d89e4aa 100644 --- a/src/PIL/ImageDraw2.py +++ b/src/PIL/ImageDraw2.py @@ -24,10 +24,10 @@ """ from __future__ import annotations -from typing import BinaryIO +from typing import AnyStr, BinaryIO from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath -from ._typing import StrOrBytesPath +from ._typing import Coords, StrOrBytesPath class Pen: @@ -74,12 +74,14 @@ class Draw: image = Image.new(image, size, color) self.draw = ImageDraw.Draw(image) self.image = image - self.transform = None + self.transform: tuple[float, float, float, float, float, float] | None = None def flush(self) -> Image.Image: return self.image - def render(self, op, xy, pen, brush=None): + def render( + self, op: str, xy: Coords, pen: Pen | Brush, brush: Brush | Pen | None = None + ) -> None: # handle color arguments outline = fill = None width = 1 @@ -95,20 +97,21 @@ class Draw: fill = pen.color # handle transformation if self.transform: - xy = ImagePath.Path(xy) - xy.transform(self.transform) + path = ImagePath.Path(xy) + path.transform(self.transform) + xy = path # render the item if op == "line": self.draw.line(xy, fill=outline, width=width) else: getattr(self.draw, op)(xy, fill=fill, outline=outline) - def settransform(self, offset): + def settransform(self, offset: tuple[float, float]) -> None: """Sets a transformation offset.""" (xoffset, yoffset) = offset self.transform = (1, 0, xoffset, 0, 1, yoffset) - def arc(self, xy, start, end, *options): + def arc(self, xy: Coords, start, end, *options) -> None: """ Draws an arc (a portion of a circle outline) between the start and end angles, inside the given bounding box. @@ -117,7 +120,7 @@ class Draw: """ self.render("arc", xy, start, end, *options) - def chord(self, xy, start, end, *options): + def chord(self, xy: Coords, start, end, *options) -> None: """ Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points with a straight line. @@ -126,7 +129,7 @@ class Draw: """ self.render("chord", xy, start, end, *options) - def ellipse(self, xy, *options): + def ellipse(self, xy: Coords, *options) -> None: """ Draws an ellipse inside the given bounding box. @@ -134,7 +137,7 @@ class Draw: """ self.render("ellipse", xy, *options) - def line(self, xy, *options): + def line(self, xy: Coords, *options) -> None: """ Draws a line between the coordinates in the ``xy`` list. @@ -142,7 +145,7 @@ class Draw: """ self.render("line", xy, *options) - def pieslice(self, xy, start, end, *options): + def pieslice(self, xy: Coords, start, end, *options) -> None: """ Same as arc, but also draws straight lines between the end points and the center of the bounding box. @@ -151,7 +154,7 @@ class Draw: """ self.render("pieslice", xy, start, end, *options) - def polygon(self, xy, *options): + def polygon(self, xy: Coords, *options) -> None: """ Draws a polygon. @@ -164,7 +167,7 @@ class Draw: """ self.render("polygon", xy, *options) - def rectangle(self, xy, *options): + def rectangle(self, xy: Coords, *options) -> None: """ Draws a rectangle. @@ -172,18 +175,21 @@ class Draw: """ self.render("rectangle", xy, *options) - def text(self, xy, text, font): + def text(self, xy: tuple[float, float], text: AnyStr, font: Font) -> None: """ Draws the string at the given position. .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.text` """ if self.transform: - xy = ImagePath.Path(xy) - xy.transform(self.transform) + path = ImagePath.Path(xy) + path.transform(self.transform) + xy = path self.draw.text(xy, text, font=font.font, fill=font.color) - def textbbox(self, xy, text, font): + def textbbox( + self, xy: tuple[float, float], text: AnyStr, font: Font + ) -> tuple[float, float, float, float]: """ Returns bounding box (in pixels) of given text. @@ -192,11 +198,12 @@ class Draw: .. seealso:: :py:meth:`PIL.ImageDraw.ImageDraw.textbbox` """ if self.transform: - xy = ImagePath.Path(xy) - xy.transform(self.transform) + path = ImagePath.Path(xy) + path.transform(self.transform) + xy = path return self.draw.textbbox(xy, text, font=font.font) - def textlength(self, text, font): + def textlength(self, text: AnyStr, font: Font) -> float: """ Returns length (in pixels) of given text. This is the amount by which following text should be offset. diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 346fe49d3..a2d946714 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -58,7 +58,7 @@ else: qt_version = None -def rgb(r, g, b, a=255): +def rgb(r: int, g: int, b: int, a: int = 255) -> int: """(Internal) Turns an RGB color into a Qt compatible color integer.""" # use qRgb to pack the colors, and then turn the resulting long # into a negative integer with the same bitpattern. diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 16a18ddfa..17243e705 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -185,7 +185,9 @@ Image.register_open(IptcImageFile.format, IptcImageFile) Image.register_extension(IptcImageFile.format, ".iim") -def getiptcinfo(im: ImageFile.ImageFile): +def getiptcinfo( + im: ImageFile.ImageFile, +) -> dict[tuple[int, int], bytes | list[bytes]] | None: """ Get IPTC information from TIFF, JPEG, or IPTC file. diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 1dab0d50b..ad050ffe4 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -584,8 +584,10 @@ class ImageFileDirectory_v2(_IFDv2Base): self.tagtype: dict[int, int] = {} """ Dictionary of tag types """ self.reset() - (self.next,) = ( - self._unpack("Q", ifh[8:]) if self._bigtiff else self._unpack("L", ifh[4:]) + self.next = ( + self._unpack("Q", ifh[8:])[0] + if self._bigtiff + else self._unpack("L", ifh[4:])[0] ) self._legacy_api = False @@ -643,7 +645,7 @@ class ImageFileDirectory_v2(_IFDv2Base): def __setitem__(self, tag: int, value) -> None: self._setitem(tag, value, self.legacy_api) - def _setitem(self, tag, value, legacy_api) -> None: + def _setitem(self, tag: int, value, legacy_api: bool) -> None: basetypes = (Number, bytes, str) info = TiffTags.lookup(tag, self.group) @@ -731,7 +733,7 @@ class ImageFileDirectory_v2(_IFDv2Base): def __iter__(self): return iter(set(self._tagdata) | set(self._tags_v2)) - def _unpack(self, fmt: str, data): + def _unpack(self, fmt: str, data: bytes): return struct.unpack(self._endian + fmt, data) def _pack(self, fmt: str, *values): @@ -755,11 +757,11 @@ class ImageFileDirectory_v2(_IFDv2Base): ) @_register_loader(1, 1) # Basic type, except for the legacy API. - def load_byte(self, data, legacy_api: bool = True): + def load_byte(self, data: bytes, legacy_api: bool = True) -> bytes: return data @_register_writer(1) # Basic type, except for the legacy API. - def write_byte(self, data) -> bytes: + def write_byte(self, data: bytes | int | IFDRational) -> bytes: if isinstance(data, IFDRational): data = int(data) if isinstance(data, int): @@ -773,7 +775,7 @@ class ImageFileDirectory_v2(_IFDv2Base): return data.decode("latin-1", "replace") @_register_writer(2) - def write_string(self, value) -> bytes: + def write_string(self, value: str | bytes | int) -> bytes: # remerge of https://github.com/python-pillow/Pillow/pull/1416 if isinstance(value, int): value = str(value) @@ -782,7 +784,7 @@ class ImageFileDirectory_v2(_IFDv2Base): return value + b"\0" @_register_loader(5, 8) - def load_rational(self, data, legacy_api=True): + def load_rational(self, data, legacy_api: bool = True): vals = self._unpack(f"{len(data) // 4}L", data) def combine(a, b): @@ -797,11 +799,11 @@ class ImageFileDirectory_v2(_IFDv2Base): ) @_register_loader(7, 1) - def load_undefined(self, data, legacy_api: bool = True): + def load_undefined(self, data: bytes, legacy_api: bool = True) -> bytes: return data @_register_writer(7) - def write_undefined(self, value) -> bytes: + def write_undefined(self, value: bytes | int | IFDRational) -> bytes: if isinstance(value, IFDRational): value = int(value) if isinstance(value, int): @@ -809,7 +811,7 @@ class ImageFileDirectory_v2(_IFDv2Base): return value @_register_loader(10, 8) - def load_signed_rational(self, data, legacy_api: bool = True): + def load_signed_rational(self, data: bytes, legacy_api: bool = True): vals = self._unpack(f"{len(data) // 4}l", data) def combine(a, b): From c85eb0cae5568ba9286ac15c3e8f1709f7bbe9e0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Jul 2024 12:53:02 +1000 Subject: [PATCH 61/61] Added type hints --- Tests/test_file_png.py | 2 ++ src/PIL/Image.py | 30 +++++++++++++++++------------- src/PIL/ImageDraw2.py | 14 +++++++------- src/PIL/PngImagePlugin.py | 38 +++++++++++++++++++------------------- src/PIL/TiffImagePlugin.py | 8 +++++--- 5 files changed, 50 insertions(+), 42 deletions(-) diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 4958b2222..0abf9866f 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -424,8 +424,10 @@ class TestFilePng: im = roundtrip(im, pnginfo=info) assert im.info == {"spam": "Eggs", "eggs": "Spam"} assert im.text == {"spam": "Eggs", "eggs": "Spam"} + assert isinstance(im.text["spam"], PngImagePlugin.iTXt) assert im.text["spam"].lang == "en" assert im.text["spam"].tkey == "Spam" + assert isinstance(im.text["eggs"], PngImagePlugin.iTXt) assert im.text["eggs"].lang == "en" assert im.text["eggs"].tkey == "Eggs" diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 72d167a8a..eb3dd5a36 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -38,7 +38,7 @@ import struct import sys import tempfile import warnings -from collections.abc import Callable, MutableMapping, Sequence +from collections.abc import Callable, Iterator, MutableMapping, Sequence from enum import IntEnum from types import ModuleType from typing import ( @@ -744,11 +744,11 @@ class Image: new["shape"], new["typestr"] = _conv_type_shape(self) return new - def __getstate__(self): + def __getstate__(self) -> list[Any]: im_data = self.tobytes() # load image first return [self.info, self.mode, self.size, self.getpalette(), im_data] - def __setstate__(self, state) -> None: + def __setstate__(self, state: list[Any]) -> None: Image.__init__(self) info, mode, size, palette, data = state self.info = info @@ -1683,7 +1683,9 @@ class Image: x, y = self.im.getprojection() return list(x), list(y) - def histogram(self, mask: Image | None = None, extrema=None) -> list[int]: + def histogram( + self, mask: Image | None = None, extrema: tuple[float, float] | None = None + ) -> list[int]: """ Returns a histogram for the image. The histogram is returned as a list of pixel counts, one for each pixel value in the source @@ -1709,12 +1711,14 @@ class Image: mask.load() return self.im.histogram((0, 0), mask.im) if self.mode in ("I", "F"): - if extrema is None: - extrema = self.getextrema() - return self.im.histogram(extrema) + return self.im.histogram( + extrema if extrema is not None else self.getextrema() + ) return self.im.histogram() - def entropy(self, mask: Image | None = None, extrema=None): + def entropy( + self, mask: Image | None = None, extrema: tuple[float, float] | None = None + ) -> float: """ Calculates and returns the entropy for the image. @@ -1735,9 +1739,9 @@ class Image: mask.load() return self.im.entropy((0, 0), mask.im) if self.mode in ("I", "F"): - if extrema is None: - extrema = self.getextrema() - return self.im.entropy(extrema) + return self.im.entropy( + extrema if extrema is not None else self.getextrema() + ) return self.im.entropy() def paste( @@ -3881,7 +3885,7 @@ class Exif(_ExifBase): # returns a dict with any single item tuples/lists as individual values return {k: self._fixup(v) for k, v in src_dict.items()} - def _get_ifd_dict(self, offset: int, group=None): + def _get_ifd_dict(self, offset: int, group: int | None = None): try: # an offset pointer to the location of the nested embedded IFD. # It should be a long, but may be corrupted. @@ -4136,7 +4140,7 @@ class Exif(_ExifBase): else: del self._data[tag] - def __iter__(self): + def __iter__(self) -> Iterator[int]: keys = set(self._data) if self._info is not None: keys.update(self._info) diff --git a/src/PIL/ImageDraw2.py b/src/PIL/ImageDraw2.py index c0d89e4aa..1ad239382 100644 --- a/src/PIL/ImageDraw2.py +++ b/src/PIL/ImageDraw2.py @@ -24,7 +24,7 @@ """ from __future__ import annotations -from typing import AnyStr, BinaryIO +from typing import Any, AnyStr, BinaryIO from . import Image, ImageColor, ImageDraw, ImageFont, ImagePath from ._typing import Coords, StrOrBytesPath @@ -111,7 +111,7 @@ class Draw: (xoffset, yoffset) = offset self.transform = (1, 0, xoffset, 0, 1, yoffset) - def arc(self, xy: Coords, start, end, *options) -> None: + def arc(self, xy: Coords, start, end, *options: Any) -> None: """ Draws an arc (a portion of a circle outline) between the start and end angles, inside the given bounding box. @@ -120,7 +120,7 @@ class Draw: """ self.render("arc", xy, start, end, *options) - def chord(self, xy: Coords, start, end, *options) -> None: + def chord(self, xy: Coords, start, end, *options: Any) -> None: """ Same as :py:meth:`~PIL.ImageDraw2.Draw.arc`, but connects the end points with a straight line. @@ -129,7 +129,7 @@ class Draw: """ self.render("chord", xy, start, end, *options) - def ellipse(self, xy: Coords, *options) -> None: + def ellipse(self, xy: Coords, *options: Any) -> None: """ Draws an ellipse inside the given bounding box. @@ -137,7 +137,7 @@ class Draw: """ self.render("ellipse", xy, *options) - def line(self, xy: Coords, *options) -> None: + def line(self, xy: Coords, *options: Any) -> None: """ Draws a line between the coordinates in the ``xy`` list. @@ -145,7 +145,7 @@ class Draw: """ self.render("line", xy, *options) - def pieslice(self, xy: Coords, start, end, *options) -> None: + def pieslice(self, xy: Coords, start, end, *options: Any) -> None: """ Same as arc, but also draws straight lines between the end points and the center of the bounding box. @@ -154,7 +154,7 @@ class Draw: """ self.render("pieslice", xy, start, end, *options) - def polygon(self, xy: Coords, *options) -> None: + def polygon(self, xy: Coords, *options: Any) -> None: """ Draws a polygon. diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 58db7777c..52cfcd13a 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -38,6 +38,7 @@ import re import struct import warnings import zlib +from collections.abc import Callable from enum import IntEnum from typing import IO, TYPE_CHECKING, Any, NamedTuple, NoReturn @@ -135,7 +136,7 @@ class Blend(IntEnum): """ -def _safe_zlib_decompress(s): +def _safe_zlib_decompress(s: bytes) -> bytes: dobj = zlib.decompressobj() plaintext = dobj.decompress(s, MAX_TEXT_CHUNK) if dobj.unconsumed_tail: @@ -783,7 +784,7 @@ class PngImageFile(ImageFile.ImageFile): self._mode = self.png.im_mode self._size = self.png.im_size self.info = self.png.im_info - self._text = None + self._text: dict[str, str | iTXt] | None = None self.tile = self.png.im_tile self.custom_mimetype = self.png.im_custom_mimetype self.n_frames = self.png.im_n_frames or 1 @@ -810,7 +811,7 @@ class PngImageFile(ImageFile.ImageFile): self.is_animated = self.n_frames > 1 @property - def text(self): + def text(self) -> dict[str, str | iTXt]: # experimental if self._text is None: # iTxt, tEXt and zTXt chunks may appear at the end of the file @@ -822,6 +823,7 @@ class PngImageFile(ImageFile.ImageFile): self.load() if self.is_animated: self.seek(frame) + assert self._text is not None return self._text def verify(self) -> None: @@ -1105,7 +1107,7 @@ def putchunk(fp: IO[bytes], cid: bytes, *data: bytes) -> None: class _idat: # wrap output from the encoder in IDAT chunks - def __init__(self, fp, chunk) -> None: + def __init__(self, fp: IO[bytes], chunk: Callable[..., None]) -> None: self.fp = fp self.chunk = chunk @@ -1116,7 +1118,7 @@ class _idat: class _fdat: # wrap encoder output in fdAT chunks - def __init__(self, fp: IO[bytes], chunk, seq_num: int) -> None: + def __init__(self, fp: IO[bytes], chunk: Callable[..., None], seq_num: int) -> None: self.fp = fp self.chunk = chunk self.seq_num = seq_num @@ -1135,7 +1137,7 @@ class _Frame(NamedTuple): def _write_multiple_frames( im: Image.Image, fp: IO[bytes], - chunk, + chunk: Callable[..., None], mode: str, rawmode: str, default_image: Image.Image | None, @@ -1275,7 +1277,11 @@ def _save_all(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: def _save( - im: Image.Image, fp, filename: str | bytes, chunk=putchunk, save_all: bool = False + im: Image.Image, + fp: IO[bytes], + filename: str | bytes, + chunk: Callable[..., None] = putchunk, + save_all: bool = False, ) -> None: # save an image to disk (called by the save method) @@ -1483,22 +1489,16 @@ def _save( def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes]]: """Return a list of PNG chunks representing this image.""" + from io import BytesIO - class collector: - data = [] + chunks = [] - def write(self, data: bytes) -> None: - pass - - def append(self, chunk: tuple[bytes, bytes, bytes]) -> None: - self.data.append(chunk) - - def append(fp: collector, cid: bytes, *data: bytes) -> None: + def append(fp: IO[bytes], cid: bytes, *data: bytes) -> None: byte_data = b"".join(data) crc = o32(_crc32(byte_data, _crc32(cid))) - fp.append((cid, byte_data, crc)) + chunks.append((cid, byte_data, crc)) - fp = collector() + fp = BytesIO() try: im.encoderinfo = params @@ -1506,7 +1506,7 @@ def getchunks(im: Image.Image, **params: Any) -> list[tuple[bytes, bytes, bytes] finally: del im.encoderinfo - return fp.data + return chunks # -------------------------------------------------------------------- diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index ad050ffe4..318e9825c 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1020,7 +1020,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): .. deprecated:: 3.0.0 """ - def __init__(self, *args, **kwargs) -> None: + def __init__(self, *args: Any, **kwargs: Any) -> None: super().__init__(*args, **kwargs) self._legacy_api = True @@ -1142,13 +1142,15 @@ class TiffImageFile(ImageFile.ImageFile): self._seek(0) @property - def n_frames(self): - if self._n_frames is None: + def n_frames(self) -> int: + current_n_frames = self._n_frames + if current_n_frames is None: current = self.tell() self._seek(len(self._frame_pos)) while self._n_frames is None: self._seek(self.tell() + 1) self.seek(current) + assert self._n_frames is not None return self._n_frames def seek(self, frame: int) -> None: