From fd59f91d174139f287c083e7b45efd2f6c968774 Mon Sep 17 00:00:00 2001 From: Christian Clauss Date: Mon, 6 Nov 2023 19:13:47 +0600 Subject: [PATCH 01/31] ruff: Minor optimizations of list comprehensions, x in set, etc. --- Tests/test_image_quantize.py | 2 +- pyproject.toml | 1 + setup.py | 22 +++++++++++----------- src/PIL/BmpImagePlugin.py | 2 +- src/PIL/CurImagePlugin.py | 1 - src/PIL/Image.py | 4 ++-- src/PIL/ImageDraw.py | 2 +- src/PIL/Jpeg2KImagePlugin.py | 4 +--- src/PIL/JpegImagePlugin.py | 2 +- src/PIL/SgiImagePlugin.py | 4 ++-- src/PIL/TiffTags.py | 2 +- winbuild/build_prepare.py | 2 +- 12 files changed, 23 insertions(+), 25 deletions(-) diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index 981753eb9..3bafc4c9c 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -67,7 +67,7 @@ def test_quantize_no_dither(): def test_quantize_no_dither2(): im = Image.new("RGB", (9, 1)) - im.putdata(list((p,) * 3 for p in range(0, 36, 4))) + im.putdata([(p,) * 3 for p in range(0, 36, 4)]) palette = Image.new("P", (1, 1)) data = (0, 0, 0, 32, 32, 32) diff --git a/pyproject.toml b/pyproject.toml index 59d8da44e..189f53279 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -81,6 +81,7 @@ version = {attr = "PIL.__version__"} target-version = "py38" line-length = 88 select = [ + "C4", # flake8-comprehensions "E", # pycodestyle errors "EM", # flake8-errmsg "F", # pyflakes errors diff --git a/setup.py b/setup.py index f13f03713..2a364ba97 100755 --- a/setup.py +++ b/setup.py @@ -440,17 +440,17 @@ class pil_build_ext(build_ext): # # add configured kits - for root_name, lib_name in dict( - JPEG_ROOT="libjpeg", - JPEG2K_ROOT="libopenjp2", - TIFF_ROOT=("libtiff-5", "libtiff-4"), - ZLIB_ROOT="zlib", - FREETYPE_ROOT="freetype2", - HARFBUZZ_ROOT="harfbuzz", - FRIBIDI_ROOT="fribidi", - LCMS_ROOT="lcms2", - IMAGEQUANT_ROOT="libimagequant", - ).items(): + for root_name, lib_name in { + "JPEG_ROOT": "libjpeg", + "JPEG2K_ROOT": "libopenjp2", + "TIFF_ROOT": ("libtiff-5", "libtiff-4"), + "ZLIB_ROOT": "zlib", + "FREETYPE_ROOT": "freetype2", + "HARFBUZZ_ROOT": "harfbuzz", + "FRIBIDI_ROOT": "fribidi", + "LCMS_ROOT": "lcms2", + "IMAGEQUANT_ROOT": "libimagequant", + }.items(): root = globals()[root_name] if root is None and root_name in os.environ: diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index ef719e3ec..be7d246dc 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -396,7 +396,7 @@ def _save(im, fp, filename, bitmap_header=True): dpi = info.get("dpi", (96, 96)) # 1 meter == 39.3701 inches - ppm = tuple(map(lambda x: int(x * 39.3701 + 0.5), dpi)) + ppm = tuple((int(x * 39.3701 + 0.5) for x in dpi)) stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3) header = 40 # or 64 for OS/2 version 2 diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index 94efff341..b9d102c39 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -64,7 +64,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): d, e, o, a = self.tile[0] self.tile[0] = d, (0, 0) + self.size, o, a - return # diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 930ca060b..8dae70a1e 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -40,7 +40,7 @@ from enum import IntEnum from pathlib import Path try: - import defusedxml.ElementTree as ElementTree + from defusedxml import ElementTree except ImportError: ElementTree = None @@ -1159,7 +1159,7 @@ class Image: if palette.mode != "P": msg = "bad mode for palette image" raise ValueError(msg) - if self.mode != "RGB" and self.mode != "L": + if self.mode not in {"RGB", "L"}: msg = "only RGB or L mode images can be quantized to a palette" raise ValueError(msg) im = self.im.convert("P", dither, palette.im) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index fbf320d72..6509d4c8e 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -921,7 +921,7 @@ def floodfill(image, xy, value, border=None, thresh=0): if border is None: fill = _color_diff(p, background) <= thresh else: - fill = p != value and p != border + fill = p not in (value, border) if fill: pixel[s, t] = value new_edge.add((s, t)) diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 963d6c1a3..04487aab7 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -334,10 +334,8 @@ def _save(im, fp, filename): if quality_layers is not None and not ( isinstance(quality_layers, (list, tuple)) and all( - [ - isinstance(quality_layer, (int, float)) + isinstance(quality_layer, (int, float)) for quality_layer in quality_layers - ] ) ): msg = "quality_layers must be a sequence of numbers" diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index c091697f5..5bc8fb3a7 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -397,7 +397,7 @@ class JpegImageFile(ImageFile.ImageFile): # self.__offset = self.fp.tell() break s = self.fp.read(1) - elif i == 0 or i == 0xFFFF: + elif i in {0, 0xFFFF}: # padded marker or junk; move on s = b"\xff" elif i == 0xFF00: # Skip extraneous data (escaped 0xFF) diff --git a/src/PIL/SgiImagePlugin.py b/src/PIL/SgiImagePlugin.py index acb9ce5a3..a2a259c89 100644 --- a/src/PIL/SgiImagePlugin.py +++ b/src/PIL/SgiImagePlugin.py @@ -123,7 +123,7 @@ class SgiImageFile(ImageFile.ImageFile): def _save(im, fp, filename): - if im.mode != "RGB" and im.mode != "RGBA" and im.mode != "L": + if im.mode not in {"RGB", "RGBA", "L"}: msg = "Unsupported SGI image mode" raise ValueError(msg) @@ -155,7 +155,7 @@ def _save(im, fp, filename): # Z Dimension: Number of channels z = len(im.mode) - if dim == 1 or dim == 2: + if dim in {1, 2}: z = 1 # assert we've got the right number of bands. diff --git a/src/PIL/TiffTags.py b/src/PIL/TiffTags.py index 30b05e4e1..a2f6ea332 100644 --- a/src/PIL/TiffTags.py +++ b/src/PIL/TiffTags.py @@ -427,7 +427,7 @@ def _populate(): TAGS_V2[k] = TagInfo(k, *v) - for group, tags in TAGS_V2_GROUPS.items(): + for tags in TAGS_V2_GROUPS.values(): for k, v in tags.items(): tags[k] = TagInfo(k, *v) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 4c47db1fb..7d9bcba9f 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -471,7 +471,7 @@ def extract_dep(url: str, filename: str) -> None: msg = "Attempted Path Traversal in Zip File" raise RuntimeError(msg) zf.extractall(sources_dir) - elif filename.endswith(".tar.gz") or filename.endswith(".tgz"): + elif filename.endswith((".tar.gz", ".tgz")): with tarfile.open(file, "r:gz") as tgz: for member in tgz.getnames(): member_abspath = os.path.abspath(os.path.join(sources_dir, member)) From eb8405baa0024a4c86d1fc07a1756c44d126e569 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 13:24:39 +0000 Subject: [PATCH 02/31] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/BmpImagePlugin.py | 2 +- src/PIL/CurImagePlugin.py | 1 - src/PIL/Jpeg2KImagePlugin.py | 3 +-- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index be7d246dc..b51019c66 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -396,7 +396,7 @@ def _save(im, fp, filename, bitmap_header=True): dpi = info.get("dpi", (96, 96)) # 1 meter == 39.3701 inches - ppm = tuple((int(x * 39.3701 + 0.5) for x in dpi)) + ppm = tuple(int(x * 39.3701 + 0.5) for x in dpi) stride = ((im.size[0] * bits + 7) // 8 + 3) & (~3) header = 40 # or 64 for OS/2 version 2 diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index b9d102c39..fc0dae44b 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -65,7 +65,6 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): self.tile[0] = d, (0, 0) + self.size, o, a - # # -------------------------------------------------------------------- diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 04487aab7..bb0cb676a 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -334,8 +334,7 @@ def _save(im, fp, filename): if quality_layers is not None and not ( isinstance(quality_layers, (list, tuple)) and all( - isinstance(quality_layer, (int, float)) - for quality_layer in quality_layers + isinstance(quality_layer, (int, float)) for quality_layer in quality_layers ) ): msg = "quality_layers must be a sequence of numbers" From 98b73009cc92575f7b361057265b0443e4234022 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 16 Nov 2023 23:01:26 +1100 Subject: [PATCH 03/31] Correct PDF palette size when saving --- src/PIL/PdfImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 09fc0c7e6..b6bb60911 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -96,7 +96,7 @@ def _write_image(im, filename, existing_pdf, image_refs): dict_obj["ColorSpace"] = [ PdfParser.PdfName("Indexed"), PdfParser.PdfName("DeviceRGB"), - 255, + len(palette) // 3 - 1, PdfParser.PdfBinary(palette), ] procset = "ImageI" # indexed color From eeeb2d436fcf744781330a6045b7a1e1d63dd009 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 20 Nov 2023 23:56:56 +1100 Subject: [PATCH 04/31] List optional dependencies apart from docs and tests --- pyproject.toml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index a49179a37..d8292a376 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,6 +47,9 @@ docs = [ "sphinx-removed-in", "sphinxext-opengraph", ] +fpx_mic = [ + "olefile", +] tests = [ "check-manifest", "coverage", @@ -59,6 +62,9 @@ tests = [ "pytest-cov", "pytest-timeout", ] +xmp = [ + "defusedxml", +] [project.urls] Changelog = "https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst" Documentation = "https://pillow.readthedocs.io" From 5c277a0e8fef7a1b2ff8ba77540db345d4be5018 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 21 Nov 2023 08:33:11 +1100 Subject: [PATCH 05/31] Split fpx_mic dependency --- pyproject.toml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index d8292a376..9ef328a16 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,10 @@ docs = [ "sphinx-removed-in", "sphinxext-opengraph", ] -fpx_mic = [ +fpx = [ + "olefile", +] +mic = [ "olefile", ] tests = [ From de86f0aeb830b2e5907be3030550de8b71264ebb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 25 Nov 2023 19:42:37 +1100 Subject: [PATCH 06/31] Updated harfbuzz to 8.3.0 --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 4b9a4d46b..2605664eb 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -16,7 +16,7 @@ ARCHIVE_SDIR=pillow-depends-main # Package versions for fresh source builds FREETYPE_VERSION=2.13.2 -HARFBUZZ_VERSION=8.2.1 +HARFBUZZ_VERSION=8.3.0 LIBPNG_VERSION=1.6.40 JPEGTURBO_VERSION=3.0.1 OPENJPEG_VERSION=2.5.0 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 6b593d499..2db646de1 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -345,9 +345,9 @@ DEPS = { "libs": [r"imagequant.lib"], }, "harfbuzz": { - "url": "https://github.com/harfbuzz/harfbuzz/archive/8.2.1.zip", - "filename": "harfbuzz-8.2.1.zip", - "dir": "harfbuzz-8.2.1", + "url": "https://github.com/harfbuzz/harfbuzz/archive/8.3.0.zip", + "filename": "harfbuzz-8.3.0.zip", + "dir": "harfbuzz-8.3.0", "license": "COPYING", "build": [ *cmds_cmake( From 959b45c945b929587bc22edabfd24c57976a49b6 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 27 Nov 2023 16:27:39 +0200 Subject: [PATCH 07/31] Activate tabs based on browser's operating system --- docs/conf.py | 4 +++ docs/installation.rst | 8 ++++++ docs/resources/js/activate_tab.js | 42 +++++++++++++++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 docs/resources/js/activate_tab.js diff --git a/docs/conf.py b/docs/conf.py index ef2cb5b88..c342fded9 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -166,6 +166,10 @@ html_static_path = ["resources"] # directly to the root of the documentation. # html_extra_path = [] +html_js_files = [ + "js/activate_tab.js", +] + # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' diff --git a/docs/installation.rst b/docs/installation.rst index ed25c551a..78900aa57 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -1,6 +1,14 @@ Installation ============ +.. raw:: html + + + Warnings -------- diff --git a/docs/resources/js/activate_tab.js b/docs/resources/js/activate_tab.js new file mode 100644 index 000000000..789052a02 --- /dev/null +++ b/docs/resources/js/activate_tab.js @@ -0,0 +1,42 @@ +// Based on https://stackoverflow.com/a/38241481/724176 +function getOS() { + const userAgent = window.navigator.userAgent, + platform = + window.navigator?.userAgentData?.platform || window.navigator.platform, + macosPlatforms = ["macOS", "Macintosh", "MacIntel", "MacPPC", "Mac68K"], + windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"], + iosPlatforms = ["iPhone", "iPad", "iPod"]; + + if (macosPlatforms.includes(platform)) { + return "macOS"; + } else if (iosPlatforms.includes(platform)) { + return "iOS"; + } else if (windowsPlatforms.includes(platform)) { + return "Windows"; + } else if (/Android/.test(userAgent)) { + return "Android"; + } else if (/Linux/.test(platform)) { + return "Linux"; + } + + return "unknown"; +} + +function activateTab(tabName) { + // Find all label elements containing the specified tab name + const labels = document.querySelectorAll(".tab-label"); + + labels.forEach((label) => { + if (label.textContent.includes(tabName)) { + // Find the associated input element using the 'for' attribute + const tabInputId = label.getAttribute("for"); + const tabInput = document.getElementById(tabInputId); + + // Check if the input element exists before attempting to set the "checked" attribute + if (tabInput) { + // Activate the tab by setting its "checked" attribute to true + tabInput.checked = true; + } + } + }); +} From ccb0a08a9b15276b22f9c44f323b578c47be63d2 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 27 Nov 2023 12:54:50 -0700 Subject: [PATCH 08/31] Select "Windows" tab instad of "Windows using MSYS2/MinGW" MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič --- docs/resources/js/activate_tab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resources/js/activate_tab.js b/docs/resources/js/activate_tab.js index 789052a02..c31c788b8 100644 --- a/docs/resources/js/activate_tab.js +++ b/docs/resources/js/activate_tab.js @@ -27,7 +27,7 @@ function activateTab(tabName) { const labels = document.querySelectorAll(".tab-label"); labels.forEach((label) => { - if (label.textContent.includes(tabName)) { + if (label.textContent == tabName) { // Find the associated input element using the 'for' attribute const tabInputId = label.getAttribute("for"); const tabInput = document.getElementById(tabInputId); From 823c0b0790f64701ddd4a06c75083d81b361c1e5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Nov 2023 19:39:38 +1100 Subject: [PATCH 09/31] Query now searches for exactly matching text --- docs/resources/js/activate_tab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resources/js/activate_tab.js b/docs/resources/js/activate_tab.js index c31c788b8..8d2303b53 100644 --- a/docs/resources/js/activate_tab.js +++ b/docs/resources/js/activate_tab.js @@ -23,7 +23,7 @@ function getOS() { } function activateTab(tabName) { - // Find all label elements containing the specified tab name + // Find all label elements with the specified tab name const labels = document.querySelectorAll(".tab-label"); labels.forEach((label) => { From cf97e8644d09a46ded83030e935401bdd198604b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Nov 2023 19:48:31 +1100 Subject: [PATCH 10/31] Do not test for iOS --- docs/resources/js/activate_tab.js | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/docs/resources/js/activate_tab.js b/docs/resources/js/activate_tab.js index 8d2303b53..c7e5ff057 100644 --- a/docs/resources/js/activate_tab.js +++ b/docs/resources/js/activate_tab.js @@ -1,16 +1,12 @@ // Based on https://stackoverflow.com/a/38241481/724176 function getOS() { const userAgent = window.navigator.userAgent, - platform = - window.navigator?.userAgentData?.platform || window.navigator.platform, + platform = window.navigator?.userAgentData?.platform || window.navigator.platform, macosPlatforms = ["macOS", "Macintosh", "MacIntel", "MacPPC", "Mac68K"], - windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"], - iosPlatforms = ["iPhone", "iPad", "iPod"]; + windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"]; if (macosPlatforms.includes(platform)) { return "macOS"; - } else if (iosPlatforms.includes(platform)) { - return "iOS"; } else if (windowsPlatforms.includes(platform)) { return "Windows"; } else if (/Android/.test(userAgent)) { @@ -18,8 +14,6 @@ function getOS() { } else if (/Linux/.test(platform)) { return "Linux"; } - - return "unknown"; } function activateTab(tabName) { @@ -28,7 +22,7 @@ function activateTab(tabName) { labels.forEach((label) => { if (label.textContent == tabName) { - // Find the associated input element using the 'for' attribute + // Find the associated input element using the "for" attribute const tabInputId = label.getAttribute("for"); const tabInput = document.getElementById(tabInputId); From 06687642b5022c0e2aefa8b6f786f88883acc9f7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Nov 2023 19:41:16 +1100 Subject: [PATCH 11/31] window.navigator has already been used --- docs/resources/js/activate_tab.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/resources/js/activate_tab.js b/docs/resources/js/activate_tab.js index c7e5ff057..92522b5ce 100644 --- a/docs/resources/js/activate_tab.js +++ b/docs/resources/js/activate_tab.js @@ -1,7 +1,7 @@ // Based on https://stackoverflow.com/a/38241481/724176 function getOS() { const userAgent = window.navigator.userAgent, - platform = window.navigator?.userAgentData?.platform || window.navigator.platform, + platform = window.navigator.userAgentData?.platform || window.navigator.platform, macosPlatforms = ["macOS", "Macintosh", "MacIntel", "MacPPC", "Mac68K"], windowsPlatforms = ["Win32", "Win64", "Windows", "WinCE"]; From 40976799c671474b76f32a70e8823a1714d8dd5e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 Nov 2023 20:04:41 +1100 Subject: [PATCH 12/31] Use html_css_files instead of setup() --- docs/conf.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/conf.py b/docs/conf.py index c342fded9..833dfa215 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -166,6 +166,8 @@ html_static_path = ["resources"] # directly to the root of the documentation. # html_extra_path = [] +html_css_files = ["css/dark.css"] + html_js_files = [ "js/activate_tab.js", ] @@ -317,10 +319,6 @@ texinfo_documents = [ # texinfo_no_detailmenu = False -def setup(app): - app.add_css_file("css/dark.css") - - linkcheck_allowed_redirects = { 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", From 106f3bcae73726db1f55d6040bb78474c9dc3c5a Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade Date: Mon, 27 Nov 2023 23:48:47 +0200 Subject: [PATCH 13/31] Install cibuildwheel from requirements file So Renovate can update them all at the same time --- .ci/requirements-cibw.txt | 1 + .github/workflows/wheels.yml | 10 +++++++--- .travis.yml | 2 +- 3 files changed, 9 insertions(+), 4 deletions(-) create mode 100644 .ci/requirements-cibw.txt diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt new file mode 100644 index 000000000..dd61634cd --- /dev/null +++ b/.ci/requirements-cibw.txt @@ -0,0 +1 @@ +cibuildwheel==2.16.2 diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 8e73fe375..1b141b14f 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -52,10 +52,14 @@ jobs: with: submodules: true - - name: Build wheels - uses: pypa/cibuildwheel@v2.16.2 + - uses: actions/setup-python@v4 with: - output-dir: wheelhouse + python-version: "3.x" + + - name: Build wheels + run: | + python3 -m pip install -r .ci/requirements-cibw.txt + python3 -m cibuildwheel --output-dir wheelhouse env: CIBW_ARCHS: ${{ matrix.archs }} CIBW_BUILD: ${{ matrix.build }} diff --git a/.travis.yml b/.travis.yml index 25c76073c..4005dbcf0 100644 --- a/.travis.yml +++ b/.travis.yml @@ -35,7 +35,7 @@ jobs: - CIBW_BUILD="*musllinux*" install: - - python3 -m pip install cibuildwheel==2.16.2 + - python3 -m pip install -r .ci/requirements-cibw.txt script: - python3 -m cibuildwheel --output-dir wheelhouse From 39ec56c6eaa015dde2280ab11e3fa5193f9ad9fb Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Tue, 28 Nov 2023 12:56:32 +0200 Subject: [PATCH 14/31] Improve error message when creating TrueType fonts of invalid size Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- Tests/test_imagefont.py | 6 ++++++ src/PIL/ImageFont.py | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index db0df047f..d71d14a34 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1071,3 +1071,9 @@ def test_raqm_missing_warning(monkeypatch): "Raqm layout was requested, but Raqm is not available. " "Falling back to basic layout." ) + + +@pytest.mark.parametrize("size", [-1, 0]) +def test_invalid_truetype_sizes_raise(layout_engine, size): + with pytest.raises(ValueError): + ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index c29562135..0331a5c45 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -788,8 +788,13 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): .. versionadded:: 4.2.0 :return: A font object. :exception OSError: If the file could not be read. + :exception ValueError: If the font size is not greater than zero. """ + if size <= 0: + msg = "font size must be greater than 0" + raise ValueError(msg) + def freetype(font): return FreeTypeFont(font, size, index, encoding, layout_engine) From 4f939a1c2dd6024a5dc27fd3d10f34732fab2e99 Mon Sep 17 00:00:00 2001 From: Nulano Date: Thu, 16 Nov 2023 00:04:36 +0100 Subject: [PATCH 15/31] use cibuildwheel on windows --- .github/workflows/wheels.yml | 93 ++++++++++++++++++++++++++++++++++-- winbuild/build_prepare.py | 5 ++ 2 files changed, 95 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 1b141b14f..7ff1b35fd 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -3,14 +3,20 @@ name: Wheels on: push: paths: - - ".github/workflows/wheels*.yml" + - ".ci/requirements-cibw.txt" + - ".github/workflows/wheel*" - "wheels/*" + - "winbuild/build_prepare.py" + - "winbuild/fribidi.cmake" tags: - "*" pull_request: paths: - - ".github/workflows/wheels*.yml" + - ".ci/requirements-cibw.txt" + - ".github/workflows/wheel*" - "wheels/*" + - "winbuild/build_prepare.py" + - "winbuild/fribidi.cmake" workflow_dispatch: permissions: @@ -75,6 +81,87 @@ jobs: name: dist path: ./wheelhouse/*.whl + windows: + name: Windows ${{ matrix.arch }} + runs-on: windows-latest + strategy: + fail-fast: false + matrix: + include: + - arch: x86 + cibw_arch: x86 + - arch: x64 + cibw_arch: AMD64 + - arch: ARM64 + cibw_arch: ARM64 + steps: + - uses: actions/checkout@v4 + + - name: Checkout extra test images + uses: actions/checkout@v4 + with: + repository: python-pillow/test-images + path: Tests\test-images + + - uses: actions/setup-python@v4 + with: + python-version: "3.11" + + - name: Prepare for build + run: | + choco install nasm --no-progress + echo "C:\Program Files\NASM" >> $env:GITHUB_PATH + + choco install ghostscript --version=10.0.0.20230317 --no-progress + echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH + + # Install extra test images + xcopy /S /Y Tests\test-images\* Tests\images + + & python.exe -m pip install -r .ci/requirements-cibw.txt + + # Cannot cross-compile FriBiDi (only used for tests) + $FLAGS = ("--no-imagequant", "--architecture=${{ matrix.arch }}") + if ('${{ matrix.arch }}' -eq 'ARM64') { $FLAGS += "--no-fribidi" } + & python.exe winbuild\build_prepare.py -v @FLAGS + shell: pwsh + + - name: Build wheels + run: | + setlocal EnableDelayedExpansion + for %%f in (winbuild\build\license\*) do ( + set x=%%~nf + rem Skip FriBiDi license, it is not included in the wheel. + set fribidi=!x:~0,7! + if NOT !fribidi!==fribidi ( + rem Skip imagequant license, it is not included in the wheel. + set libimagequant=!x:~0,13! + if NOT !libimagequant!==libimagequant ( + echo. >> LICENSE + echo ===== %%~nf ===== >> LICENSE + echo. >> LICENSE + type %%f >> LICENSE + ) + ) + ) + call winbuild\\build\\build_env.cmd + %pythonLocation%\python.exe -m cibuildwheel . --output-dir wheelhouse + env: + CIBW_ARCHS: ${{ matrix.cibw_arch }} + CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" + CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor + CIBW_TEST_SKIP: "*-win_arm64" + CIBW_TEST_COMMAND: >- + cd /d {project} && + python.exe selftest.py && + python.exe -m pytest -vx -W always Tests + shell: cmd + + - uses: actions/upload-artifact@v3 + with: + name: dist + path: ./wheelhouse/*.whl + sdist: runs-on: ubuntu-latest steps: @@ -97,7 +184,7 @@ jobs: success: permissions: contents: none - needs: [build, sdist] + needs: [build, windows, sdist] runs-on: ubuntu-latest name: Wheels Successful steps: diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 6b593d499..01777c617 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -586,14 +586,19 @@ def build_dep(name: str) -> str: def build_dep_all() -> None: lines = [r'call "{build_dir}\build_env.cmd"'] + gha_groups = "GITHUB_ACTIONS" in os.environ for dep_name in DEPS: print() if dep_name in disabled: print(f"Skipping disabled dependency {dep_name}") continue script = build_dep(dep_name) + if gha_groups: + lines.append(f"@echo ::group::Running {script}") lines.append(rf'cmd.exe /c "{{build_dir}}\{script}"') lines.append("if errorlevel 1 echo Build failed! && exit /B 1") + if gha_groups: + lines.append("@echo ::endgroup::") print() lines.append("@echo All Pillow dependencies built successfully!") write_script("build_dep_all.cmd", lines) From bf51d716604ca496c267fd6b9b066b52ac978d39 Mon Sep 17 00:00:00 2001 From: Nulano Date: Sun, 26 Nov 2023 01:01:12 +0100 Subject: [PATCH 16/31] enable heap verification --- .github/workflows/wheels.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 7ff1b35fd..98e4fd46a 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -152,6 +152,7 @@ jobs: CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_COMMAND: >- + reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f && cd /d {project} && python.exe selftest.py && python.exe -m pytest -vx -W always Tests From 3d49244d36beefe7bbc225e860b31364dcebc5b1 Mon Sep 17 00:00:00 2001 From: Nulano Date: Sun, 26 Nov 2023 02:52:32 +0100 Subject: [PATCH 17/31] specify build config settings in pyproject.toml --- .github/workflows/wheels.yml | 2 -- .travis.yml | 1 - pyproject.toml | 1 + 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 98e4fd46a..1634c7834 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -69,7 +69,6 @@ jobs: env: CIBW_ARCHS: ${{ matrix.archs }} CIBW_BUILD: ${{ matrix.build }} - CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_SKIP: pp38-* @@ -149,7 +148,6 @@ jobs: env: CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" - CIBW_CONFIG_SETTINGS: raqm=enable raqm=vendor fribidi=vendor CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_COMMAND: >- reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f && diff --git a/.travis.yml b/.travis.yml index 4005dbcf0..8f8250809 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ if: tag IS present OR type = api env: global: - CIBW_ARCHS=aarch64 - - CIBW_CONFIG_SETTINGS="raqm=enable raqm=vendor fribidi=vendor" - CIBW_SKIP=pp38-* language: python diff --git a/pyproject.toml b/pyproject.toml index 81a51f0d8..f55651c44 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,6 +88,7 @@ version = {attr = "PIL.__version__"} [tool.cibuildwheel] before-all = ".github/workflows/wheels-dependencies.sh" +config-settings = "raqm=enable raqm=vendor fribidi=vendor" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" From 1fdb0668d862ef5aa04f635aa478e2b2e8ff7a27 Mon Sep 17 00:00:00 2001 From: nulano Date: Sun, 1 Jan 2023 03:18:30 +0100 Subject: [PATCH 18/31] test cibuildwheel wheels in Docker on Windows --- .github/workflows/wheels-test.ps1 | 22 ++++++++++++++++++ .github/workflows/wheels-test.sh | 22 +----------------- .github/workflows/wheels.yml | 12 ++++++---- Tests/check_wheel.py | 38 +++++++++++++++++++++++++++++++ Tests/helper.py | 15 ++++++++++-- Tests/test_imagegrab.py | 2 ++ pyproject.toml | 2 +- 7 files changed, 85 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/wheels-test.ps1 create mode 100644 Tests/check_wheel.py diff --git a/.github/workflows/wheels-test.ps1 b/.github/workflows/wheels-test.ps1 new file mode 100644 index 000000000..f593c7228 --- /dev/null +++ b/.github/workflows/wheels-test.ps1 @@ -0,0 +1,22 @@ +param ([string]$venv, [string]$pillow="C:\pillow") +$ErrorActionPreference = 'Stop' +$ProgressPreference = 'SilentlyContinue' +Set-PSDebug -Trace 1 +if ("$venv" -like "*\cibw-run-*\pp*-win_amd64\*") { + # unlike CPython, PyPy requires Visual C++ Redistributable to be installed + [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 + Invoke-WebRequest -Uri 'https://aka.ms/vs/15/release/vc_redist.x64.exe' -OutFile 'vc_redist.x64.exe' + C:\vc_redist.x64.exe /install /quiet /norestart | Out-Null +} +$env:path += ";$pillow\winbuild\build\bin\" +& "$venv\Scripts\activate.ps1" +& reg add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f +cd $pillow +& python -VV +if (!$?) { exit $LASTEXITCODE } +& python selftest.py +if (!$?) { exit $LASTEXITCODE } +& python -m pytest -vx Tests\check_wheel.py +if (!$?) { exit $LASTEXITCODE } +& python -m pytest -vx Tests +if (!$?) { exit $LASTEXITCODE } diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index 34dfeaf14..207ec1567 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -1,10 +1,6 @@ #!/bin/bash set -e -EXP_CODECS="jpg jpg_2000 libtiff zlib" -EXP_MODULES="freetype2 littlecms2 pil tkinter webp" -EXP_FEATURES="fribidi harfbuzz libjpeg_turbo raqm transp_webp webp_anim webp_mux xcb" - if [[ "$OSTYPE" == "darwin"* ]]; then brew install fribidi export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig" @@ -25,21 +21,5 @@ fi # Runs tests python3 selftest.py +python3 -m pytest Tests/check_wheel.py python3 -m pytest - -# Test against expected codecs, modules and features -codecs=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_codecs())))') -if [ "$codecs" != "$EXP_CODECS" ]; then - echo "Codecs should be: '$EXP_CODECS'; but are '$codecs'" - exit 1 -fi -modules=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_modules())))') -if [ "$modules" != "$EXP_MODULES" ]; then - echo "Modules should be: '$EXP_MODULES'; but are '$modules'" - exit 1 -fi -features=$(python3 -c 'from PIL.features import *; print(" ".join(sorted(get_supported_features())))') -if [ "$features" != "$EXP_FEATURES" ]; then - echo "Features should be: '$EXP_FEATURES'; but are '$features'" - exit 1 -fi diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 1634c7834..cbe7eac12 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -148,12 +148,16 @@ jobs: env: CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" + CIBW_CACHE_PATH: "C:\\cibw" CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_COMMAND: >- - reg.exe add "HKLM\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\python.exe" /v "GlobalFlag" /t REG_SZ /d "0x02000000" /f && - cd /d {project} && - python.exe selftest.py && - python.exe -m pytest -vx -W always Tests + docker run --rm + -v {project}:C:\pillow + -v C:\cibw:C:\cibw + -v %CD%\..\venv-test:%CD%\..\venv-test + -e CI -e GITHUB_ACTIONS + mcr.microsoft.com/windows/servercore:ltsc2022 + powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test shell: cmd - uses: actions/upload-artifact@v3 diff --git a/Tests/check_wheel.py b/Tests/check_wheel.py new file mode 100644 index 000000000..38ec9c5d4 --- /dev/null +++ b/Tests/check_wheel.py @@ -0,0 +1,38 @@ +import importlib.util +import sys + +from PIL import features + + +def test_wheel_modules(): + expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"} + + # tkinter is not available in cibuildwheel installed CPython on Windows + if not importlib.util.find_spec("tkinter"): + expected_modules.remove("tkinter") + + assert set(features.get_supported_modules()) == expected_modules + + +def test_wheel_codecs(): + expected_codecs = {"jpg", "jpg_2000", "zlib", "libtiff"} + + assert set(features.get_supported_codecs()) == expected_codecs + + +def test_wheel_features(): + expected_features = { + "webp_anim", + "webp_mux", + "transp_webp", + "raqm", + "fribidi", + "harfbuzz", + "libjpeg_turbo", + "xcb", + } + + if sys.platform == "win32": + expected_features.remove("xcb") + + assert set(features.get_supported_features()) == expected_features diff --git a/Tests/helper.py b/Tests/helper.py index aacd95564..b985a571d 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -5,6 +5,7 @@ Helper functions. import logging import os import shutil +import subprocess import sys import sysconfig import tempfile @@ -258,11 +259,21 @@ def hopper(mode=None, cache={}): def djpeg_available(): - return bool(shutil.which("djpeg")) + if shutil.which("djpeg"): + try: + subprocess.check_call(["djpeg", "-version"]) + return True + except subprocess.CalledProcessError: + return False def cjpeg_available(): - return bool(shutil.which("cjpeg")) + if shutil.which("cjpeg"): + try: + subprocess.check_call(["cjpeg", "-version"]) + return True + except subprocess.CalledProcessError: + return False def netpbm_available(): diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index f8059eca4..d230b9d81 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -15,6 +15,8 @@ class TestImageGrab: sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS" ) def test_grab(self): + if os.environ.get("USERNAME") == "ContainerAdministrator": + pytest.skip("can't grab screen when running in Docker") ImageGrab.grab() ImageGrab.grab(include_layered_windows=True) ImageGrab.grab(all_screens=True) diff --git a/pyproject.toml b/pyproject.toml index f55651c44..aff97a3cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,7 +88,7 @@ version = {attr = "PIL.__version__"} [tool.cibuildwheel] before-all = ".github/workflows/wheels-dependencies.sh" -config-settings = "raqm=enable raqm=vendor fribidi=vendor" +config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" From 7dc3a8ffa565acfab84e6ce10a73a2717a1d3141 Mon Sep 17 00:00:00 2001 From: Nulano Date: Sun, 26 Nov 2023 21:12:38 +0100 Subject: [PATCH 19/31] =?UTF-8?q?=EF=BB=BFdo=20not=20build=20Windows=20whe?= =?UTF-8?q?els=20on=20every=20push?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/test-windows.yml | 48 ++---------------------------- .github/workflows/wheels.yml | 20 ++++++++++--- 2 files changed, 19 insertions(+), 49 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 887c7dd96..8d8cb0b15 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -72,10 +72,10 @@ jobs: - name: Install dependencies id: install run: | - 7z x winbuild\depends\nasm-2.16.01-win64.zip "-o$env:RUNNER_WORKSPACE\" - echo "$env:RUNNER_WORKSPACE\nasm-2.16.01" >> $env:GITHUB_PATH + choco install nasm --no-progress + echo "C:\Program Files\NASM" >> $env:GITHUB_PATH - choco install ghostscript --version=10.0.0.20230317 + choco install ghostscript --version=10.0.0.20230317 --no-progress echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH # Install extra test images @@ -167,7 +167,6 @@ jobs: - name: Build Pillow run: | $FLAGS="-C raqm=vendor -C fribidi=vendor" - if ('${{ github.event_name }}' -ne 'pull_request') { $FLAGS+=" -C imagequant=disable" } cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ." & $env:pythonLocation\python.exe selftest.py --installed shell: pwsh @@ -209,47 +208,6 @@ jobs: flags: GHA_Windows name: ${{ runner.os }} Python ${{ matrix.python-version }} - - name: Build wheel - id: wheel - if: "github.event_name != 'pull_request'" - run: | - mkdir fribidi - copy winbuild\build\bin\fribidi* fribidi - setlocal EnableDelayedExpansion - for %%f in (winbuild\build\license\*) do ( - set x=%%~nf - rem Skip FriBiDi license, it is not included in the wheel. - set fribidi=!x:~0,7! - if NOT !fribidi!==fribidi ( - rem Skip imagequant license, it is not included in the wheel. - set libimagequant=!x:~0,13! - if NOT !libimagequant!==libimagequant ( - echo. >> LICENSE - echo ===== %%~nf ===== >> LICENSE - echo. >> LICENSE - type %%f >> LICENSE - ) - ) - ) - for /f "tokens=3 delims=/" %%a in ("${{ github.ref }}") do echo dist=dist-%%a >> %GITHUB_OUTPUT% - call winbuild\\build\\build_env.cmd - %pythonLocation%\python.exe -m pip wheel -v -C raqm=vendor -C fribidi=vendor -C imagequant=disable . - shell: cmd - - - name: Upload wheel - uses: actions/upload-artifact@v3 - if: "github.event_name != 'pull_request'" - with: - name: ${{ steps.wheel.outputs.dist }} - path: "*.whl" - - - name: Upload fribidi.dll - if: "github.event_name != 'pull_request' && matrix.python-version == 3.11" - uses: actions/upload-artifact@v3 - with: - name: fribidi - path: fribidi\* - success: permissions: contents: none diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index cbe7eac12..e30dbbc40 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -111,9 +111,6 @@ jobs: choco install nasm --no-progress echo "C:\Program Files\NASM" >> $env:GITHUB_PATH - choco install ghostscript --version=10.0.0.20230317 --no-progress - echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH - # Install extra test images xcopy /S /Y Tests\test-images\* Tests\images @@ -160,11 +157,26 @@ jobs: powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test shell: cmd - - uses: actions/upload-artifact@v3 + - name: Upload wheels + uses: actions/upload-artifact@v3 with: name: dist path: ./wheelhouse/*.whl + - name: Prepare to upload FriBiDi + if: "matrix.arch != 'ARM64'" + run: | + mkdir fribidi\${{ matrix.arch }} + copy winbuild\build\bin\fribidi* fribidi\${{ matrix.arch }} + shell: cmd + + - name: Upload fribidi.dll + if: "matrix.arch != 'ARM64'" + uses: actions/upload-artifact@v3 + with: + name: fribidi + path: fribidi\* + sdist: runs-on: ubuntu-latest steps: From d88ab8a668227c84f9ed12a1386efc3e996234b0 Mon Sep 17 00:00:00 2001 From: Nulano Date: Sun, 26 Nov 2023 21:41:04 +0100 Subject: [PATCH 20/31] use verbose flag when building wheels --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index aff97a3cb..41b5aa87f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -88,6 +88,7 @@ version = {attr = "PIL.__version__"} [tool.cibuildwheel] before-all = ".github/workflows/wheels-dependencies.sh" +build-verbosity = 1 config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" From 6fe42bddd934d4bfd1a60874615c7444c77ba534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Baranovi=C4=8D?= Date: Mon, 27 Nov 2023 22:32:00 +0100 Subject: [PATCH 21/31] =?UTF-8?q?=EF=BB=BFApply=20suggestions=20from=20cod?= =?UTF-8?q?e=20review?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Hugo van Kemenade --- .github/workflows/wheels.yml | 7 +++---- Tests/helper.py | 4 ++-- Tests/test_imagegrab.py | 6 ++++-- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index e30dbbc40..c4737bfc7 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -104,7 +104,7 @@ jobs: - uses: actions/setup-python@v4 with: - python-version: "3.11" + python-version: "3.x" - name: Prepare for build run: | @@ -147,14 +147,13 @@ jobs: CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" CIBW_CACHE_PATH: "C:\\cibw" CIBW_TEST_SKIP: "*-win_arm64" - CIBW_TEST_COMMAND: >- - docker run --rm + CIBW_TEST_COMMAND: 'docker run --rm -v {project}:C:\pillow -v C:\cibw:C:\cibw -v %CD%\..\venv-test:%CD%\..\venv-test -e CI -e GITHUB_ACTIONS mcr.microsoft.com/windows/servercore:ltsc2022 - powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test + powershell C:\pillow\.github\workflows\wheels-test.ps1 %CD%\..\venv-test' shell: cmd - name: Upload wheels diff --git a/Tests/helper.py b/Tests/helper.py index b985a571d..cce7eca3a 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -263,7 +263,7 @@ def djpeg_available(): try: subprocess.check_call(["djpeg", "-version"]) return True - except subprocess.CalledProcessError: + except subprocess.CalledProcessError: # pragma: no cover return False @@ -272,7 +272,7 @@ def cjpeg_available(): try: subprocess.check_call(["cjpeg", "-version"]) return True - except subprocess.CalledProcessError: + except subprocess.CalledProcessError: # pragma: no cover return False diff --git a/Tests/test_imagegrab.py b/Tests/test_imagegrab.py index d230b9d81..a75cbadc4 100644 --- a/Tests/test_imagegrab.py +++ b/Tests/test_imagegrab.py @@ -11,12 +11,14 @@ from .helper import assert_image_equal_tofile, skip_unless_feature class TestImageGrab: + @pytest.mark.skipif( + os.environ.get("USERNAME") == "ContainerAdministrator", + reason="can't grab screen when running in Docker", + ) @pytest.mark.skipif( sys.platform not in ("win32", "darwin"), reason="requires Windows or macOS" ) def test_grab(self): - if os.environ.get("USERNAME") == "ContainerAdministrator": - pytest.skip("can't grab screen when running in Docker") ImageGrab.grab() ImageGrab.grab(include_layered_windows=True) ImageGrab.grab(all_screens=True) From 36e0b5312a0d9e984dbe21b144e703440ac4efdf Mon Sep 17 00:00:00 2001 From: Aarni Koskela Date: Wed, 29 Nov 2023 09:21:51 +0200 Subject: [PATCH 22/31] Update Tests/test_imagefont.py Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- Tests/test_imagefont.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index d71d14a34..e21bf8cbf 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -1074,6 +1074,6 @@ def test_raqm_missing_warning(monkeypatch): @pytest.mark.parametrize("size", [-1, 0]) -def test_invalid_truetype_sizes_raise(layout_engine, size): +def test_invalid_truetype_sizes_raise_valueerror(layout_engine, size): with pytest.raises(ValueError): ImageFont.truetype(FONT_PATH, size, layout_engine=layout_engine) From e1291b880dafb9aa87e652bd16d520758c3d0670 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 30 Nov 2023 07:38:27 +1100 Subject: [PATCH 23/31] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 455afb66a..251917654 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.2.0 (unreleased) ------------------- +- Raise ValueError when TrueType font size is not greater than zero #7584 + [akx, radarhere] + - If absent, do not try to close fp when closing image #7557 [RaphaelVRossi, radarhere] From f23d029d5f356a1636342a0dfb12d1d6083fa5b5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 30 Nov 2023 07:41:02 +1100 Subject: [PATCH 24/31] Moved error from truetype() to FreeTypeFont --- src/PIL/ImageFont.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 0331a5c45..18de10375 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -188,6 +188,10 @@ class FreeTypeFont: def __init__(self, font=None, size=10, index=0, encoding="", layout_engine=None): # FIXME: use service provider instead + if size <= 0: + msg = "font size must be greater than 0" + raise ValueError(msg) + self.path = font self.size = size self.index = index @@ -791,10 +795,6 @@ def truetype(font=None, size=10, index=0, encoding="", layout_engine=None): :exception ValueError: If the font size is not greater than zero. """ - if size <= 0: - msg = "font size must be greater than 0" - raise ValueError(msg) - def freetype(font): return FreeTypeFont(font, size, index, encoding, layout_engine) From e1059767d8309c8203d134f6ac0899f2f21ec7c3 Mon Sep 17 00:00:00 2001 From: Nulano Date: Thu, 30 Nov 2023 19:08:35 +0100 Subject: [PATCH 25/31] replace importlib.util.find_spec with try import except ImportError --- Tests/check_wheel.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Tests/check_wheel.py b/Tests/check_wheel.py index 38ec9c5d4..cc52cb75e 100644 --- a/Tests/check_wheel.py +++ b/Tests/check_wheel.py @@ -1,4 +1,3 @@ -import importlib.util import sys from PIL import features @@ -8,7 +7,11 @@ def test_wheel_modules(): expected_modules = {"pil", "tkinter", "freetype2", "littlecms2", "webp"} # tkinter is not available in cibuildwheel installed CPython on Windows - if not importlib.util.find_spec("tkinter"): + try: + import tkinter + + assert tkinter + except ImportError: expected_modules.remove("tkinter") assert set(features.get_supported_modules()) == expected_modules From bd7874a6f1045d8e3597dd5d2ac2adafe4a83fc3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 1 Dec 2023 12:01:34 +1100 Subject: [PATCH 26/31] Update Windows wheels info --- docs/installation.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/docs/installation.rst b/docs/installation.rst index 78900aa57..aa78a7a00 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -95,11 +95,10 @@ and :pypi:`olefile` for Pillow to read FPX and MIC images:: .. tab:: Windows - .. warning:: Pillow > 9.5.0 no longer includes 32-bit wheels. - - We provide Pillow binaries for Windows compiled for the matrix of - supported Pythons in 64-bit versions in the wheel format. These binaries include - support for all optional libraries except libimagequant and libxcb. Raqm support + We provide Pillow binaries for Windows compiled for the matrix of supported + Pythons in the wheel format. These include x86, x86-64 and arm64 versions + (with the exception of Python 3.8 on arm64). These binaries include support + for all optional libraries except libimagequant and libxcb. Raqm support requires FriBiDi to be installed separately:: python3 -m pip install --upgrade pip From 0e523d986858e7c0b4acd45ea1c5a3a639e39b4b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Dec 2023 10:57:16 +1100 Subject: [PATCH 27/31] Fixed closing file pointer with olefile 0.47 --- src/PIL/FpxImagePlugin.py | 1 + src/PIL/MicImagePlugin.py | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index a878cbfd2..3027ef45b 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -227,6 +227,7 @@ class FpxImageFile(ImageFile.ImageFile): break # isn't really required self.stream = stream + self._fp = self.fp self.fp = None def load(self): diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py index 801318930..e4154902f 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -66,6 +66,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): self._n_frames = len(self.images) self.is_animated = self._n_frames > 1 + self.__fp = self.fp self.seek(0) def seek(self, frame): @@ -87,10 +88,12 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): return self.frame def close(self): + self.__fp.close() self.ole.close() super().close() def __exit__(self, *args): + self.__fp.close() self.ole.close() super().__exit__() From 11e226ee9065b9181a6d47d0a5094419aced16d5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Dec 2023 13:18:13 +1100 Subject: [PATCH 28/31] All GitHub Actions wheels are now in the "Wheels" workflow --- RELEASING.md | 9 --------- 1 file changed, 9 deletions(-) diff --git a/RELEASING.md b/RELEASING.md index 8b0673203..74f427f03 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -94,7 +94,6 @@ Released as needed privately to individual vendors for critical security-related ## Source and Binary Distributions -### macOS and Linux * [ ] 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 @@ -104,14 +103,6 @@ Released as needed privately to individual vendors for critical security-related * [ ] Download the Linux aarch64 wheels created by Travis CI from [GitHub releases](https://github.com/python-pillow/Pillow/releases) and copy into `dist`. -### Windows -* [ ] Download the artifacts from the [GitHub Actions "Test Windows" workflow](https://github.com/python-pillow/Pillow/actions/workflows/test-windows.yml) - and copy into `dist/`. For example using [GitHub CLI](https://github.com/cli/cli): - ```bash - gh run download --dir dist - # select dist-x.y.z - ``` - ## Publicize Release * [ ] Announce release availability via [Twitter](https://twitter.com/pythonpillow) and [Mastodon](https://fosstodon.org/@pillow) e.g. https://twitter.com/PythonPillow/status/1013789184354603010 From 77a96a00ce56dbba3e5809f3b2e643e33c2e39f1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Dec 2023 21:50:11 +1100 Subject: [PATCH 29/31] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 251917654..5f58a2cad 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.2.0 (unreleased) ------------------- +- Fixed closing file pointer with olefile 0.47 #7594 + [radarhere] + - Raise ValueError when TrueType font size is not greater than zero #7584 [akx, radarhere] From 2b734a33c765d6a9f12c64b534231c8c39761275 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 4 Dec 2023 08:32:39 +1100 Subject: [PATCH 30/31] Updated lcms2 to 2.16 --- .github/workflows/wheels-dependencies.sh | 2 +- docs/installation.rst | 2 +- winbuild/build_prepare.py | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 2605664eb..3ec314873 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -22,7 +22,7 @@ JPEGTURBO_VERSION=3.0.1 OPENJPEG_VERSION=2.5.0 XZ_VERSION=5.4.5 TIFF_VERSION=4.6.0 -LCMS2_VERSION=2.15 +LCMS2_VERSION=2.16 if [[ -n "$IS_MACOS" ]]; then GIFLIB_VERSION=5.1.4 else diff --git a/docs/installation.rst b/docs/installation.rst index aa78a7a00..fbcfbb907 100644 --- a/docs/installation.rst +++ b/docs/installation.rst @@ -175,7 +175,7 @@ Many of Pillow's features require external libraries: * **littlecms** provides color management * Pillow version 2.2.1 and below uses liblcms1, Pillow 2.3.0 and - above uses liblcms2. Tested with **1.19** and **2.7-2.15**. + above uses liblcms2. Tested with **1.19** and **2.7-2.16**. * **libwebp** provides the WebP format. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index c5c9441d4..f7e145fb9 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -279,10 +279,10 @@ DEPS = { "libs": [r"objs\{msbuild_arch}\Release Static\freetype.lib"], }, "lcms2": { - "url": SF_PROJECTS + "/lcms/files/lcms/2.15/lcms2-2.15.tar.gz/download", - "filename": "lcms2-2.15.tar.gz", - "dir": "lcms2-2.15", - "license": "COPYING", + "url": SF_PROJECTS + "/lcms/files/lcms/2.16/lcms2-2.16.tar.gz/download", + "filename": "lcms2-2.16.tar.gz", + "dir": "lcms2-2.16", + "license": "LICENSE", "patch": { r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": { # default is /MD for x86 and /MT for x64, we need /MD always From d7fa0b9d966a14b81c6bcc63ca94668465c0d9e3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 4 Dec 2023 22:20:22 +1100 Subject: [PATCH 31/31] Update CHANGES.rst [ci skip] --- CHANGES.rst | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGES.rst b/CHANGES.rst index 5f58a2cad..d1fa91bfc 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,10 +5,13 @@ Changelog (Pillow) 10.2.0 (unreleased) ------------------- +- Correct PDF palette size when saving #7555 + [radarhere] + - Fixed closing file pointer with olefile 0.47 #7594 [radarhere] -- Raise ValueError when TrueType font size is not greater than zero #7584 +- Raise ValueError when TrueType font size is not greater than zero #7584, #7587 [akx, radarhere] - If absent, do not try to close fp when closing image #7557