diff --git a/.appveyor.yml b/.appveyor.yml index cc4d56d0bc9..0f5dea9c515 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -10,7 +10,7 @@ environment: TEST_OPTIONS: DEPLOY: YES matrix: - - PYTHON: C:/Python311 + - PYTHON: C:/Python312 ARCHITECTURE: x86 APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2022 - PYTHON: C:/Python38-x64 @@ -43,7 +43,7 @@ build_script: test_script: - cd c:\pillow -- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout' +- '%PYTHON%\%EXECUTABLE% -m pip install pytest pytest-cov pytest-timeout defusedxml numpy olefile pyroma' - c:\"Program Files (x86)"\"Windows Kits"\10\Debuggers\x86\gflags.exe /p /enable %PYTHON%\%EXECUTABLE% - '%PYTHON%\%EXECUTABLE% -c "from PIL import Image"' - '%PYTHON%\%EXECUTABLE% -m pytest -vx --cov PIL --cov Tests --cov-report term --cov-report xml Tests' diff --git a/.ci/install.sh b/.ci/install.sh index cd9035f6a6e..30b64349d70 100755 --- a/.ci/install.sh +++ b/.ci/install.sh @@ -28,7 +28,8 @@ fi python3 -m pip install --upgrade pip python3 -m pip install --upgrade wheel -PYTHONOPTIMIZE=0 python3 -m pip install cffi +# TODO Update condition when cffi supports 3.13 +if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi python3 -m pip install coverage python3 -m pip install defusedxml python3 -m pip install olefile @@ -38,7 +39,8 @@ 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 3.13 + if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi # PyQt6 doesn't support PyPy3 if [[ $GHA_PYTHON_VERSION == 3.* ]]; then diff --git a/.editorconfig b/.editorconfig index d74549fe2ac..c3627ae4fad 100644 --- a/.editorconfig +++ b/.editorconfig @@ -13,7 +13,7 @@ indent_style = space trim_trailing_whitespace = true -[*.yml] +[*.{toml,yml}] # Two-space indentation indent_size = 2 diff --git a/.flake8 b/.flake8 deleted file mode 100644 index e19c0a58536..00000000000 --- a/.flake8 +++ /dev/null @@ -1,3 +0,0 @@ -[flake8] -extend-ignore = E203 -max-line-length = 88 diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index a20838a1507..f41324c4ba6 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -5,7 +5,9 @@ set -e brew install libtiff libjpeg openjpeg libimagequant webp little-cms2 freetype libraqm export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig" -PYTHONOPTIMIZE=0 python3 -m pip install cffi +# TODO Update condition when cffi supports 3.13 +if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then PYTHONOPTIMIZE=0 python3 -m pip install cffi ; fi + python3 -m pip install coverage python3 -m pip install defusedxml python3 -m pip install olefile @@ -14,7 +16,8 @@ python3 -m pip install -U pytest-cov python3 -m pip install -U pytest-timeout python3 -m pip install pyroma -python3 -m pip install numpy +# TODO Update condition when NumPy supports 3.13 +if ! [[ "$GHA_PYTHON_VERSION" == "3.13" ]]; then python3 -m pip install numpy ; fi # extra test images pushd depends && ./install_extra_test_images.sh && popd diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index c8fd69ba045..eb27b4bf75b 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -51,8 +51,8 @@ jobs: debian-11-bullseye-amd64, debian-12-bookworm-x86, debian-12-bookworm-amd64, - fedora-37-amd64, fedora-38-amd64, + fedora-39-amd64, gentoo, ubuntu-20.04-focal-amd64, ubuntu-22.04-jammy-amd64, diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 3d7ec8e6720..887c7dd9670 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -32,7 +32,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: ["pypy3.10", "pypy3.9", "3.8", "3.9", "3.10", "3.11", "3.12", "3.13"] timeout-minutes: 30 @@ -59,14 +59,15 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true cache: pip cache-dependency-path: ".github/workflows/test-windows.yml" - name: Print build system information run: python3 .github/workflows/system-info.py - - name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml - run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml + - name: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma + run: python3 -m pip install pytest pytest-cov pytest-timeout defusedxml olefile pyroma - name: Install dependencies id: install diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 201f9ef7768..33dc561e561 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,6 +41,7 @@ jobs: python-version: [ "pypy3.10", "pypy3.9", + "3.13", "3.12", "3.11", "3.10", @@ -64,6 +65,7 @@ jobs: uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true cache: pip cache-dependency-path: ".ci/*.sh" diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 4381a985697..bcffe6839c0 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -20,21 +20,43 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true +env: + FORCE_COLOR: 1 + jobs: macos: uses: ./.github/workflows/wheels-macos.yml with: - artifacts-name: "wheels" + artifacts-name: "dist" linux: uses: ./.github/workflows/wheels-linux.yml with: - artifacts-name: "wheels" + artifacts-name: "dist" + + sdist: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: "3.x" + cache: pip + cache-dependency-path: "Makefile" + + - run: make sdist + + - uses: actions/upload-artifact@v3 + with: + name: dist + path: dist/*.tar.gz success: permissions: contents: none - needs: [macos, linux] + needs: [macos, linux, sdist] runs-on: ubuntu-latest name: Wheels Successful steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index a8c7696df62..8b2dc06ae91 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,20 +1,14 @@ repos: - - repo: https://github.com/asottile/pyupgrade - rev: v3.13.0 + - repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.1.4 hooks: - - id: pyupgrade - args: [--py38-plus] + - id: ruff + args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 23.9.1 + rev: 23.10.1 hooks: - id: black - args: [--target-version=py38] - - - repo: https://github.com/PyCQA/isort - rev: 5.12.0 - hooks: - - id: isort - repo: https://github.com/PyCQA/bandit rev: 1.7.5 @@ -23,32 +17,19 @@ repos: args: [--severity-level=high] files: ^src/ - - repo: https://github.com/asottile/yesqa - rev: v1.5.0 - hooks: - - id: yesqa - - repo: https://github.com/Lucas-C/pre-commit-hooks rev: v1.5.4 hooks: - id: remove-tabs exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) - - repo: https://github.com/PyCQA/flake8 - rev: 6.1.0 - hooks: - - id: flake8 - additional_dependencies: - [flake8-2020, flake8-errmsg, flake8-implicit-str-concat, flake8-logging] - - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: - - id: python-check-blanket-noqa - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 + rev: v4.5.0 hooks: - id: check-executables-have-shebangs - id: check-merge-conflict @@ -61,17 +42,17 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ - repo: https://github.com/sphinx-contrib/sphinx-lint - rev: v0.6.8 + rev: v0.8.1 hooks: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: 1.2.0 + rev: 1.4.1 hooks: - id: pyproject-fmt - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.14 + rev: v0.15 hooks: - id: validate-pyproject diff --git a/CHANGES.rst b/CHANGES.rst index f4d11ba48e6..6a80cb25bf0 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,6 +2,24 @@ Changelog (Pillow) ================== +10.2.0 (unreleased) +------------------- + +- Allow configuring JPEG restart marker interval on save #7488 + [bgilbert, radarhere] + +- Decrement reference count for PyObject #7549 + [radarhere] + +- Implement ``streamtype=1`` option for tables-only JPEG encoding #7491 + [bgilbert, radarhere] + +- If save_all PNG only has one frame, do not create animated image #7522 + [radarhere] + +- Fixed frombytes() for images with a zero dimension #7493 + [radarhere] + 10.1.0 (2023-10-15) ------------------- diff --git a/MANIFEST.in b/MANIFEST.in index 9401ebbbf37..af25dfd2db5 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,6 +5,7 @@ include *.md include *.py include *.rst include *.sh +include *.toml include *.txt include *.yaml include .flake8 diff --git a/Makefile b/Makefile index 57d756b47e3..ad0a1adab50 100644 --- a/Makefile +++ b/Makefile @@ -49,7 +49,7 @@ help: @echo " install make and install" @echo " install-coverage make and install with C coverage" @echo " lint run the lint checks" - @echo " lint-fix run Black and isort to (mostly) fix lint issues" + @echo " lint-fix run Ruff to (mostly) fix lint issues" @echo " release-test run code and package tests before release" @echo " test run tests on installed Pillow" @@ -118,6 +118,6 @@ lint: .PHONY: lint-fix lint-fix: python3 -c "import black" > /dev/null 2>&1 || python3 -m pip install black - python3 -c "import isort" > /dev/null 2>&1 || python3 -m pip install isort - python3 -m black --target-version py38 . - python3 -m isort . + python3 -m black . + python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff + python3 -m ruff --fix . diff --git a/RELEASING.md b/RELEASING.md index 02551a3a9da..8b067320378 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -20,12 +20,8 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th. git tag 5.2.0 git push --tags ``` -* [ ] Create and check source distribution: - ```bash - make sdist - ``` -* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) -* [ ] Check and upload all binaries and source distributions e.g.: +* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions) +* [ ] Check and upload all source and binary distributions e.g.: ```bash python3 -m twine check --strict dist/* python3 -m twine upload dist/Pillow-5.2.0* @@ -59,8 +55,8 @@ Released as needed for security, installation or critical bug fixes. ```bash make sdist ``` -* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) -* [ ] Check and upload all binaries and source distributions e.g.: +* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions) +* [ ] Check and upload all source and binary distributions e.g.: ```bash python3 -m twine check --strict dist/* python3 -m twine upload dist/Pillow-5.2.1* @@ -90,20 +86,20 @@ Released as needed privately to individual vendors for critical security-related ```bash make sdist ``` -* [ ] Create [binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#binary-distributions) +* [ ] Create [source and binary distributions](https://github.com/python-pillow/Pillow/blob/main/RELEASING.md#source-and-binary-distributions) * [ ] Publish the [release on GitHub](https://github.com/python-pillow/Pillow/releases) and then: ```bash git push origin 2.5.x ``` -## Binary Distributions +## Source and Binary Distributions ### macOS and Linux -* [ ] Download wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) +* [ ] Download sdist and wheels from the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli): ```bash gh run download --dir dist - # select wheels + # select dist ``` * [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases) and copy into `dist`. diff --git a/Tests/bench_cffi_access.py b/Tests/bench_cffi_access.py index 69ebef9b458..36ce63296e4 100644 --- a/Tests/bench_cffi_access.py +++ b/Tests/bench_cffi_access.py @@ -45,7 +45,7 @@ def test_direct(): assert caccess[(0, 0)] == access[(0, 0)] - print("Size: %sx%s" % im.size) + print(f"Size: {im.width}x{im.height}") timer(iterate_get, "PyAccess - get", im.size, access) timer(iterate_set, "PyAccess - set", im.size, access) timer(iterate_get, "C-api - get", im.size, caccess) diff --git a/Tests/helper.py b/Tests/helper.py index de5468d8463..aacd9556493 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -95,7 +95,7 @@ def assert_image_equal(a, b, msg=None): except Exception: pass - assert False, msg or "got different content" + pytest.fail(msg or "got different content") def assert_image_equal_tofile(a, filename, msg=None, mode=None): diff --git a/Tests/test_file_apng.py b/Tests/test_file_apng.py index fffbc54caf5..d0c81b5e9d7 100644 --- a/Tests/test_file_apng.py +++ b/Tests/test_file_apng.py @@ -350,7 +350,7 @@ def test_apng_save(tmp_path): im.load() assert not im.is_animated assert im.n_frames == 1 - assert im.get_format_mimetype() == "image/apng" + assert im.get_format_mimetype() == "image/png" assert im.info.get("default_image") is None assert im.getpixel((0, 0)) == (0, 255, 0, 255) assert im.getpixel((64, 32)) == (0, 255, 0, 255) @@ -450,26 +450,29 @@ def test_apng_save_duration_loop(tmp_path): test_file, save_all=True, append_images=[frame, frame], duration=[500, 100, 150] ) with Image.open(test_file) as im: - im.load() assert im.n_frames == 1 - assert im.info.get("duration") == 750 + assert "duration" not in im.info - # test info duration - frame.info["duration"] = 750 - frame.save(test_file, save_all=True) + different_frame = Image.new("RGBA", (128, 64)) + frame.save( + test_file, + save_all=True, + append_images=[frame, different_frame], + duration=[500, 100, 150], + ) with Image.open(test_file) as im: - assert im.info.get("duration") == 750 - + assert im.n_frames == 2 + assert im.info["duration"] == 600 -def test_apng_save_duplicate_duration(tmp_path): - test_file = str(tmp_path / "temp.png") - frame = Image.new("RGB", (1, 1)) + im.seek(1) + assert im.info["duration"] == 150 - # Test a single duration is correctly combined across duplicate frames - frame.save(test_file, save_all=True, append_images=[frame, frame], duration=500) + # test info duration + frame.info["duration"] = 300 + frame.save(test_file, save_all=True, append_images=[frame, different_frame]) with Image.open(test_file) as im: - assert im.n_frames == 1 - assert im.info.get("duration") == 1500 + assert im.n_frames == 2 + assert im.info["duration"] == 600 def test_apng_save_disposal(tmp_path): @@ -674,7 +677,8 @@ def test_seek_after_close(): @pytest.mark.parametrize("mode", ("RGBA", "RGB", "P")) @pytest.mark.parametrize("default_image", (True, False)) -def test_different_modes_in_later_frames(mode, default_image, tmp_path): +@pytest.mark.parametrize("duplicate", (True, False)) +def test_different_modes_in_later_frames(mode, default_image, duplicate, tmp_path): test_file = str(tmp_path / "temp.png") im = Image.new("L", (1, 1)) @@ -682,7 +686,7 @@ def test_different_modes_in_later_frames(mode, default_image, tmp_path): test_file, save_all=True, default_image=default_image, - append_images=[Image.new(mode, (1, 1))], + append_images=[im.convert(mode) if duplicate else Image.new(mode, (1, 1), 1)], ) with Image.open(test_file) as reloaded: assert reloaded.mode == mode diff --git a/Tests/test_file_iptc.py b/Tests/test_file_iptc.py index d2edcfc27c9..dac35a8d0e8 100644 --- a/Tests/test_file_iptc.py +++ b/Tests/test_file_iptc.py @@ -1,6 +1,8 @@ import sys from io import BytesIO, StringIO +import pytest + from PIL import Image, IptcImagePlugin from .helper import hopper @@ -44,7 +46,7 @@ def test_getiptcinfo_fotostation(): for tag in iptc.keys(): if tag[0] == 240: return - assert False, "FotoStation tag not found" + pytest.fail("FotoStation tag not found") def test_getiptcinfo_zero_padding(): diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index a0822d0002c..ef070b6c5ba 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -643,6 +643,23 @@ def test_save_low_quality_baseline_qtables(self): assert max(im2.quantization[0]) <= 255 assert max(im2.quantization[1]) <= 255 + @pytest.mark.parametrize( + "blocks, rows, markers", + ((0, 0, 0), (1, 0, 15), (3, 0, 5), (8, 0, 1), (0, 1, 3), (0, 2, 1)), + ) + def test_restart_markers(self, blocks, rows, markers): + im = Image.new("RGB", (32, 32)) # 16 MCUs + out = BytesIO() + im.save( + out, + format="JPEG", + restart_marker_blocks=blocks, + restart_marker_rows=rows, + # force 8x8 pixel MCUs + subsampling=0, + ) + assert len(re.findall(b"\xff[\xd0-\xd7]", out.getvalue())) == markers + @pytest.mark.skipif(not djpeg_available(), reason="djpeg not available") def test_load_djpeg(self): with Image.open(TEST_FILE) as img: @@ -961,6 +978,28 @@ def closure(mode, *args): im.load() ImageFile.LOAD_TRUNCATED_IMAGES = False + def test_separate_tables(self): + im = hopper() + data = [] # [interchange, tables-only, image-only] + for streamtype in range(3): + out = BytesIO() + im.save(out, format="JPEG", streamtype=streamtype) + data.append(out.getvalue()) + + # SOI, EOI + for marker in b"\xff\xd8", b"\xff\xd9": + assert marker in data[1] and marker in data[2] + # DHT, DQT + for marker in b"\xff\xc4", b"\xff\xdb": + assert marker in data[1] and marker not in data[2] + # SOF0, SOS, APP0 (JFIF header) + for marker in b"\xff\xc0", b"\xff\xda", b"\xff\xe0": + assert marker not in data[1] and marker in data[2] + + with Image.open(BytesIO(data[0])) as interchange_im: + with Image.open(BytesIO(data[1] + data[2])) as combined_im: + assert_image_equal(interchange_im, combined_im) + def test_repr_jpeg(self): im = hopper() diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 99df26fc905..2016b3ccbe3 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -416,7 +416,7 @@ def test_plt_marker(): while True: marker = out.read(2) if not marker: - assert False, "End of stream without PLT" + pytest.fail("End of stream without PLT") jp2_boxid = _binary.i16be(marker) if jp2_boxid == 0xFF4F: @@ -426,7 +426,7 @@ def test_plt_marker(): # PLT return elif jp2_boxid == 0xFF93: - assert False, "SOD without finding PLT first" + pytest.fail("SOD without finding PLT first") hdr = out.read(2) length = _binary.i16be(hdr) diff --git a/Tests/test_image.py b/Tests/test_image.py index 83dac70802f..9efd4c4677d 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -906,6 +906,13 @@ def test_zero_tobytes(self, size): im = Image.new("RGB", size) assert im.tobytes() == b"" + @pytest.mark.parametrize("size", ((1, 0), (0, 1), (0, 0))) + def test_zero_frombytes(self, size): + Image.frombytes("RGB", size, b"") + + im = Image.new("RGB", size) + im.frombytes(b"") + def test_has_transparency_data(self): for mode in ("1", "L", "P", "RGB"): im = Image.new(mode, (1, 1)) @@ -992,7 +999,7 @@ def test_overrun(self, path): with Image.open(os.path.join("Tests/images", path)) as im: try: im.load() - assert False + pytest.fail() except OSError as e: buffer_overrun = str(e) == "buffer overrun when reading image file" truncated = "image file is truncated" in str(e) @@ -1003,7 +1010,7 @@ def test_fli_overrun2(self): with Image.open("Tests/images/fli_overrun2.bin") as im: try: im.seek(1) - assert False + pytest.fail() except OSError as e: assert str(e) == "buffer overrun when reading image file" diff --git a/Tests/test_image_resize.py b/Tests/test_image_resize.py index 83c54cf6211..b5bfa903fe2 100644 --- a/Tests/test_image_resize.py +++ b/Tests/test_image_resize.py @@ -195,7 +195,7 @@ def test_reducing_gap_1(self, gradients_image, box, epsilon): (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=1.0 ) - with pytest.raises(AssertionError): + with pytest.raises(pytest.fail.Exception): assert_image_equal(ref, im) assert_image_similar(ref, im, epsilon) @@ -210,7 +210,7 @@ def test_reducing_gap_2(self, gradients_image, box, epsilon): (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=2.0 ) - with pytest.raises(AssertionError): + with pytest.raises(pytest.fail.Exception): assert_image_equal(ref, im) assert_image_similar(ref, im, epsilon) @@ -225,7 +225,7 @@ def test_reducing_gap_3(self, gradients_image, box, epsilon): (52, 34), Image.Resampling.BICUBIC, box=box, reducing_gap=3.0 ) - with pytest.raises(AssertionError): + with pytest.raises(pytest.fail.Exception): assert_image_equal(ref, im) assert_image_similar(ref, im, epsilon) diff --git a/Tests/test_image_thumbnail.py b/Tests/test_image_thumbnail.py index 4fd07a2b4d2..96a2c26623e 100644 --- a/Tests/test_image_thumbnail.py +++ b/Tests/test_image_thumbnail.py @@ -147,7 +147,7 @@ def test_reducing_gap_values(): ref = hopper() ref.thumbnail((18, 18), Image.Resampling.BICUBIC, reducing_gap=None) - with pytest.raises(AssertionError): + with pytest.raises(pytest.fail.Exception): assert_image_equal(ref, im) assert_image_similar(ref, im, 3.5) diff --git a/Tests/test_imageshow.py b/Tests/test_imageshow.py index e54372b60de..3e73339ed3f 100644 --- a/Tests/test_imageshow.py +++ b/Tests/test_imageshow.py @@ -85,7 +85,7 @@ def test_ipythonviewer(): test_viewer = viewer break else: - assert False + pytest.fail() im = hopper() assert test_viewer.show(im) == 1 diff --git a/_custom_build/backend.py b/_custom_build/backend.py index 9b3265a949f..23225d6b8eb 100644 --- a/_custom_build/backend.py +++ b/_custom_build/backend.py @@ -1,6 +1,6 @@ import sys -from setuptools.build_meta import * # noqa: F401, F403 +from setuptools.build_meta import * # noqa: F403 from setuptools.build_meta import build_wheel backend_class = build_wheel.__self__.__class__ diff --git a/docs/conf.py b/docs/conf.py index 7dffcfae28d..ef2cb5b8826 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -318,14 +318,14 @@ def setup(app): linkcheck_allowed_redirects = { - r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*", # noqa: E501 - r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", # noqa: E501 - r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", # noqa: E501 - r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", # noqa: E501 + r"https://www.bestpractices.dev/projects/6331": r"https://www.bestpractices.dev/en/.*", + r"https://badges.gitter.im/python-pillow/Pillow.svg": r"https://badges.gitter.im/repo.svg", + r"https://gitter.im/python-pillow/Pillow?.*": r"https://app.gitter.im/#/room/#python-pillow_Pillow:gitter.im?.*", + r"https://pillow.readthedocs.io/?badge=latest": r"https://pillow.readthedocs.io/en/stable/?badge=latest", r"https://pillow.readthedocs.io": r"https://pillow.readthedocs.io/en/stable/", - r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*", # noqa: E501 - r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg", # noqa: E501 - r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", # noqa: E501 + r"https://tidelift.com/badges/package/pypi/Pillow?.*": r"https://img.shields.io/badge/.*", + r"https://zenodo.org/badge/17549/python-pillow/Pillow.svg": r"https://zenodo.org/badge/doi/[\.0-9]+/zenodo.[0-9]+.svg", + r"https://zenodo.org/badge/latestdoi/17549/python-pillow/Pillow": r"https://zenodo.org/record/[0-9]+", } # sphinx.ext.extlinks @@ -338,6 +338,7 @@ def setup(app): "cwe": ("https://cwe.mitre.org/data/definitions/%s.html", "CWE-%s"), "issue": (_repo + "issues/%s", "#%s"), "pr": (_repo + "pull/%s", "#%s"), + "pypi": ("https://pypi.org/project/%s/", "%s"), } # sphinxext.opengraph diff --git a/docs/deprecations.rst b/docs/deprecations.rst index ce956cadeff..b4fbb8d5053 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -10,7 +10,7 @@ Deprecated features ------------------- Below are features which are considered deprecated. Where appropriate, -a ``DeprecationWarning`` is issued. +a :py:exc:`DeprecationWarning` is issued. PSFile ~~~~~~ @@ -267,7 +267,7 @@ ImageFile.raise_ioerror .. deprecated:: 7.2.0 .. versionremoved:: 9.0.0 -``IOError`` was merged into ``OSError`` in Python 3.3. +:py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3. So, ``ImageFile.raise_ioerror`` has been removed. Use ``ImageFile.raise_oserror`` instead. @@ -293,9 +293,9 @@ im.offset ``im.offset()`` has been removed, call :py:func:`.ImageChops.offset()` instead. It was documented as deprecated in PIL 1.1.2, -raised a ``DeprecationWarning`` since 1.1.5, -an ``Exception`` since Pillow 3.0.0 -and ``NotImplementedError`` since 3.3.0. +raised a :py:exc:`DeprecationWarning` since 1.1.5, +an :py:exc:`Exception` since Pillow 3.0.0 +and :py:exc:`NotImplementedError` since 3.3.0. Image.fromstring, im.fromstring and im.tostring ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -307,9 +307,9 @@ Image.fromstring, im.fromstring and im.tostring * ``im.fromstring()`` has been removed, call :py:meth:`~PIL.Image.Image.frombytes()` instead. * ``im.tostring()`` has been removed, call :py:meth:`~PIL.Image.Image.tobytes()` instead. -They issued a ``DeprecationWarning`` since 2.0.0, -an ``Exception`` since 3.0.0 -and ``NotImplementedError`` since 3.3.0. +They issued a :py:exc:`DeprecationWarning` since 2.0.0, +an :py:exc:`Exception` since 3.0.0 +and :py:exc:`NotImplementedError` since 3.3.0. ImageCms.CmsProfile attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -318,7 +318,7 @@ ImageCms.CmsProfile attributes .. versionremoved:: 8.0.0 Some attributes in :py:class:`PIL.ImageCms.CmsProfile` have been removed. From 6.0.0, -they issued a ``DeprecationWarning``: +they issued a :py:exc:`DeprecationWarning`: ======================== =================================================== Removed Use instead @@ -442,7 +442,7 @@ PIL.OleFileIO .. deprecated:: 4.0.0 .. versionremoved:: 6.0.0 -PIL.OleFileIO was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of -the upstream olefile Python package, and replaced with an ``ImportError`` in 5.0.0 +``PIL.OleFileIO`` was removed as a vendored file in Pillow 4.0.0 (2017-01) in favour of +the upstream :pypi:`olefile` Python package, and replaced with an :py:exc:`ImportError` in 5.0.0 (2018-01). The deprecated file has now been removed from Pillow. If needed, install from PyPI (eg. ``python3 -m pip install olefile``). diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index d5d95d3ce1a..fe310df6443 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -494,6 +494,18 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: If absent, the setting will be determined by libjpeg or libjpeg-turbo. +**restart_marker_blocks** + If present, emit a restart marker whenever the specified number of MCU + blocks has been produced. + + .. versionadded:: 10.2.0 + +**restart_marker_rows** + If present, emit a restart marker whenever the specified number of MCU + rows has been produced. + + .. versionadded:: 10.2.0 + **qtables** If present, sets the qtables for the encoder. This is listed as an advanced option for wizards in the JPEG documentation. Use with @@ -1296,6 +1308,8 @@ Pillow reads Kodak FlashPix files. In the current version, only the highest resolution image is read from the file, and the viewing transform is not taken into account. +To enable FPX support, you must install :pypi:`olefile`. + .. note:: To enable full FlashPix support, you need to build and install the IJG JPEG @@ -1372,6 +1386,8 @@ the first sprite in the file is loaded. You can use :py:meth:`~PIL.Image.Image.s Note that there may be an embedded gamma of 2.2 in MIC files. +To enable MIC support, you must install :pypi:`olefile`. + MPO ^^^ diff --git a/docs/installation.rst b/docs/installation.rst index 38e495199c5..5838b989d21 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -42,6 +42,11 @@ Install Pillow with :command:`pip`:: python3 -m pip install --upgrade pip python3 -m pip install --upgrade Pillow +Optionally, install :pypi:`defusedxml` for Pillow to read XMP data, +and :pypi:`olefile` for Pillow to read FPX and MIC images:: + + python3 -m pip install --upgrade defusedxml olefile + .. tab:: Linux @@ -460,10 +465,10 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Debian 12 Bookworm | 3.11 | x86, x86-64 | +----------------------------------+----------------------------+---------------------+ -| Fedora 37 | 3.11 | x86-64 | -+----------------------------------+----------------------------+---------------------+ | Fedora 38 | 3.11 | x86-64 | +----------------------------------+----------------------------+---------------------+ +| Fedora 39 | 3.12 | x86-64 | ++----------------------------------+----------------------------+---------------------+ | Gentoo | 3.9 | x86-64 | +----------------------------------+----------------------------+---------------------+ | macOS 12 Monterey | 3.8, 3.9, 3.10, 3.11, | x86-64 | @@ -482,7 +487,7 @@ These platforms are built and tested for every change. | Windows Server 2022 | 3.8, 3.9, 3.10, 3.11, | x86-64 | | | 3.12, PyPy3 | | | +----------------------------+---------------------+ -| | 3.11 | x86 | +| | 3.12 | x86 | | +----------------------------+---------------------+ | | 3.9 (MinGW) | x86-64 | | +----------------------------+---------------------+ @@ -575,6 +580,10 @@ These platforms have been reported to work at the versions mentioned. +----------------------------------+----------------------------+------------------+--------------+ | FreeBSD 10.2 | 2.7, 3.4 | 3.1.0 |x86-64 | +----------------------------------+----------------------------+------------------+--------------+ +| Windows 11 | 3.9, 3.10, 3.11, 3.12 | 10.1.0 |arm64 | ++----------------------------------+----------------------------+------------------+--------------+ +| Windows 11 Pro | 3.11, 3.12 | 10.1.0 |x86-64 | ++----------------------------------+----------------------------+------------------+--------------+ | Windows 10 | 3.7 | 7.1.0 |x86-64 | +----------------------------------+----------------------------+------------------+--------------+ | Windows 10/Cygwin 3.3 | 3.6, 3.7, 3.8, 3.9 | 8.4.0 |x86-64 | diff --git a/docs/reference/ImageFont.rst b/docs/reference/ImageFont.rst index 2abfa0cc997..a944a13fa37 100644 --- a/docs/reference/ImageFont.rst +++ b/docs/reference/ImageFont.rst @@ -10,7 +10,7 @@ this class store bitmap fonts, and are used with the PIL uses its own font file format to store bitmap fonts, limited to 256 characters. You can use `pilfont.py `_ -from `pillow-scripts `_ to convert BDF and +from :pypi:`pillow-scripts` to convert BDF and PCF font descriptors (X window font formats) to this format. Starting with version 1.1.4, PIL can be configured to support TrueType and @@ -20,7 +20,7 @@ the imToolkit package. .. warning:: To protect against potential DOS attacks when using arbitrary strings as - text input, Pillow will raise a ``ValueError`` if the number of characters + text input, Pillow will raise a :py:exc:`ValueError` if the number of characters is over a certain limit, :py:data:`MAX_STRING_LENGTH`. This threshold can be changed by setting @@ -89,5 +89,5 @@ Constants .. data:: MAX_STRING_LENGTH Set to 1,000,000, to protect against potential DOS attacks. Pillow will - raise a ``ValueError`` if the number of characters is over this limit. The + 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``. diff --git a/docs/releasenotes/10.0.0.rst b/docs/releasenotes/10.0.0.rst index 06acfc7afd2..a3f238119f0 100644 --- a/docs/releasenotes/10.0.0.rst +++ b/docs/releasenotes/10.0.0.rst @@ -173,8 +173,8 @@ been processed before Pillow started checking for decompression bombs. Added ImageFont.MAX_STRING_LENGTH ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -To protect against potential DOS attacks when using arbitrary strings as text -input, Pillow will now raise a ``ValueError`` if the number of characters +:cve:`2023-44271`: To protect against potential DOS attacks when using arbitrary strings as text +input, Pillow will now raise a :py:exc:`ValueError` if the number of characters passed into ImageFont methods is over a certain limit, :py:data:`PIL.ImageFont.MAX_STRING_LENGTH`. diff --git a/docs/releasenotes/10.1.0.rst b/docs/releasenotes/10.1.0.rst index 8c3413c8c56..fd556bdf17f 100644 --- a/docs/releasenotes/10.1.0.rst +++ b/docs/releasenotes/10.1.0.rst @@ -8,7 +8,7 @@ Setting image mode ^^^^^^^^^^^^^^^^^^ If you attempt to set the mode of an image directly, e.g. -``im.mode = "RGBA"``, you will now receive an ``AttributeError``. This is +``im.mode = "RGBA"``, you will now receive an :py:exc:`AttributeError`. This is not about removing existing functionality, but instead about raising an explicit error to prevent later consequences. The ``convert`` method is the correct way to change an image's mode. diff --git a/docs/releasenotes/2.8.0.rst b/docs/releasenotes/2.8.0.rst index c522fe8b0a3..4dbbc0bdd29 100644 --- a/docs/releasenotes/2.8.0.rst +++ b/docs/releasenotes/2.8.0.rst @@ -10,7 +10,7 @@ operations. As a result PIL was unable to open them as images, requiring a wrap ``cStringIO`` or ``BytesIO``. Now new functionality has been added to ``Image.open()`` by way of an ``.seek(0)`` check and -catch on exception ``AttributeError`` or ``io.UnsupportedOperation``. If this is caught we +catch on exception :py:exc:`AttributeError` or :py:exc:`io.UnsupportedOperation`. If this is caught we attempt to wrap the object using ``io.BytesIO`` (which will only work on buffer-file-like objects). diff --git a/docs/releasenotes/3.4.0.rst b/docs/releasenotes/3.4.0.rst index dc5e2e29598..2bbafe741d2 100644 --- a/docs/releasenotes/3.4.0.rst +++ b/docs/releasenotes/3.4.0.rst @@ -19,7 +19,7 @@ Deprecation Warning when Saving JPEGs JPEG images cannot contain an alpha channel. Pillow prior to 3.4.0 silently drops the alpha channel. With this release Pillow will now -issue a ``DeprecationWarning`` when attempting to save a ``RGBA`` mode +issue a :py:exc:`DeprecationWarning` when attempting to save a ``RGBA`` mode image as a JPEG. This will become an error in Pillow 4.2. New DDS Decoders diff --git a/docs/releasenotes/4.0.0.rst b/docs/releasenotes/4.0.0.rst index cbf131c9311..5778de26a82 100644 --- a/docs/releasenotes/4.0.0.rst +++ b/docs/releasenotes/4.0.0.rst @@ -17,8 +17,8 @@ Pillow 4.0 supports Python 3.6. OleFileIO.py ============ -OleFileIO.py has been removed as a vendored file and is now installed -from the upstream olefile pypi package. All internal dependencies are +``OleFileIO.py`` has been removed as a vendored file and is now installed +from the upstream :pypi:`olefile` PyPI package. All internal dependencies are redirected to the olefile package. Direct accesses to ``PIL.OlefileIO`` raises a deprecation warning, then patches the upstream olefile into ``sys.modules`` in its place. diff --git a/docs/releasenotes/5.0.0.rst b/docs/releasenotes/5.0.0.rst index 509edbe6df8..be00a45cd87 100644 --- a/docs/releasenotes/5.0.0.rst +++ b/docs/releasenotes/5.0.0.rst @@ -28,7 +28,7 @@ Scripts The scripts formerly installed by Pillow have been split into a separate package, pillow-scripts, living at -https://github.com/python-pillow/pillow-scripts . +https://github.com/python-pillow/pillow-scripts. API Changes @@ -37,7 +37,7 @@ API Changes OleFileIO.py ^^^^^^^^^^^^ -The olefile module is no longer a required dependency when installing Pillow. +The :pypi:`olefile` module is no longer a required dependency when installing Pillow. Support for plugins requiring olefile will not be loaded if it is not installed. This allows library consumers to avoid installing this dependency if they choose. Some library consumers have little interest in the format diff --git a/docs/releasenotes/5.3.0.rst b/docs/releasenotes/5.3.0.rst index bff56566b66..8f276da2407 100644 --- a/docs/releasenotes/5.3.0.rst +++ b/docs/releasenotes/5.3.0.rst @@ -8,7 +8,7 @@ Image size ^^^^^^^^^^ If you attempt to set the size of an image directly, e.g. -``im.size = (100, 100)``, you will now receive an ``AttributeError``. This is +``im.size = (100, 100)``, you will now receive an :py:exc:`AttributeError`. This is not about removing existing functionality, but instead about raising an explicit error to prevent later consequences. The ``resize`` method is the correct way to change an image's size. @@ -16,7 +16,8 @@ correct way to change an image's size. The exceptions to this are: * The ICO and ICNS image formats, which use ``im.size = (100, 100)`` to select a subimage. -* The TIFF image format, which now has a ``DeprecationWarning`` for this action, as direct image size setting was previously necessary to work around an issue with tile extents. +* The TIFF image format, which now has a :py:exc:`DeprecationWarning` for this action, + as direct image size setting was previously necessary to work around an issue with tile extents. API Additions diff --git a/docs/releasenotes/5.4.1.rst b/docs/releasenotes/5.4.1.rst index 78f483db658..bbabd652090 100644 --- a/docs/releasenotes/5.4.1.rst +++ b/docs/releasenotes/5.4.1.rst @@ -15,7 +15,7 @@ PNG: Handle IDAT chunks after image end Some PNG images have multiple IDAT chunks. In some cases, Pillow will stop reading image data before the IDAT chunks finish. A regression caused an -``EOFError`` exception when previously there was none. This is now fixed, and +:py:exc:`EOFError` exception when previously there was none. This is now fixed, and file reading continues in case there are subsequent text chunks. PNG: MIME type @@ -30,7 +30,7 @@ File closing ^^^^^^^^^^^^ A regression caused an unsupported image file to report a -``ValueError: seek of closed file`` exception instead of an ``OSError``. This +``ValueError: seek of closed file`` exception instead of an :py:exc:`OSError`. This has been fixed by ensuring that image plugins only close their internal ``__fp`` if they are not the same as ``ImageFile``'s ``fp``, allowing each to manage their own file pointers. diff --git a/docs/releasenotes/6.0.0.rst b/docs/releasenotes/6.0.0.rst index 3e3b945a0a9..5e69f0b6b5a 100644 --- a/docs/releasenotes/6.0.0.rst +++ b/docs/releasenotes/6.0.0.rst @@ -14,8 +14,8 @@ Pillow for Python 3.4 is 5.4.1. Removed deprecated PIL.OleFileIO ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -PIL.OleFileIO was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of -the upstream olefile Python package, and replaced with an ``ImportError``. The +``PIL.OleFileIO`` was removed as a vendored file and in Pillow 4.0.0 (2017-01) in favour of +the upstream :pypi:`olefile` Python package, and replaced with an :py:exc:`ImportError`. The deprecated file has now been removed from Pillow. If needed, install from PyPI (eg. ``python3 -m pip install olefile``). @@ -103,7 +103,7 @@ ImageCms.CmsProfile attributes ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Some attributes in ``ImageCms.CmsProfile`` have been deprecated since Pillow 3.2.0. From -6.0.0, they issue a ``DeprecationWarning``: +6.0.0, they issue a :py:exc:`DeprecationWarning`: ======================== =============================== Deprecated Use instead diff --git a/docs/releasenotes/6.1.0.rst b/docs/releasenotes/6.1.0.rst index e7c593e6e63..ce3edc5fa9b 100644 --- a/docs/releasenotes/6.1.0.rst +++ b/docs/releasenotes/6.1.0.rst @@ -58,7 +58,7 @@ file. ``ImageFont.FreeTypeFont`` has four new methods, :py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_name` for using named styles, and :py:meth:`PIL.ImageFont.FreeTypeFont.get_variation_axes` and :py:meth:`PIL.ImageFont.FreeTypeFont.set_variation_by_axes` for using font axes -instead. An ``IOError`` will be raised if the font is not a variation font. FreeType +instead. An :py:exc:`IOError` will be raised if the font is not a variation font. FreeType 2.9.1 or greater is required. Other Changes diff --git a/docs/releasenotes/7.0.0.rst b/docs/releasenotes/7.0.0.rst index f2e2352897a..ed6026593e6 100644 --- a/docs/releasenotes/7.0.0.rst +++ b/docs/releasenotes/7.0.0.rst @@ -85,7 +85,7 @@ Custom unidentified image error ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Pillow will now throw a custom ``UnidentifiedImageError`` when an image cannot be -identified. For backwards compatibility, this will inherit from ``OSError``. +identified. For backwards compatibility, this will inherit from :py:exc:`OSError`. New argument ``reducing_gap`` for Image.resize() and Image.thumbnail() methods ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/docs/releasenotes/7.1.2.rst b/docs/releasenotes/7.1.2.rst index b12d84e33bd..ec0063e7953 100644 --- a/docs/releasenotes/7.1.2.rst +++ b/docs/releasenotes/7.1.2.rst @@ -7,7 +7,7 @@ Fix another regression seeking PNG files This fixes a regression introduced in 7.1.0 when adding support for APNG files. When calling ``seek(n)`` on a regular PNG where ``n > 0``, it failed to raise an -``EOFError`` as it should have done, resulting in: +:py:exc:`EOFError` as it should have done, resulting in: .. code-block:: pycon diff --git a/docs/releasenotes/7.2.0.rst b/docs/releasenotes/7.2.0.rst index ff1b7c9e764..91e54da1999 100644 --- a/docs/releasenotes/7.2.0.rst +++ b/docs/releasenotes/7.2.0.rst @@ -53,6 +53,6 @@ a custom :py:class:`~PIL.ImageShow.Viewer` class. ImageFile.raise_ioerror ~~~~~~~~~~~~~~~~~~~~~~~ -``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` +:py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3. So, ``ImageFile.raise_ioerror`` is now deprecated and will be removed in a future release. Use ``ImageFile.raise_oserror`` instead. diff --git a/docs/releasenotes/8.0.0.rst b/docs/releasenotes/8.0.0.rst index 00c691a7459..2bf299dd3d8 100644 --- a/docs/releasenotes/8.0.0.rst +++ b/docs/releasenotes/8.0.0.rst @@ -168,7 +168,7 @@ offset. Error for large BMP files ^^^^^^^^^^^^^^^^^^^^^^^^^ -Previously, if a BMP file was too large, an ``OSError`` would be raised. Now, +Previously, if a BMP file was too large, an :py:exc:`OSError` would be raised. Now, ``DecompressionBombError`` is used instead, as Pillow already uses for other formats. Dark theme for docs diff --git a/docs/releasenotes/8.3.1.rst b/docs/releasenotes/8.3.1.rst index e97070c111c..6af2b37bfe1 100644 --- a/docs/releasenotes/8.3.1.rst +++ b/docs/releasenotes/8.3.1.rst @@ -22,9 +22,10 @@ Catch OSError when checking if destination is sys.stdout ======================================================== In 8.3.0, a check to see if the destination was ``sys.stdout`` when saving an image was -updated. This lead to an OSError being raised if the environment restricted access. +updated. This lead to an :py:exc:`OSError` being raised if the environment restricted +access. -The OSError is now silently caught. +The :py:exc:`OSError` is now silently caught. Fixed removing orientation in ImageOps.exif_transpose ===================================================== @@ -34,7 +35,7 @@ original image EXIF data was not modified, and the orientation was only removed the modified copy. However, for certain images the orientation was already missing from the modified -image, leading to a KeyError. +image, leading to a :py:exc:`KeyError`. This error has been resolved, and the copying of metadata to the modified image improved. diff --git a/docs/releasenotes/9.0.0.rst b/docs/releasenotes/9.0.0.rst index 73e77ad3ef6..090ec802467 100644 --- a/docs/releasenotes/9.0.0.rst +++ b/docs/releasenotes/9.0.0.rst @@ -63,7 +63,7 @@ a custom :py:class:`~PIL.ImageShow.Viewer` class. ImageFile.raise_ioerror ^^^^^^^^^^^^^^^^^^^^^^^ -``IOError`` was merged into ``OSError`` in Python 3.3. So, ``ImageFile.raise_ioerror`` +:py:exc:`IOError` was merged into :py:exc:`OSError` in Python 3.3. So, ``ImageFile.raise_ioerror`` has been removed. Use ``ImageFile.raise_oserror`` instead. diff --git a/docs/releasenotes/9.1.0.rst b/docs/releasenotes/9.1.0.rst index 19690ca59b5..02da702a799 100644 --- a/docs/releasenotes/9.1.0.rst +++ b/docs/releasenotes/9.1.0.rst @@ -8,14 +8,14 @@ Raise an error when performing a negative crop ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Performing a negative crop on an image previously just returned a ``(0, 0)`` image. Now -it will raise a ``ValueError``, to help reduce confusion if a user has unintentionally +it will raise a :py:exc:`ValueError`, to help reduce confusion if a user has unintentionally provided the wrong arguments. Added specific error if path coordinate type is incorrect ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Rather than returning a ``SystemError``, passing the incorrect types of coordinates into -a path will now raise a more specific ``ValueError``, with the message "incorrect +Rather than returning a :py:exc:`SystemError`, passing the incorrect types of coordinates into +a path will now raise a more specific :py:exc:`ValueError`, with the message "incorrect coordinate type". Replace requirements.txt with extras diff --git a/pyproject.toml b/pyproject.toml index 6f6ed6e9336..a49179a3734 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -77,8 +77,33 @@ package-dir = {"" = "src"} [tool.setuptools.dynamic] version = {attr = "PIL.__version__"} -[tool.isort] -profile = "black" +[tool.ruff] +line-length = 88 +select = [ + "E", # pycodestyle errors + "EM", # flake8-errmsg + "F", # pyflakes errors + "I", # isort + "ISC", # flake8-implicit-str-concat + "PGH", # pygrep-hooks + "RUF100", # unused noqa (yesqa) + "UP", # pyupgrade + "W", # pycodestyle warnings + "YTT", # flake8-2020 + # "LOG", # TODO: enable flake8-logging when it's not in preview anymore +] +extend-ignore = [ + "E203", # Whitespace before ':' + "E221", # Multiple spaces before operator + "E226", # Missing whitespace around arithmetic operator + "E241", # Multiple spaces after ',' +] + +[tool.ruff.per-file-ignores] +"Tests/*.py" = ["I001"] + +[tool.ruff.isort] +known-first-party = ["PIL"] [tool.pytest.ini_options] addopts = "-ra --color=yes" diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 9b2fce0ac01..c05208c8026 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -77,14 +77,11 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False): # Hack to support hi-res rendering scale = int(scale) or 1 - # orig_size = size - # orig_bbox = bbox - size = (size[0] * scale, size[1] * scale) + width = size[0] * scale + height = size[1] * scale # resolution is dependent on bbox and size - res = ( - 72.0 * size[0] / (bbox[2] - bbox[0]), - 72.0 * size[1] / (bbox[3] - bbox[1]), - ) + res_x = 72.0 * width / (bbox[2] - bbox[0]) + res_y = 72.0 * height / (bbox[3] - bbox[1]) out_fd, outfile = tempfile.mkstemp() os.close(out_fd) @@ -121,8 +118,8 @@ def Ghostscript(tile, size, fp, scale=1, transparency=False): command = [ gs_binary, "-q", # quiet mode - "-g%dx%d" % size, # set output geometry (pixels) - "-r%fx%f" % res, # set input DPI (dots per inch) + f"-g{width:d}x{height:d}", # set output geometry (pixels) + f"-r{res_x:f}x{res_y:f}", # set input DPI (dots per inch) "-dBATCH", # exit after processing "-dNOPAUSE", # don't pause between pages "-dSAFER", # safe mode diff --git a/src/PIL/FitsImagePlugin.py b/src/PIL/FitsImagePlugin.py index e0e51aaac73..1ff8a7e913b 100644 --- a/src/PIL/FitsImagePlugin.py +++ b/src/PIL/FitsImagePlugin.py @@ -54,12 +54,10 @@ def _open(self): self._mode = "L" elif number_of_bits == 16: self._mode = "I" - # rawmode = "I;16S" elif number_of_bits == 32: self._mode = "I" elif number_of_bits in (-32, -64): self._mode = "F" - # rawmode = "F" if number_of_bits == -32 else "F;64F" offset = math.ceil(self.fp.tell() / 2880) * 2880 self.tile = [("raw", (0, 0) + self.size, offset, (self.mode, 0, -1))] diff --git a/src/PIL/FontFile.py b/src/PIL/FontFile.py index 5ec0a6632e3..085917ac38e 100644 --- a/src/PIL/FontFile.py +++ b/src/PIL/FontFile.py @@ -78,7 +78,6 @@ def compile(self): if glyph: d, dst, src, im = glyph xx = src[2] - src[0] - # yy = src[3] - src[1] x0, y0 = x, y x = x + xx if x > WIDTH: diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 4ce295f7f21..bdea02005e7 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -281,14 +281,9 @@ def _seek(self, frame, update_image=True): bits = self.fp.read(1)[0] self.__offset = self.fp.tell() break - - else: - pass - # raise OSError, "illegal GIF tag `%x`" % s[0] s = None if interlace is None: - # self._fp = None msg = "image not found in GIF frame" raise EOFError(msg) diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index 0aa4f7a8458..b415a32194d 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -391,8 +391,8 @@ def _accept(prefix): with open(sys.argv[1], "rb") as fp: imf = IcnsImageFile(fp) for size in imf.info["sizes"]: - imf.size = size - imf.save("out-%s-%s-%s.png" % size) + width, height, scale = imf.size = size + imf.save(f"out-{width}-{height}-{scale}.png") with Image.open(sys.argv[1]) as im: im.save("out.png") if sys.platform == "windows": diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index 0445a2ab22f..7f0f0047cdc 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -174,9 +174,7 @@ def __init__(self, buf): 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['width']) - self.entry = sorted(self.entry, key=lambda x: x["square"]) - self.entry.reverse() + self.entry = sorted(self.entry, key=lambda x: x["square"], reverse=True) def sizes(self): """ diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 771cb33c3de..546d9020334 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -791,6 +791,9 @@ def frombytes(self, data, decoder_name="raw", *args): but loads data into this image instead of creating a new image object. """ + if self.width == 0 or self.height == 0: + return + # may pass tuple instead of argument list if len(args) == 1 and isinstance(args[0], tuple): args = args[0] @@ -2967,15 +2970,16 @@ def frombytes(mode, size, data, decoder_name="raw", *args): _check_size(size) - # may pass tuple instead of argument list - if len(args) == 1 and isinstance(args[0], tuple): - args = args[0] + im = new(mode, size) + if im.width != 0 and im.height != 0: + # may pass tuple instead of argument list + if len(args) == 1 and isinstance(args[0], tuple): + args = args[0] - if decoder_name == "raw" and args == (): - args = mode + if decoder_name == "raw" and args == (): + args = mode - im = new(mode, size) - im.frombytes(data, decoder_name, args) + im.frombytes(data, decoder_name, args) return im @@ -3096,7 +3100,8 @@ def fromarray(obj, mode=None): try: mode, rawmode = _fromarray_typemap[typekey] except KeyError as e: - msg = "Cannot handle this data type: %s, %s" % typekey + typekey_shape, typestr = typekey + msg = f"Cannot handle this data type: {typekey_shape}, {typestr}" raise TypeError(msg) from e else: rawmode = mode diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 8432a187f85..902e8ce5ff6 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -430,7 +430,6 @@ def feed(self, data): with io.BytesIO(self.data) as fp: im = Image.open(fp) except OSError: - # traceback.print_exc() pass # not enough data else: flag = hasattr(im, "load_seek") or hasattr(im, "load_read") diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 57268b8f53d..c24f86ef38f 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -222,7 +222,7 @@ class UnsharpMask(MultibandFilter): .. _digital unsharp masking: https://en.wikipedia.org/wiki/Unsharp_masking#Digital_unsharp_masking - """ # noqa: E501 + """ name = "UnsharpMask" diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 1ba50b5ec6b..cb4f1dba115 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -257,8 +257,6 @@ def load(filename): if lut: break except (SyntaxError, ValueError): - # import traceback - # traceback.print_exc() pass else: msg = "cannot load palette" diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index 9b7245454df..d017565a9e4 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -83,16 +83,6 @@ def fromqimage(im): def fromqpixmap(im): return fromqimage(im) - # buffer = QBuffer() - # buffer.open(QIODevice.ReadWrite) - # # im.save(buffer) - # # What if png doesn't support some image features like animation? - # im.save(buffer, 'ppm') - # bytes_io = BytesIO() - # bytes_io.write(buffer.data()) - # buffer.close() - # bytes_io.seek(0) - # return Image.open(bytes_io) def align8to32(bytes, width, mode): @@ -208,9 +198,5 @@ def toqimage(im): def toqpixmap(im): - # # This doesn't work. For now using a dumb approach. - # im_data = _toqclass_helper(im) - # result = QPixmap(im_data["size"][0], im_data["size"][1]) - # result.loadFromData(im_data["data"]) qimage = toqimage(im) return QPixmap.fromImage(qimage) diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 316cd17c732..3a40cf987f4 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -18,10 +18,9 @@ import tempfile from . import Image, ImageFile -from ._binary import i8 +from ._binary import i8, o8 from ._binary import i16be as i16 from ._binary import i32be as i32 -from ._binary import o8 COMPRESSION = {1: "raw", 5: "jpeg"} diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index c091697f52d..3596e089949 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -787,6 +787,8 @@ def validate_qtables(qtables): dpi[0], dpi[1], subsampling, + info.get("restart_marker_blocks", 0), + info.get("restart_marker_rows", 0), qtables, comment, extra, diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index f9261c77d68..89083b4ff76 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -33,9 +33,6 @@ from ._binary import i16be as i16 from ._binary import o32le -# def _accept(prefix): -# return JpegImagePlugin._accept(prefix) - def _save(im, fp, filename): JpegImagePlugin._save(im, fp, filename) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index dc1012f54d3..8bdb65cce23 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -82,7 +82,7 @@ class IndirectReference( collections.namedtuple("IndirectReferenceTuple", ["object_id", "generation"]) ): def __str__(self): - return "%s %s R" % self + return f"{self.object_id} {self.generation} R" def __bytes__(self): return self.__str__().encode("us-ascii") @@ -103,7 +103,7 @@ def __hash__(self): class IndirectObjectDef(IndirectReference): def __str__(self): - return "%s %s obj" % self + return f"{self.object_id} {self.generation} obj" class XrefTable: diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 1bd0f442f76..dbcdee1c2d7 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1156,6 +1156,9 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) encoderinfo["duration"] = duration im_frames.append({"im": im_frame, "bbox": bbox, "encoderinfo": encoderinfo}) + if len(im_frames) == 1 and not default_image: + return im_frames[0]["im"] + # animation control chunk( fp, @@ -1391,8 +1394,10 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): chunk(fp, b"eXIf", exif) if save_all: - _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) - else: + im = _write_multiple_frames( + im, fp, chunk, rawmode, default_image, append_images + ) + if im: ImageFile._save(im, _idat(fp, chunk), [("zip", (0, 0) + im.size, 0, rawmode)]) if info: diff --git a/src/PIL/PpmImagePlugin.py b/src/PIL/PpmImagePlugin.py index e480ab05581..93f1528c5c5 100644 --- a/src/PIL/PpmImagePlugin.py +++ b/src/PIL/PpmImagePlugin.py @@ -328,9 +328,6 @@ def _save(im, fp, filename): fp.write(b"65535\n") ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) - # ALTERNATIVE: save via builtin debug function - # im._dump(filename) - # # -------------------------------------------------------------------- diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 99b46a4a66c..24d30d2a6eb 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -244,7 +244,7 @@ def set_pixel(self, x, y, color): except TypeError: color = min(color[0], 65535) - pixel.l = color & 0xFF # noqa: E741 + pixel.l = color & 0xFF pixel.r = color >> 8 @@ -265,7 +265,7 @@ def set_pixel(self, x, y, color): except Exception: color = min(color[0], 65535) - pixel.l = color >> 8 # noqa: E741 + pixel.l = color >> 8 pixel.r = color & 0xFF diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index dabf8dbfb5f..a78a5aef1bc 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1885,13 +1885,14 @@ class AppendingTiffWriter: 8, # long8 ] - # StripOffsets = 273 - # FreeOffsets = 288 - # TileOffsets = 324 - # JPEGQTables = 519 - # JPEGDCTables = 520 - # JPEGACTables = 521 - Tags = {273, 288, 324, 519, 520, 521} + Tags = { + 273, # StripOffsets + 288, # FreeOffsets + 324, # TileOffsets + 519, # JPEGQTables + 520, # JPEGDCTables + 521, # JPEGACTables + } def __init__(self, fn, new=False): if hasattr(fn, "read"): @@ -1941,8 +1942,6 @@ def finalize(self): iimm = self.f.read(4) if not iimm: - # msg = "nothing written into new page" - # raise RuntimeError(msg) # Make it easy to finish a frame without committing to a new one. return diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 30b05e4e1d4..edcae41e45a 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -56,7 +56,7 @@ def lookup(tag, group=None): ## # Map tag numbers to tag info. # -# id: (Name, Type, Length, enum_values) +# id: (Name, Type, Length[, enum_values]) # # The length here differs from the length in the tiff spec. For # numbers, the tiff spec is for the number of fields returned. We @@ -438,22 +438,6 @@ def _populate(): TYPES = {} -# was: -# TYPES = { -# 1: "byte", -# 2: "ascii", -# 3: "short", -# 4: "long", -# 5: "rational", -# 6: "signed byte", -# 7: "undefined", -# 8: "signed short", -# 9: "signed long", -# 10: "signed rational", -# 11: "float", -# 12: "double", -# } - # # These tags are handled by default in libtiff, without # adding to the custom dictionary. From tif_dir.c, searching for diff --git a/src/_imagingft.c b/src/_imagingft.c index 64175de8bef..7849c821d0c 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -885,7 +885,9 @@ font_render(FontObject *self, PyObject *args) { PyMem_Del(glyph_info); return NULL; } - id = PyLong_AsSsize_t(PyObject_GetAttrString(image, "id")); + PyObject *imageId = PyObject_GetAttrString(image, "id"); + id = PyLong_AsSsize_t(imageId); + Py_XDECREF(imageId); im = (Imaging)id; x_offset -= stroke_width; diff --git a/src/encode.c b/src/encode.c index 08544aedeb0..4664ad0f32a 100644 --- a/src/encode.c +++ b/src/encode.c @@ -1045,6 +1045,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { Py_ssize_t streamtype = 0; /* 0=interchange, 1=tables only, 2=image only */ Py_ssize_t xdpi = 0, ydpi = 0; Py_ssize_t subsampling = -1; /* -1=default, 0=none, 1=medium, 2=high */ + Py_ssize_t restart_marker_blocks = 0; + Py_ssize_t restart_marker_rows = 0; PyObject *qtables = NULL; unsigned int *qarrays = NULL; int qtablesLen = 0; @@ -1057,7 +1059,7 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { if (!PyArg_ParseTuple( args, - "ss|nnnnnnnnOz#y#y#", + "ss|nnnnnnnnnnOz#y#y#", &mode, &rawmode, &quality, @@ -1068,6 +1070,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { &xdpi, &ydpi, &subsampling, + &restart_marker_blocks, + &restart_marker_rows, &qtables, &comment, &comment_size, @@ -1156,6 +1160,8 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { ((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; diff --git a/src/libImaging/Jpeg.h b/src/libImaging/Jpeg.h index 1d755081871..5cc74e69bf5 100644 --- a/src/libImaging/Jpeg.h +++ b/src/libImaging/Jpeg.h @@ -83,6 +83,10 @@ typedef struct { /* Chroma Subsampling (-1=default, 0=none, 1=medium, 2=high) */ int subsampling; + /* Restart marker interval, in MCU blocks or MCU rows, or 0 for none */ + unsigned int restart_marker_blocks; + unsigned int restart_marker_rows; + /* Converter input mode (input to the shuffler) */ char rawmode[8 + 1]; diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c index 2a24eff39ca..9da830b186f 100644 --- a/src/libImaging/JpegEncode.c +++ b/src/libImaging/JpegEncode.c @@ -210,6 +210,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } context->cinfo.smoothing_factor = context->smooth; context->cinfo.optimize_coding = (boolean)context->optimize; + context->cinfo.restart_interval = context->restart_marker_blocks; + context->cinfo.restart_in_rows = context->restart_marker_rows; if (context->xdpi > 0 && context->ydpi > 0) { context->cinfo.write_JFIF_header = TRUE; context->cinfo.density_unit = 1; /* dots per inch */ @@ -218,9 +220,9 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } switch (context->streamtype) { case 1: - /* tables only -- not yet implemented */ - state->errcode = IMAGING_CODEC_CONFIG; - return -1; + /* tables only */ + jpeg_write_tables(&context->cinfo); + goto cleanup; case 2: /* image only */ jpeg_suppress_tables(&context->cinfo, TRUE); @@ -316,6 +318,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } jpeg_finish_compress(&context->cinfo); +cleanup: /* Clean up */ if (context->comment) { free(context->comment); diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 35122f18245..e3b81590ec2 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -14,6 +14,10 @@ #ifdef HAVE_LIBTIFF +#ifdef HAVE_UNISTD_H +#include /* lseek */ +#endif + #ifndef uint #define uint uint32 #endif diff --git a/src/libImaging/TiffDecode.h b/src/libImaging/TiffDecode.h index c7c7d48ed02..02454ba0396 100644 --- a/src/libImaging/TiffDecode.h +++ b/src/libImaging/TiffDecode.h @@ -13,12 +13,6 @@ #include #endif -/* UNDONE -- what are we using from this? */ -/*#ifndef _UNISTD_H - # include - # endif -*/ - #ifndef min #define min(x, y) ((x > y) ? y : x) #define max(x, y) ((x < y) ? y : x) diff --git a/wheels/config.sh b/wheels/config.sh index f36b98f2579..655ea295b84 100644 --- a/wheels/config.sh +++ b/wheels/config.sh @@ -9,7 +9,7 @@ HARFBUZZ_VERSION=8.2.1 LIBPNG_VERSION=1.6.40 JPEGTURBO_VERSION=3.0.1 OPENJPEG_VERSION=2.5.0 -XZ_VERSION=5.4.4 +XZ_VERSION=5.4.5 TIFF_VERSION=4.6.0 LCMS2_VERSION=2.15 if [[ -n "$IS_MACOS" ]]; then diff --git a/wheels/dependency_licenses/FREETYPE2.txt b/wheels/dependency_licenses/FREETYPE2.txt index cca8d8ce10b..93efc612676 100644 --- a/wheels/dependency_licenses/FREETYPE2.txt +++ b/wheels/dependency_licenses/FREETYPE2.txt @@ -38,3 +38,615 @@ the 'Old MIT' license, compatible to the above two licenses. The MD5 checksum support (only used for debugging in development builds) is in the public domain. + +-------------------------------------------------------------------------- + + The FreeType Project LICENSE + ---------------------------- + + 2006-Jan-27 + + Copyright 1996-2002, 2006 by + David Turner, Robert Wilhelm, and Werner Lemberg + + + +Introduction +============ + + The FreeType Project is distributed in several archive packages; + some of them may contain, in addition to the FreeType font engine, + various tools and contributions which rely on, or relate to, the + FreeType Project. + + This license applies to all files found in such packages, and + which do not fall under their own explicit license. The license + affects thus the FreeType font engine, the test programs, + documentation and makefiles, at the very least. + + This license was inspired by the BSD, Artistic, and IJG + (Independent JPEG Group) licenses, which all encourage inclusion + and use of free software in commercial and freeware products + alike. As a consequence, its main points are that: + + o We don't promise that this software works. However, we will be + interested in any kind of bug reports. (`as is' distribution) + + o You can use this software for whatever you want, in parts or + full form, without having to pay us. (`royalty-free' usage) + + o You may not pretend that you wrote this software. If you use + it, or only parts of it, in a program, you must acknowledge + somewhere in your documentation that you have used the + FreeType code. (`credits') + + We specifically permit and encourage the inclusion of this + software, with or without modifications, in commercial products. + We disclaim all warranties covering The FreeType Project and + assume no liability related to The FreeType Project. + + + Finally, many people asked us for a preferred form for a + credit/disclaimer to use in compliance with this license. We thus + encourage you to use the following text: + + """ + Portions of this software are copyright © The FreeType + Project (www.freetype.org). All rights reserved. + """ + + Please replace with the value from the FreeType version you + actually use. + + +Legal Terms +=========== + +0. Definitions +-------------- + + Throughout this license, the terms `package', `FreeType Project', + and `FreeType archive' refer to the set of files originally + distributed by the authors (David Turner, Robert Wilhelm, and + Werner Lemberg) as the `FreeType Project', be they named as alpha, + beta or final release. + + `You' refers to the licensee, or person using the project, where + `using' is a generic term including compiling the project's source + code as well as linking it to form a `program' or `executable'. + This program is referred to as `a program using the FreeType + engine'. + + This license applies to all files distributed in the original + FreeType Project, including all source code, binaries and + documentation, unless otherwise stated in the file in its + original, unmodified form as distributed in the original archive. + If you are unsure whether or not a particular file is covered by + this license, you must contact us to verify this. + + The FreeType Project is copyright (C) 1996-2000 by David Turner, + Robert Wilhelm, and Werner Lemberg. All rights reserved except as + specified below. + +1. No Warranty +-------------- + + THE FREETYPE PROJECT IS PROVIDED `AS IS' WITHOUT WARRANTY OF ANY + KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, + WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + PURPOSE. IN NO EVENT WILL ANY OF THE AUTHORS OR COPYRIGHT HOLDERS + BE LIABLE FOR ANY DAMAGES CAUSED BY THE USE OR THE INABILITY TO + USE, OF THE FREETYPE PROJECT. + +2. Redistribution +----------------- + + This license grants a worldwide, royalty-free, perpetual and + irrevocable right and license to use, execute, perform, compile, + display, copy, create derivative works of, distribute and + sublicense the FreeType Project (in both source and object code + forms) and derivative works thereof for any purpose; and to + authorize others to exercise some or all of the rights granted + herein, subject to the following conditions: + + o Redistribution of source code must retain this license file + (`FTL.TXT') unaltered; any additions, deletions or changes to + the original files must be clearly indicated in accompanying + documentation. The copyright notices of the unaltered, + original files must be preserved in all copies of source + files. + + o Redistribution in binary form must provide a disclaimer that + states that the software is based in part of the work of the + FreeType Team, in the distribution documentation. We also + encourage you to put an URL to the FreeType web page in your + documentation, though this isn't mandatory. + + These conditions apply to any software derived from or based on + the FreeType Project, not just the unmodified files. If you use + our work, you must acknowledge us. However, no fee need be paid + to us. + +3. Advertising +-------------- + + Neither the FreeType authors and contributors nor you shall use + the name of the other for commercial, advertising, or promotional + purposes without specific prior written permission. + + We suggest, but do not require, that you use one or more of the + following phrases to refer to this software in your documentation + or advertising materials: `FreeType Project', `FreeType Engine', + `FreeType library', or `FreeType Distribution'. + + As you have not signed this license, you are not required to + accept it. However, as the FreeType Project is copyrighted + material, only this license, or another one contracted with the + authors, grants you the right to use, distribute, and modify it. + Therefore, by using, distributing, or modifying the FreeType + Project, you indicate that you understand and accept all the terms + of this license. + +4. Contacts +----------- + + There are two mailing lists related to FreeType: + + o freetype@nongnu.org + + Discusses general use and applications of FreeType, as well as + future and wanted additions to the library and distribution. + If you are looking for support, start in this list if you + haven't found anything to help you in the documentation. + + o freetype-devel@nongnu.org + + Discusses bugs, as well as engine internals, design issues, + specific licenses, porting, etc. + + Our home page can be found at + + https://www.freetype.org + + +--- end of FTL.TXT --- + +-------------------------------------------------------------------------- + + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Library General +Public License instead of this License. + +-------------------------------------------------------------------------- + +The following license details are part of `src/bdf/README`: + +``` +License +******* + +Copyright (C) 2001-2002 by Francesco Zappa Nardelli + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +*** Portions of the driver (that is, bdflib.c and bdf.h): + +Copyright 2000 Computing Research Labs, New Mexico State University +Copyright 2001-2002, 2011 Francesco Zappa Nardelli + +Permission is hereby granted, free of charge, to any person obtaining a +copy of this software and associated documentation files (the "Software"), +to deal in the Software without restriction, including without limitation +the rights to use, copy, modify, merge, publish, distribute, sublicense, +and/or sell copies of the Software, and to permit persons to whom the +Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +THE COMPUTING RESEARCH LAB OR NEW MEXICO STATE UNIVERSITY BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT +OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR +THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Credits +******* + +This driver is based on excellent Mark Leisher's bdf library. If you +find something good in this driver you should probably thank him, not +me. +``` + +The following license details are part of `src/pcf/README`: + +``` +License +******* + +Copyright (C) 2000 by Francesco Zappa Nardelli + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY +CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, +TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +Credits +******* + +Keith Packard wrote the pcf driver found in XFree86. His work is at +the same time the specification and the sample implementation of the +PCF format. Undoubtedly, this driver is inspired from his work. +``` diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index a1f1755be6c..6b593d499ea 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -118,6 +118,12 @@ def cmd_msbuild( "(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n==========" ".+(libjpeg-turbo Licenses\n======================\n\n.+)$" ), + "patch": { + r"CMakeLists.txt": { + # libjpeg-turbo does not detect MSVC x86_arm64 cross-compiler correctly + 'if(MSVC_IDE AND CMAKE_GENERATOR_PLATFORM MATCHES "arm64")': "if({architecture} STREQUAL ARM64)", # noqa: E501 + }, + }, "build": [ *cmds_cmake( ("jpeg-static", "cjpeg-static", "djpeg-static"), @@ -148,9 +154,9 @@ def cmd_msbuild( "libs": [r"*.lib"], }, "xz": { - "url": SF_PROJECTS + "/lzmautils/files/xz-5.4.4.tar.gz/download", - "filename": "xz-5.4.4.tar.gz", - "dir": "xz-5.4.4", + "url": SF_PROJECTS + "/lzmautils/files/xz-5.4.5.tar.gz/download", + "filename": "xz-5.4.5.tar.gz", + "dir": "xz-5.4.5", "license": "COPYING", "build": [ *cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"), @@ -213,7 +219,6 @@ def cmd_msbuild( ], "headers": [r"libtiff\tiff*.h"], "libs": [r"libtiff\*.lib"], - # "bins": [r"libtiff\*.dll"], }, "libpng": { "url": SF_PROJECTS + "/libpng/files/libpng16/1.6.39/lpng1639.zip/download", @@ -239,7 +244,7 @@ def cmd_msbuild( "libs": ["*.lib"], }, "freetype": { - "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz", # noqa: E501 + "url": "https://download.savannah.gnu.org/releases/freetype/freetype-2.13.2.tar.gz", "filename": "freetype-2.13.2.tar.gz", "dir": "freetype-2.13.2", "license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"], @@ -272,7 +277,6 @@ def cmd_msbuild( cmd_xcopy("include", "{inc_dir}"), ], "libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"], - # "bins": [r"objs\{msbuild_arch}\Release\freetype.dll"], }, "lcms2": { "url": SF_PROJECTS + "/lcms/files/lcms/2.15/lcms2-2.15.tar.gz/download", @@ -321,7 +325,7 @@ def cmd_msbuild( }, "libimagequant": { # commit: Merge branch 'master' into msvc (matches 2.17.0 tag) - "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", # noqa: E501 + "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", "filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip", "dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab", "license": "COPYRIGHT", @@ -329,6 +333,8 @@ def cmd_msbuild( "CMakeLists.txt": { "if(OPENMP_FOUND)": "if(false)", "install": "#install", + # libimagequant does not detect MSVC x86_arm64 cross-compiler correctly + "if(${{CMAKE_SYSTEM_PROCESSOR}} STREQUAL ARM64)": "if({architecture} STREQUAL ARM64)", # noqa: E501 } }, "build": [ @@ -369,12 +375,17 @@ def cmd_msbuild( # based on distutils._msvccompiler from CPython 3.7.4 -def find_msvs() -> dict[str, str] | None: +def find_msvs(architecture: str) -> dict[str, str] | None: root = os.environ.get("ProgramFiles(x86)") or os.environ.get("ProgramFiles") if not root: print("Program Files not found") return None + if architecture == "ARM64": + tools = "Microsoft.VisualStudio.Component.VC.Tools.ARM64" + else: + tools = "Microsoft.VisualStudio.Component.VC.Tools.x86.x64" + try: vspath = ( subprocess.check_output( @@ -385,7 +396,7 @@ def find_msvs() -> dict[str, str] | None: "-latest", "-prerelease", "-requires", - "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", + tools, "-property", "installationPath", "-products", @@ -656,7 +667,7 @@ def build_dep_all() -> None: arch_prefs = ARCHITECTURES[args.architecture] print("Target architecture:", args.architecture) - msvs = find_msvs() + msvs = find_msvs(args.architecture) if msvs is None: msg = "Visual Studio not found. Please install Visual Studio 2017 or newer." raise RuntimeError(msg) @@ -691,6 +702,11 @@ def build_dep_all() -> None: disabled += ["libimagequant"] if args.no_fribidi: disabled += ["fribidi"] + elif args.architecture == "ARM64" and platform.machine() != "ARM64": + import warnings + + warnings.warn("Cross-compiling FriBiDi is currently not supported, disabling") + disabled += ["fribidi"] prefs = { "architecture": args.architecture,