From 5b065970753d464c0d283f9e0c2f4a0971800ed1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 18 Oct 2024 19:29:22 +1100 Subject: [PATCH 001/107] Use fixture to re-open image for each test --- Tests/test_file_jpeg2k.py | 57 +++++++++++++++++++++++++-------------- 1 file changed, 37 insertions(+), 20 deletions(-) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 26b085601b6..79f53e21195 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -2,6 +2,7 @@ import os import re +from collections.abc import Generator from io import BytesIO from pathlib import Path from typing import Any @@ -29,8 +30,16 @@ pytestmark = skip_unless_feature("jpg_2000") -test_card = Image.open("Tests/images/test-card.png") -test_card.load() + +@pytest.fixture +def test_card() -> Generator[ImageFile.ImageFile, None, None]: + with Image.open("Tests/images/test-card.png") as im: + im.load() + try: + yield im + finally: + im.close() + # OpenJPEG 2.0.0 outputs this debugging message sometimes; we should # ignore it---it doesn't represent a test failure. @@ -74,7 +83,7 @@ def test_invalid_file() -> None: Jpeg2KImagePlugin.Jpeg2KImageFile(invalid_file) -def test_bytesio() -> None: +def test_bytesio(test_card: ImageFile.ImageFile) -> None: with open("Tests/images/test-card-lossless.jp2", "rb") as f: data = BytesIO(f.read()) with Image.open(data) as im: @@ -86,7 +95,7 @@ def test_bytesio() -> None: # PIL (they were made using Adobe Photoshop) -def test_lossless(tmp_path: Path) -> None: +def test_lossless(test_card: ImageFile.ImageFile, tmp_path: Path) -> None: with Image.open("Tests/images/test-card-lossless.jp2") as im: im.load() outfile = str(tmp_path / "temp_test-card.png") @@ -94,54 +103,56 @@ def test_lossless(tmp_path: Path) -> None: assert_image_similar(im, test_card, 1.0e-3) -def test_lossy_tiled() -> None: +def test_lossy_tiled(test_card: ImageFile.ImageFile) -> None: assert_image_similar_tofile( test_card, "Tests/images/test-card-lossy-tiled.jp2", 2.0 ) -def test_lossless_rt() -> None: +def test_lossless_rt(test_card: ImageFile.ImageFile) -> None: im = roundtrip(test_card) assert_image_equal(im, test_card) -def test_lossy_rt() -> None: +def test_lossy_rt(test_card: ImageFile.ImageFile) -> None: im = roundtrip(test_card, quality_layers=[20]) assert_image_similar(im, test_card, 2.0) -def test_tiled_rt() -> None: +def test_tiled_rt(test_card: ImageFile.ImageFile) -> None: im = roundtrip(test_card, tile_size=(128, 128)) assert_image_equal(im, test_card) -def test_tiled_offset_rt() -> None: +def test_tiled_offset_rt(test_card: ImageFile.ImageFile) -> None: im = roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32)) assert_image_equal(im, test_card) -def test_tiled_offset_too_small() -> None: +def test_tiled_offset_too_small(test_card: ImageFile.ImageFile) -> None: with pytest.raises(ValueError): roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32)) -def test_irreversible_rt() -> None: +def test_irreversible_rt(test_card: ImageFile.ImageFile) -> None: im = roundtrip(test_card, irreversible=True, quality_layers=[20]) assert_image_similar(im, test_card, 2.0) -def test_prog_qual_rt() -> None: +def test_prog_qual_rt(test_card: ImageFile.ImageFile) -> None: im = roundtrip(test_card, quality_layers=[60, 40, 20], progression="LRCP") assert_image_similar(im, test_card, 2.0) -def test_prog_res_rt() -> None: +def test_prog_res_rt(test_card: ImageFile.ImageFile) -> None: im = roundtrip(test_card, num_resolutions=8, progression="RLCP") assert_image_equal(im, test_card) @pytest.mark.parametrize("num_resolutions", range(2, 6)) -def test_default_num_resolutions(num_resolutions: int) -> None: +def test_default_num_resolutions( + test_card: ImageFile.ImageFile, num_resolutions: int +) -> None: d = 1 << (num_resolutions - 1) im = test_card.resize((d - 1, d - 1)) with pytest.raises(OSError): @@ -205,7 +216,7 @@ def test_header_errors() -> None: pass -def test_layers_type(tmp_path: Path) -> None: +def test_layers_type(test_card: ImageFile.ImageFile, tmp_path: Path) -> None: outfile = str(tmp_path / "temp_layers.jp2") for quality_layers in [[100, 50, 10], (100, 50, 10), None]: test_card.save(outfile, quality_layers=quality_layers) @@ -215,7 +226,7 @@ def test_layers_type(tmp_path: Path) -> None: test_card.save(outfile, quality_layers=quality_layers_str) -def test_layers() -> None: +def test_layers(test_card: ImageFile.ImageFile) -> None: out = BytesIO() test_card.save(out, "JPEG2000", quality_layers=[100, 50, 10], progression="LRCP") out.seek(0) @@ -245,7 +256,13 @@ def test_layers() -> None: (None, {"no_jp2": False}, 4, b"jP"), ), ) -def test_no_jp2(name: str, args: dict[str, bool], offset: int, data: bytes) -> None: +def test_no_jp2( + test_card: ImageFile.ImageFile, + name: str, + args: dict[str, bool], + offset: int, + data: bytes, +) -> None: out = BytesIO() if name: out.name = name @@ -254,7 +271,7 @@ def test_no_jp2(name: str, args: dict[str, bool], offset: int, data: bytes) -> N assert out.read(2) == data -def test_mct() -> None: +def test_mct(test_card: ImageFile.ImageFile) -> None: # Three component for val in (0, 1): out = BytesIO() @@ -419,7 +436,7 @@ def test_comment() -> None: pass -def test_save_comment() -> None: +def test_save_comment(test_card: ImageFile.ImageFile) -> None: for comment in ("Created by Pillow", b"Created by Pillow"): out = BytesIO() test_card.save(out, "JPEG2000", comment=comment) @@ -457,7 +474,7 @@ def test_crashes(test_file: str) -> None: @skip_unless_feature_version("jpg_2000", "2.4.0") -def test_plt_marker() -> None: +def test_plt_marker(test_card: ImageFile.ImageFile) -> None: # Search the start of the codesteam for PLT out = BytesIO() test_card.save(out, "JPEG2000", no_jp2=True, plt=True) From 55579084cd57461517dfe77d7804dfa24219a9f6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 19 Oct 2024 20:40:13 +1100 Subject: [PATCH 002/107] Corrected EMF DPI --- Tests/test_file_wmf.py | 7 +++++++ src/PIL/WmfImagePlugin.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 79e707263d6..424640d7b18 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -1,5 +1,6 @@ from __future__ import annotations +from io import BytesIO from pathlib import Path from typing import IO @@ -61,6 +62,12 @@ def test_load_float_dpi() -> None: with Image.open("Tests/images/drawing.emf") as im: assert im.info["dpi"] == 1423.7668161434979 + with open("Tests/images/drawing.emf", "rb") as fp: + data = fp.read() + b = BytesIO(data[:8] + b"\x06\xFA" + data[10:]) + with Image.open(b) as im: + assert im.info["dpi"][0] == 2540 + def test_load_set_dpi() -> None: with Image.open("Tests/images/drawing.wmf") as im: diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 68f8a74f599..cad6c98d53f 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -128,7 +128,7 @@ def _open(self) -> None: size = x1 - x0, y1 - y0 # calculate dots per inch from bbox and frame - xdpi = 2540.0 * (x1 - y0) / (frame[2] - frame[0]) + xdpi = 2540.0 * (x1 - x0) / (frame[2] - frame[0]) ydpi = 2540.0 * (y1 - y0) / (frame[3] - frame[1]) self.info["wmf_bbox"] = x0, y0, x1, y1 From 8af2d7640ead3ddeba9fdc598dc8e72d580dfdfb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 24 Oct 2024 23:26:13 +1100 Subject: [PATCH 003/107] Pass palette mode to putpalette --- Tests/test_file_gif.py | 6 ++++-- src/PIL/GifImagePlugin.py | 5 +++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 16c8466f331..4eb8c0d02a7 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -4,6 +4,7 @@ from collections.abc import Generator from io import BytesIO from pathlib import Path +from typing import Any import pytest @@ -1431,7 +1432,8 @@ def test_saving_rgba(tmp_path: Path) -> None: assert reloaded_rgba.load()[0, 0][3] == 0 -def test_optimizing_p_rgba(tmp_path: Path) -> None: +@pytest.mark.parametrize("params", ({}, {"disposal": 2, "optimize": False})) +def test_p_rgba(tmp_path: Path, params: dict[str, Any]) -> None: out = str(tmp_path / "temp.gif") im1 = Image.new("P", (100, 100)) @@ -1443,7 +1445,7 @@ def test_optimizing_p_rgba(tmp_path: Path) -> None: im2 = Image.new("P", (100, 100)) im2.putpalette(data, "RGBA") - im1.save(out, save_all=True, append_images=[im2]) + im1.save(out, save_all=True, append_images=[im2], **params) with Image.open(out) as reloaded: assert reloaded.n_frames == 2 diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index a7c4f8b2c57..47022d58436 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -695,8 +695,9 @@ def _write_multiple_frames( ) background = _get_background(im_frame, color) background_im = Image.new("P", im_frame.size, background) - assert im_frames[0].im.palette is not None - background_im.putpalette(im_frames[0].im.palette) + first_palette = im_frames[0].im.palette + assert first_palette is not None + background_im.putpalette(first_palette, first_palette.mode) bbox = _getbbox(background_im, im_frame)[1] elif encoderinfo.get("optimize") and im_frame.mode != "1": if "transparency" not in encoderinfo: From c8e301c47456114f245261fccd316b4fd8964f3e Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Thu, 24 Oct 2024 15:52:59 +0200 Subject: [PATCH 004/107] Fix SEGFAULT from calling FT_New_Face/FT_Done_Face in multiple threads --- src/_imagingft.c | 12 +- src/thirdparty/pythoncapi_compat.h | 341 ++++++++++++++++++++++++++++- 2 files changed, 351 insertions(+), 2 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 7f246412455..6fed821bc0c 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -81,7 +81,10 @@ struct { /* -------------------------------------------------------------------- */ /* font objects */ - static FT_Library library; +static FT_Library library; +#ifdef Py_GIL_DISABLED +PyMutex ft_library_mutex; +#endif typedef struct { PyObject_HEAD FT_Face face; @@ -187,7 +190,9 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { if (filename && font_bytes_size <= 0) { self->font_bytes = NULL; + MUTEX_LOCK(&ft_library_mutex); error = FT_New_Face(library, filename, index, &self->face); + MUTEX_UNLOCK(&ft_library_mutex); } else { /* need to have allocated storage for font_bytes for the life of the object.*/ /* Don't free this before FT_Done_Face */ @@ -197,6 +202,7 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { } if (!error) { memcpy(self->font_bytes, font_bytes, (size_t)font_bytes_size); + MUTEX_LOCK(&ft_library_mutex); error = FT_New_Memory_Face( library, (FT_Byte *)self->font_bytes, @@ -204,6 +210,7 @@ getfont(PyObject *self_, PyObject *args, PyObject *kw) { index, &self->face ); + MUTEX_UNLOCK(&ft_library_mutex); } } @@ -1433,7 +1440,9 @@ font_setvaraxes(FontObject *self, PyObject *args) { static void font_dealloc(FontObject *self) { if (self->face) { + MUTEX_LOCK(&ft_library_mutex); FT_Done_Face(self->face); + MUTEX_UNLOCK(&ft_library_mutex); } if (self->font_bytes) { PyMem_Free(self->font_bytes); @@ -1639,6 +1648,7 @@ PyInit__imagingft(void) { } #ifdef Py_GIL_DISABLED + ft_library_mutex = (PyMutex) {0}; PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); #endif diff --git a/src/thirdparty/pythoncapi_compat.h b/src/thirdparty/pythoncapi_compat.h index 51e8c0de752..ca23d5ffa9a 100644 --- a/src/thirdparty/pythoncapi_compat.h +++ b/src/thirdparty/pythoncapi_compat.h @@ -7,7 +7,10 @@ // https://github.com/python/pythoncapi_compat // // Latest version: -// https://raw.githubusercontent.com/python/pythoncapi_compat/master/pythoncapi_compat.h +// https://raw.githubusercontent.com/python/pythoncapi-compat/main/pythoncapi_compat.h +// +// This file was vendored from the following commit: +// https://github.com/python/pythoncapi-compat/commit/0041177c4f348c8952b4c8980b2c90856e61c7c7 // // SPDX-License-Identifier: 0BSD @@ -45,6 +48,13 @@ extern "C" { # define _PyObject_CAST(op) _Py_CAST(PyObject*, op) #endif +#ifndef Py_BUILD_ASSERT +# define Py_BUILD_ASSERT(cond) \ + do { \ + (void)sizeof(char [1 - 2 * !(cond)]); \ + } while(0) +#endif + // bpo-42262 added Py_NewRef() to Python 3.10.0a3 #if PY_VERSION_HEX < 0x030A00A3 && !defined(Py_NewRef) @@ -1338,6 +1348,166 @@ PyDict_SetDefaultRef(PyObject *d, PyObject *key, PyObject *default_value, } #endif +#if PY_VERSION_HEX < 0x030D00B3 +# define Py_BEGIN_CRITICAL_SECTION(op) { +# define Py_END_CRITICAL_SECTION() } +# define Py_BEGIN_CRITICAL_SECTION2(a, b) { +# define Py_END_CRITICAL_SECTION2() } +#endif + +#if PY_VERSION_HEX < 0x030E0000 && PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) +typedef struct PyUnicodeWriter PyUnicodeWriter; + +static inline void PyUnicodeWriter_Discard(PyUnicodeWriter *writer) +{ + _PyUnicodeWriter_Dealloc((_PyUnicodeWriter*)writer); + PyMem_Free(writer); +} + +static inline PyUnicodeWriter* PyUnicodeWriter_Create(Py_ssize_t length) +{ + if (length < 0) { + PyErr_SetString(PyExc_ValueError, + "length must be positive"); + return NULL; + } + + const size_t size = sizeof(_PyUnicodeWriter); + PyUnicodeWriter *pub_writer = (PyUnicodeWriter *)PyMem_Malloc(size); + if (pub_writer == _Py_NULL) { + PyErr_NoMemory(); + return _Py_NULL; + } + _PyUnicodeWriter *writer = (_PyUnicodeWriter *)pub_writer; + + _PyUnicodeWriter_Init(writer); + if (_PyUnicodeWriter_Prepare(writer, length, 127) < 0) { + PyUnicodeWriter_Discard(pub_writer); + return NULL; + } + writer->overallocate = 1; + return pub_writer; +} + +static inline PyObject* PyUnicodeWriter_Finish(PyUnicodeWriter *writer) +{ + PyObject *str = _PyUnicodeWriter_Finish((_PyUnicodeWriter*)writer); + assert(((_PyUnicodeWriter*)writer)->buffer == NULL); + PyMem_Free(writer); + return str; +} + +static inline int +PyUnicodeWriter_WriteChar(PyUnicodeWriter *writer, Py_UCS4 ch) +{ + if (ch > 0x10ffff) { + PyErr_SetString(PyExc_ValueError, + "character must be in range(0x110000)"); + return -1; + } + + return _PyUnicodeWriter_WriteChar((_PyUnicodeWriter*)writer, ch); +} + +static inline int +PyUnicodeWriter_WriteStr(PyUnicodeWriter *writer, PyObject *obj) +{ + PyObject *str = PyObject_Str(obj); + if (str == NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str); + Py_DECREF(str); + return res; +} + +static inline int +PyUnicodeWriter_WriteRepr(PyUnicodeWriter *writer, PyObject *obj) +{ + PyObject *str = PyObject_Repr(obj); + if (str == NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str); + Py_DECREF(str); + return res; +} + +static inline int +PyUnicodeWriter_WriteUTF8(PyUnicodeWriter *writer, + const char *str, Py_ssize_t size) +{ + if (size < 0) { + size = (Py_ssize_t)strlen(str); + } + + PyObject *str_obj = PyUnicode_FromStringAndSize(str, size); + if (str_obj == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj); + Py_DECREF(str_obj); + return res; +} + +static inline int +PyUnicodeWriter_WriteWideChar(PyUnicodeWriter *writer, + const wchar_t *str, Py_ssize_t size) +{ + if (size < 0) { + size = (Py_ssize_t)wcslen(str); + } + + PyObject *str_obj = PyUnicode_FromWideChar(str, size); + if (str_obj == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str_obj); + Py_DECREF(str_obj); + return res; +} + +static inline int +PyUnicodeWriter_WriteSubstring(PyUnicodeWriter *writer, PyObject *str, + Py_ssize_t start, Py_ssize_t end) +{ + if (!PyUnicode_Check(str)) { + PyErr_Format(PyExc_TypeError, "expect str, not %T", str); + return -1; + } + if (start < 0 || start > end) { + PyErr_Format(PyExc_ValueError, "invalid start argument"); + return -1; + } + if (end > PyUnicode_GET_LENGTH(str)) { + PyErr_Format(PyExc_ValueError, "invalid end argument"); + return -1; + } + + return _PyUnicodeWriter_WriteSubstring((_PyUnicodeWriter*)writer, str, + start, end); +} + +static inline int +PyUnicodeWriter_Format(PyUnicodeWriter *writer, const char *format, ...) +{ + va_list vargs; + va_start(vargs, format); + PyObject *str = PyUnicode_FromFormatV(format, vargs); + va_end(vargs); + if (str == _Py_NULL) { + return -1; + } + + int res = _PyUnicodeWriter_WriteStr((_PyUnicodeWriter*)writer, str); + Py_DECREF(str); + return res; +} +#endif // PY_VERSION_HEX < 0x030E0000 // gh-116560 added PyLong_GetSign() to Python 3.14.0a0 #if PY_VERSION_HEX < 0x030E00A0 @@ -1354,6 +1524,175 @@ static inline int PyLong_GetSign(PyObject *obj, int *sign) #endif +// gh-124502 added PyUnicode_Equal() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyUnicode_Equal(PyObject *str1, PyObject *str2) +{ + if (!PyUnicode_Check(str1)) { + PyErr_Format(PyExc_TypeError, "first argument must be str, not %s", + Py_TYPE(str1)->tp_name); + return -1; + } + if (!PyUnicode_Check(str2)) { + PyErr_Format(PyExc_TypeError, "second argument must be str, not %s", + Py_TYPE(str2)->tp_name); + return -1; + } + +#if PY_VERSION_HEX >= 0x030d0000 && !defined(PYPY_VERSION) + PyAPI_FUNC(int) _PyUnicode_Equal(PyObject *str1, PyObject *str2); + + return _PyUnicode_Equal(str1, str2); +#elif PY_VERSION_HEX >= 0x03060000 && !defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#elif PY_VERSION_HEX >= 0x03090000 && defined(PYPY_VERSION) + return _PyUnicode_EQ(str1, str2); +#else + return (PyUnicode_Compare(str1, str2) == 0); +#endif +} +#endif + + +// gh-121645 added PyBytes_Join() to Python 3.14.0a0 +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyBytes_Join(PyObject *sep, PyObject *iterable) +{ + return _PyBytes_Join(sep, iterable); +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline Py_hash_t Py_HashBuffer(const void *ptr, Py_ssize_t len) +{ +#if PY_VERSION_HEX >= 0x03000000 && !defined(PYPY_VERSION) + PyAPI_FUNC(Py_hash_t) _Py_HashBytes(const void *src, Py_ssize_t len); + + return _Py_HashBytes(ptr, len); +#else + Py_hash_t hash; + PyObject *bytes = PyBytes_FromStringAndSize((const char*)ptr, len); + if (bytes == NULL) { + return -1; + } + hash = PyObject_Hash(bytes); + Py_DECREF(bytes); + return hash; +#endif +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline int PyIter_NextItem(PyObject *iter, PyObject **item) +{ + iternextfunc tp_iternext; + + assert(iter != NULL); + assert(item != NULL); + + tp_iternext = Py_TYPE(iter)->tp_iternext; + if (tp_iternext == NULL) { + *item = NULL; + PyErr_Format(PyExc_TypeError, "expected an iterator, got '%s'", + Py_TYPE(iter)->tp_name); + return -1; + } + + if ((*item = tp_iternext(iter))) { + return 1; + } + if (!PyErr_Occurred()) { + return 0; + } + if (PyErr_ExceptionMatches(PyExc_StopIteration)) { + PyErr_Clear(); + return 0; + } + return -1; +} +#endif + + +#if PY_VERSION_HEX < 0x030E00A0 +static inline PyObject* PyLong_FromInt32(int32_t value) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + return PyLong_FromLong(value); +} + +static inline PyObject* PyLong_FromInt64(int64_t value) +{ + Py_BUILD_ASSERT(sizeof(long long) >= 8); + return PyLong_FromLongLong(value); +} + +static inline PyObject* PyLong_FromUInt32(uint32_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long) >= 4); + return PyLong_FromUnsignedLong(value); +} + +static inline PyObject* PyLong_FromUInt64(uint64_t value) +{ + Py_BUILD_ASSERT(sizeof(unsigned long long) >= 8); + return PyLong_FromUnsignedLongLong(value); +} + +static inline int PyLong_AsInt32(PyObject *obj, int32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(int) == 4); + int value = PyLong_AsInt(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int32_t)value; + return 0; +} + +static inline int PyLong_AsInt64(PyObject *obj, int64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + long long value = PyLong_AsLongLong(obj); + if (value == -1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (int64_t)value; + return 0; +} + +static inline int PyLong_AsUInt32(PyObject *obj, uint32_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long) >= 4); + unsigned long value = PyLong_AsUnsignedLong(obj); + if (value == (unsigned long)-1 && PyErr_Occurred()) { + return -1; + } +#if SIZEOF_LONG > 4 + if ((unsigned long)UINT32_MAX < value) { + PyErr_SetString(PyExc_OverflowError, + "Python int too large to convert to C uint32_t"); + return -1; + } +#endif + *pvalue = (uint32_t)value; + return 0; +} + +static inline int PyLong_AsUInt64(PyObject *obj, uint64_t *pvalue) +{ + Py_BUILD_ASSERT(sizeof(long long) == 8); + unsigned long long value = PyLong_AsUnsignedLongLong(obj); + if (value == (unsigned long long)-1 && PyErr_Occurred()) { + return -1; + } + *pvalue = (uint64_t)value; + return 0; +} +#endif + + #ifdef __cplusplus } #endif From 7999da38a7ff5aa359aa6ae3a7f88b835c7f7f06 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:07:43 +0000 Subject: [PATCH 005/107] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_imagingft.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 6fed821bc0c..884b8f96c2e 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -81,7 +81,7 @@ struct { /* -------------------------------------------------------------------- */ /* font objects */ -static FT_Library library; + static FT_Library library; #ifdef Py_GIL_DISABLED PyMutex ft_library_mutex; #endif @@ -1648,7 +1648,7 @@ PyInit__imagingft(void) { } #ifdef Py_GIL_DISABLED - ft_library_mutex = (PyMutex) {0}; + ft_library_mutex = (PyMutex){0}; PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); #endif From 0fe55d611505fefaaa0d4b7b3a87b803714762de Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 25 Oct 2024 09:23:13 +0800 Subject: [PATCH 006/107] Isolate macOS build from Homebrew. --- .github/workflows/wheels-dependencies.sh | 98 +++++++++++++++++------- .github/workflows/wheels-test.sh | 15 ++-- .gitignore | 1 + pyproject.toml | 7 ++ setup.py | 90 +++++++++++++--------- wheels/multibuild | 2 +- 6 files changed, 139 insertions(+), 74 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 97c1adf098a..35b04c6d652 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -23,6 +23,8 @@ OPENJPEG_VERSION=2.5.2 XZ_VERSION=5.6.3 TIFF_VERSION=4.6.0 LCMS2_VERSION=2.16 +RAQM_VERSION=0.7.1 +FRIBIDI_VERSION=1.0.16 if [[ -n "$IS_MACOS" ]]; then GIFLIB_VERSION=5.2.2 else @@ -38,7 +40,23 @@ BZIP2_VERSION=1.0.8 LIBXCB_VERSION=1.17.0 BROTLI_VERSION=1.1.0 +function build_pkg_config { + if [ -e pkg-config-stamp ]; then return; fi + # This essentially duplicates the Homebrew recipe: + # https://github.com/Homebrew/homebrew-core/blob/master/Formula/p/pkg-config.rb + ORIGINAL_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -Wno-int-conversion" + build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \ + --disable-debug --disable-host-tool --with-internal-glib \ + --with-pc-path=$BUILD_PREFIX/share/pkgconfig:$BUILD_PREFIX/lib/pkgconfig \ + --with-system-include-path=$(xcrun --show-sdk-path --sdk macosx)/usr/include + CFLAGS=$ORIGINAL_CFLAGS + export PKG_CONFIG=$BUILD_PREFIX/bin/pkg-config + touch pkg-config-stamp +} + function build_brotli { + if [ -e brotli-stamp ]; then return; fi local cmake=$(get_modern_cmake) local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz) (cd $out_dir \ @@ -48,25 +66,25 @@ function build_brotli { cp /usr/local/lib64/libbrotli* /usr/local/lib cp /usr/local/lib64/pkgconfig/libbrotli* /usr/local/lib/pkgconfig fi + touch brotli-stamp } function build_harfbuzz { - python3 -m pip install meson ninja + if [ -e harfbuzz-stamp ]; then return; fi + python -m pip install meson ninja local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) (cd $out_dir \ - && meson setup build --buildtype=release -Dfreetype=enabled -Dglib=disabled) + && meson setup build --prefix=$BUILD_PREFIX --buildtype=release -Dfreetype=enabled -Dglib=disabled) (cd $out_dir/build \ && meson install) if [[ "$MB_ML_LIBC" == "manylinux" ]]; then cp /usr/local/lib64/libharfbuzz* /usr/local/lib fi + touch harfbuzz-stamp } function build { - if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "arm64" ]]; then - sudo chown -R runner /usr/local - fi build_xz if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then yum remove -y zlib-devel @@ -77,17 +95,23 @@ function build { if [ -n "$IS_MACOS" ]; then build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib + build_simple libXdmcp 1.1.5 https://www.x.org/pub/individual/lib build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist - if [[ "$CIBW_ARCHS" == "arm64" ]]; then - cp /usr/local/share/pkgconfig/xcb-proto.pc /usr/local/lib/pkgconfig - fi else sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc fi build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib build_libjpeg_turbo - build_tiff + if [ -n "$IS_MACOS" ]; then + # Custom tiff build to include jpeg; by default, configure won't include + # headers/libs in the custom macOS prefix + build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \ + --with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib + else + build_tiff + fi + build_libpng build_lcms2 build_openjpeg @@ -113,33 +137,55 @@ function build { fi build_harfbuzz + + if [ -n "$IS_MACOS" ]; then + build_simple fribidi $FRIBIDI_VERSION https://github.com/fribidi/fribidi/releases/download/v$FRIBIDI_VERSION tar.xz --enable-shared + build_simple raqm $RAQM_VERSION https://github.com/Host_Oman/libraqm/releases/download/v$RAQM_VERSION tar.gz --enable-shared + fi } +# Perform all dependency builds in the build subfolder. +mkdir -p build +pushd build > /dev/null + # Any stuff that you need to do before you start building the wheels # Runs in the root directory of this repository. -curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip -untar pillow-depends-main.zip - -if [[ -n "$IS_MACOS" ]]; then - # libtiff and libxcb cause a conflict with building libtiff and libxcb - # libxau and libxdmcp cause an issue on macOS < 11 - # remove cairo to fix building harfbuzz on arm64 - # remove lcms2 and libpng to fix building openjpeg on arm64 - # remove jpeg-turbo to avoid inclusion on arm64 - # remove webp and zstd to avoid inclusion on x86_64 - # curl from brew requires zstd, use system curl - brew remove --ignore-dependencies libpng libtiff libxcb libxau libxdmcp curl cairo lcms2 zstd - if [[ "$CIBW_ARCHS" == "arm64" ]]; then - brew remove --ignore-dependencies jpeg-turbo - else - brew remove --ignore-dependencies webp +if [[ ! -d pillow-depends-main ]]; then + if [[ ! -f pillow-depends-main.zip ]]; then + echo "Download pillow dependency sources..." + curl -fSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip fi + untar pillow-depends-main.zip +fi - brew install pkg-config +if [[ -n "$IS_MACOS" ]]; then + # Build and install into the `deps` folder. + BUILD_PREFIX=$(pwd)/deps + + # Homebrew (or similar packaging environments) install can contain some of + # the libraries that we're going to build. However, they may be compiled + # with a MACOSX_DEPLOYMENT_TARGET that doesn't match what we want to use, + # and they may bring in other dependencies that we don't want. The same will + # be true of any other locations on the path. To avoid conflicts, strip the + # path down to the bare mimimum (which, on macOS, won't include any + # development dependencies). + export PATH="$BUILD_PREFIX/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:$(dirname $(which python))" + export CMAKE_PREFIX_PATH=$BUILD_PREFIX + + # Link the brew command into our isolated build directory. + mkdir -p "$BUILD_PREFIX/bin" + mkdir -p "$BUILD_PREFIX/lib" + + # Ensure pkg-confg and cmake are available + build_pkg_config + python -m pip install cmake fi wrap_wheel_builder build +# Return to the project root to finish the build +popd > /dev/null + # Append licenses for filename in wheels/dependency_licenses/*; do echo -e "\n\n----\n\n$(basename $filename | cut -f 1 -d '.')\n" | cat >> LICENSE diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index b30b1725f94..05f5162c78a 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -1,16 +1,13 @@ #!/bin/bash set -e -if [[ "$OSTYPE" == "darwin"* ]]; then - brew install fribidi - export PKG_CONFIG_PATH="/usr/local/opt/openblas/lib/pkgconfig" - if [ -f /opt/homebrew/lib/libfribidi.dylib ]; then - sudo cp /opt/homebrew/lib/libfribidi.dylib /usr/local/lib +# For Unix, Ensure fibidi is installed by the system. +if [[ "$OSTYPE" != "darwin"* ]]; then + if [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then + apk add curl fribidi + else + yum install -y fribidi fi -elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then - apk add curl fribidi -else - yum install -y fribidi fi python3 -m pip install numpy diff --git a/.gitignore b/.gitignore index 1dd6c917524..9b577325730 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ lib64/ parts/ sdist/ var/ +wheelhouse/ *.egg-info/ .installed.cfg *.egg diff --git a/pyproject.toml b/pyproject.toml index c55be769341..53546f0ab57 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -93,10 +93,17 @@ 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" +# Add an explicit dependencies prefix for macOS, and don't request vendored libraries. +macos.config-settings = "raqm=enable imagequant=disable dependencies-prefix=./build/deps" + test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" +[tool.cibuildwheel.macos.environment] +PATH = "$(pwd)/build/deps/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:$(dirname $(which python))" + [tool.black] exclude = "wheels/multibuild" diff --git a/setup.py b/setup.py index 60707083f6e..cb1da7c73f3 100644 --- a/setup.py +++ b/setup.py @@ -344,6 +344,11 @@ def __iter__(self) -> Iterator[str]: for x in ("raqm", "fribidi") ] + [ + ( + "dependencies-prefix", + None, + "The prefix where build dependencies are located.", + ), ("disable-platform-guessing", None, "Disable platform guessing on Linux"), ("debug", None, "Debug logging"), ] @@ -355,6 +360,7 @@ def check_configuration(option: str, value: str) -> bool | None: return True if value in configuration.get(option, []) else None def initialize_options(self) -> None: + self.dependencies_prefix = configuration.get("dependencies-prefix", []) self.disable_platform_guessing = self.check_configuration( "platform-guessing", "disable" ) @@ -564,48 +570,56 @@ def build_extensions(self) -> None: ) elif sys.platform == "darwin": - # attempt to make sure we pick freetype2 over other versions - _add_directory(include_dirs, "/sw/include/freetype2") - _add_directory(include_dirs, "/sw/lib/freetype2/include") - # fink installation directories - _add_directory(library_dirs, "/sw/lib") - _add_directory(include_dirs, "/sw/include") - # darwin ports installation directories - _add_directory(library_dirs, "/opt/local/lib") - _add_directory(include_dirs, "/opt/local/include") - - # if Homebrew is installed, use its lib and include directories - try: - prefix = ( - subprocess.check_output(["brew", "--prefix"]) - .strip() - .decode("latin1") - ) - except Exception: - # Homebrew not installed - prefix = None + if self.dependencies_prefix: + # Use the explicitly provided prefixes for dependencies. + for prefix in self.dependencies_prefix: + _add_directory(library_dirs, os.path.join(prefix, "lib")) + _add_directory(include_dirs, os.path.join(prefix, "include")) + else: + # Guess the dependency locations based on homebrew/fink/macports + # attempt to make sure we pick freetype2 over other versions + _add_directory(include_dirs, "/sw/include/freetype2") + _add_directory(include_dirs, "/sw/lib/freetype2/include") + # fink installation directories + _add_directory(library_dirs, "/sw/lib") + _add_directory(include_dirs, "/sw/include") + # darwin ports installation directories + _add_directory(library_dirs, "/opt/local/lib") + _add_directory(include_dirs, "/opt/local/include") + + # if Homebrew is installed, use its lib and include directories + try: + prefix = ( + subprocess.check_output(["brew", "--prefix"]) + .strip() + .decode("latin1") + ) + except Exception: + # Homebrew not installed + prefix = None - ft_prefix = None + ft_prefix = None - if prefix: - # add Homebrew's include and lib directories - _add_directory(library_dirs, os.path.join(prefix, "lib")) - _add_directory(include_dirs, os.path.join(prefix, "include")) - _add_directory( - include_dirs, os.path.join(prefix, "opt", "zlib", "include") - ) - ft_prefix = os.path.join(prefix, "opt", "freetype") + if prefix: + # add Homebrew's include and lib directories + _add_directory(library_dirs, os.path.join(prefix, "lib")) + _add_directory(include_dirs, os.path.join(prefix, "include")) + _add_directory( + include_dirs, os.path.join(prefix, "opt", "zlib", "include") + ) + ft_prefix = os.path.join(prefix, "opt", "freetype") - if ft_prefix and os.path.isdir(ft_prefix): - # freetype might not be linked into Homebrew's prefix - _add_directory(library_dirs, os.path.join(ft_prefix, "lib")) - _add_directory(include_dirs, os.path.join(ft_prefix, "include")) - else: - # fall back to freetype from XQuartz if - # Homebrew's freetype is missing - _add_directory(library_dirs, "/usr/X11/lib") - _add_directory(include_dirs, "/usr/X11/include") + if ft_prefix and os.path.isdir(ft_prefix): + # freetype might not be linked into Homebrew's prefix + _add_directory(library_dirs, os.path.join(ft_prefix, "lib")) + _add_directory(include_dirs, os.path.join(ft_prefix, "include")) + else: + # fall back to freetype from XQuartz if + # Homebrew's freetype is missing + _add_directory(library_dirs, "/usr/X11/lib") + _add_directory(include_dirs, "/usr/X11/include") + # Add the macOS SDK path. sdk_path = self.get_macos_sdk_path() if sdk_path: _add_directory(library_dirs, os.path.join(sdk_path, "usr", "lib")) diff --git a/wheels/multibuild b/wheels/multibuild index 452dd2d1705..8a9333e307f 160000 --- a/wheels/multibuild +++ b/wheels/multibuild @@ -1 +1 @@ -Subproject commit 452dd2d1705f6b2375369a6570c415beb3163f70 +Subproject commit 8a9333e307fa3f24db2089a66f25e38a8272c261 From fc35fcc5ea844cd0184af6e999d0f35835a4254a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 25 Oct 2024 10:55:30 +0800 Subject: [PATCH 007/107] Cleanups and typos identified by code review. --- .github/workflows/wheels-dependencies.sh | 8 ++++---- .github/workflows/wheels-test.sh | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 35b04c6d652..2c0835e7e56 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -23,7 +23,7 @@ OPENJPEG_VERSION=2.5.2 XZ_VERSION=5.6.3 TIFF_VERSION=4.6.0 LCMS2_VERSION=2.16 -RAQM_VERSION=0.7.1 +RAQM_VERSION=0.10.2 FRIBIDI_VERSION=1.0.16 if [[ -n "$IS_MACOS" ]]; then GIFLIB_VERSION=5.2.2 @@ -71,7 +71,7 @@ function build_brotli { function build_harfbuzz { if [ -e harfbuzz-stamp ]; then return; fi - python -m pip install meson ninja + python3 -m pip install meson ninja local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) (cd $out_dir \ @@ -176,9 +176,9 @@ if [[ -n "$IS_MACOS" ]]; then mkdir -p "$BUILD_PREFIX/bin" mkdir -p "$BUILD_PREFIX/lib" - # Ensure pkg-confg and cmake are available + # Ensure pkg-config and cmake are available build_pkg_config - python -m pip install cmake + python3 -m pip install cmake fi wrap_wheel_builder build diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index 05f5162c78a..3a8fc87ba1e 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -1,7 +1,7 @@ #!/bin/bash set -e -# For Unix, Ensure fibidi is installed by the system. +# For Unix, ensure fribidi is installed by the system. if [[ "$OSTYPE" != "darwin"* ]]; then if [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then apk add curl fribidi From 00809a25516e1396e3e3ee38a760733efffbd1a9 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 25 Oct 2024 13:42:46 +0800 Subject: [PATCH 008/107] Tweaks to ensure isolation from Homebrew on x86_64. --- .github/workflows/wheels-dependencies.sh | 34 +++++++++++++++++------- pyproject.toml | 2 +- 2 files changed, 26 insertions(+), 10 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 2c0835e7e56..dbabd4aef28 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -4,6 +4,9 @@ if [ -z "$IS_MACOS" ]; then export MB_ML_LIBC=${AUDITWHEEL_POLICY::9} export MB_ML_VER=${AUDITWHEEL_POLICY:9} + + # Build and install into the `build/deps` folder. + BUILD_PREFIX=$(pwd)/build/deps fi export PLAT=$CIBW_ARCHS source wheels/multibuild/common_utils.sh @@ -84,6 +87,18 @@ function build_harfbuzz { touch harfbuzz-stamp } +function build_raqm { + if [ -e raqm-stamp ]; then return; fi + python3 -m pip install meson ninja + + local out_dir=$(fetch_unpack https://github.com/HOST-Oman/libraqm/releases/download/v$RAQM_VERSION/raqm-$RAQM_VERSION.tar.xz raqm-$RAQM_VERSION.tar.xz) + (cd $out_dir \ + && meson setup build --prefix=$BUILD_PREFIX) + (cd $out_dir/build \ + && meson install) + touch raqm-stamp +} + function build { build_xz if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then @@ -105,9 +120,12 @@ function build { build_libjpeg_turbo if [ -n "$IS_MACOS" ]; then # Custom tiff build to include jpeg; by default, configure won't include - # headers/libs in the custom macOS prefix + # headers/libs in the custom macOS prefix. Explicitly disable webp and + # zstd, because on x86_64 macs, it will pick up the Homebrew versions of + # webp and zstd from /usr/local. build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \ - --with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib + --with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \ + --disable-webp --disable-zstd else build_tiff fi @@ -140,7 +158,7 @@ function build { if [ -n "$IS_MACOS" ]; then build_simple fribidi $FRIBIDI_VERSION https://github.com/fribidi/fribidi/releases/download/v$FRIBIDI_VERSION tar.xz --enable-shared - build_simple raqm $RAQM_VERSION https://github.com/Host_Oman/libraqm/releases/download/v$RAQM_VERSION tar.gz --enable-shared + build_raqm fi } @@ -159,9 +177,6 @@ if [[ ! -d pillow-depends-main ]]; then fi if [[ -n "$IS_MACOS" ]]; then - # Build and install into the `deps` folder. - BUILD_PREFIX=$(pwd)/deps - # Homebrew (or similar packaging environments) install can contain some of # the libraries that we're going to build. However, they may be compiled # with a MACOSX_DEPLOYMENT_TARGET that doesn't match what we want to use, @@ -169,16 +184,17 @@ if [[ -n "$IS_MACOS" ]]; then # be true of any other locations on the path. To avoid conflicts, strip the # path down to the bare mimimum (which, on macOS, won't include any # development dependencies). - export PATH="$BUILD_PREFIX/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:$(dirname $(which python))" + export PATH="$BUILD_PREFIX/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" export CMAKE_PREFIX_PATH=$BUILD_PREFIX # Link the brew command into our isolated build directory. mkdir -p "$BUILD_PREFIX/bin" mkdir -p "$BUILD_PREFIX/lib" - # Ensure pkg-config and cmake are available + # Ensure pkg-config is available build_pkg_config - python3 -m pip install cmake + # Ensure cmake is available + python3 -m pip cmake fi wrap_wheel_builder build diff --git a/pyproject.toml b/pyproject.toml index 53546f0ab57..3839cf8402a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -102,7 +102,7 @@ test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" [tool.cibuildwheel.macos.environment] -PATH = "$(pwd)/build/deps/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:$(dirname $(which python))" +PATH = "$(pwd)/build/deps/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" [tool.black] exclude = "wheels/multibuild" From 06dbfedb0fbfcf38f6d6298ac4979e82fb653956 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 25 Oct 2024 13:51:37 +0800 Subject: [PATCH 009/107] Bump the multibuild version to fix jpeg-turbo issue. --- wheels/multibuild | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wheels/multibuild b/wheels/multibuild index 8a9333e307f..22e8ad0c69a 160000 --- a/wheels/multibuild +++ b/wheels/multibuild @@ -1 +1 @@ -Subproject commit 8a9333e307fa3f24db2089a66f25e38a8272c261 +Subproject commit 22e8ad0c69acdb0c94abab74da6e09f2810d50d5 From 5a8373e527bbea93cb9c3be87c206683f488ad8e Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 25 Oct 2024 14:47:51 +0800 Subject: [PATCH 010/107] Correct a dumb pip invocation error. --- .github/workflows/wheels-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index dbabd4aef28..0990943d32a 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -194,7 +194,7 @@ if [[ -n "$IS_MACOS" ]]; then # Ensure pkg-config is available build_pkg_config # Ensure cmake is available - python3 -m pip cmake + python3 -m pip install cmake fi wrap_wheel_builder build From 140a06e0fda60b7474dbdfde53b2ad2f1d344603 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 25 Oct 2024 16:04:56 +0800 Subject: [PATCH 011/107] Explicitly disable libdeflate on libtiff. --- .github/workflows/wheels-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 0990943d32a..c6f57200f40 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -125,7 +125,7 @@ function build { # webp and zstd from /usr/local. build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \ --with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \ - --disable-webp --disable-zstd + --disable-webp --disable-zstd --disable-libdeflate else build_tiff fi From 0961d3d22bc966f19f68ce01db6d07373dfbe6bc Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 25 Oct 2024 16:10:29 +0800 Subject: [PATCH 012/107] Possible fix for linux build failures. --- .github/workflows/wheels-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index c6f57200f40..4d58bee901f 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -113,7 +113,7 @@ function build { build_simple libXdmcp 1.1.5 https://www.x.org/pub/individual/lib build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist else - sed s/\${pc_sysrootdir\}// /usr/local/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc + sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc fi build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib From 43c34fc0a5c22b1c00152a246fd39fe02925043d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 25 Oct 2024 17:20:34 +0800 Subject: [PATCH 013/107] Copy manylinux lib64 files from the correct built prefix. --- .github/workflows/wheels-dependencies.sh | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 4d58bee901f..9dae287170a 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -66,8 +66,8 @@ function build_brotli { && $cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \ && make install) if [[ "$MB_ML_LIBC" == "manylinux" ]]; then - cp /usr/local/lib64/libbrotli* /usr/local/lib - cp /usr/local/lib64/pkgconfig/libbrotli* /usr/local/lib/pkgconfig + cp $BUILD_PREFIX/lib64/libbrotli* $BUILD_PREFIX/lib + cp $BUILD_PREFIX/lib64/pkgconfig/libbrotli* $BUILD_PREFIX/lib/pkgconfig fi touch brotli-stamp } @@ -82,7 +82,7 @@ function build_harfbuzz { (cd $out_dir/build \ && meson install) if [[ "$MB_ML_LIBC" == "manylinux" ]]; then - cp /usr/local/lib64/libharfbuzz* /usr/local/lib + cp $BUILD_PREFIX/lib64/libharfbuzz* $BUILD_PREFIX/lib fi touch harfbuzz-stamp } @@ -113,7 +113,7 @@ function build { build_simple libXdmcp 1.1.5 https://www.x.org/pub/individual/lib build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist else - sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > /usr/local/lib/pkgconfig/xcb-proto.pc + sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc fi build_simple libxcb $LIBXCB_VERSION https://www.x.org/releases/individual/lib @@ -133,8 +133,8 @@ function build { build_libpng build_lcms2 build_openjpeg - if [ -f /usr/local/lib64/libopenjp2.so ]; then - cp /usr/local/lib64/libopenjp2.so /usr/local/lib + if [ -f $BUILD_PREFIX/lib64/libopenjp2.so ]; then + cp $BUILD_PREFIX/lib64/libopenjp2.so $BUILD_PREFIX/lib fi ORIGINAL_CFLAGS=$CFLAGS From bb3515d649cda179037161ee3bea264a4d289b32 Mon Sep 17 00:00:00 2001 From: Lysandros Nikolaou Date: Fri, 25 Oct 2024 17:32:29 +0200 Subject: [PATCH 014/107] Make PyMutex static and get rid of initialization --- src/_imagingft.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 884b8f96c2e..d38279f3e4b 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -83,7 +83,7 @@ struct { static FT_Library library; #ifdef Py_GIL_DISABLED -PyMutex ft_library_mutex; +static PyMutex ft_library_mutex; #endif typedef struct { @@ -1648,7 +1648,6 @@ PyInit__imagingft(void) { } #ifdef Py_GIL_DISABLED - ft_library_mutex = (PyMutex){0}; PyUnstable_Module_SetGIL(m, Py_MOD_GIL_NOT_USED); #endif From 3e0849bfb56f6fa8c73ff1bde044551ed72ac2d3 Mon Sep 17 00:00:00 2001 From: Nulano Date: Fri, 25 Oct 2024 20:35:17 +0200 Subject: [PATCH 015/107] winbuild: Replace zlib with zlib-ng built with CMake --- winbuild/build_prepare.py | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index a21fbef9148..2065e41a96e 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -120,11 +120,10 @@ def cmd_msbuild( "OPENJPEG": "2.5.2", "TIFF": "4.6.0", "XZ": "5.6.3", - "ZLIB": "1.3.1", + "ZLIBNG": "2.2.2", } V["LIBPNG_DOTLESS"] = V["LIBPNG"].replace(".", "") V["LIBPNG_XY"] = "".join(V["LIBPNG"].split(".")[:2]) -V["ZLIB_DOTLESS"] = V["ZLIB"].replace(".", "") # dependencies, listed in order of compilation @@ -161,18 +160,22 @@ def cmd_msbuild( "bins": ["cjpeg.exe", "djpeg.exe"], }, "zlib": { - "url": f"https://zlib.net/zlib{V['ZLIB_DOTLESS']}.zip", - "filename": f"zlib{V['ZLIB_DOTLESS']}.zip", - "dir": f"zlib-{V['ZLIB']}", - "license": "README", - "license_pattern": "Copyright notice:\n\n(.+)$", + "url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['ZLIBNG']}.zip", + "filename": f"zlib-ng-{V['ZLIBNG']}.zip", + "dir": f"zlib-ng-{V['ZLIBNG']}", + "license": "LICENSE.md", + "patch": { + r"CMakeLists.txt": { + "set_target_properties(zlib PROPERTIES OUTPUT_NAME zlibstatic${{SUFFIX}})": "set_target_properties(zlib PROPERTIES OUTPUT_NAME zlib)", # noqa: E501 + }, + }, "build": [ - cmd_nmake(r"win32\Makefile.msc", "clean"), - cmd_nmake(r"win32\Makefile.msc", "zlib.lib"), - cmd_copy("zlib.lib", "z.lib"), + *cmds_cmake( + "zlib", "-DBUILD_SHARED_LIBS:BOOL=OFF", "-DZLIB_COMPAT:BOOL=ON" + ), ], "headers": [r"z*.h"], - "libs": [r"*.lib"], + "libs": [r"zlib.lib"], }, "xz": { "url": f"https://github.com/tukaani-project/xz/releases/download/v{V['XZ']}/xz-{V['XZ']}.tar.gz", From 7885066e5fc6ae56db0bef18eb2c188a6aa9a181 Mon Sep 17 00:00:00 2001 From: Nulano Date: Fri, 25 Oct 2024 20:37:21 +0200 Subject: [PATCH 016/107] PIL.features: Add a compile-time zlib-ng feature flag and version number --- Tests/check_wheel.py | 3 +++ docs/reference/features.rst | 1 + src/PIL/features.py | 7 ++++++- src/_imaging.c | 14 ++++++++++++++ 4 files changed, 24 insertions(+), 1 deletion(-) diff --git a/Tests/check_wheel.py b/Tests/check_wheel.py index 4b91984f58a..374e48e8ad3 100644 --- a/Tests/check_wheel.py +++ b/Tests/check_wheel.py @@ -34,10 +34,13 @@ def test_wheel_features() -> None: "fribidi", "harfbuzz", "libjpeg_turbo", + "zlib_ng", "xcb", } if sys.platform == "win32": expected_features.remove("xcb") + else: + expected_features.remove("zlib_ng") assert set(features.get_supported_features()) == expected_features diff --git a/docs/reference/features.rst b/docs/reference/features.rst index fcff9673567..427c0f606d2 100644 --- a/docs/reference/features.rst +++ b/docs/reference/features.rst @@ -54,6 +54,7 @@ Feature version numbers are available only where stated. Support for the following features can be checked: * ``libjpeg_turbo``: (compile time) Whether Pillow was compiled against the libjpeg-turbo version of libjpeg. Compile-time version number is available. +* ``zlib_ng``: (compile time) Whether Pillow was compiled against the zlib-ng version of zlib. Compile-time version number is available. * ``raqm``: Raqm library, required for ``ImageFont.Layout.RAQM`` in :py:func:`PIL.ImageFont.truetype`. Run-time version number is available for Raqm 0.7.0 or newer. * ``libimagequant``: (compile time) ImageQuant quantization support in :py:func:`PIL.Image.Image.quantize`. Run-time version number is available. * ``xcb``: (compile time) Support for X11 in :py:func:`PIL.ImageGrab.grab` via the XCB library. diff --git a/src/PIL/features.py b/src/PIL/features.py index 75d59e01c40..3645e3defc4 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -127,6 +127,7 @@ def get_supported_codecs() -> list[str]: "fribidi": ("PIL._imagingft", "HAVE_FRIBIDI", "fribidi_version"), "harfbuzz": ("PIL._imagingft", "HAVE_HARFBUZZ", "harfbuzz_version"), "libjpeg_turbo": ("PIL._imaging", "HAVE_LIBJPEGTURBO", "libjpeg_turbo_version"), + "zlib_ng": ("PIL._imaging", "HAVE_ZLIBNG", "zlib_ng_version"), "libimagequant": ("PIL._imaging", "HAVE_LIBIMAGEQUANT", "imagequant_version"), "xcb": ("PIL._imaging", "HAVE_XCB", None), } @@ -308,7 +309,11 @@ def pilinfo(out: IO[str] | None = None, supported_formats: bool = True) -> None: # this check is also in src/_imagingcms.c:setup_module() version_static = tuple(int(x) for x in v.split(".")) < (2, 7) t = "compiled for" if version_static else "loaded" - if name == "raqm": + if name == "zlib": + zlib_ng_version = version_feature("zlib_ng") + if zlib_ng_version is not None: + v += ", compiled for zlib-ng " + zlib_ng_version + elif name == "raqm": for f in ("fribidi", "harfbuzz"): v2 = version_feature(f) if v2 is not None: diff --git a/src/_imaging.c b/src/_imaging.c index 2db4486b23e..5d6d97bedab 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -4397,6 +4397,20 @@ setup_module(PyObject *m) { } #endif + PyObject *have_zlibng; +#ifdef ZLIBNG_VERSION + have_zlibng = Py_True; + { + PyObject *v = PyUnicode_FromString(ZLIBNG_VERSION); + PyDict_SetItemString(d, "zlib_ng_version", v ? v : Py_None); + Py_XDECREF(v); + } +#else + have_zlibng = Py_False; +#endif + Py_INCREF(have_zlibng); + PyModule_AddObject(m, "HAVE_ZLIBNG", have_zlibng); + #ifdef HAVE_LIBTIFF { extern const char *ImagingTiffVersion(void); From a43e5bb7354006766a67a353aa21d9a0198cdcab Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Oct 2024 14:26:47 +1100 Subject: [PATCH 017/107] brew remove libdeflate --- .github/workflows/wheels-dependencies.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 97c1adf098a..7970d4d1525 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -121,6 +121,7 @@ curl -fsSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-de untar pillow-depends-main.zip if [[ -n "$IS_MACOS" ]]; then + # libdeflate may cause a minimum target error when repairing the wheel # libtiff and libxcb cause a conflict with building libtiff and libxcb # libxau and libxdmcp cause an issue on macOS < 11 # remove cairo to fix building harfbuzz on arm64 @@ -132,7 +133,7 @@ if [[ -n "$IS_MACOS" ]]; then if [[ "$CIBW_ARCHS" == "arm64" ]]; then brew remove --ignore-dependencies jpeg-turbo else - brew remove --ignore-dependencies webp + brew remove --ignore-dependencies libdeflate webp fi brew install pkg-config From e1f4b5a68fa783126f5a81ccba5fac4526f6ab00 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Oct 2024 15:10:41 +1100 Subject: [PATCH 018/107] Move MPO into "Fully supported formats" --- docs/handbook/image-file-formats.rst | 48 ++++++++++++++-------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/docs/handbook/image-file-formats.rst b/docs/handbook/image-file-formats.rst index 8183473e4f5..bf3087f6f68 100644 --- a/docs/handbook/image-file-formats.rst +++ b/docs/handbook/image-file-formats.rst @@ -692,6 +692,30 @@ The :py:meth:`~PIL.Image.Image.save` method supports the following options: you fail to do this, you will get errors about not being able to load the ``_imaging`` DLL). +MPO +^^^ + +Pillow reads and writes Multi Picture Object (MPO) files. When first opened, it loads +the primary image. The :py:meth:`~PIL.Image.Image.seek` and +:py:meth:`~PIL.Image.Image.tell` methods may be used to read other pictures from the +file. The pictures are zero-indexed and random access is supported. + +.. _mpo-saving: + +Saving +~~~~~~ + +When calling :py:meth:`~PIL.Image.Image.save` to write an MPO file, by default +only the first frame of a multiframe image will be saved. If the ``save_all`` +argument is present and true, then all frames will be saved, and the following +option will also be available. + +**append_images** + A list of images to append as additional pictures. Each of the + images in the list can be single or multiframe images. + + .. versionadded:: 9.3.0 + MSP ^^^ @@ -1435,30 +1459,6 @@ Note that there may be an embedded gamma of 2.2 in MIC files. To enable MIC support, you must install :pypi:`olefile`. -MPO -^^^ - -Pillow identifies and reads Multi Picture Object (MPO) files, loading the primary -image when first opened. The :py:meth:`~PIL.Image.Image.seek` and :py:meth:`~PIL.Image.Image.tell` -methods may be used to read other pictures from the file. The pictures are -zero-indexed and random access is supported. - -.. _mpo-saving: - -Saving -~~~~~~ - -When calling :py:meth:`~PIL.Image.Image.save` to write an MPO file, by default -only the first frame of a multiframe image will be saved. If the ``save_all`` -argument is present and true, then all frames will be saved, and the following -option will also be available. - -**append_images** - A list of images to append as additional pictures. Each of the - images in the list can be single or multiframe images. - - .. versionadded:: 9.3.0 - PCD ^^^ From 413bbb31c959a3e2a6929d4a12861a79d82f8e80 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Oct 2024 16:15:46 +1100 Subject: [PATCH 019/107] Fixed catching warnings --- Tests/test_bmp_reference.py | 2 ++ Tests/test_file_dcx.py | 4 ++++ Tests/test_file_fli.py | 4 ++++ Tests/test_file_gif.py | 4 ++++ Tests/test_file_icns.py | 2 ++ Tests/test_file_im.py | 4 ++++ Tests/test_file_jpeg.py | 2 ++ Tests/test_file_mpo.py | 4 ++++ Tests/test_file_png.py | 2 ++ Tests/test_file_psd.py | 4 ++++ Tests/test_file_spider.py | 4 ++++ Tests/test_file_tar.py | 4 ++++ Tests/test_file_tiff.py | 4 ++++ Tests/test_file_webp.py | 2 ++ Tests/test_image.py | 2 ++ Tests/test_imageqt.py | 2 ++ Tests/test_numpy.py | 2 ++ 17 files changed, 52 insertions(+) diff --git a/Tests/test_bmp_reference.py b/Tests/test_bmp_reference.py index 7f848792131..82cab39c613 100644 --- a/Tests/test_bmp_reference.py +++ b/Tests/test_bmp_reference.py @@ -22,6 +22,8 @@ def test_bad() -> None: for f in get_files("b"): # Assert that there is no unclosed file warning with warnings.catch_warnings(): + warnings.simplefilter("error") + try: with Image.open(f) as im: im.load() diff --git a/Tests/test_file_dcx.py b/Tests/test_file_dcx.py index 65337cad9b1..5deacd878ea 100644 --- a/Tests/test_file_dcx.py +++ b/Tests/test_file_dcx.py @@ -36,6 +36,8 @@ def open() -> None: def test_closed_file() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + im = Image.open(TEST_FILE) im.load() im.close() @@ -43,6 +45,8 @@ def test_closed_file() -> None: def test_context_manager() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + with Image.open(TEST_FILE) as im: im.load() diff --git a/Tests/test_file_fli.py b/Tests/test_file_fli.py index f86fb8d0925..0a7740cc87d 100644 --- a/Tests/test_file_fli.py +++ b/Tests/test_file_fli.py @@ -65,6 +65,8 @@ def open() -> None: def test_closed_file() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + im = Image.open(static_test_file) im.load() im.close() @@ -81,6 +83,8 @@ def test_seek_after_close() -> None: def test_context_manager() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + with Image.open(static_test_file) as im: im.load() diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 16c8466f331..248347d5bb9 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -46,6 +46,8 @@ def open() -> None: def test_closed_file() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + im = Image.open(TEST_GIF) im.load() im.close() @@ -67,6 +69,8 @@ def test_seek_after_close() -> None: def test_context_manager() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + with Image.open(TEST_GIF) as im: im.load() diff --git a/Tests/test_file_icns.py b/Tests/test_file_icns.py index 16f6b36516d..141b88dfa01 100644 --- a/Tests/test_file_icns.py +++ b/Tests/test_file_icns.py @@ -21,6 +21,8 @@ def test_sanity() -> None: with Image.open(TEST_FILE) as im: # Assert that there is no unclosed file warning with warnings.catch_warnings(): + warnings.simplefilter("error") + im.load() assert im.mode == "RGBA" diff --git a/Tests/test_file_im.py b/Tests/test_file_im.py index 036965bf5d8..1d3fa485f3b 100644 --- a/Tests/test_file_im.py +++ b/Tests/test_file_im.py @@ -41,6 +41,8 @@ def open() -> None: def test_closed_file() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + im = Image.open(TEST_IM) im.load() im.close() @@ -48,6 +50,8 @@ def test_closed_file() -> None: def test_context_manager() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + with Image.open(TEST_IM) as im: im.load() diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index cde951395a7..2c66652e5d2 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -850,6 +850,8 @@ def test_exif_x_resolution(self, tmp_path: Path) -> None: out = str(tmp_path / "out.jpg") with warnings.catch_warnings(): + warnings.simplefilter("error") + im.save(out, exif=exif) with Image.open(out) as reloaded: diff --git a/Tests/test_file_mpo.py b/Tests/test_file_mpo.py index e0f42a26649..94958318557 100644 --- a/Tests/test_file_mpo.py +++ b/Tests/test_file_mpo.py @@ -48,6 +48,8 @@ def open() -> None: def test_closed_file() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + im = Image.open(test_files[0]) im.load() im.close() @@ -63,6 +65,8 @@ def test_seek_after_close() -> None: def test_context_manager() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + with Image.open(test_files[0]) as im: im.load() diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 0abf9866ff2..ffafc3c582a 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -338,6 +338,8 @@ def test_load_verify(self) -> None: with Image.open(TEST_PNG_FILE) as im: # Assert that there is no unclosed file warning with warnings.catch_warnings(): + warnings.simplefilter("error") + im.verify() with Image.open(TEST_PNG_FILE) as im: diff --git a/Tests/test_file_psd.py b/Tests/test_file_psd.py index e6c79e40b6e..5f22001f324 100644 --- a/Tests/test_file_psd.py +++ b/Tests/test_file_psd.py @@ -35,6 +35,8 @@ def open() -> None: def test_closed_file() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + im = Image.open(test_file) im.load() im.close() @@ -42,6 +44,8 @@ def test_closed_file() -> None: def test_context_manager() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + with Image.open(test_file) as im: im.load() diff --git a/Tests/test_file_spider.py b/Tests/test_file_spider.py index 66c88e9d8eb..4cafda86536 100644 --- a/Tests/test_file_spider.py +++ b/Tests/test_file_spider.py @@ -34,6 +34,8 @@ def open() -> None: def test_closed_file() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + im = Image.open(TEST_FILE) im.load() im.close() @@ -41,6 +43,8 @@ def test_closed_file() -> None: def test_context_manager() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + with Image.open(TEST_FILE) as im: im.load() diff --git a/Tests/test_file_tar.py b/Tests/test_file_tar.py index 6217ebedd8a..49220a8b690 100644 --- a/Tests/test_file_tar.py +++ b/Tests/test_file_tar.py @@ -37,11 +37,15 @@ def test_unclosed_file() -> None: def test_close() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + tar = TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg") tar.close() def test_contextmanager() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + with TarIO.TarIO(TEST_TAR_FILE, "hopper.jpg"): pass diff --git a/Tests/test_file_tiff.py b/Tests/test_file_tiff.py index af766022b5c..6f51d46513e 100644 --- a/Tests/test_file_tiff.py +++ b/Tests/test_file_tiff.py @@ -72,6 +72,8 @@ def open() -> None: def test_closed_file(self) -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + im = Image.open("Tests/images/multipage.tiff") im.load() im.close() @@ -88,6 +90,8 @@ def test_seek_after_close(self) -> None: def test_context_manager(self) -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + with Image.open("Tests/images/multipage.tiff") as im: im.load() diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 79f6bb4e0e4..ad5aa9ed62c 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -191,6 +191,8 @@ def test_no_resource_warning(self, tmp_path: Path) -> None: file_path = "Tests/images/hopper.webp" with Image.open(file_path) as image: with warnings.catch_warnings(): + warnings.simplefilter("error") + image.save(tmp_path / "temp.webp") def test_file_pointer_could_be_reused(self) -> None: diff --git a/Tests/test_image.py b/Tests/test_image.py index 9b65041f4b4..c8df474f493 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -737,6 +737,8 @@ def test_no_resource_warning_on_save(self, tmp_path: Path) -> None: # Act/Assert with Image.open(test_file) as im: with warnings.catch_warnings(): + warnings.simplefilter("error") + im.save(temp_file) def test_no_new_file_on_error(self, tmp_path: Path) -> None: diff --git a/Tests/test_imageqt.py b/Tests/test_imageqt.py index 22cd674ce28..2d7ca0ae0fd 100644 --- a/Tests/test_imageqt.py +++ b/Tests/test_imageqt.py @@ -52,4 +52,6 @@ def test_image(mode: str) -> None: def test_closed_file() -> None: with warnings.catch_warnings(): + warnings.simplefilter("error") + ImageQt.ImageQt("Tests/images/hopper.gif") diff --git a/Tests/test_numpy.py b/Tests/test_numpy.py index 040472d69e3..79cd14b6690 100644 --- a/Tests/test_numpy.py +++ b/Tests/test_numpy.py @@ -264,4 +264,6 @@ def test_no_resource_warning_for_numpy_array() -> None: with Image.open(test_file) as im: # Act/Assert with warnings.catch_warnings(): + warnings.simplefilter("error") + array(im) From f92599aa9394d9b1f59a503a796bc0fb6ddb8cda Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Oct 2024 19:05:16 +1100 Subject: [PATCH 020/107] Renamed fixture --- Tests/test_file_jpeg2k.py | 102 +++++++++++++++++++------------------- 1 file changed, 50 insertions(+), 52 deletions(-) diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index 79f53e21195..fbf72ae0518 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -32,7 +32,7 @@ @pytest.fixture -def test_card() -> Generator[ImageFile.ImageFile, None, None]: +def card() -> Generator[ImageFile.ImageFile, None, None]: with Image.open("Tests/images/test-card.png") as im: im.load() try: @@ -83,78 +83,76 @@ def test_invalid_file() -> None: Jpeg2KImagePlugin.Jpeg2KImageFile(invalid_file) -def test_bytesio(test_card: ImageFile.ImageFile) -> None: +def test_bytesio(card: ImageFile.ImageFile) -> None: with open("Tests/images/test-card-lossless.jp2", "rb") as f: data = BytesIO(f.read()) with Image.open(data) as im: im.load() - assert_image_similar(im, test_card, 1.0e-3) + assert_image_similar(im, card, 1.0e-3) # These two test pre-written JPEG 2000 files that were not written with # PIL (they were made using Adobe Photoshop) -def test_lossless(test_card: ImageFile.ImageFile, tmp_path: Path) -> None: +def test_lossless(card: ImageFile.ImageFile, tmp_path: Path) -> None: with Image.open("Tests/images/test-card-lossless.jp2") as im: im.load() outfile = str(tmp_path / "temp_test-card.png") im.save(outfile) - assert_image_similar(im, test_card, 1.0e-3) + assert_image_similar(im, card, 1.0e-3) -def test_lossy_tiled(test_card: ImageFile.ImageFile) -> None: - assert_image_similar_tofile( - test_card, "Tests/images/test-card-lossy-tiled.jp2", 2.0 - ) +def test_lossy_tiled(card: ImageFile.ImageFile) -> None: + assert_image_similar_tofile(card, "Tests/images/test-card-lossy-tiled.jp2", 2.0) -def test_lossless_rt(test_card: ImageFile.ImageFile) -> None: - im = roundtrip(test_card) - assert_image_equal(im, test_card) +def test_lossless_rt(card: ImageFile.ImageFile) -> None: + im = roundtrip(card) + assert_image_equal(im, card) -def test_lossy_rt(test_card: ImageFile.ImageFile) -> None: - im = roundtrip(test_card, quality_layers=[20]) - assert_image_similar(im, test_card, 2.0) +def test_lossy_rt(card: ImageFile.ImageFile) -> None: + im = roundtrip(card, quality_layers=[20]) + assert_image_similar(im, card, 2.0) -def test_tiled_rt(test_card: ImageFile.ImageFile) -> None: - im = roundtrip(test_card, tile_size=(128, 128)) - assert_image_equal(im, test_card) +def test_tiled_rt(card: ImageFile.ImageFile) -> None: + im = roundtrip(card, tile_size=(128, 128)) + assert_image_equal(im, card) -def test_tiled_offset_rt(test_card: ImageFile.ImageFile) -> None: - im = roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32)) - assert_image_equal(im, test_card) +def test_tiled_offset_rt(card: ImageFile.ImageFile) -> None: + im = roundtrip(card, tile_size=(128, 128), tile_offset=(0, 0), offset=(32, 32)) + assert_image_equal(im, card) -def test_tiled_offset_too_small(test_card: ImageFile.ImageFile) -> None: +def test_tiled_offset_too_small(card: ImageFile.ImageFile) -> None: with pytest.raises(ValueError): - roundtrip(test_card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32)) + roundtrip(card, tile_size=(128, 128), tile_offset=(0, 0), offset=(128, 32)) -def test_irreversible_rt(test_card: ImageFile.ImageFile) -> None: - im = roundtrip(test_card, irreversible=True, quality_layers=[20]) - assert_image_similar(im, test_card, 2.0) +def test_irreversible_rt(card: ImageFile.ImageFile) -> None: + im = roundtrip(card, irreversible=True, quality_layers=[20]) + assert_image_similar(im, card, 2.0) -def test_prog_qual_rt(test_card: ImageFile.ImageFile) -> None: - im = roundtrip(test_card, quality_layers=[60, 40, 20], progression="LRCP") - assert_image_similar(im, test_card, 2.0) +def test_prog_qual_rt(card: ImageFile.ImageFile) -> None: + im = roundtrip(card, quality_layers=[60, 40, 20], progression="LRCP") + assert_image_similar(im, card, 2.0) -def test_prog_res_rt(test_card: ImageFile.ImageFile) -> None: - im = roundtrip(test_card, num_resolutions=8, progression="RLCP") - assert_image_equal(im, test_card) +def test_prog_res_rt(card: ImageFile.ImageFile) -> None: + im = roundtrip(card, num_resolutions=8, progression="RLCP") + assert_image_equal(im, card) @pytest.mark.parametrize("num_resolutions", range(2, 6)) def test_default_num_resolutions( - test_card: ImageFile.ImageFile, num_resolutions: int + card: ImageFile.ImageFile, num_resolutions: int ) -> None: d = 1 << (num_resolutions - 1) - im = test_card.resize((d - 1, d - 1)) + im = card.resize((d - 1, d - 1)) with pytest.raises(OSError): roundtrip(im, num_resolutions=num_resolutions) reloaded = roundtrip(im) @@ -216,31 +214,31 @@ def test_header_errors() -> None: pass -def test_layers_type(test_card: ImageFile.ImageFile, tmp_path: Path) -> None: +def test_layers_type(card: ImageFile.ImageFile, tmp_path: Path) -> None: outfile = str(tmp_path / "temp_layers.jp2") for quality_layers in [[100, 50, 10], (100, 50, 10), None]: - test_card.save(outfile, quality_layers=quality_layers) + card.save(outfile, quality_layers=quality_layers) for quality_layers_str in ["quality_layers", ("100", "50", "10")]: with pytest.raises(ValueError): - test_card.save(outfile, quality_layers=quality_layers_str) + card.save(outfile, quality_layers=quality_layers_str) -def test_layers(test_card: ImageFile.ImageFile) -> None: +def test_layers(card: ImageFile.ImageFile) -> None: out = BytesIO() - test_card.save(out, "JPEG2000", quality_layers=[100, 50, 10], progression="LRCP") + card.save(out, "JPEG2000", quality_layers=[100, 50, 10], progression="LRCP") out.seek(0) with Image.open(out) as im: im.layers = 1 im.load() - assert_image_similar(im, test_card, 13) + assert_image_similar(im, card, 13) out.seek(0) with Image.open(out) as im: im.layers = 3 im.load() - assert_image_similar(im, test_card, 0.4) + assert_image_similar(im, card, 0.4) @pytest.mark.parametrize( @@ -257,7 +255,7 @@ def test_layers(test_card: ImageFile.ImageFile) -> None: ), ) def test_no_jp2( - test_card: ImageFile.ImageFile, + card: ImageFile.ImageFile, name: str, args: dict[str, bool], offset: int, @@ -266,20 +264,20 @@ def test_no_jp2( out = BytesIO() if name: out.name = name - test_card.save(out, "JPEG2000", **args) + card.save(out, "JPEG2000", **args) out.seek(offset) assert out.read(2) == data -def test_mct(test_card: ImageFile.ImageFile) -> None: +def test_mct(card: ImageFile.ImageFile) -> None: # Three component for val in (0, 1): out = BytesIO() - test_card.save(out, "JPEG2000", mct=val, no_jp2=True) + card.save(out, "JPEG2000", mct=val, no_jp2=True) assert out.getvalue()[59] == val with Image.open(out) as im: - assert_image_similar(im, test_card, 1.0e-3) + assert_image_similar(im, card, 1.0e-3) # Single component should have MCT disabled for val in (0, 1): @@ -436,22 +434,22 @@ def test_comment() -> None: pass -def test_save_comment(test_card: ImageFile.ImageFile) -> None: +def test_save_comment(card: ImageFile.ImageFile) -> None: for comment in ("Created by Pillow", b"Created by Pillow"): out = BytesIO() - test_card.save(out, "JPEG2000", comment=comment) + card.save(out, "JPEG2000", comment=comment) with Image.open(out) as im: assert im.info["comment"] == b"Created by Pillow" out = BytesIO() long_comment = b" " * 65531 - test_card.save(out, "JPEG2000", comment=long_comment) + card.save(out, "JPEG2000", comment=long_comment) with Image.open(out) as im: assert im.info["comment"] == long_comment with pytest.raises(ValueError): - test_card.save(out, "JPEG2000", comment=long_comment + b" ") + card.save(out, "JPEG2000", comment=long_comment + b" ") @pytest.mark.parametrize( @@ -474,10 +472,10 @@ def test_crashes(test_file: str) -> None: @skip_unless_feature_version("jpg_2000", "2.4.0") -def test_plt_marker(test_card: ImageFile.ImageFile) -> None: +def test_plt_marker(card: ImageFile.ImageFile) -> None: # Search the start of the codesteam for PLT out = BytesIO() - test_card.save(out, "JPEG2000", no_jp2=True, plt=True) + card.save(out, "JPEG2000", no_jp2=True, plt=True) out.seek(0) while True: marker = out.read(2) From ed910a68d637661ccb4c4a7c1245769176fec93d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Oct 2024 11:42:35 +1100 Subject: [PATCH 021/107] Only replace version suffix if zlib-ng is present --- Tests/test_features.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Tests/test_features.py b/Tests/test_features.py index ed792997376..f8f7f6eec59 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -36,10 +36,11 @@ def test(name: str, function: Callable[[str], str | None]) -> None: else: assert function(name) == version if name != "PIL": - if name == "zlib" and version is not None: - version = re.sub(".zlib-ng$", "", version) - elif name == "libtiff" and version is not None: - version = re.sub("t$", "", version) + if version is not None: + if name == "zlib" and features.check_feature("zlib_ng"): + version = re.sub(".zlib-ng$", "", version) + elif name == "libtiff": + version = re.sub("t$", "", version) assert version is None or re.search(r"\d+(\.\d+)*$", version) for module in features.modules: From 29cdbce39e1d3860486319b46ea829e0c3566279 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 26 Oct 2024 21:13:01 +1100 Subject: [PATCH 022/107] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index ae384156963..87945bc8456 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 11.1.0 (unreleased) ------------------- +- Corrected EMF DPI #8485 + [radarhere] + - Fix IFDRational with a zero denominator #8474 [radarhere] From 73600eea94bfb695c52f7746c3523b5ce332d567 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 29 Oct 2024 00:34:24 +1100 Subject: [PATCH 023/107] Detach PyQt6 QPixmap instance before returning --- .github/workflows/test-windows.yml | 6 ++++++ src/PIL/ImageQt.py | 5 ++++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index c8842e37b2c..f6d0aeb1d4a 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -80,6 +80,12 @@ jobs: pytest-cov pytest-timeout + - name: Install CPython dependencies + if: "!contains(matrix.python-version, 'pypy')" + run: > + python3 -m pip install + PyQt6 + - name: Install dependencies id: install run: | diff --git a/src/PIL/ImageQt.py b/src/PIL/ImageQt.py index a3d647138df..2cc40f85535 100644 --- a/src/PIL/ImageQt.py +++ b/src/PIL/ImageQt.py @@ -213,4 +213,7 @@ def toqimage(im: Image.Image | str | QByteArray) -> ImageQt: def toqpixmap(im: Image.Image | str | QByteArray) -> QPixmap: qimage = toqimage(im) - return getattr(QPixmap, "fromImage")(qimage) + pixmap = getattr(QPixmap, "fromImage")(qimage) + if qt_version == "6": + pixmap.detach() + return pixmap From e5706a590b16fcfc2b826de19addc2e33d3c3508 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 29 Oct 2024 09:04:06 +1100 Subject: [PATCH 024/107] Upgraded multibuild to remove openjpeg lib64 copy --- .github/workflows/wheels-dependencies.sh | 3 --- wheels/multibuild | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 7970d4d1525..3a80a7e741d 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -91,9 +91,6 @@ function build { build_libpng build_lcms2 build_openjpeg - if [ -f /usr/local/lib64/libopenjp2.so ]; then - cp /usr/local/lib64/libopenjp2.so /usr/local/lib - fi ORIGINAL_CFLAGS=$CFLAGS CFLAGS="$CFLAGS -O3 -DNDEBUG" diff --git a/wheels/multibuild b/wheels/multibuild index 452dd2d1705..9a9d1275f02 160000 --- a/wheels/multibuild +++ b/wheels/multibuild @@ -1 +1 @@ -Subproject commit 452dd2d1705f6b2375369a6570c415beb3163f70 +Subproject commit 9a9d1275f025f737cdaa3c451ba07129dd95f361 From 08554684b3f382f3d729db5e6cd1fe94126dab66 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 29 Oct 2024 07:46:59 +0800 Subject: [PATCH 025/107] Revert fribidi/raqm changes for macOS builds. --- .github/workflows/wheels-dependencies.sh | 5 ----- .github/workflows/wheels-test.sh | 21 ++++++++++++++++----- pyproject.toml | 4 ++-- setup.py | 2 +- 4 files changed, 19 insertions(+), 13 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 9dae287170a..22084237118 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -155,11 +155,6 @@ function build { fi build_harfbuzz - - if [ -n "$IS_MACOS" ]; then - build_simple fribidi $FRIBIDI_VERSION https://github.com/fribidi/fribidi/releases/download/v$FRIBIDI_VERSION tar.xz --enable-shared - build_raqm - fi } # Perform all dependency builds in the build subfolder. diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index 3a8fc87ba1e..e671c8d45a8 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -1,13 +1,24 @@ #!/bin/bash set -e -# For Unix, ensure fribidi is installed by the system. -if [[ "$OSTYPE" != "darwin"* ]]; then - if [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then - apk add curl fribidi +# Ensure fribidi is installed by the system. +if [[ "$OSTYPE" == "darwin"* ]]; then + # If Homebrew is on the path during the build, it may leak into the wheels. + # However, we need a *do* need Homebrew to provide a copy of fribidi for + # testing purposes so that we can verify the fribidi shim works as expected. + if [[ "$(uname -m)" == "x86_64" ]]; then + HOMEBREW_HOME=/usr/local/homebrew else - yum install -y fribidi + HOMEBREW_HOME=/opt/homebrew fi + $HOMEBREW_HOME/bin/brew install fribidi + + # Add the Homebrew lib folder so that vendored libraries can be found. + export DYLD_LIBRARY_PATH=$HOMEBREW_HOME/lib +elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then + apk add curl fribidi +else + yum install -y fribidi fi python3 -m pip install numpy diff --git a/pyproject.toml b/pyproject.toml index 3839cf8402a..da8548dcbd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,8 +95,8 @@ before-all = ".github/workflows/wheels-dependencies.sh" build-verbosity = 1 config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable" -# Add an explicit dependencies prefix for macOS, and don't request vendored libraries. -macos.config-settings = "raqm=enable imagequant=disable dependencies-prefix=./build/deps" +# Add an explicit dependencies prefix for macOS. +macos.config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable dependencies-prefix=./build/deps" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" diff --git a/setup.py b/setup.py index 16c25cbda80..38d793b1412 100644 --- a/setup.py +++ b/setup.py @@ -454,7 +454,7 @@ def _remove_extension(self, name: str) -> None: def get_macos_sdk_path(self) -> str | None: try: sdk_path = ( - subprocess.check_output(["xcrun", "--show-sdk-path"]) + subprocess.check_output(["xcrun", "--show-sdk-path", "--sdk", "macosx"]) .strip() .decode("latin1") ) From 8308bf361e7d4bac39b1710e4c3c0725c7ad07ac Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 29 Oct 2024 07:47:30 +0800 Subject: [PATCH 026/107] Bump multibuild to include more cmake changes. --- wheels/multibuild | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wheels/multibuild b/wheels/multibuild index 22e8ad0c69a..9a9d1275f02 160000 --- a/wheels/multibuild +++ b/wheels/multibuild @@ -1 +1 @@ -Subproject commit 22e8ad0c69acdb0c94abab74da6e09f2810d50d5 +Subproject commit 9a9d1275f025f737cdaa3c451ba07129dd95f361 From c74a5bdd85c3314fbdbbe2a72d17adad85fae5f0 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 29 Oct 2024 09:02:05 +0800 Subject: [PATCH 027/107] Correct paths used for Linux build. --- .github/workflows/wheels-dependencies.sh | 31 ++++-------------------- pyproject.toml | 4 +++ setup.py | 3 +-- 3 files changed, 10 insertions(+), 28 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 22084237118..0eb15d55315 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -26,8 +26,6 @@ OPENJPEG_VERSION=2.5.2 XZ_VERSION=5.6.3 TIFF_VERSION=4.6.0 LCMS2_VERSION=2.16 -RAQM_VERSION=0.10.2 -FRIBIDI_VERSION=1.0.16 if [[ -n "$IS_MACOS" ]]; then GIFLIB_VERSION=5.2.2 else @@ -63,12 +61,8 @@ function build_brotli { local cmake=$(get_modern_cmake) local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz) (cd $out_dir \ - && $cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \ + && $cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \ && make install) - if [[ "$MB_ML_LIBC" == "manylinux" ]]; then - cp $BUILD_PREFIX/lib64/libbrotli* $BUILD_PREFIX/lib - cp $BUILD_PREFIX/lib64/pkgconfig/libbrotli* $BUILD_PREFIX/lib/pkgconfig - fi touch brotli-stamp } @@ -78,27 +72,12 @@ function build_harfbuzz { local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) (cd $out_dir \ - && meson setup build --prefix=$BUILD_PREFIX --buildtype=release -Dfreetype=enabled -Dglib=disabled) + && meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=release -Dfreetype=enabled -Dglib=disabled) (cd $out_dir/build \ && meson install) - if [[ "$MB_ML_LIBC" == "manylinux" ]]; then - cp $BUILD_PREFIX/lib64/libharfbuzz* $BUILD_PREFIX/lib - fi touch harfbuzz-stamp } -function build_raqm { - if [ -e raqm-stamp ]; then return; fi - python3 -m pip install meson ninja - - local out_dir=$(fetch_unpack https://github.com/HOST-Oman/libraqm/releases/download/v$RAQM_VERSION/raqm-$RAQM_VERSION.tar.xz raqm-$RAQM_VERSION.tar.xz) - (cd $out_dir \ - && meson setup build --prefix=$BUILD_PREFIX) - (cd $out_dir/build \ - && meson install) - touch raqm-stamp -} - function build { build_xz if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then @@ -133,9 +112,6 @@ function build { build_libpng build_lcms2 build_openjpeg - if [ -f $BUILD_PREFIX/lib64/libopenjp2.so ]; then - cp $BUILD_PREFIX/lib64/libopenjp2.so $BUILD_PREFIX/lib - fi ORIGINAL_CFLAGS=$CFLAGS CFLAGS="$CFLAGS -O3 -DNDEBUG" @@ -190,6 +166,9 @@ if [[ -n "$IS_MACOS" ]]; then build_pkg_config # Ensure cmake is available python3 -m pip install cmake +else + # Ensure that any built libraries are on the linker path. + export LD_LIBRARY_PATH=$BUILD_PREFIX/lib fi wrap_wheel_builder build diff --git a/pyproject.toml b/pyproject.toml index da8548dcbd3..af3d0475750 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,6 +104,10 @@ test-extras = "tests" [tool.cibuildwheel.macos.environment] PATH = "$(pwd)/build/deps/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" +[tool.cibuildwheel.linux.environment] +LD_LIBRARY_PATH = "$(pwd)/build/deps/lib" +PKG_CONFIG_PATH = "$(pwd)/build/deps/lib/pkgconfig:$(pwd)/build/deps/share/pkgconfig" + [tool.black] exclude = "wheels/multibuild" diff --git a/setup.py b/setup.py index 38d793b1412..d7e305fc0e1 100644 --- a/setup.py +++ b/setup.py @@ -131,7 +131,7 @@ def get_version() -> str: "codec_fd", ) -DEBUG = False +DEBUG = True class DependencyException(Exception): @@ -479,7 +479,6 @@ def build_extensions(self) -> None: pkg_config = None if _cmd_exists(os.environ.get("PKG_CONFIG", "pkg-config")): pkg_config = _pkg_config - # # add configured kits for root_name, lib_name in { From 72d81e24a81e6e765f21e5794218f080e7718ec7 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 29 Oct 2024 09:25:47 +0800 Subject: [PATCH 028/107] Simplify Linux config by correcting a logic error in macOS config. --- .github/workflows/wheels-dependencies.sh | 7 ++----- pyproject.toml | 4 ---- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 0eb15d55315..2e915364abb 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -4,8 +4,8 @@ if [ -z "$IS_MACOS" ]; then export MB_ML_LIBC=${AUDITWHEEL_POLICY::9} export MB_ML_VER=${AUDITWHEEL_POLICY:9} - - # Build and install into the `build/deps` folder. +else + # Build and install macOS builds into the `build/deps` folder. BUILD_PREFIX=$(pwd)/build/deps fi export PLAT=$CIBW_ARCHS @@ -166,9 +166,6 @@ if [[ -n "$IS_MACOS" ]]; then build_pkg_config # Ensure cmake is available python3 -m pip install cmake -else - # Ensure that any built libraries are on the linker path. - export LD_LIBRARY_PATH=$BUILD_PREFIX/lib fi wrap_wheel_builder build diff --git a/pyproject.toml b/pyproject.toml index af3d0475750..da8548dcbd3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,10 +104,6 @@ test-extras = "tests" [tool.cibuildwheel.macos.environment] PATH = "$(pwd)/build/deps/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" -[tool.cibuildwheel.linux.environment] -LD_LIBRARY_PATH = "$(pwd)/build/deps/lib" -PKG_CONFIG_PATH = "$(pwd)/build/deps/lib/pkgconfig:$(pwd)/build/deps/share/pkgconfig" - [tool.black] exclude = "wheels/multibuild" From ec214e4d7caa471c087e0d690b20d75b6dd929fc Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 29 Oct 2024 09:39:58 +0800 Subject: [PATCH 029/107] Can't check IS_MACOS until common_utils is invoked. --- .github/workflows/wheels-dependencies.sh | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 2e915364abb..9ce851f0f93 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -1,19 +1,21 @@ #!/bin/bash # Define custom utilities +export PLAT=$CIBW_ARCHS +source wheels/multibuild/common_utils.sh +source wheels/multibuild/library_builders.sh +if [ -z "$IS_MACOS" ]; then + source wheels/multibuild/manylinux_utils.sh +fi + # Test for macOS with [ -n "$IS_MACOS" ] +echo "IS MACOS: $IS_MACOS" if [ -z "$IS_MACOS" ]; then export MB_ML_LIBC=${AUDITWHEEL_POLICY::9} export MB_ML_VER=${AUDITWHEEL_POLICY:9} -else +elseac # Build and install macOS builds into the `build/deps` folder. BUILD_PREFIX=$(pwd)/build/deps fi -export PLAT=$CIBW_ARCHS -source wheels/multibuild/common_utils.sh -source wheels/multibuild/library_builders.sh -if [ -z "$IS_MACOS" ]; then - source wheels/multibuild/manylinux_utils.sh -fi ARCHIVE_SDIR=pillow-depends-main From d1a4f8029b2d89f19e4652c96e3b1909b90aa5df Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 29 Oct 2024 09:54:15 +0800 Subject: [PATCH 030/107] Don't use multibuild variables before invoking multibuild. --- .github/workflows/wheels-dependencies.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 9ce851f0f93..fd13930db46 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -1,4 +1,14 @@ #!/bin/bash + +# Setup that needs to be done before multibuild utils are invoked +if [[ "$(uname -s)" == "Darwin" ]]; then + # Build and install macOS builds into the `build/deps` folder. + BUILD_PREFIX=$(pwd)/build/deps +else + export MB_ML_LIBC=${AUDITWHEEL_POLICY::9} + export MB_ML_VER=${AUDITWHEEL_POLICY:9} +fi + # Define custom utilities export PLAT=$CIBW_ARCHS source wheels/multibuild/common_utils.sh @@ -7,16 +17,6 @@ if [ -z "$IS_MACOS" ]; then source wheels/multibuild/manylinux_utils.sh fi -# Test for macOS with [ -n "$IS_MACOS" ] -echo "IS MACOS: $IS_MACOS" -if [ -z "$IS_MACOS" ]; then - export MB_ML_LIBC=${AUDITWHEEL_POLICY::9} - export MB_ML_VER=${AUDITWHEEL_POLICY:9} -elseac - # Build and install macOS builds into the `build/deps` folder. - BUILD_PREFIX=$(pwd)/build/deps -fi - ARCHIVE_SDIR=pillow-depends-main # Package versions for fresh source builds From 6d137043f712d6790eba835a8ec727e0a4fbb603 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Tue, 29 Oct 2024 10:01:46 +0800 Subject: [PATCH 031/107] Remove stray debug. --- setup.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.py b/setup.py index d7e305fc0e1..38d793b1412 100644 --- a/setup.py +++ b/setup.py @@ -131,7 +131,7 @@ def get_version() -> str: "codec_fd", ) -DEBUG = True +DEBUG = False class DependencyException(Exception): @@ -479,6 +479,7 @@ def build_extensions(self) -> None: pkg_config = None if _cmd_exists(os.environ.get("PKG_CONFIG", "pkg-config")): pkg_config = _pkg_config + # # add configured kits for root_name, lib_name in { From 2d1d801ec032bfb760bede88da6dcaab5ff3c75e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 29 Oct 2024 22:15:56 +1100 Subject: [PATCH 032/107] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 87945bc8456..9d45e2214ca 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 11.1.0 (unreleased) ------------------- +- Detach PyQt6 QPixmap instance before returning #8509 + [radarhere] + - Corrected EMF DPI #8485 [radarhere] From 624848ff97e438e5ae7bd239254edceaa2751af1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 29 Oct 2024 22:21:53 +1100 Subject: [PATCH 033/107] Do not repeatedly save to the same path --- Tests/test_file_jpeg.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 2c66652e5d2..c41a61f4bbb 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -543,10 +543,10 @@ def test_truncated_jpeg_throws_oserror(self) -> None: ) def test_qtables(self, tmp_path: Path) -> None: def _n_qtables_helper(n: int, test_file: str) -> None: + b = BytesIO() with Image.open(test_file) as im: - f = str(tmp_path / "temp.jpg") - im.save(f, qtables=[[n] * 64] * n) - with Image.open(f) as im: + im.save(b, "JPEG", qtables=[[n] * 64] * n) + with Image.open(b) as im: assert len(im.quantization) == n reloaded = self.roundtrip(im, qtables="keep") assert im.quantization == reloaded.quantization From 80cf74030d533a5ebc8e921ae8094316123b6837 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Tue, 29 Oct 2024 23:13:01 +1100 Subject: [PATCH 034/107] Removed fixture Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- Tests/test_file_jpeg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index c41a61f4bbb..347a162a58e 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -541,7 +541,7 @@ def test_truncated_jpeg_throws_oserror(self) -> None: @mark_if_feature_version( pytest.mark.valgrind_known_error, "libjpeg_turbo", "2.0", reason="Known Failing" ) - def test_qtables(self, tmp_path: Path) -> None: + def test_qtables(self) -> None: def _n_qtables_helper(n: int, test_file: str) -> None: b = BytesIO() with Image.open(test_file) as im: From c6912f81ff20211c2638be64bc198024aa9a5b40 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 30 Oct 2024 05:43:23 +0800 Subject: [PATCH 035/107] Corrected typo in code comment. Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/workflows/wheels-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index e671c8d45a8..9bcf42458cc 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -4,7 +4,7 @@ set -e # Ensure fribidi is installed by the system. if [[ "$OSTYPE" == "darwin"* ]]; then # If Homebrew is on the path during the build, it may leak into the wheels. - # However, we need a *do* need Homebrew to provide a copy of fribidi for + # However, we *do* need Homebrew to provide a copy of fribidi for # testing purposes so that we can verify the fribidi shim works as expected. if [[ "$(uname -m)" == "x86_64" ]]; then HOMEBREW_HOME=/usr/local/homebrew From 67c2e04f706f9f7f8efa6bd6437632ab8e546cae Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 30 Oct 2024 08:37:19 +0200 Subject: [PATCH 036/107] Add trove-classifiers>=2024.10.12 to 'tests' extra and use for Windows CI --- .github/workflows/test-windows.yml | 13 +------------ pyproject.toml | 1 + 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index f6d0aeb1d4a..4ac6f8769a5 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -69,17 +69,6 @@ jobs: - name: Print build system information run: python3 .github/workflows/system-info.py - - name: Install Python dependencies - run: > - python3 -m pip install - coverage>=7.4.2 - defusedxml - olefile - pyroma - pytest - pytest-cov - pytest-timeout - - name: Install CPython dependencies if: "!contains(matrix.python-version, 'pypy')" run: > @@ -184,7 +173,7 @@ jobs: - name: Build Pillow run: | $FLAGS="-C raqm=vendor -C fribidi=vendor" - cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS ." + cmd /c "winbuild\build\build_env.cmd && $env:pythonLocation\python.exe -m pip install -v $FLAGS .[tests]" & $env:pythonLocation\python.exe selftest.py --installed shell: pwsh diff --git a/pyproject.toml b/pyproject.toml index c55be769341..45d5c269261 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -65,6 +65,7 @@ optional-dependencies.tests = [ "pytest", "pytest-cov", "pytest-timeout", + "trove-classifiers>=2024.10.12", ] optional-dependencies.typing = [ "typing-extensions; python_version<'3.10'", From 0bf15f0f2a856907bf925fc797a107a33069b734 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Wed, 30 Oct 2024 22:24:37 +0200 Subject: [PATCH 037/107] Upgrade pip --- .github/workflows/test-windows.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 4ac6f8769a5..728182b1ec1 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -71,9 +71,9 @@ jobs: - name: Install CPython dependencies if: "!contains(matrix.python-version, 'pypy')" - run: > - python3 -m pip install - PyQt6 + run: | + python3 -m pip install --upgrade pip + python3 -m pip install PyQt6 - name: Install dependencies id: install From 01270b5859800355b12fa4403c7684d9bf72c6f5 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 31 Oct 2024 06:27:54 +0800 Subject: [PATCH 038/107] Use the intended entry point for the x86_64 brew binary. Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/workflows/wheels-test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index 9bcf42458cc..e2ef337119e 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -7,7 +7,7 @@ if [[ "$OSTYPE" == "darwin"* ]]; then # However, we *do* need Homebrew to provide a copy of fribidi for # testing purposes so that we can verify the fribidi shim works as expected. if [[ "$(uname -m)" == "x86_64" ]]; then - HOMEBREW_HOME=/usr/local/homebrew + HOMEBREW_HOME=/usr/local else HOMEBREW_HOME=/opt/homebrew fi From 51e36230660667df38c68379314b652b34da47e5 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Thu, 31 Oct 2024 09:50:38 +0800 Subject: [PATCH 039/107] Revert x86_64 homebrew location change (with explanation). --- .github/workflows/wheels-test.sh | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index e2ef337119e..b697b740ec0 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -7,7 +7,10 @@ if [[ "$OSTYPE" == "darwin"* ]]; then # However, we *do* need Homebrew to provide a copy of fribidi for # testing purposes so that we can verify the fribidi shim works as expected. if [[ "$(uname -m)" == "x86_64" ]]; then - HOMEBREW_HOME=/usr/local + # Use the "installed" location, rather than /usr/local, for two reasons: + # firstly, Homebrew allows libraries to be *installed*, but not linked; + # and secondly, we don't want any *other* leakage from /usr/local. + HOMEBREW_HOME=/usr/local/Homebrew else HOMEBREW_HOME=/opt/homebrew fi From aae90c3c016ee36b838d1ca5d522301d98731261 Mon Sep 17 00:00:00 2001 From: "Pietro F. Fontana" Date: Fri, 1 Nov 2024 15:42:08 +0100 Subject: [PATCH 040/107] Allow linking to shared zlib If compiled as shared library zlib produces `zlib1.dll` and `zdll.lib`. --- setup.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/setup.py b/setup.py index def3417845d..48863c3974e 100644 --- a/setup.py +++ b/setup.py @@ -690,6 +690,8 @@ def build_extensions(self) -> None: feature.set("zlib", "z") elif sys.platform == "win32" and _find_library_file(self, "zlib"): feature.set("zlib", "zlib") # alternative name + elif sys.platform == "win32" and _find_library_file(self, "zdll"): + feature.set("zlib", "zdll") # different name if shared if feature.want("jpeg"): _dbg("Looking for jpeg") From 71016f23b4b441536a5c713d3cad53e2870655fb Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 2 Nov 2024 17:51:01 +1100 Subject: [PATCH 041/107] Added Fedora 41 --- .github/workflows/test-docker.yml | 1 + docs/installation/platform-support.rst | 2 ++ 2 files changed, 3 insertions(+) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 880fe3eea24..5bfbe1bdbc2 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -47,6 +47,7 @@ jobs: debian-12-bookworm-x86, debian-12-bookworm-amd64, fedora-40-amd64, + fedora-41-amd64, gentoo, ubuntu-22.04-jammy-amd64, ubuntu-24.04-noble-amd64, diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index 21ebd1ad520..da6763736f1 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -31,6 +31,8 @@ These platforms are built and tested for every change. +----------------------------------+----------------------------+---------------------+ | Fedora 40 | 3.12 | x86-64 | +----------------------------------+----------------------------+---------------------+ +| Fedora 41 | 3.13 | x86-64 | ++----------------------------------+----------------------------+---------------------+ | Gentoo | 3.12 | x86-64 | +----------------------------------+----------------------------+---------------------+ | macOS 13 Ventura | 3.9 | x86-64 | From 141e8d25462cfaa56e6d707b704c832b950b86bf Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 2 Nov 2024 12:08:23 +0200 Subject: [PATCH 042/107] Remove unused 'gcov: true' for codecov-action@v4 --- .github/workflows/test-docker.yml | 1 - .github/workflows/test.yml | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 5bfbe1bdbc2..101807745e5 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -102,7 +102,6 @@ jobs: with: flags: GHA_Docker name: ${{ matrix.docker }} - gcov: true token: ${{ secrets.CODECOV_ORG_TOKEN }} success: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6576292b5e4..29949f4e0ab 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -158,7 +158,6 @@ jobs: with: flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} - gcov: true token: ${{ secrets.CODECOV_ORG_TOKEN }} success: From 6fe7160cb97f13a93735a365ff33b5f388d8067d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ondrej=20Baranovi=C4=8D?= Date: Sat, 2 Nov 2024 14:03:53 +0100 Subject: [PATCH 043/107] Update Windows 11 Arm64 tested versions --- docs/installation/platform-support.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index 21ebd1ad520..d8671ee140a 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -146,7 +146,7 @@ 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.2.0 |arm64 | +| Windows 11 23H2 | 3.9, 3.10, 3.11, 3.12, 3.13| 11.0.0 |arm64 | +----------------------------------+----------------------------+------------------+--------------+ | Windows 11 Pro | 3.11, 3.12 | 10.2.0 |x86-64 | +----------------------------------+----------------------------+------------------+--------------+ From 9faf598c890ced5828ba13ac7f5445b4310fff0c Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 2 Nov 2024 23:29:56 +0200 Subject: [PATCH 044/107] Fix warning[artipacked]: credential persistence through GitHub Actions artifacts --- .github/workflows/docs.yml | 2 ++ .github/workflows/lint.yml | 2 ++ .github/workflows/test-cygwin.yml | 2 ++ .github/workflows/test-docker.yml | 2 ++ .github/workflows/test-mingw.yml | 2 ++ .github/workflows/test-valgrind.yml | 2 ++ .github/workflows/test-windows.yml | 4 ++++ .github/workflows/test.yml | 2 ++ .github/workflows/wheels.yml | 7 +++++++ 9 files changed, 25 insertions(+) diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 92e860cb547..626824f3830 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -33,6 +33,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Set up Python uses: actions/setup-python@v5 diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index cc4760288e5..8e789a73489 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -21,6 +21,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: pre-commit cache uses: actions/cache@v4 diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index 0aa79e4235a..656054e8924 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -48,6 +48,8 @@ jobs: - name: Checkout Pillow uses: actions/checkout@v4 + with: + persist-credentials: false - name: Install Cygwin uses: cygwin/cygwin-install-action@v4 diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 101807745e5..03608319a60 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -65,6 +65,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Build system information run: python3 .github/workflows/system-info.py diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index c7a73439ca9..bfd393db5a2 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -46,6 +46,8 @@ jobs: steps: - name: Checkout Pillow uses: actions/checkout@v4 + with: + persist-credentials: false - name: Set up shell run: echo "C:\msys64\usr\bin\" >> $env:GITHUB_PATH diff --git a/.github/workflows/test-valgrind.yml b/.github/workflows/test-valgrind.yml index 63aec586b79..8818b3b2357 100644 --- a/.github/workflows/test-valgrind.yml +++ b/.github/workflows/test-valgrind.yml @@ -40,6 +40,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Build system information run: python3 .github/workflows/system-info.py diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index f6d0aeb1d4a..c1ba52719ae 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -44,16 +44,20 @@ jobs: steps: - name: Checkout Pillow uses: actions/checkout@v4 + with: + persist-credentials: false - name: Checkout cached dependencies uses: actions/checkout@v4 with: + persist-credentials: false repository: python-pillow/pillow-depends path: winbuild\depends - name: Checkout extra test images uses: actions/checkout@v4 with: + persist-credentials: false repository: python-pillow/test-images path: Tests\test-images diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 29949f4e0ab..87acd7ddbc0 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -63,6 +63,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 34452fa563c..45f18634100 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -61,6 +61,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + persist-credentials: false submodules: true - uses: actions/setup-python@v5 @@ -132,6 +133,7 @@ jobs: steps: - uses: actions/checkout@v4 with: + persist-credentials: false submodules: true - uses: actions/setup-python@v5 @@ -173,10 +175,13 @@ jobs: - cibw_arch: ARM64 steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Checkout extra test images uses: actions/checkout@v4 with: + persist-credentials: false repository: python-pillow/test-images path: Tests\test-images @@ -253,6 +258,8 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + persist-credentials: false - name: Set up Python uses: actions/setup-python@v5 From d3db931f21bcaa723071c7b4d669eead0fbdabab Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sat, 2 Nov 2024 23:31:21 +0200 Subject: [PATCH 045/107] Fix error[excessive-permissions]: overly broad workflow or job-level permissions --- .github/workflows/stale.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 545c2e3644a..61ccf58e2ea 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -6,7 +6,7 @@ on: workflow_dispatch: permissions: - issues: write + contents: read concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -15,6 +15,8 @@ concurrency: jobs: stale: if: github.repository_owner == 'python-pillow' + permissions: + issues: write runs-on: ubuntu-latest From 924df0ac5c44815b397f375c7bed7a69ba02b956 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 3 Nov 2024 23:22:57 +0000 Subject: [PATCH 046/107] Migrate config .github/renovate.json --- .github/renovate.json | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/.github/renovate.json b/.github/renovate.json index d1d82433553..f48b670ecdc 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -1,7 +1,7 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", "extends": [ - "config:base" + "config:recommended" ], "labels": [ "Dependency" @@ -9,9 +9,13 @@ "packageRules": [ { "groupName": "github-actions", - "matchManagers": ["github-actions"], - "separateMajorMinor": "false" + "matchManagers": [ + "github-actions" + ], + "separateMajorMinor": false } ], - "schedule": ["on the 3rd day of the month"] + "schedule": [ + "on the 3rd day of the month" + ] } From 4b7f6a6eb083b3c112f4713cd1fb74bd93ee3e7c Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 4 Nov 2024 17:35:35 +0000 Subject: [PATCH 047/107] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.6.9 → v0.7.2](https://github.com/astral-sh/ruff-pre-commit/compare/v0.6.9...v0.7.2) - [github.com/psf/black-pre-commit-mirror: 24.8.0 → 24.10.0](https://github.com/psf/black-pre-commit-mirror/compare/24.8.0...24.10.0) - [github.com/pre-commit/mirrors-clang-format: v19.1.1 → v19.1.3](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.1...v19.1.3) - [github.com/python-jsonschema/check-jsonschema: 0.29.3 → 0.29.4](https://github.com/python-jsonschema/check-jsonschema/compare/0.29.3...0.29.4) - [github.com/tox-dev/pyproject-fmt: 2.2.4 → v2.5.0](https://github.com/tox-dev/pyproject-fmt/compare/2.2.4...v2.5.0) - [github.com/abravalheri/validate-pyproject: v0.20.2 → v0.22](https://github.com/abravalheri/validate-pyproject/compare/v0.20.2...v0.22) --- .pre-commit-config.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6254b89416f..ddc98fdc356 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,12 +1,12 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.6.9 + rev: v0.7.2 hooks: - id: ruff args: [--exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.8.0 + rev: 24.10.0 hooks: - id: black @@ -24,7 +24,7 @@ repos: exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v19.1.1 + rev: v19.1.3 hooks: - id: clang-format types: [c] @@ -50,7 +50,7 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.29.3 + rev: 0.29.4 hooks: - id: check-github-workflows - id: check-readthedocs @@ -62,12 +62,12 @@ repos: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: 2.2.4 + rev: v2.5.0 hooks: - id: pyproject-fmt - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.20.2 + rev: v0.22 hooks: - id: validate-pyproject additional_dependencies: [trove-classifiers>=2024.10.12] From 5628213ab03b59e2a70087c65cf4f05aa8de8cdf Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 4 Nov 2024 21:17:47 +0200 Subject: [PATCH 048/107] Upgrade pip for all --- .github/workflows/test-windows.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 728182b1ec1..9fcb8cb94c1 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -69,10 +69,13 @@ jobs: - name: Print build system information run: python3 .github/workflows/system-info.py + - name: Upgrade pip + run: | + python3 -m pip install --upgrade pip + - name: Install CPython dependencies if: "!contains(matrix.python-version, 'pypy')" run: | - python3 -m pip install --upgrade pip python3 -m pip install PyQt6 - name: Install dependencies From 2d23a84049eb22f62107eb5f4cbe7c421b074d12 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 5 Nov 2024 17:18:46 +1100 Subject: [PATCH 049/107] Fixed type hint --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index def3417845d..1a8c03eb337 100644 --- a/setup.py +++ b/setup.py @@ -1001,7 +1001,7 @@ def debug_build() -> bool: return hasattr(sys, "gettotalrefcount") or FUZZING_BUILD -files = ["src/_imaging.c"] +files: list[str | os.PathLike[str]] = ["src/_imaging.c"] for src_file in _IMAGING: files.append("src/" + src_file + ".c") for src_file in _LIB_IMAGING: From 48db4a1d4d1baec7fdd77474db00029f24c2cef1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 5 Nov 2024 19:40:03 +1100 Subject: [PATCH 050/107] Use test image filename --- docs/reference/ImageFile.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/ImageFile.rst b/docs/reference/ImageFile.rst index fdfeb60f99f..64abd71d156 100644 --- a/docs/reference/ImageFile.rst +++ b/docs/reference/ImageFile.rst @@ -19,7 +19,7 @@ Example: Parse an image from PIL import ImageFile - fp = open("hopper.pgm", "rb") + fp = open("hopper.ppm", "rb") p = ImageFile.Parser() From a1d862f1d67e621213e0239d3e45838abeab363e Mon Sep 17 00:00:00 2001 From: "Pietro F. Fontana" Date: Tue, 5 Nov 2024 15:39:03 +0100 Subject: [PATCH 051/107] Be more specific in comment MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ondrej Baranovič --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 48863c3974e..68252f3a98b 100644 --- a/setup.py +++ b/setup.py @@ -691,7 +691,7 @@ def build_extensions(self) -> None: elif sys.platform == "win32" and _find_library_file(self, "zlib"): feature.set("zlib", "zlib") # alternative name elif sys.platform == "win32" and _find_library_file(self, "zdll"): - feature.set("zlib", "zdll") # different name if shared + feature.set("zlib", "zdll") # dll import library if feature.want("jpeg"): _dbg("Looking for jpeg") From e82b5398e750c5dd37dfcc2edaf6b947371b53ef Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 6 Nov 2024 14:12:07 +0800 Subject: [PATCH 052/107] Correct handling of vendored fribidi. --- .github/workflows/wheels-dependencies.sh | 46 ++++++++++++++++-------- .github/workflows/wheels-test.sh | 8 +++-- .gitignore | 4 +++ pyproject.toml | 4 +-- 4 files changed, 43 insertions(+), 19 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index fd13930db46..ff06e99d276 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -1,16 +1,34 @@ #!/bin/bash # Setup that needs to be done before multibuild utils are invoked +PROJECTDIR=$(pwd) if [[ "$(uname -s)" == "Darwin" ]]; then - # Build and install macOS builds into the `build/deps` folder. - BUILD_PREFIX=$(pwd)/build/deps + # Safety check - macOS builds require that CIBW_ARCHS is set, and that it + # only contains a single value (even though cwbuildwheel) allows multiple + # values in CIBW_ARCHS. + if [[ -z "$CIBW_ARCHS" ]]; then + echo "ERROR: Pillow macOS builds require CIBW_ARCHS be defined." + exit 1 + fi + if [[ "$CIBW_ARCHS" == *" "* ]]; then + echo "ERROR: Pillow macOS builds only support a single architecture in CIBW_ARCHS." + exit 1 + fi + + # Build macOS dependencies in `build/darwin` + # Install them into `build/deps/darwin` + WORKDIR=$(pwd)/build/darwin + BUILD_PREFIX=$(pwd)/build/deps/darwin + PLAT=$CIBW_ARCHS else - export MB_ML_LIBC=${AUDITWHEEL_POLICY::9} - export MB_ML_VER=${AUDITWHEEL_POLICY:9} + # Build prefix will default to /usr/local + WORKDIR=$(pwd)/build + PLAT=$CIBW_ARCHS + MB_ML_LIBC=${AUDITWHEEL_POLICY::9} + MB_ML_VER=${AUDITWHEEL_POLICY:9} fi # Define custom utilities -export PLAT=$CIBW_ARCHS source wheels/multibuild/common_utils.sh source wheels/multibuild/library_builders.sh if [ -z "$IS_MACOS" ]; then @@ -117,9 +135,6 @@ function build { ORIGINAL_CFLAGS=$CFLAGS CFLAGS="$CFLAGS -O3 -DNDEBUG" - if [[ -n "$IS_MACOS" ]]; then - CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names" - fi build_libwebp CFLAGS=$ORIGINAL_CFLAGS @@ -127,7 +142,7 @@ function build { if [ -n "$IS_MACOS" ]; then # Custom freetype build - build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no + build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --without-harfbuzz else build_freetype fi @@ -136,17 +151,18 @@ function build { } # Perform all dependency builds in the build subfolder. -mkdir -p build -pushd build > /dev/null +mkdir -p $WORKDIR +pushd $WORKDIR > /dev/null # Any stuff that you need to do before you start building the wheels # Runs in the root directory of this repository. -if [[ ! -d pillow-depends-main ]]; then - if [[ ! -f pillow-depends-main.zip ]]; then +if [[ ! -d $WORKDIR/pillow-depends-main ]]; then + if [[ ! -f $PROJECTDIR/pillow-depends-main.zip ]]; then echo "Download pillow dependency sources..." - curl -fSL -o pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip + curl -fSL -o $PROJECTDIR/pillow-depends-main.zip https://github.com/python-pillow/pillow-depends/archive/main.zip fi - untar pillow-depends-main.zip + echo "Unpacking pillow dependency sources..." + untar $PROJECTDIR/pillow-depends-main.zip fi if [[ -n "$IS_MACOS" ]]; then diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index b697b740ec0..d1db8e8a5ec 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -16,8 +16,12 @@ if [[ "$OSTYPE" == "darwin"* ]]; then fi $HOMEBREW_HOME/bin/brew install fribidi - # Add the Homebrew lib folder so that vendored libraries can be found. - export DYLD_LIBRARY_PATH=$HOMEBREW_HOME/lib + # Add the lib folder for fribidi so that the vendored library can be found. + # Don't use /opt/homebrew/lib directly - use the lib folder where the + # installed copy of fribidi is cellared. This ensures we don't pick up the + # Homebrew version of any other library that we're dependent on (most notably, + # freetype). + export DYLD_LIBRARY_PATH=$(dirname $(realpath $HOMEBREW_HOME/lib/libfribidi.dylib)) elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then apk add curl fribidi else diff --git a/.gitignore b/.gitignore index 9b577325730..3033c2ea7ae 100644 --- a/.gitignore +++ b/.gitignore @@ -91,5 +91,9 @@ Tests/images/msp Tests/images/picins Tests/images/sunraster +# Test and dependency downloads +pillow-depends-main.zip +pillow-test-images.zip + # pyinstaller *.spec diff --git a/pyproject.toml b/pyproject.toml index da8548dcbd3..26fa4581706 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -96,13 +96,13 @@ build-verbosity = 1 config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable" # Add an explicit dependencies prefix for macOS. -macos.config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable dependencies-prefix=./build/deps" +macos.config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable dependencies-prefix=./build/deps/darwin" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" [tool.cibuildwheel.macos.environment] -PATH = "$(pwd)/build/deps/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" +PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" [tool.black] exclude = "wheels/multibuild" From b0345c1c7b7605063064118563e8068fde5f9343 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 6 Nov 2024 22:08:22 +1100 Subject: [PATCH 053/107] Updated macOS tested Pillow versions --- docs/installation/platform-support.rst | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index a0bada7b428..b9d633142e7 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -75,7 +75,9 @@ These platforms have been reported to work at the versions mentioned. | Operating system | | Tested Python | | Latest tested | | Tested | | | | versions | | Pillow version | | processors | +==================================+============================+==================+==============+ -| macOS 15 Sequoia | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.4.0 |arm | +| macOS 15 Sequoia | 3.9, 3.10, 3.11, 3.12, 3.13| 11.0.0 |arm | +| +----------------------------+------------------+ | +| | 3.8 | 10.4.0 | | +----------------------------------+----------------------------+------------------+--------------+ | macOS 14 Sonoma | 3.8, 3.9, 3.10, 3.11, 3.12 | 10.4.0 |arm | +----------------------------------+----------------------------+------------------+--------------+ From 042f3ff083d98d211c2035dd45e511a63800d06e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 7 Nov 2024 07:52:18 +1100 Subject: [PATCH 054/107] Require coverage>=7.4.2 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 45d5c269261..bff295bc60f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -56,7 +56,7 @@ optional-dependencies.mic = [ ] optional-dependencies.tests = [ "check-manifest", - "coverage", + "coverage>=7.4.2", "defusedxml", "markdown2", "olefile", From 4e35852ccc97f3e455f5130f1cd22f2f10292955 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Fri, 8 Nov 2024 05:34:35 +0800 Subject: [PATCH 055/107] Correct typo in comment. Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/workflows/wheels-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index ff06e99d276..2dea8c62a95 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -4,7 +4,7 @@ PROJECTDIR=$(pwd) if [[ "$(uname -s)" == "Darwin" ]]; then # Safety check - macOS builds require that CIBW_ARCHS is set, and that it - # only contains a single value (even though cwbuildwheel) allows multiple + # only contains a single value (even though cibuildwheel) allows multiple # values in CIBW_ARCHS. if [[ -z "$CIBW_ARCHS" ]]; then echo "ERROR: Pillow macOS builds require CIBW_ARCHS be defined." From 0a5b0f29e58cf9763b886e3ce300a79b5f4bac63 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Nov 2024 00:18:59 +1100 Subject: [PATCH 056/107] Run gcc problem matcher on Python 3.13 --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 87acd7ddbc0..c33309bbf03 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -116,7 +116,7 @@ jobs: GHA_PYTHON_VERSION: ${{ matrix.python-version }} - name: Register gcc problem matcher - if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.12'" + if: "matrix.os == 'ubuntu-latest' && matrix.python-version == '3.13'" run: echo "::add-matcher::.github/problem-matchers/gcc.json" - name: Build From 681a03b1c38b26a36cddda812ead882b4af24eb4 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 9 Nov 2024 10:29:29 +0800 Subject: [PATCH 057/107] Apply suggestions from code review Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- .github/workflows/wheels-dependencies.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 2dea8c62a95..f4106429d46 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -4,8 +4,8 @@ PROJECTDIR=$(pwd) if [[ "$(uname -s)" == "Darwin" ]]; then # Safety check - macOS builds require that CIBW_ARCHS is set, and that it - # only contains a single value (even though cibuildwheel) allows multiple - # values in CIBW_ARCHS. + # only contains a single value (even though cibuildwheel allows multiple + # values in CIBW_ARCHS). if [[ -z "$CIBW_ARCHS" ]]; then echo "ERROR: Pillow macOS builds require CIBW_ARCHS be defined." exit 1 @@ -171,7 +171,7 @@ if [[ -n "$IS_MACOS" ]]; then # with a MACOSX_DEPLOYMENT_TARGET that doesn't match what we want to use, # and they may bring in other dependencies that we don't want. The same will # be true of any other locations on the path. To avoid conflicts, strip the - # path down to the bare mimimum (which, on macOS, won't include any + # path down to the bare minimum (which, on macOS, won't include any # development dependencies). export PATH="$BUILD_PREFIX/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" export CMAKE_PREFIX_PATH=$BUILD_PREFIX From 9f09d48f3710409725beef1ca1be66da33855922 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Nov 2024 18:20:52 +1100 Subject: [PATCH 058/107] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 9d45e2214ca..7e8e94ed9cf 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 11.1.0 (unreleased) ------------------- +- Allow linking to zlib import library on Windows #8519 + [cubanpit, nulano] + - Detach PyQt6 QPixmap instance before returning #8509 [radarhere] From 7674b1a07a1dfdf1c0944aea1fefb95c4f62f5b4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Nov 2024 18:43:12 +1100 Subject: [PATCH 059/107] Removed quotes after dropping support for Python 3.8 --- src/PIL/_typing.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py index 0a7d87cc24b..335804b792f 100644 --- a/src/PIL/_typing.py +++ b/src/PIL/_typing.py @@ -47,7 +47,7 @@ class SupportsRead(Protocol[_T_co]): def read(self, __length: int = ...) -> _T_co: ... -StrOrBytesPath = Union[str, bytes, "os.PathLike[str]", "os.PathLike[bytes]"] +StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] __all__ = ["Buffer", "IntegralLike", "StrOrBytesPath", "SupportsRead", "TypeGuard"] From 2303018ebf751b7d6bc9d0e3d830cdd5f529a5b4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 9 Nov 2024 20:06:33 +1100 Subject: [PATCH 060/107] Removed use of os.path.realpath --- src/PIL/Image.py | 6 +++--- src/PIL/ImageFile.py | 2 +- src/PIL/ImageFont.py | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 44270392c4d..9b76ce8bd86 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2550,7 +2550,7 @@ def save( filename: str | bytes = "" open_fp = False if is_path(fp): - filename = os.path.realpath(os.fspath(fp)) + filename = os.fspath(fp) open_fp = True elif fp == sys.stdout: try: @@ -2559,7 +2559,7 @@ def save( pass if not filename and hasattr(fp, "name") and is_path(fp.name): # only set the name for metadata purposes - filename = os.path.realpath(os.fspath(fp.name)) + filename = os.fspath(fp.name) # may mutate self! self._ensure_mutable() @@ -3463,7 +3463,7 @@ def open( exclusive_fp = False filename: str | bytes = "" if is_path(fp): - filename = os.path.realpath(os.fspath(fp)) + filename = os.fspath(fp) if filename: fp = builtins.open(filename, "rb") diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index d69d8456850..7f27d54dcc3 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -130,7 +130,7 @@ def __init__( if is_path(fp): # filename self.fp = open(fp, "rb") - self.filename = os.path.realpath(os.fspath(fp)) + self.filename = os.fspath(fp) self._exclusive_fp = True else: # stream diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index b694b817e65..d8c2655609e 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -270,7 +270,7 @@ def load_from_bytes(f: IO[bytes]) -> None: ) if is_path(font): - font = os.path.realpath(os.fspath(font)) + font = os.fspath(font) if sys.platform == "win32": font_bytes_path = font if isinstance(font, bytes) else font.encode() try: From 5bff2f3b2894ec6923c590d0c37b18177d0634bd Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 10 Nov 2024 21:32:54 +1100 Subject: [PATCH 061/107] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 7e8e94ed9cf..f6ed5eb82fb 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 11.1.0 (unreleased) ------------------- +- Removed use of os.path.realpath #8545 + [radarhere] + - Allow linking to zlib import library on Windows #8519 [cubanpit, nulano] From 378df7a5b22e47a0652619bd2f3218674c579d91 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 12 Nov 2024 22:41:57 +1100 Subject: [PATCH 062/107] Disable platform guessing instead of adding dependencies-prefix --- pyproject.toml | 4 +-- setup.py | 89 +++++++++++++++++++++----------------------------- 2 files changed, 40 insertions(+), 53 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 26fa4581706..abb7be6458c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -95,8 +95,8 @@ before-all = ".github/workflows/wheels-dependencies.sh" build-verbosity = 1 config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable" -# Add an explicit dependencies prefix for macOS. -macos.config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable dependencies-prefix=./build/deps/darwin" +# Disable platform guessing on macOS +macos.config-settings = "raqm=enable raqm=vendor fribidi=vendor imagequant=disable platform-guessing=disable" test-command = "cd {project} && .github/workflows/wheels-test.sh" test-extras = "tests" diff --git a/setup.py b/setup.py index e4ce04883dd..4770c5eb78f 100644 --- a/setup.py +++ b/setup.py @@ -344,11 +344,6 @@ def __iter__(self) -> Iterator[str]: for x in ("raqm", "fribidi") ] + [ - ( - "dependencies-prefix", - None, - "The prefix where build dependencies are located.", - ), ("disable-platform-guessing", None, "Disable platform guessing on Linux"), ("debug", None, "Debug logging"), ] @@ -360,7 +355,6 @@ def check_configuration(option: str, value: str) -> bool | None: return True if value in configuration.get(option, []) else None def initialize_options(self) -> None: - self.dependencies_prefix = configuration.get("dependencies-prefix", []) self.disable_platform_guessing = self.check_configuration( "platform-guessing", "disable" ) @@ -570,54 +564,47 @@ def build_extensions(self) -> None: ) elif sys.platform == "darwin": - if self.dependencies_prefix: - # Use the explicitly provided prefixes for dependencies. - for prefix in self.dependencies_prefix: - _add_directory(library_dirs, os.path.join(prefix, "lib")) - _add_directory(include_dirs, os.path.join(prefix, "include")) - else: - # Guess the dependency locations based on homebrew/fink/macports - # attempt to make sure we pick freetype2 over other versions - _add_directory(include_dirs, "/sw/include/freetype2") - _add_directory(include_dirs, "/sw/lib/freetype2/include") - # fink installation directories - _add_directory(library_dirs, "/sw/lib") - _add_directory(include_dirs, "/sw/include") - # darwin ports installation directories - _add_directory(library_dirs, "/opt/local/lib") - _add_directory(include_dirs, "/opt/local/include") - - # if Homebrew is installed, use its lib and include directories - try: - prefix = ( - subprocess.check_output(["brew", "--prefix"]) - .strip() - .decode("latin1") - ) - except Exception: - # Homebrew not installed - prefix = None + # attempt to make sure we pick freetype2 over other versions + _add_directory(include_dirs, "/sw/include/freetype2") + _add_directory(include_dirs, "/sw/lib/freetype2/include") + # fink installation directories + _add_directory(library_dirs, "/sw/lib") + _add_directory(include_dirs, "/sw/include") + # darwin ports installation directories + _add_directory(library_dirs, "/opt/local/lib") + _add_directory(include_dirs, "/opt/local/include") - ft_prefix = None + # if Homebrew is installed, use its lib and include directories + try: + prefix = ( + subprocess.check_output(["brew", "--prefix"]) + .strip() + .decode("latin1") + ) + except Exception: + # Homebrew not installed + prefix = None - if prefix: - # add Homebrew's include and lib directories - _add_directory(library_dirs, os.path.join(prefix, "lib")) - _add_directory(include_dirs, os.path.join(prefix, "include")) - _add_directory( - include_dirs, os.path.join(prefix, "opt", "zlib", "include") - ) - ft_prefix = os.path.join(prefix, "opt", "freetype") + ft_prefix = None - if ft_prefix and os.path.isdir(ft_prefix): - # freetype might not be linked into Homebrew's prefix - _add_directory(library_dirs, os.path.join(ft_prefix, "lib")) - _add_directory(include_dirs, os.path.join(ft_prefix, "include")) - else: - # fall back to freetype from XQuartz if - # Homebrew's freetype is missing - _add_directory(library_dirs, "/usr/X11/lib") - _add_directory(include_dirs, "/usr/X11/include") + if prefix: + # add Homebrew's include and lib directories + _add_directory(library_dirs, os.path.join(prefix, "lib")) + _add_directory(include_dirs, os.path.join(prefix, "include")) + _add_directory( + include_dirs, os.path.join(prefix, "opt", "zlib", "include") + ) + ft_prefix = os.path.join(prefix, "opt", "freetype") + + if ft_prefix and os.path.isdir(ft_prefix): + # freetype might not be linked into Homebrew's prefix + _add_directory(library_dirs, os.path.join(ft_prefix, "lib")) + _add_directory(include_dirs, os.path.join(ft_prefix, "include")) + else: + # fall back to freetype from XQuartz if + # Homebrew's freetype is missing + _add_directory(library_dirs, "/usr/X11/lib") + _add_directory(include_dirs, "/usr/X11/include") # Add the macOS SDK path. sdk_path = self.get_macos_sdk_path() From 9dc690427f009f898ef74b8799569346eb0b3fd5 Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Wed, 13 Nov 2024 11:03:26 +0800 Subject: [PATCH 063/107] Correct the lookup of libfribidi on x86 macOS installs. --- .github/workflows/wheels-test.sh | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index d1db8e8a5ec..ce83a4278cd 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -7,21 +7,18 @@ if [[ "$OSTYPE" == "darwin"* ]]; then # However, we *do* need Homebrew to provide a copy of fribidi for # testing purposes so that we can verify the fribidi shim works as expected. if [[ "$(uname -m)" == "x86_64" ]]; then - # Use the "installed" location, rather than /usr/local, for two reasons: - # firstly, Homebrew allows libraries to be *installed*, but not linked; - # and secondly, we don't want any *other* leakage from /usr/local. - HOMEBREW_HOME=/usr/local/Homebrew + HOMEBREW_PREFIX=/usr/local else - HOMEBREW_HOME=/opt/homebrew + HOMEBREW_PREFIX=/opt/homebrew fi - $HOMEBREW_HOME/bin/brew install fribidi + $HOMEBREW_PREFIX/bin/brew install fribidi # Add the lib folder for fribidi so that the vendored library can be found. - # Don't use /opt/homebrew/lib directly - use the lib folder where the + # Don't use $HOMEWBREW_PREFIX/lib directly - use the lib folder where the # installed copy of fribidi is cellared. This ensures we don't pick up the # Homebrew version of any other library that we're dependent on (most notably, # freetype). - export DYLD_LIBRARY_PATH=$(dirname $(realpath $HOMEBREW_HOME/lib/libfribidi.dylib)) + export DYLD_LIBRARY_PATH=$(dirname $(realpath $HOMEBREW_PREFIX/lib/libfribidi.dylib)) elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then apk add curl fribidi else From 54f23345d2fd1894c7cf89475e385fcbb3d3f16d Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Sat, 16 Nov 2024 09:58:29 +0800 Subject: [PATCH 064/107] More tweaks from code review. --- .github/workflows/wheels-dependencies.sh | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index f4106429d46..ce2e40103d0 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -19,14 +19,13 @@ if [[ "$(uname -s)" == "Darwin" ]]; then # Install them into `build/deps/darwin` WORKDIR=$(pwd)/build/darwin BUILD_PREFIX=$(pwd)/build/deps/darwin - PLAT=$CIBW_ARCHS else # Build prefix will default to /usr/local WORKDIR=$(pwd)/build - PLAT=$CIBW_ARCHS MB_ML_LIBC=${AUDITWHEEL_POLICY::9} MB_ML_VER=${AUDITWHEEL_POLICY:9} fi +PLAT=$CIBW_ARCHS # Define custom utilities source wheels/multibuild/common_utils.sh @@ -119,9 +118,9 @@ function build { build_libjpeg_turbo if [ -n "$IS_MACOS" ]; then # Custom tiff build to include jpeg; by default, configure won't include - # headers/libs in the custom macOS prefix. Explicitly disable webp and - # zstd, because on x86_64 macs, it will pick up the Homebrew versions of - # webp and zstd from /usr/local. + # headers/libs in the custom macOS prefix. Explicitly disable webp, + # libdeflate and zstd, because on x86_64 macs, it will pick up the + # Homebrew versions of those libraries from /usr/local. build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \ --with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \ --disable-webp --disable-zstd --disable-libdeflate From 6136a92d061e77e622c974c35aa0605f96eb05e0 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sat, 16 Nov 2024 09:48:28 +0000 Subject: [PATCH 065/107] Update codecov/codecov-action action to v5 --- .github/workflows/test-cygwin.yml | 2 +- .github/workflows/test-docker.yml | 2 +- .github/workflows/test-mingw.yml | 2 +- .github/workflows/test-windows.yml | 2 +- .github/workflows/test.yml | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index 656054e8924..b906ada92e0 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -135,7 +135,7 @@ jobs: bash.exe .ci/after_success.sh - name: Upload coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: file: ./coverage.xml flags: GHA_Cygwin diff --git a/.github/workflows/test-docker.yml b/.github/workflows/test-docker.yml index 03608319a60..cc5f9d4a5a9 100644 --- a/.github/workflows/test-docker.yml +++ b/.github/workflows/test-docker.yml @@ -100,7 +100,7 @@ jobs: MATRIX_DOCKER: ${{ matrix.docker }} - name: Upload coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: flags: GHA_Docker name: ${{ matrix.docker }} diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index bfd393db5a2..a14e7e1ed7f 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -85,7 +85,7 @@ jobs: .ci/test.sh - name: Upload coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: file: ./coverage.xml flags: GHA_Windows diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 9b5da137d8f..d1924eb2018 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -215,7 +215,7 @@ jobs: shell: pwsh - name: Upload coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: file: ./coverage.xml flags: GHA_Windows diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c33309bbf03..1405c64f9aa 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -156,7 +156,7 @@ jobs: .ci/after_success.sh - name: Upload coverage - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: flags: ${{ matrix.os == 'ubuntu-latest' && 'GHA_Ubuntu' || 'GHA_macOS' }} name: ${{ matrix.os }} Python ${{ matrix.python-version }} From c70dacff62c07341c4bb9d9c28e26e4e703ab5ae Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 16 Nov 2024 21:44:00 +1100 Subject: [PATCH 066/107] Updated from deprecated "file" to "files" --- .github/workflows/test-cygwin.yml | 2 +- .github/workflows/test-mingw.yml | 2 +- .github/workflows/test-windows.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index b906ada92e0..2076cad7a9a 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -137,7 +137,7 @@ jobs: - name: Upload coverage uses: codecov/codecov-action@v5 with: - file: ./coverage.xml + files: ./coverage.xml flags: GHA_Cygwin name: Cygwin Python 3.${{ matrix.python-minor-version }} token: ${{ secrets.CODECOV_ORG_TOKEN }} diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index a14e7e1ed7f..cca25edba42 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -87,7 +87,7 @@ jobs: - name: Upload coverage uses: codecov/codecov-action@v5 with: - file: ./coverage.xml + files: ./coverage.xml flags: GHA_Windows name: "MSYS2 MinGW" token: ${{ secrets.CODECOV_ORG_TOKEN }} diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index d1924eb2018..d905a392585 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -217,7 +217,7 @@ jobs: - name: Upload coverage uses: codecov/codecov-action@v5 with: - file: ./coverage.xml + files: ./coverage.xml flags: GHA_Windows name: ${{ runner.os }} Python ${{ matrix.python-version }} token: ${{ secrets.CODECOV_ORG_TOKEN }} From 96b898c826982c5b99f2a9387e0d2011a73fe20a Mon Sep 17 00:00:00 2001 From: Russell Keith-Magee Date: Mon, 18 Nov 2024 09:56:45 +0800 Subject: [PATCH 067/107] A couple more cleanups from code review. --- .github/workflows/wheels-dependencies.sh | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index ce2e40103d0..f86099af16e 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -62,8 +62,7 @@ BROTLI_VERSION=1.1.0 function build_pkg_config { if [ -e pkg-config-stamp ]; then return; fi - # This essentially duplicates the Homebrew recipe: - # https://github.com/Homebrew/homebrew-core/blob/master/Formula/p/pkg-config.rb + # This essentially duplicates the Homebrew recipe ORIGINAL_CFLAGS=$CFLAGS CFLAGS="$CFLAGS -Wno-int-conversion" build_simple pkg-config 0.29.2 https://pkg-config.freedesktop.org/releases tar.gz \ @@ -108,7 +107,6 @@ function build { if [ -n "$IS_MACOS" ]; then build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib - build_simple libXdmcp 1.1.5 https://www.x.org/pub/individual/lib build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist else sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc @@ -123,7 +121,7 @@ function build { # Homebrew versions of those libraries from /usr/local. build_simple tiff $TIFF_VERSION https://download.osgeo.org/libtiff tar.gz \ --with-jpeg-include-dir=$BUILD_PREFIX/include --with-jpeg-lib-dir=$BUILD_PREFIX/lib \ - --disable-webp --disable-zstd --disable-libdeflate + --disable-webp --disable-libdeflate --disable-zstd else build_tiff fi @@ -134,6 +132,9 @@ function build { ORIGINAL_CFLAGS=$CFLAGS CFLAGS="$CFLAGS -O3 -DNDEBUG" + if [[ -n "$IS_MACOS" ]]; then + CFLAGS="$CFLAGS -Wl,-headerpad_max_install_names" + fi build_libwebp CFLAGS=$ORIGINAL_CFLAGS @@ -141,7 +142,7 @@ function build { if [ -n "$IS_MACOS" ]; then # Custom freetype build - build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --without-harfbuzz + build_simple freetype $FREETYPE_VERSION https://download.savannah.gnu.org/releases/freetype tar.gz --with-harfbuzz=no else build_freetype fi @@ -175,7 +176,7 @@ if [[ -n "$IS_MACOS" ]]; then export PATH="$BUILD_PREFIX/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" export CMAKE_PREFIX_PATH=$BUILD_PREFIX - # Link the brew command into our isolated build directory. + # Ensure the basic structure of the build prefix directory exists. mkdir -p "$BUILD_PREFIX/bin" mkdir -p "$BUILD_PREFIX/lib" From e02e4aaf1de9503e6e88a60f292998d174d23034 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 5 Nov 2024 19:31:54 +1100 Subject: [PATCH 068/107] Updated harfbuzz to 10.1.0 --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index f86099af16e..89226810a09 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -38,7 +38,7 @@ ARCHIVE_SDIR=pillow-depends-main # Package versions for fresh source builds FREETYPE_VERSION=2.13.2 -HARFBUZZ_VERSION=10.0.1 +HARFBUZZ_VERSION=10.1.0 LIBPNG_VERSION=1.6.44 JPEGTURBO_VERSION=3.0.4 OPENJPEG_VERSION=2.5.2 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index c8332d11c7e..5606ac7f7ed 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -112,7 +112,7 @@ def cmd_msbuild( "BROTLI": "1.1.0", "FREETYPE": "2.13.3", "FRIBIDI": "1.0.16", - "HARFBUZZ": "10.0.1", + "HARFBUZZ": "10.1.0", "JPEGTURBO": "3.0.4", "LCMS2": "2.16", "LIBPNG": "1.6.44", From c7a9582eab34bc46b74c245aa137424b9cfaee74 Mon Sep 17 00:00:00 2001 From: Tom Flanagan Date: Tue, 19 Nov 2024 20:40:14 -0800 Subject: [PATCH 069/107] Fix file position desync when calling into libtiff --- Tests/test_libtiff_file_position.py | 23 +++++++++++++++++++++++ src/PIL/TiffImagePlugin.py | 17 +++++++++-------- 2 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 Tests/test_libtiff_file_position.py diff --git a/Tests/test_libtiff_file_position.py b/Tests/test_libtiff_file_position.py new file mode 100644 index 00000000000..d614ea8eca1 --- /dev/null +++ b/Tests/test_libtiff_file_position.py @@ -0,0 +1,23 @@ +from __future__ import annotations + + +import pytest + +from PIL import Image + + +@pytest.mark.parametrize('test_file', [ + 'Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif', + 'Tests/images/old-style-jpeg-compression.tif', +]) +def test_libtiff_exif_loading(test_file) -> None: + # loading image before exif + im1 = Image.open(open(test_file, 'rb', buffering=1048576)) + im1.load() + exif1 = dict(im1.getexif()) + + # loading exif before image + im2 = Image.open(open(test_file, 'rb', buffering=1048576)) + exif2 = dict(im2.getexif()) + + assert exif1 == exif2 diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 6bf39b75a5f..72628677278 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1216,10 +1216,6 @@ def seek(self, frame: int) -> None: def _seek(self, frame: int) -> None: self.fp = self._fp - # reset buffered io handle in case fp - # was passed to libtiff, invalidating the buffer - self.fp.tell() - while len(self._frame_pos) <= frame: if not self.__next: msg = "no more images in TIFF file" @@ -1303,10 +1299,6 @@ def load_end(self) -> None: if not self.is_animated: self._close_exclusive_fp_after_loading = True - # reset buffered io handle in case fp - # was passed to libtiff, invalidating the buffer - self.fp.tell() - # load IFD data from fp before it is closed exif = self.getexif() for key in TiffTags.TAGS_V2_GROUPS: @@ -1381,8 +1373,17 @@ def _load_libtiff(self) -> Image.core.PixelAccess | None: logger.debug("have fileno, calling fileno version of the decoder.") if not close_self_fp: self.fp.seek(0) + # Save and restore the file position, because libtiff will move it + # outside of the python runtime, and that will confuse + # io.BufferedReader and possible others. + # NOTE: This must use os.lseek(), and not fp.tell()/fp.seek(), + # because the buffer read head already may not equal the actual + # file position, and fp.seek() may just adjust it's internal + # pointer and not actually seek the OS file handle. + pos = os.lseek(fp, 0, os.SEEK_CUR) # 4 bytes, otherwise the trace might error out n, err = decoder.decode(b"fpfp") + os.lseek(fp, pos, os.SEEK_SET) else: # we have something else. logger.debug("don't have fileno or getvalue. just reading") From 44cc0bebb8998897f03d77479df750f208d9d00e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 05:52:50 +0000 Subject: [PATCH 070/107] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- Tests/test_libtiff_file_position.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/Tests/test_libtiff_file_position.py b/Tests/test_libtiff_file_position.py index d614ea8eca1..43ed82c30e1 100644 --- a/Tests/test_libtiff_file_position.py +++ b/Tests/test_libtiff_file_position.py @@ -1,23 +1,25 @@ from __future__ import annotations - import pytest from PIL import Image -@pytest.mark.parametrize('test_file', [ - 'Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif', - 'Tests/images/old-style-jpeg-compression.tif', -]) +@pytest.mark.parametrize( + "test_file", + [ + "Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif", + "Tests/images/old-style-jpeg-compression.tif", + ], +) def test_libtiff_exif_loading(test_file) -> None: # loading image before exif - im1 = Image.open(open(test_file, 'rb', buffering=1048576)) + im1 = Image.open(open(test_file, "rb", buffering=1048576)) im1.load() exif1 = dict(im1.getexif()) # loading exif before image - im2 = Image.open(open(test_file, 'rb', buffering=1048576)) + im2 = Image.open(open(test_file, "rb", buffering=1048576)) exif2 = dict(im2.getexif()) assert exif1 == exif2 From cb1653f6272ebc543530ae03cd7cf8577ed13231 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 20 Nov 2024 22:33:23 +1100 Subject: [PATCH 071/107] Updated comment --- src/PIL/TiffImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 72628677278..08bb7891512 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1374,7 +1374,7 @@ def _load_libtiff(self) -> Image.core.PixelAccess | None: if not close_self_fp: self.fp.seek(0) # Save and restore the file position, because libtiff will move it - # outside of the python runtime, and that will confuse + # outside of the Python runtime, and that will confuse # io.BufferedReader and possible others. # NOTE: This must use os.lseek(), and not fp.tell()/fp.seek(), # because the buffer read head already may not equal the actual From 925db4552658ac0d8c037b0ac0b39bd7eb96f8f9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 20 Nov 2024 22:40:29 +1100 Subject: [PATCH 072/107] Moved test --- Tests/test_file_libtiff.py | 19 +++++++++++++++++++ Tests/test_libtiff_file_position.py | 25 ------------------------- 2 files changed, 19 insertions(+), 25 deletions(-) delete mode 100644 Tests/test_libtiff_file_position.py diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 62f8719af53..9c49b1534ed 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -1098,6 +1098,25 @@ def test_exif_transpose(self) -> None: assert_image_similar(base_im, im, 0.7) + @pytest.mark.parametrize( + "test_file", + [ + "Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif", + "Tests/images/old-style-jpeg-compression.tif", + ], + ) + def test_buffering(self, test_file: str) -> None: + # load exif first + with Image.open(open(test_file, "rb", buffering=1048576)) as im: + exif = dict(im.getexif()) + + # load image before exif + with Image.open(open(test_file, "rb", buffering=1048576)) as im2: + im2.load() + exif_after_load = dict(im2.getexif()) + + assert exif == exif_after_load + @pytest.mark.valgrind_known_error(reason="Backtrace in Python Core") def test_sampleformat_not_corrupted(self) -> None: # Assert that a TIFF image with SampleFormat=UINT tag is not corrupted diff --git a/Tests/test_libtiff_file_position.py b/Tests/test_libtiff_file_position.py deleted file mode 100644 index 43ed82c30e1..00000000000 --- a/Tests/test_libtiff_file_position.py +++ /dev/null @@ -1,25 +0,0 @@ -from __future__ import annotations - -import pytest - -from PIL import Image - - -@pytest.mark.parametrize( - "test_file", - [ - "Tests/images/old-style-jpeg-compression-no-samplesperpixel.tif", - "Tests/images/old-style-jpeg-compression.tif", - ], -) -def test_libtiff_exif_loading(test_file) -> None: - # loading image before exif - im1 = Image.open(open(test_file, "rb", buffering=1048576)) - im1.load() - exif1 = dict(im1.getexif()) - - # loading exif before image - im2 = Image.open(open(test_file, "rb", buffering=1048576)) - exif2 = dict(im2.getexif()) - - assert exif1 == exif2 From 82dfbc35b190a818a1cbdbee6524eaf31605b7bd Mon Sep 17 00:00:00 2001 From: Tom Flanagan Date: Wed, 20 Nov 2024 05:21:14 -0800 Subject: [PATCH 073/107] Update src/PIL/TiffImagePlugin.py Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/TiffImagePlugin.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 72628677278..08bb7891512 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1374,7 +1374,7 @@ def _load_libtiff(self) -> Image.core.PixelAccess | None: if not close_self_fp: self.fp.seek(0) # Save and restore the file position, because libtiff will move it - # outside of the python runtime, and that will confuse + # outside of the Python runtime, and that will confuse # io.BufferedReader and possible others. # NOTE: This must use os.lseek(), and not fp.tell()/fp.seek(), # because the buffer read head already may not equal the actual From bfe40b5129e9250243b1d425172c18f7c4e15e10 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 21 Nov 2024 19:22:39 +1100 Subject: [PATCH 074/107] Prevent Cygwin bash from being used by codecov-action --- .github/workflows/test-cygwin.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test-cygwin.yml b/.github/workflows/test-cygwin.yml index 2076cad7a9a..5b0a0394688 100644 --- a/.github/workflows/test-cygwin.yml +++ b/.github/workflows/test-cygwin.yml @@ -133,6 +133,7 @@ jobs: - name: After success run: | bash.exe .ci/after_success.sh + rm C:\cygwin\bin\bash.EXE - name: Upload coverage uses: codecov/codecov-action@v5 From 69c9a7ffcfd42b0486f02f17119c6d05f1d04969 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 20 Nov 2024 20:03:15 +1100 Subject: [PATCH 075/107] Use zlib-ng on Linux --- .github/workflows/wheels-dependencies.sh | 20 +++++++++++++++++--- Tests/check_wheel.py | 2 +- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index f86099af16e..5d23fc036ee 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -50,10 +50,10 @@ if [[ -n "$IS_MACOS" ]]; then else GIFLIB_VERSION=5.2.1 fi -if [[ -n "$IS_MACOS" ]] || [[ "$MB_ML_VER" != 2014 ]]; then +if [[ -n "$IS_MACOS" ]]; then ZLIB_VERSION=1.3.1 else - ZLIB_VERSION=1.2.8 + ZLIB_NG_VERSION=2.2.2 fi LIBWEBP_VERSION=1.4.0 BZIP2_VERSION=1.0.8 @@ -74,6 +74,16 @@ function build_pkg_config { touch pkg-config-stamp } +function build_zlib_ng { + if [ -e zlib-stamp ]; then return; fi + fetch_unpack https://github.com/zlib-ng/zlib-ng/archive/$ZLIB_NG_VERSION.tar.gz zlib-ng-$ZLIB_NG_VERSION.tar.gz + (cd zlib-ng-$ZLIB_NG_VERSION \ + && ./configure --prefix=$BUILD_PREFIX --zlib-compat \ + && make -j4 \ + && make install) + touch zlib-stamp +} + function build_brotli { if [ -e brotli-stamp ]; then return; fi local cmake=$(get_modern_cmake) @@ -101,7 +111,11 @@ function build { if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then yum remove -y zlib-devel fi - build_new_zlib + if [ -n "$IS_MACOS" ]; then + build_new_zlib + else + build_zlib_ng + fi build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto if [ -n "$IS_MACOS" ]; then diff --git a/Tests/check_wheel.py b/Tests/check_wheel.py index 374e48e8ad3..787d4f7431a 100644 --- a/Tests/check_wheel.py +++ b/Tests/check_wheel.py @@ -40,7 +40,7 @@ def test_wheel_features() -> None: if sys.platform == "win32": expected_features.remove("xcb") - else: + elif sys.platform == "darwin": expected_features.remove("zlib_ng") assert set(features.get_supported_features()) == expected_features From 9c3d8fb581cab18abb60117f08358b51b18a44b0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 22 Nov 2024 11:02:50 +1100 Subject: [PATCH 076/107] Do not install Pyroma, to use only system packages --- .github/workflows/test-mingw.yml | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index bfd393db5a2..fd8faba71bf 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -68,12 +68,13 @@ jobs: mingw-w64-x86_64-openjpeg2 \ mingw-w64-x86_64-python3-numpy \ mingw-w64-x86_64-python3-olefile \ + mingw-w64-x86_64-python3-pip \ mingw-w64-x86_64-python3-setuptools \ + mingw-w64-x86_64-python-pytest \ + mingw-w64-x86_64-python-pytest-cov \ + mingw-w64-x86_64-python-pytest-timeout \ mingw-w64-x86_64-python-pyqt6 - python3 -m ensurepip - python3 -m pip install pyroma pytest pytest-cov pytest-timeout - pushd depends && ./install_extra_test_images.sh && popd - name: Build Pillow From 30f03b2bff689af1f395c183cf991bc34f8b0bab Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 22 Nov 2024 11:05:33 +1100 Subject: [PATCH 077/107] Removed SETUPTOOLS_USE_DISTUTILS="stdlib" --- .github/workflows/test-mingw.yml | 3 +-- docs/installation/building-from-source.rst | 5 ----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/.github/workflows/test-mingw.yml b/.github/workflows/test-mingw.yml index fd8faba71bf..93fe8a06465 100644 --- a/.github/workflows/test-mingw.yml +++ b/.github/workflows/test-mingw.yml @@ -69,7 +69,6 @@ jobs: mingw-w64-x86_64-python3-numpy \ mingw-w64-x86_64-python3-olefile \ mingw-w64-x86_64-python3-pip \ - mingw-w64-x86_64-python3-setuptools \ mingw-w64-x86_64-python-pytest \ mingw-w64-x86_64-python-pytest-cov \ mingw-w64-x86_64-python-pytest-timeout \ @@ -78,7 +77,7 @@ jobs: pushd depends && ./install_extra_test_images.sh && popd - name: Build Pillow - run: SETUPTOOLS_USE_DISTUTILS="stdlib" CFLAGS="-coverage" python3 -m pip install . + run: CFLAGS="-coverage" python3 -m pip install . - name: Test Pillow run: | diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 4b517582786..19294f72c1c 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -195,11 +195,6 @@ Many of Pillow's features require external libraries: mingw-w64-x86_64-libimagequant \ mingw-w64-x86_64-libraqm - https://www.msys2.org/docs/python/ states that setuptools >= 60 does not work with - MSYS2. To workaround this, before installing Pillow you must run:: - - export SETUPTOOLS_USE_DISTUTILS=stdlib - .. tab:: FreeBSD .. Note:: Only FreeBSD 10 and 11 tested From 16e409a8d28fecbf83cd67409224e65ebb371a93 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 24 Nov 2024 13:46:28 +0200 Subject: [PATCH 078/107] Test 3.13t (free-threaded) from Quansight-Labs/setup-python on Linux/macOS --- .github/workflows/test.yml | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1405c64f9aa..83a696f5f8b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -42,6 +42,7 @@ jobs: ] python-version: [ "pypy3.10", + "3.13t", "3.13", "3.12", "3.11", @@ -52,14 +53,14 @@ jobs: - { python-version: "3.11", PYTHONOPTIMIZE: 1, REVERSE: "--reverse" } - { python-version: "3.10", PYTHONOPTIMIZE: 2 } # Free-threaded - - { os: "ubuntu-latest", python-version: "3.13-dev", disable-gil: true } + - { python-version: "3.13t", disable-gil: true } # M1 only available for 3.10+ - { os: "macos-13", python-version: "3.9" } exclude: - { os: "macos-latest", python-version: "3.9" } runs-on: ${{ matrix.os }} - name: ${{ matrix.os }} Python ${{ matrix.python-version }} ${{ matrix.disable-gil && 'free-threaded' || '' }} + name: ${{ matrix.os }} Python ${{ matrix.python-version }} steps: - uses: actions/checkout@v4 @@ -67,8 +68,7 @@ jobs: persist-credentials: false - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - if: "${{ !matrix.disable-gil }}" + uses: Quansight-Labs/setup-python@v5 with: python-version: ${{ matrix.python-version }} allow-prereleases: true @@ -77,13 +77,6 @@ jobs: ".ci/*.sh" "pyproject.toml" - - name: Set up Python ${{ matrix.python-version }} (free-threaded) - uses: deadsnakes/action@v3.2.0 - if: "${{ matrix.disable-gil }}" - with: - python-version: ${{ matrix.python-version }} - nogil: ${{ matrix.disable-gil }} - - name: Set PYTHON_GIL if: "${{ matrix.disable-gil }}" run: | From 540a0b73a7ebb3ec5571e3ea027c73cbe95b2fc6 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 23 Nov 2024 00:20:40 +1100 Subject: [PATCH 079/107] Use installed cmake --- .github/workflows/wheels-dependencies.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index f86099af16e..ddda43423cd 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -76,10 +76,9 @@ function build_pkg_config { function build_brotli { if [ -e brotli-stamp ]; then return; fi - local cmake=$(get_modern_cmake) local out_dir=$(fetch_unpack https://github.com/google/brotli/archive/v$BROTLI_VERSION.tar.gz brotli-$BROTLI_VERSION.tar.gz) (cd $out_dir \ - && $cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \ + && cmake -DCMAKE_INSTALL_PREFIX=$BUILD_PREFIX -DCMAKE_INSTALL_LIBDIR=$BUILD_PREFIX/lib -DCMAKE_INSTALL_NAME_DIR=$BUILD_PREFIX/lib . \ && make install) touch brotli-stamp } From 5174c596ca6c78725efdb2cda8ace39f11d8e215 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Tue, 26 Nov 2024 22:06:47 +0200 Subject: [PATCH 080/107] Release drafter: move removals, deprecations, documentation up, and uncategorised changes last --- .github/release-drafter.yml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index 3711d91f0d5..de0ab480519 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -3,18 +3,19 @@ tag-template: "$NEXT_MINOR_VERSION" change-template: '- $TITLE #$NUMBER [@$AUTHOR]' categories: - - title: "Dependencies" - label: "Dependency" + - title: "Removals" + label: "Removal" - title: "Deprecations" label: "Deprecation" - title: "Documentation" label: "Documentation" - - title: "Removals" - label: "Removal" + - title: "Dependencies" + label: "Dependency" - title: "Testing" label: "Testing" - title: "Type hints" label: "Type hints" + - title: "Other changes" exclude-labels: - "changelog: skip" @@ -23,6 +24,4 @@ template: | https://pillow.readthedocs.io/en/stable/releasenotes/$NEXT_MINOR_VERSION.html - ## Changes - $CHANGES From 02250e81b729ddb32952f96e57bf9d952ed70cf8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 28 Nov 2024 07:58:10 +1100 Subject: [PATCH 081/107] Added link to GitHub releases --- CHANGES.rst | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/CHANGES.rst b/CHANGES.rst index f6ed5eb82fb..dfbbd24b331 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -2,26 +2,12 @@ Changelog (Pillow) ================== -11.1.0 (unreleased) -------------------- - -- Removed use of os.path.realpath #8545 - [radarhere] - -- Allow linking to zlib import library on Windows #8519 - [cubanpit, nulano] - -- Detach PyQt6 QPixmap instance before returning #8509 - [radarhere] +11.1.0 and newer +---------------- -- Corrected EMF DPI #8485 - [radarhere] +See GitHub Releases: -- Fix IFDRational with a zero denominator #8474 - [radarhere] - -- Fixed disabling a feature during install #8469 - [radarhere] +- https://github.com/python-pillow/Pillow/releases 11.0.0 (2024-10-15) ------------------- From c36a5b669b225fdcb6b4b3a41fc5b5e647a651c1 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 28 Nov 2024 10:29:05 +0200 Subject: [PATCH 082/107] Update/remove references to CHANGES.rst --- .github/CONTRIBUTING.md | 1 - README.md | 2 +- RELEASING.md | 2 -- codecov.yml | 2 +- pyproject.toml | 2 +- 5 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index d03fcf0d9da..ba2b7d8ed26 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -19,7 +19,6 @@ Please send a pull request to the `main` branch. Please include [documentation]( - Follow PEP 8. - When committing only documentation changes please include `[ci skip]` in the commit message to avoid running tests on AppVeyor. - Include [release notes](https://github.com/python-pillow/Pillow/tree/main/docs/releasenotes) as needed or appropriate with your bug fixes, feature additions and tests. -- Do not add to the [changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) for proposed changes, as that is updated after changes are merged. ## Reporting Issues diff --git a/README.md b/README.md index 5bbebaccb4a..057d0acf0ce 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ The core image library is designed for fast access to data stored in a few basic - [Issues](https://github.com/python-pillow/Pillow/issues) - [Pull requests](https://github.com/python-pillow/Pillow/pulls) - [Release notes](https://pillow.readthedocs.io/en/stable/releasenotes/index.html) -- [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) +- [Changelog](https://github.com/python-pillow/Pillow/releases) - [Pre-fork](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst#pre-fork) ## Report a Vulnerability diff --git a/RELEASING.md b/RELEASING.md index 9e6ec5dd4c1..ebdbb6406e8 100644 --- a/RELEASING.md +++ b/RELEASING.md @@ -12,7 +12,6 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th. * [ ] Check [GitHub Actions](https://github.com/python-pillow/Pillow/actions) and [AppVeyor](https://ci.appveyor.com/project/python-pillow/Pillow) to confirm passing tests in `main` branch. * [ ] Check that all the wheel builds pass the tests in the [GitHub Actions "Wheels" workflow](https://github.com/python-pillow/Pillow/actions/workflows/wheels.yml) jobs by manually triggering them. * [ ] In compliance with [PEP 440](https://peps.python.org/pep-0440/), update version identifier in `src/PIL/_version.py` -* [ ] Update `CHANGES.rst`. * [ ] Run pre-release check via `make release-test` in a freshly cloned repo. * [ ] Create branch and tag for release e.g.: ```bash @@ -34,7 +33,6 @@ Released quarterly on January 2nd, April 1st, July 1st and October 15th. Released as needed for security, installation or critical bug fixes. * [ ] Make necessary changes in `main` branch. -* [ ] Update `CHANGES.rst`. * [ ] Check out release branch e.g.: ```bash git checkout -t remotes/origin/5.2.x diff --git a/codecov.yml b/codecov.yml index 8646576bb44..84920238ffc 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,7 +1,7 @@ # Documentation: https://docs.codecov.com/docs/codecov-yaml codecov: - # Avoid "Missing base report" due to committing CHANGES.rst with "[CI skip]" + # Avoid "Missing base report" due to committing with "[CI skip]" # https://github.com/codecov/support/issues/363 # https://docs.codecov.com/docs/comparing-commits allow_coverage_offsets: true diff --git a/pyproject.toml b/pyproject.toml index c6d95f2abe3..1c7b2c4b28e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -73,7 +73,7 @@ optional-dependencies.typing = [ optional-dependencies.xmp = [ "defusedxml", ] -urls.Changelog = "https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst" +urls.Changelog = "https://github.com/python-pillow/Pillow/releases" urls.Documentation = "https://pillow.readthedocs.io" urls.Funding = "https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi" urls.Homepage = "https://python-pillow.org" From 4986609938c6fd38af6efd8501fe157b0443e5bc Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 22 Nov 2024 23:44:32 +1100 Subject: [PATCH 083/107] Use zlib-ng on macOS --- .github/workflows/wheels-dependencies.sh | 12 ++---------- Tests/check_wheel.py | 2 -- pyproject.toml | 1 + 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 5d23fc036ee..a2aec07c32d 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -50,11 +50,7 @@ if [[ -n "$IS_MACOS" ]]; then else GIFLIB_VERSION=5.2.1 fi -if [[ -n "$IS_MACOS" ]]; then - ZLIB_VERSION=1.3.1 -else - ZLIB_NG_VERSION=2.2.2 -fi +ZLIB_NG_VERSION=2.2.2 LIBWEBP_VERSION=1.4.0 BZIP2_VERSION=1.0.8 LIBXCB_VERSION=1.17.0 @@ -111,11 +107,7 @@ function build { if [ -z "$IS_ALPINE" ] && [ -z "$IS_MACOS" ]; then yum remove -y zlib-devel fi - if [ -n "$IS_MACOS" ]; then - build_new_zlib - else - build_zlib_ng - fi + build_zlib_ng build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto if [ -n "$IS_MACOS" ]; then diff --git a/Tests/check_wheel.py b/Tests/check_wheel.py index 787d4f7431a..563be0b7489 100644 --- a/Tests/check_wheel.py +++ b/Tests/check_wheel.py @@ -40,7 +40,5 @@ def test_wheel_features() -> None: if sys.platform == "win32": expected_features.remove("xcb") - elif sys.platform == "darwin": - expected_features.remove("zlib_ng") assert set(features.get_supported_features()) == expected_features diff --git a/pyproject.toml b/pyproject.toml index c6d95f2abe3..af09ccbc1f2 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -104,6 +104,7 @@ test-extras = "tests" [tool.cibuildwheel.macos.environment] PATH = "$(pwd)/build/deps/darwin/bin:$(dirname $(which python3)):/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin" +DYLD_LIBRARY_PATH = "$(pwd)/build/deps/darwin/lib" [tool.black] exclude = "wheels/multibuild" From 10527918e3570f1e6772ac90db42d2288cce7ae4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 29 Nov 2024 17:13:07 +1100 Subject: [PATCH 084/107] Use brew formula to install libraqm --- docs/installation/building-from-source.rst | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 19294f72c1c..ff60120aedc 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -148,13 +148,7 @@ Many of Pillow's features require external libraries: The easiest way to install external libraries is via `Homebrew `_. After you install Homebrew, run:: - brew install libjpeg libtiff little-cms2 openjpeg webp - - To install libraqm on macOS use Homebrew to install its dependencies:: - - brew install freetype harfbuzz fribidi - - Then see ``depends/install_raqm_cmake.sh`` to install libraqm. + brew install libjpeg libraqm libtiff little-cms2 openjpeg webp .. tab:: Windows From 1fab591f42fa5a9502fade7873481b567c033849 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 30 Nov 2024 21:17:31 +1100 Subject: [PATCH 085/107] Python 3.12 is tested on MinGW --- docs/installation/platform-support.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/platform-support.rst b/docs/installation/platform-support.rst index b9d633142e7..35f863374d1 100644 --- a/docs/installation/platform-support.rst +++ b/docs/installation/platform-support.rst @@ -55,7 +55,7 @@ These platforms are built and tested for every change. | +----------------------------+---------------------+ | | 3.13 | x86 | | +----------------------------+---------------------+ -| | 3.9 (MinGW) | x86-64 | +| | 3.12 (MinGW) | x86-64 | | +----------------------------+---------------------+ | | 3.9 (Cygwin) | x86-64 | +----------------------------------+----------------------------+---------------------+ From 1d15ce8c494075f521600c21b9efb0d5eaa9aa03 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 30 Nov 2024 23:17:42 +1100 Subject: [PATCH 086/107] Updated build option error messages --- setup.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/setup.py b/setup.py index fbd23a5685a..eea21fa3407 100644 --- a/setup.py +++ b/setup.py @@ -393,13 +393,14 @@ def finalize_options(self) -> None: self.feature.required.discard(x) _dbg("Disabling %s", x) if getattr(self, f"enable_{x}"): - msg = f"Conflicting options: --enable-{x} and --disable-{x}" + msg = f"Conflicting options: '-C {x}=enable' and '-C {x}=disable'" raise ValueError(msg) if x == "freetype": - _dbg("--disable-freetype implies --disable-raqm") + _dbg("'-C freetype=disable' implies '-C raqm=disable'") if getattr(self, "enable_raqm"): msg = ( - "Conflicting options: --enable-raqm and --disable-freetype" + "Conflicting options: " + "'-C raqm=enable' and '-C freetype=disable'" ) raise ValueError(msg) setattr(self, "disable_raqm", True) @@ -407,15 +408,17 @@ def finalize_options(self) -> None: _dbg("Requiring %s", x) self.feature.required.add(x) if x == "raqm": - _dbg("--enable-raqm implies --enable-freetype") + _dbg("'-C raqm=enable' implies '-C freetype=enable'") self.feature.required.add("freetype") for x in ("raqm", "fribidi"): if getattr(self, f"vendor_{x}"): if getattr(self, "disable_raqm"): - msg = f"Conflicting options: --vendor-{x} and --disable-raqm" + msg = f"Conflicting options: '-C {x}=vendor' and '-C raqm=disable'" raise ValueError(msg) if x == "fribidi" and not getattr(self, "vendor_raqm"): - msg = f"Conflicting options: --vendor-{x} and not --vendor-raqm" + msg = ( + f"Conflicting options: '-C {x}=vendor' and not '-C raqm=vendor'" + ) raise ValueError(msg) _dbg("Using vendored version of %s", x) self.feature.vendor.add(x) @@ -1047,7 +1050,7 @@ def debug_build() -> bool: msg = f""" The headers or library files could not be found for {str(err)}, -which was requested by the option flag --enable-{str(err)} +which was requested by the option flag '-C {str(err)}=enable' """ sys.stderr.write(msg) From 513fe2ca2d23c7f1685b089fc6087ab02b2828df Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 1 Dec 2024 23:15:54 +0200 Subject: [PATCH 087/107] Only use start year in copyright, remove end years --- LICENSE | 2 +- docs/COPYING | 2 +- docs/conf.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/LICENSE b/LICENSE index 8837c290c30..10dd42d9eda 100644 --- a/LICENSE +++ b/LICENSE @@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is Pillow is the friendly PIL fork. It is - Copyright © 2010-2024 by Jeffrey A. Clark and contributors + Copyright © 2010 by Jeffrey A. Clark and contributors Like PIL, Pillow is licensed under the open source MIT-CMU License: diff --git a/docs/COPYING b/docs/COPYING index d5ee19f81a6..17fba5b87ff 100644 --- a/docs/COPYING +++ b/docs/COPYING @@ -5,7 +5,7 @@ The Python Imaging Library (PIL) is Pillow is the friendly PIL fork. It is - Copyright © 2010-2024 by Jeffrey A. Clark and contributors + Copyright © 2010 by Jeffrey A. Clark and contributors Like PIL, Pillow is licensed under the open source PIL Software License: diff --git a/docs/conf.py b/docs/conf.py index b81d86c6ca2..e1e3f1b8f8a 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -55,7 +55,7 @@ project = "Pillow (PIL Fork)" copyright = ( "1995-2011 Fredrik Lundh and contributors, " - "2010-2024 Jeffrey A. Clark and contributors." + "2010 Jeffrey A. Clark and contributors." ) author = "Fredrik Lundh (PIL), Jeffrey A. Clark (Pillow)" From 0000729f2a79da62ba64332484103ffacf1700a1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 2 Dec 2024 17:34:54 +0000 Subject: [PATCH 088/107] [pre-commit.ci] pre-commit autoupdate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.7.2 → v0.8.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.7.2...v0.8.1) - [github.com/PyCQA/bandit: 1.7.10 → 1.8.0](https://github.com/PyCQA/bandit/compare/1.7.10...1.8.0) - [github.com/pre-commit/mirrors-clang-format: v19.1.3 → v19.1.4](https://github.com/pre-commit/mirrors-clang-format/compare/v19.1.3...v19.1.4) - [github.com/python-jsonschema/check-jsonschema: 0.29.4 → 0.30.0](https://github.com/python-jsonschema/check-jsonschema/compare/0.29.4...0.30.0) - [github.com/abravalheri/validate-pyproject: v0.22 → v0.23](https://github.com/abravalheri/validate-pyproject/compare/v0.22...v0.23) --- .pre-commit-config.yaml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ddc98fdc356..f91260c724f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ repos: - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.7.2 + rev: v0.8.1 hooks: - id: ruff args: [--exit-non-zero-on-fix] @@ -11,7 +11,7 @@ repos: - id: black - repo: https://github.com/PyCQA/bandit - rev: 1.7.10 + rev: 1.8.0 hooks: - id: bandit args: [--severity-level=high] @@ -24,7 +24,7 @@ repos: exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v19.1.3 + rev: v19.1.4 hooks: - id: clang-format types: [c] @@ -50,7 +50,7 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.29.4 + rev: 0.30.0 hooks: - id: check-github-workflows - id: check-readthedocs @@ -67,7 +67,7 @@ repos: - id: pyproject-fmt - repo: https://github.com/abravalheri/validate-pyproject - rev: v0.22 + rev: v0.23 hooks: - id: validate-pyproject additional_dependencies: [trove-classifiers>=2024.10.12] From 0ab21dff1ed0b16944a5458ab73dfa3a5c060b88 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Mon, 2 Dec 2024 20:29:38 +0200 Subject: [PATCH 089/107] Fix new Ruff errors --- src/PIL/ImImagePlugin.py | 2 +- src/PIL/Image.py | 21 ++++++--------------- src/PIL/JpegImagePlugin.py | 2 +- src/PIL/PcxImagePlugin.py | 2 +- src/PIL/PngImagePlugin.py | 2 +- src/PIL/TiffImagePlugin.py | 13 +++++++------ src/PIL/_typing.py | 2 +- 7 files changed, 18 insertions(+), 26 deletions(-) diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index f9f47348c66..b4215a0b1e5 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -357,7 +357,7 @@ def _save(im: Image.Image, fp: IO[bytes], filename: str | bytes) -> None: name = "".join([name[: 92 - len(ext)], ext]) fp.write(f"Name: {name}\r\n".encode("ascii")) - fp.write(("Image size (x*y): %d*%d\r\n" % im.size).encode("ascii")) + fp.write(f"Image size (x*y): {im.size[0]}*{im.size[1]}\r\n".encode("ascii")) fp.write(f"File size (no of images): {frames}\r\n".encode("ascii")) if im.mode in ["P", "PA"]: fp.write(b"Lut: 1\r\n") diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 9b76ce8bd86..1e289b6c38b 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -692,13 +692,10 @@ def __eq__(self, other: object) -> bool: ) def __repr__(self) -> str: - return "<%s.%s image mode=%s size=%dx%d at 0x%X>" % ( - self.__class__.__module__, - self.__class__.__name__, - self.mode, - self.size[0], - self.size[1], - id(self), + return ( + f"<{self.__class__.__module__}.{self.__class__.__name__} " + f"image mode={self.mode} size={self.size[0]}x{self.size[1]} " + f"at 0x{id(self):X}>" ) def _repr_pretty_(self, p: PrettyPrinter, cycle: bool) -> None: @@ -707,14 +704,8 @@ def _repr_pretty_(self, p: PrettyPrinter, cycle: bool) -> None: # Same as __repr__ but without unpredictable id(self), # to keep Jupyter notebook `text/plain` output stable. p.text( - "<%s.%s image mode=%s size=%dx%d>" - % ( - self.__class__.__module__, - self.__class__.__name__, - self.mode, - self.size[0], - self.size[1], - ) + f"<{self.__class__.__module__}.{self.__class__.__name__} " + f"image mode={self.mode} size={self.size[0]}x{self.size[1]}>" ) def _repr_image(self, image_format: str, **kwargs: Any) -> bytes | None: diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 6510e072e5e..ab6a2f497fb 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -72,7 +72,7 @@ def APP(self: JpegImageFile, marker: int) -> None: n = i16(self.fp.read(2)) - 2 s = ImageFile._safe_read(self.fp, n) - app = "APP%d" % (marker & 15) + app = f"APP{marker & 15}" self.app[app] = s # compatibility self.applist.append((app, s)) diff --git a/src/PIL/PcxImagePlugin.py b/src/PIL/PcxImagePlugin.py index 8445d5cc740..32436cea3af 100644 --- a/src/PIL/PcxImagePlugin.py +++ b/src/PIL/PcxImagePlugin.py @@ -86,7 +86,7 @@ def _open(self) -> None: elif bits == 1 and planes in (2, 4): mode = "P" - rawmode = "P;%dL" % planes + rawmode = f"P;{planes}L" self.palette = ImagePalette.raw("RGB", s[16:64]) elif version == 5 and bits == 8 and planes == 1: diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 4e12272041d..4b97992a31f 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -523,7 +523,7 @@ def chunk_cHRM(self, pos: int, length: int) -> bytes: assert self.fp is not None s = ImageFile._safe_read(self.fp, length) - raw_vals = struct.unpack(">%dI" % (len(s) // 4), s) + raw_vals = struct.unpack(f">{len(s) // 4}I", s) self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals) return s diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 08bb7891512..9ee55347aaf 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -935,9 +935,9 @@ def load(self, fp: IO[bytes]) -> None: self._tagdata[tag] = data self.tagtype[tag] = typ - msg += " - value: " + ( - "" % size if size > 32 else repr(data) - ) + bytes_value = size if size > 32 else repr(data) + msg += f" - value: " + logger.debug(msg) (self.next,) = ( @@ -981,9 +981,10 @@ def tobytes(self, offset: int = 0) -> bytes: tagname = TiffTags.lookup(tag, self.group).name typname = "ifd" if is_ifd else TYPES.get(typ, "unknown") - msg = f"save: {tagname} ({tag}) - type: {typname} ({typ})" - msg += " - value: " + ( - "" % len(data) if len(data) >= 16 else str(values) + bytes_value = len(data) if len(data) >= 16 else str(values) + msg = ( + f"save: {tagname} ({tag}) - type: {typname} ({typ})" + f" - value: " ) logger.debug(msg) diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py index 335804b792f..34a9a81e11f 100644 --- a/src/PIL/_typing.py +++ b/src/PIL/_typing.py @@ -44,7 +44,7 @@ def __class_getitem__(cls, item: Any) -> type[bool]: class SupportsRead(Protocol[_T_co]): - def read(self, __length: int = ...) -> _T_co: ... + def read(self, length: int = ..., /) -> _T_co: ... StrOrBytesPath = Union[str, bytes, os.PathLike[str], os.PathLike[bytes]] From c16ae6fefa6cb939d274c95d1969889dced9dd15 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 3 Dec 2024 09:19:26 +1100 Subject: [PATCH 090/107] Do not describe raw data as a table --- src/PIL/TiffImagePlugin.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 9ee55347aaf..be4a57b7445 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -935,8 +935,8 @@ def load(self, fp: IO[bytes]) -> None: self._tagdata[tag] = data self.tagtype[tag] = typ - bytes_value = size if size > 32 else repr(data) - msg += f" - value: " + msg += " - value: " + msg += f"" if size > 32 else repr(data) logger.debug(msg) @@ -981,11 +981,8 @@ def tobytes(self, offset: int = 0) -> bytes: tagname = TiffTags.lookup(tag, self.group).name typname = "ifd" if is_ifd else TYPES.get(typ, "unknown") - bytes_value = len(data) if len(data) >= 16 else str(values) - msg = ( - f"save: {tagname} ({tag}) - type: {typname} ({typ})" - f" - value: " - ) + msg = f"save: {tagname} ({tag}) - type: {typname} ({typ}) - value: " + msg += f"" if len(data) >= 16 else str(values) logger.debug(msg) # count is sum of lengths for string and arbitrary data From 80c562573a4b5723b1094fb9b07a582912e3d62b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 02:28:52 +0000 Subject: [PATCH 091/107] Update dependency cibuildwheel to v2.22.0 --- .ci/requirements-cibw.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.ci/requirements-cibw.txt b/.ci/requirements-cibw.txt index c4511439ccb..833aca23d4a 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==2.21.3 +cibuildwheel==2.22.0 From f4a9304bb358f45a088d3c237ada88703e0e0d8e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 3 Dec 2024 13:35:00 +1100 Subject: [PATCH 092/107] Use CIBW_ENABLE --- .github/workflows/wheels.yml | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 45f18634100..c5e55aa621d 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -85,7 +85,7 @@ jobs: CIBW_ARCHS: "aarch64" # Likewise, select only one Python version per job to speed this up. CIBW_BUILD: "${{ matrix.python-version }}-${{ matrix.spec == 'musllinux' && 'musllinux' || 'manylinux' }}*" - CIBW_PRERELEASE_PYTHONS: True + CIBW_ENABLE: cpython-prerelease # Extra options for manylinux. CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.spec }} CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.spec }} @@ -150,10 +150,9 @@ jobs: env: CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_BUILD: ${{ matrix.build }} - CIBW_FREE_THREADED_SUPPORT: True + CIBW_ENABLE: cpython-prerelease cpython-freethreading CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} - CIBW_PRERELEASE_PYTHONS: True CIBW_SKIP: pp39-* MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} @@ -228,8 +227,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" CIBW_CACHE_PATH: "C:\\cibw" - CIBW_FREE_THREADED_SUPPORT: True - CIBW_PRERELEASE_PYTHONS: True + CIBW_ENABLE: cpython-prerelease cpython-freethreading CIBW_SKIP: pp39-* CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_COMMAND: 'docker run --rm From 3ce33a253a1bd76fdde6eb557cf271b3b2f01553 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 3 Dec 2024 20:14:03 +1100 Subject: [PATCH 093/107] self.tile is not None --- src/PIL/ImageFile.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 7f27d54dcc3..9836cd68884 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -120,7 +120,7 @@ def __init__( self.custom_mimetype: str | None = None self.tile: list[_Tile] = [] - """ A list of tile descriptors, or ``None`` """ + """ A list of tile descriptors """ self.readonly = 1 # until we know better From c5474ed43363e24602c92e5ca7b37899ce37b9a5 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Fri, 6 Dec 2024 20:26:07 +0200 Subject: [PATCH 094/107] Replace python-pillow.org with python-pillow.github.io --- docs/handbook/tutorial.rst | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/handbook/tutorial.rst b/docs/handbook/tutorial.rst index 3df8e0d20be..f771ae7aea5 100644 --- a/docs/handbook/tutorial.rst +++ b/docs/handbook/tutorial.rst @@ -678,7 +678,7 @@ Reading from URL from PIL import Image from urllib.request import urlopen - url = "https://python-pillow.org/assets/images/pillow-logo.png" + url = "https://python-pillow.github.io/assets/images/pillow-logo.png" img = Image.open(urlopen(url)) diff --git a/pyproject.toml b/pyproject.toml index 1c7b2c4b28e..aaaba0032e7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -76,7 +76,7 @@ optional-dependencies.xmp = [ urls.Changelog = "https://github.com/python-pillow/Pillow/releases" urls.Documentation = "https://pillow.readthedocs.io" urls.Funding = "https://tidelift.com/subscription/pkg/pypi-pillow?utm_source=pypi-pillow&utm_medium=pypi" -urls.Homepage = "https://python-pillow.org" +urls.Homepage = "https://python-pillow.github.io" urls.Mastodon = "https://fosstodon.org/@pillow" urls."Release notes" = "https://pillow.readthedocs.io/en/stable/releasenotes/index.html" urls.Source = "https://github.com/python-pillow/Pillow" From 2215eaf21d8e9096fdd13c34225c67c0893d82db Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 10 Dec 2024 06:25:48 +1100 Subject: [PATCH 095/107] Updated openjpeg to 2.5.3 --- .github/workflows/wheels-dependencies.sh | 2 +- depends/install_openjpeg.sh | 2 +- docs/installation/building-from-source.rst | 2 +- winbuild/build_prepare.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index ddda43423cd..e88a010ef5c 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -41,7 +41,7 @@ FREETYPE_VERSION=2.13.2 HARFBUZZ_VERSION=10.0.1 LIBPNG_VERSION=1.6.44 JPEGTURBO_VERSION=3.0.4 -OPENJPEG_VERSION=2.5.2 +OPENJPEG_VERSION=2.5.3 XZ_VERSION=5.6.3 TIFF_VERSION=4.6.0 LCMS2_VERSION=2.16 diff --git a/depends/install_openjpeg.sh b/depends/install_openjpeg.sh index 8c2967bc21b..1f8d781931b 100755 --- a/depends/install_openjpeg.sh +++ b/depends/install_openjpeg.sh @@ -1,7 +1,7 @@ #!/bin/bash # install openjpeg -archive=openjpeg-2.5.2 +archive=openjpeg-2.5.3 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index ff60120aedc..03359de31cd 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -58,7 +58,7 @@ Many of Pillow's features require external libraries: * **openjpeg** provides JPEG 2000 functionality. * Pillow has been tested with openjpeg **2.0.0**, **2.1.0**, **2.3.1**, - **2.4.0**, **2.5.0** and **2.5.2**. + **2.4.0**, **2.5.0**, **2.5.2** and **2.5.3**. * Pillow does **not** support the earlier **1.5** series which ships with Debian Jessie. diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index c8332d11c7e..dc802a3e52f 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -117,7 +117,7 @@ def cmd_msbuild( "LCMS2": "2.16", "LIBPNG": "1.6.44", "LIBWEBP": "1.4.0", - "OPENJPEG": "2.5.2", + "OPENJPEG": "2.5.3", "TIFF": "4.6.0", "XZ": "5.6.3", "ZLIB": "1.3.1", From 9ed96704d3788fa58d9682d293c4190e5da07c2d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 13 Dec 2024 07:28:48 +1100 Subject: [PATCH 096/107] Do not attempt to install cmake if it is already present --- wheels/multibuild | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wheels/multibuild b/wheels/multibuild index 9a9d1275f02..74a9795bc64 160000 --- a/wheels/multibuild +++ b/wheels/multibuild @@ -1 +1 @@ -Subproject commit 9a9d1275f025f737cdaa3c451ba07129dd95f361 +Subproject commit 74a9795bc64ff786b7e7d33bdec2843cf17e512e From e361930936cd1208beeb292654a3af7548d863b3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 13 Dec 2024 18:56:59 +1100 Subject: [PATCH 097/107] Updated libjpeg-turbo to 3.1.0 --- .github/workflows/wheels-dependencies.sh | 2 +- winbuild/build_prepare.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 5f8a4cf78e4..86f51a586dc 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -40,7 +40,7 @@ ARCHIVE_SDIR=pillow-depends-main FREETYPE_VERSION=2.13.2 HARFBUZZ_VERSION=10.1.0 LIBPNG_VERSION=1.6.44 -JPEGTURBO_VERSION=3.0.4 +JPEGTURBO_VERSION=3.1.0 OPENJPEG_VERSION=2.5.3 XZ_VERSION=5.6.3 TIFF_VERSION=4.6.0 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 1efe0dcac8b..fd104eb23f0 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -113,7 +113,7 @@ def cmd_msbuild( "FREETYPE": "2.13.3", "FRIBIDI": "1.0.16", "HARFBUZZ": "10.1.0", - "JPEGTURBO": "3.0.4", + "JPEGTURBO": "3.1.0", "LCMS2": "2.16", "LIBPNG": "1.6.44", "LIBWEBP": "1.4.0", @@ -155,7 +155,7 @@ def cmd_msbuild( cmd_copy("cjpeg-static.exe", "cjpeg.exe"), cmd_copy("djpeg-static.exe", "djpeg.exe"), ], - "headers": ["j*.h"], + "headers": ["jconfig.h", r"src\j*.h"], "libs": ["libjpeg.lib"], "bins": ["cjpeg.exe", "djpeg.exe"], }, From 642b44c3159077d0863c29181c8c6fbe60039e5b Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 14 Dec 2024 01:25:28 +1100 Subject: [PATCH 098/107] Test libjpeg-turbo on macOS --- .github/workflows/macos-install.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/macos-install.sh b/.github/workflows/macos-install.sh index ddb4212301d..2301a3a7ef3 100755 --- a/.github/workflows/macos-install.sh +++ b/.github/workflows/macos-install.sh @@ -8,8 +8,8 @@ fi brew install \ freetype \ ghostscript \ + jpeg-turbo \ libimagequant \ - libjpeg \ libtiff \ little-cms2 \ openjpeg \ From 0e5f5fcb297b1e08ec83773562663b8ad9e53aee Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 14 Dec 2024 16:33:20 +1100 Subject: [PATCH 099/107] Updated libXau to 1.0.12 --- .github/workflows/wheels-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 86f51a586dc..36036ff2685 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -105,7 +105,7 @@ function build { build_simple xcb-proto 1.17.0 https://xorg.freedesktop.org/archive/individual/proto if [ -n "$IS_MACOS" ]; then build_simple xorgproto 2024.1 https://www.x.org/pub/individual/proto - build_simple libXau 1.0.11 https://www.x.org/pub/individual/lib + build_simple libXau 1.0.12 https://www.x.org/pub/individual/lib build_simple libpthread-stubs 0.5 https://xcb.freedesktop.org/dist else sed s/\${pc_sysrootdir\}// $BUILD_PREFIX/share/pkgconfig/xcb-proto.pc > $BUILD_PREFIX/lib/pkgconfig/xcb-proto.pc From 62d8ccc015d68a9bb3da555f9800786652de4962 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 15 Dec 2024 12:11:41 +1100 Subject: [PATCH 100/107] Derive dir from filename if root is the same --- winbuild/build_prepare.py | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 5bdb5fafedb..2d804d55778 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -131,7 +131,6 @@ def cmd_msbuild( "libjpeg": { "url": f"{SF_PROJECTS}/libjpeg-turbo/files/{V['JPEGTURBO']}/FILENAME/download", "filename": f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz", - "dir": f"libjpeg-turbo-{V['JPEGTURBO']}", "license": ["README.ijg", "LICENSE.md"], "license_pattern": ( "(LEGAL ISSUES\n============\n\n.+?)\n\nREFERENCES\n==========" @@ -161,7 +160,6 @@ def cmd_msbuild( "zlib": { "url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['ZLIBNG']}.zip", "filename": f"zlib-ng-{V['ZLIBNG']}.zip", - "dir": f"zlib-ng-{V['ZLIBNG']}", "license": "LICENSE.md", "patch": { r"CMakeLists.txt": { @@ -179,7 +177,6 @@ def cmd_msbuild( "xz": { "url": f"https://github.com/tukaani-project/xz/releases/download/v{V['XZ']}/FILENAME", "filename": f"xz-{V['XZ']}.tar.gz", - "dir": f"xz-{V['XZ']}", "license": "COPYING", "build": [ *cmds_cmake("liblzma", "-DBUILD_SHARED_LIBS:BOOL=OFF"), @@ -192,7 +189,6 @@ def cmd_msbuild( "libwebp": { "url": "http://downloads.webmproject.org/releases/webp/FILENAME", "filename": f"libwebp-{V['LIBWEBP']}.tar.gz", - "dir": f"libwebp-{V['LIBWEBP']}", "license": "COPYING", "patch": { r"src\enc\picture_csp_enc.c": { @@ -214,7 +210,6 @@ def cmd_msbuild( "libtiff": { "url": "https://download.osgeo.org/libtiff/FILENAME", "filename": f"tiff-{V['TIFF']}.tar.gz", - "dir": f"tiff-{V['TIFF']}", "license": "LICENSE.md", "patch": { r"libtiff\tif_lzma.c": { @@ -247,7 +242,6 @@ def cmd_msbuild( "url": f"{SF_PROJECTS}/libpng/files/libpng{V['LIBPNG_XY']}/{V['LIBPNG']}/" f"lpng{V['LIBPNG_DOTLESS']}.zip/download", "filename": f"lpng{V['LIBPNG_DOTLESS']}.zip", - "dir": f"lpng{V['LIBPNG_DOTLESS']}", "license": "LICENSE", "build": [ *cmds_cmake("png_static", "-DPNG_SHARED:BOOL=OFF", "-DPNG_TESTS:BOOL=OFF"), @@ -261,7 +255,6 @@ def cmd_msbuild( "brotli": { "url": f"https://github.com/google/brotli/archive/refs/tags/v{V['BROTLI']}.tar.gz", "filename": f"brotli-{V['BROTLI']}.tar.gz", - "dir": f"brotli-{V['BROTLI']}", "license": "LICENSE", "build": [ *cmds_cmake(("brotlicommon", "brotlidec"), "-DBUILD_SHARED_LIBS:BOOL=OFF"), @@ -272,7 +265,6 @@ def cmd_msbuild( "freetype": { "url": "https://download.savannah.gnu.org/releases/freetype/FILENAME", "filename": f"freetype-{V['FREETYPE']}.tar.gz", - "dir": f"freetype-{V['FREETYPE']}", "license": ["LICENSE.TXT", r"docs\FTL.TXT", r"docs\GPLv2.TXT"], "patch": { r"builds\windows\vc2010\freetype.vcxproj": { @@ -307,7 +299,6 @@ def cmd_msbuild( "lcms2": { "url": f"{SF_PROJECTS}/lcms/files/lcms/{V['LCMS2']}/FILENAME/download", "filename": f"lcms2-{V['LCMS2']}.tar.gz", - "dir": f"lcms2-{V['LCMS2']}", "license": "LICENSE", "patch": { r"Projects\VC2022\lcms2_static\lcms2_static.vcxproj": { @@ -333,7 +324,6 @@ def cmd_msbuild( "openjpeg": { "url": f"https://github.com/uclouvain/openjpeg/archive/v{V['OPENJPEG']}.tar.gz", "filename": f"openjpeg-{V['OPENJPEG']}.tar.gz", - "dir": f"openjpeg-{V['OPENJPEG']}", "license": "LICENSE", "build": [ *cmds_cmake( @@ -348,7 +338,6 @@ def cmd_msbuild( # commit: Merge branch 'master' into msvc (matches 2.17.0 tag) "url": "https://github.com/ImageOptim/libimagequant/archive/e4c1334be0eff290af5e2b4155057c2953a313ab.zip", "filename": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab.zip", - "dir": "libimagequant-e4c1334be0eff290af5e2b4155057c2953a313ab", "license": "COPYRIGHT", "patch": { "CMakeLists.txt": { @@ -368,7 +357,6 @@ def cmd_msbuild( "harfbuzz": { "url": f"https://github.com/harfbuzz/harfbuzz/archive/{V['HARFBUZZ']}.zip", "filename": f"harfbuzz-{V['HARFBUZZ']}.zip", - "dir": f"harfbuzz-{V['HARFBUZZ']}", "license": "COPYING", "build": [ *cmds_cmake( @@ -383,7 +371,6 @@ def cmd_msbuild( "fribidi": { "url": f"https://github.com/fribidi/fribidi/archive/v{V['FRIBIDI']}.zip", "filename": f"fribidi-{V['FRIBIDI']}.zip", - "dir": f"fribidi-{V['FRIBIDI']}", "license": "COPYING", "build": [ cmd_copy(r"COPYING", rf"{{bin_dir}}\fribidi-{V['FRIBIDI']}-COPYING"), @@ -763,6 +750,8 @@ def main() -> None: } for k, v in DEPS.items(): + if "dir" not in v: + v["dir"] = re.sub(r"\.(tar\.gz|zip)", "", v["filename"]) prefs[f"dir_{k}"] = os.path.join(sources_dir, v["dir"]) print() From 6373e8bcc470ffa8849a17fa8e90e1146e0c3c6c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 15 Dec 2024 22:58:35 +1100 Subject: [PATCH 101/107] Use .tar.gz zlib to match macOS and Linux --- winbuild/build_prepare.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 5bdb5fafedb..a68a0b132b2 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -159,8 +159,8 @@ def cmd_msbuild( "bins": ["cjpeg.exe", "djpeg.exe"], }, "zlib": { - "url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['ZLIBNG']}.zip", - "filename": f"zlib-ng-{V['ZLIBNG']}.zip", + "url": f"https://github.com/zlib-ng/zlib-ng/archive/refs/tags/{V['ZLIBNG']}.tar.gz", + "filename": f"zlib-ng-{V['ZLIBNG']}.tar.gz", "dir": f"zlib-ng-{V['ZLIBNG']}", "license": "LICENSE.md", "patch": { From 7f6ebfb8c597d8d1a074993f463992b6dac34c41 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 15 Dec 2024 23:40:38 +1100 Subject: [PATCH 102/107] Added release notes for #8500 --- docs/releasenotes/11.1.0.rst | 59 ++++++++++++++++++++++++++++++++++++ docs/releasenotes/index.rst | 1 + 2 files changed, 60 insertions(+) create mode 100644 docs/releasenotes/11.1.0.rst diff --git a/docs/releasenotes/11.1.0.rst b/docs/releasenotes/11.1.0.rst new file mode 100644 index 00000000000..c5d0afd58d8 --- /dev/null +++ b/docs/releasenotes/11.1.0.rst @@ -0,0 +1,59 @@ +11.1.0 +------ + +Security +======== + +TODO +^^^^ + +TODO + +:cve:`YYYY-XXXXX`: TODO +^^^^^^^^^^^^^^^^^^^^^^^ + +TODO + +Backwards Incompatible Changes +============================== + +TODO +^^^^ + +Deprecations +============ + +TODO +^^^^ + +TODO + +API Changes +=========== + +TODO +^^^^ + +TODO + +API Additions +============= + +Check for zlib-ng +^^^^^^^^^^^^^^^^^ + +You can check if Pillow has been built against the zlib-ng version of the +zlib library, and what version of zlib-ng is being used:: + + from PIL import features + features.check_feature("zlib_ng") # True or False + features.version_feature("zlib_ng") # "2.2.2" for example, or None + +Other Changes +============= + +zlib-ng in wheels +^^^^^^^^^^^^^^^^^ + +Wheels are now built against zlib-ng for improved speed. In tests, saving a PNG +was found to be more than twice as fast at higher compression levels. diff --git a/docs/releasenotes/index.rst b/docs/releasenotes/index.rst index 641cda4efb5..bd8e5536f71 100644 --- a/docs/releasenotes/index.rst +++ b/docs/releasenotes/index.rst @@ -14,6 +14,7 @@ expected to be backported to earlier versions. .. toctree:: :maxdepth: 2 + 11.1.0 11.0.0 10.4.0 10.3.0 From 5e35ca359c4182af07c0c02e61ebd8d270bb34b3 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 16 Dec 2024 20:17:36 +1100 Subject: [PATCH 103/107] Updated libjpeg-turbo URL --- winbuild/build_prepare.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index a68a0b132b2..e702c7014d7 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -129,7 +129,7 @@ def cmd_msbuild( # dependencies, listed in order of compilation DEPS: dict[str, dict[str, Any]] = { "libjpeg": { - "url": f"{SF_PROJECTS}/libjpeg-turbo/files/{V['JPEGTURBO']}/FILENAME/download", + "url": f"https://github.com/libjpeg-turbo/libjpeg-turbo/releases/download/{V['JPEGTURBO']}/libjpeg-turbo-{V['JPEGTURBO']}.tar.gz", "filename": f"libjpeg-turbo-{V['JPEGTURBO']}.tar.gz", "dir": f"libjpeg-turbo-{V['JPEGTURBO']}", "license": ["README.ijg", "LICENSE.md"], From 095811452172bc4efd4be172f850c2f564e35cba Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 16 Dec 2024 21:03:33 +1100 Subject: [PATCH 104/107] Corrected harfbuzz URL --- .github/workflows/wheels-dependencies.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index de2471c3f30..3e2a14462dc 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -93,7 +93,7 @@ function build_harfbuzz { if [ -e harfbuzz-stamp ]; then return; fi python3 -m pip install meson ninja - local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) + local out_dir=$(fetch_unpack https://github.com/harfbuzz/harfbuzz/releases/download/$HARFBUZZ_VERSION/harfbuzz-$HARFBUZZ_VERSION.tar.xz harfbuzz-$HARFBUZZ_VERSION.tar.xz) (cd $out_dir \ && meson setup build --prefix=$BUILD_PREFIX --libdir=$BUILD_PREFIX/lib --buildtype=release -Dfreetype=enabled -Dglib=disabled) (cd $out_dir/build \ From 962bfc6fd5e949cfdf8c0d18877acc2b8f82a78c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 18 Dec 2024 10:39:52 +1100 Subject: [PATCH 105/107] Updated libjpeg-turbo URL --- wheels/multibuild | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wheels/multibuild b/wheels/multibuild index 74a9795bc64..42d761728d1 160000 --- a/wheels/multibuild +++ b/wheels/multibuild @@ -1 +1 @@ -Subproject commit 74a9795bc64ff786b7e7d33bdec2843cf17e512e +Subproject commit 42d761728d141d8462cd9943f4329f12fe62b155 From de8335ba8fe121cf1f1b7b1565f535fb066094bf Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:07:34 +1100 Subject: [PATCH 106/107] Extract tar files with "data" filter in Windows build scripts (#8606) Co-authored-by: Andrew Murray Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- winbuild/build_prepare.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 607f672ba9a..188872dfc69 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -7,6 +7,7 @@ import shutil import struct import subprocess +import sys from typing import Any @@ -507,7 +508,10 @@ def extract_dep(url: str, filename: str, prefs: dict[str, str]) -> None: if sources_dir_abs != member_prefix: msg = "Attempted Path Traversal in Tar File" raise RuntimeError(msg) - tgz.extractall(sources_dir) + if sys.version_info >= (3, 12): + tgz.extractall(sources_dir, filter="data") + else: + tgz.extractall(sources_dir) else: msg = "Unknown archive type: " + filename raise RuntimeError(msg) From cbc55c4621b79176702bf14f188e5a439c7804a4 Mon Sep 17 00:00:00 2001 From: Andrew Murray <3112309+radarhere@users.noreply.github.com> Date: Wed, 18 Dec 2024 17:08:51 +1100 Subject: [PATCH 107/107] Raise ValueError when WMF inch is zero (#8600) Co-authored-by: Andrew Murray --- Tests/test_file_wmf.py | 7 +++++++ src/PIL/WmfImagePlugin.py | 3 +++ 2 files changed, 10 insertions(+) diff --git a/Tests/test_file_wmf.py b/Tests/test_file_wmf.py index 424640d7b18..2f1f8cdbc85 100644 --- a/Tests/test_file_wmf.py +++ b/Tests/test_file_wmf.py @@ -35,6 +35,13 @@ def test_load() -> None: assert im.load()[0, 0] == (255, 255, 255) +def test_load_zero_inch() -> None: + b = BytesIO(b"\xd7\xcd\xc6\x9a\x00\x00" + b"\x00" * 10) + with pytest.raises(ValueError): + with Image.open(b): + pass + + def test_register_handler(tmp_path: Path) -> None: class TestHandler(ImageFile.StubHandler): methodCalled = False diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index cad6c98d53f..48e9823e8ad 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -92,6 +92,9 @@ def _open(self) -> None: # get units per inch self._inch = word(s, 14) + if self._inch == 0: + msg = "Invalid inch" + raise ValueError(msg) # get bounding box x0 = short(s, 6)