From 7b9a276c7f2dcba699bb2d9c7530f70bb499fa00 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 13 Apr 2024 13:47:52 +1000 Subject: [PATCH 01/63] Updated libwebp to 1.4.0 --- .github/workflows/wheels-dependencies.sh | 2 +- depends/install_webp.sh | 2 +- winbuild/build_prepare.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/wheels-dependencies.sh b/.github/workflows/wheels-dependencies.sh index 0d45d5a20..0cf5c58ab 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -33,7 +33,7 @@ if [[ -n "$IS_MACOS" ]] || [[ "$MB_ML_VER" != 2014 ]]; then else ZLIB_VERSION=1.2.8 fi -LIBWEBP_VERSION=1.3.2 +LIBWEBP_VERSION=1.4.0 BZIP2_VERSION=1.0.8 LIBXCB_VERSION=1.16.1 BROTLI_VERSION=1.1.0 diff --git a/depends/install_webp.sh b/depends/install_webp.sh index 6f867ab37..c47fb35f1 100755 --- a/depends/install_webp.sh +++ b/depends/install_webp.sh @@ -1,7 +1,7 @@ #!/bin/bash # install webp -archive=libwebp-1.3.2 +archive=libwebp-1.4.0 ./download-and-extract.sh $archive https://raw.githubusercontent.com/python-pillow/pillow-depends/main/$archive.tar.gz diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 0d6da7754..7ff645fc9 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -117,7 +117,7 @@ V = { "JPEGTURBO": "3.0.2", "LCMS2": "2.16", "LIBPNG": "1.6.43", - "LIBWEBP": "1.3.2", + "LIBWEBP": "1.4.0", "OPENJPEG": "2.5.2", "TIFF": "4.6.0", "XZ": "5.4.5", From 1af66df732f1842494d4eb8470f81c4c1e94b5b9 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 16 Apr 2024 07:13:40 +1000 Subject: [PATCH 02/63] Updated xcb-proto to 1.17.0 --- .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 0d45d5a20..2d5e174ce 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -70,7 +70,7 @@ function build { fi build_new_zlib - build_simple xcb-proto 1.16.0 https://xorg.freedesktop.org/archive/individual/proto + 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 From 712aa994f27aba19209e33be382f1a8c85ade82f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 16 Apr 2024 07:14:04 +1000 Subject: [PATCH 03/63] Updated libxcb to 1.17.0 --- .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 2d5e174ce..e140665fe 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -35,7 +35,7 @@ else fi LIBWEBP_VERSION=1.3.2 BZIP2_VERSION=1.0.8 -LIBXCB_VERSION=1.16.1 +LIBXCB_VERSION=1.17.0 BROTLI_VERSION=1.1.0 if [[ -n "$IS_MACOS" ]] && [[ "$CIBW_ARCHS" == "x86_64" ]]; then From 4171435db45822f6ebf696e36a7edfacdb1e6756 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 15 Apr 2024 18:25:03 +1000 Subject: [PATCH 04/63] Added more modes --- src/PIL/Image.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index c65cf3850..8efaf8b78 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -248,7 +248,28 @@ def _conv_type_shape(im): return shape, m.typestr -MODES = ["1", "CMYK", "F", "HSV", "I", "L", "LAB", "P", "RGB", "RGBA", "RGBX", "YCbCr"] +MODES = [ + "1", + "CMYK", + "F", + "HSV", + "I", + "I;16", + "I;16B", + "I;16L", + "I;16N", + "L", + "LA", + "La", + "LAB", + "P", + "PA", + "RGB", + "RGBA", + "RGBa", + "RGBX", + "YCbCr", +] # raw modes that may be memory mapped. NOTE: if you change this, you # may have to modify the stride calculation in map.c too! From f690b7f6915ed0cfee0b1b4246741d17aa2e70b1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 22 Apr 2024 13:39:35 +1000 Subject: [PATCH 05/63] Added MPEG accept function --- Tests/test_file_mpeg.py | 39 ++++++++++++++++++++++++++++++++++++++ src/PIL/MpegImagePlugin.py | 6 +++++- 2 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 Tests/test_file_mpeg.py diff --git a/Tests/test_file_mpeg.py b/Tests/test_file_mpeg.py new file mode 100644 index 000000000..468aef8a9 --- /dev/null +++ b/Tests/test_file_mpeg.py @@ -0,0 +1,39 @@ +from __future__ import annotations + +from io import BytesIO + +import pytest + +from PIL import Image, MpegImagePlugin + + +def test_identify() -> None: + # Arrange + b = BytesIO(b"\x00\x00\x01\xb3\x01\x00\x01") + + # Act + with Image.open(b) as im: + # Assert + assert im.format == "MPEG" + + assert im.mode == "RGB" + assert im.size == (16, 1) + + +def test_invalid_file() -> None: + # Arrange + invalid_file = "Tests/images/flower.jpg" + + # Act / Assert + with pytest.raises(SyntaxError): + MpegImagePlugin.MpegImageFile(invalid_file) + + +def test_load() -> None: + # Arrange + b = BytesIO(b"\x00\x00\x01\xb3\x01\x00\x01") + + with Image.open(b) as im: + # Act / Assert: cannot load + with pytest.raises(OSError): + im.load() diff --git a/src/PIL/MpegImagePlugin.py b/src/PIL/MpegImagePlugin.py index 1565612f8..ad4d3e937 100644 --- a/src/PIL/MpegImagePlugin.py +++ b/src/PIL/MpegImagePlugin.py @@ -53,6 +53,10 @@ class BitStream: return v +def _accept(prefix: bytes) -> bool: + return prefix[:4] == b"\x00\x00\x01\xb3" + + ## # Image plugin for MPEG streams. This plugin can identify a stream, # but it cannot read it. @@ -77,7 +81,7 @@ class MpegImageFile(ImageFile.ImageFile): # -------------------------------------------------------------------- # Registry stuff -Image.register_open(MpegImageFile.format, MpegImageFile) +Image.register_open(MpegImageFile.format, MpegImageFile, _accept) Image.register_extensions(MpegImageFile.format, [".mpg", ".mpeg"]) From 1b1c825f7ba5ffe6b62fbffdad4d094f13e254e8 Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 25 Apr 2024 14:39:51 +0300 Subject: [PATCH 06/63] Add ClangFormat to pre-commit --- .pre-commit-config.yaml | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 51625eb4c..f81f03da1 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.3.4 + rev: v0.4.1 hooks: - id: ruff args: [--exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.3.0 + rev: 24.4.1 hooks: - id: black @@ -23,13 +23,20 @@ repos: - id: remove-tabs exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) + - repo: https://github.com/pre-commit/mirrors-clang-format + rev: v18.1.4 + hooks: + - id: clang-format + types: [c] + exclude: ^src/thirdparty/ + - repo: https://github.com/pre-commit/pygrep-hooks rev: v1.10.0 hooks: - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-executables-have-shebangs - id: check-shebang-scripts-are-executable @@ -43,7 +50,7 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.1 + rev: 0.28.2 hooks: - id: check-github-workflows - id: check-readthedocs @@ -55,7 +62,7 @@ repos: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: 1.7.0 + rev: 1.8.0 hooks: - id: pyproject-fmt From 617e7295a80f2067f4be2861c43fc9e2345455e5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 12:51:20 +0000 Subject: [PATCH 07/63] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/Tk/tkImaging.c | 19 +- src/_imaging.c | 54 +++--- src/_imagingcms.c | 16 +- src/_imagingft.c | 127 +++++++++----- src/encode.c | 10 +- src/libImaging/Access.c | 10 +- src/libImaging/BcnDecode.c | 24 +-- src/libImaging/BoxBlur.c | 14 +- src/libImaging/Convert.c | 26 +-- src/libImaging/Draw.c | 66 +++++-- src/libImaging/Filter.c | 9 +- src/libImaging/FliDecode.c | 5 +- src/libImaging/Geometry.c | 16 +- src/libImaging/GetBBox.c | 10 +- src/libImaging/Gif.h | 8 +- src/libImaging/GifEncode.c | 318 ++++++++++++++++++---------------- src/libImaging/Imaging.h | 20 ++- src/libImaging/ImagingUtils.h | 2 +- src/libImaging/Jpeg2KDecode.c | 10 +- src/libImaging/Jpeg2KEncode.c | 6 +- src/libImaging/JpegEncode.c | 12 +- src/libImaging/Paste.c | 9 +- src/libImaging/Point.c | 2 +- src/libImaging/Quant.c | 63 +++---- src/libImaging/QuantOctree.c | 4 +- src/libImaging/Reduce.c | 2 +- src/libImaging/Resample.c | 2 +- src/libImaging/SgiRleDecode.c | 9 +- src/libImaging/Storage.c | 5 +- src/libImaging/TiffDecode.c | 136 +++++++++------ src/libImaging/TiffDecode.h | 2 +- src/libImaging/Unpack.c | 49 +++--- src/path.c | 36 ++-- 33 files changed, 608 insertions(+), 493 deletions(-) diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index bd3cafe95..ef1c00a94 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -128,14 +128,7 @@ PyImagingPhotoPut( block.pixelPtr = (unsigned char *)im->block; TK_PHOTO_PUT_BLOCK( - interp, - photo, - &block, - 0, - 0, - block.width, - block.height, - TK_PHOTO_COMPOSITE_SET); + interp, photo, &block, 0, 0, block.width, block.height, TK_PHOTO_COMPOSITE_SET); return TCL_OK; } @@ -287,7 +280,7 @@ load_tkinter_funcs(void) { * Return 0 for success, non-zero for failure. */ - HMODULE* hMods = NULL; + HMODULE *hMods = NULL; HANDLE hProcess; DWORD cbNeeded; unsigned int i; @@ -313,7 +306,7 @@ load_tkinter_funcs(void) { #endif return 1; } - if (!(hMods = (HMODULE*) malloc(cbNeeded))) { + if (!(hMods = (HMODULE *)malloc(cbNeeded))) { PyErr_NoMemory(); return 1; } @@ -345,7 +338,7 @@ load_tkinter_funcs(void) { } else if (found_tk == 0) { PyErr_SetString(PyExc_RuntimeError, "Could not find Tk routines"); } - return (int) ((found_tcl != 1) || (found_tk != 1)); + return (int)((found_tcl != 1) || (found_tk != 1)); } #else /* not Windows */ @@ -400,8 +393,8 @@ _func_loader(void *lib) { return 1; } return ( - (TK_PHOTO_PUT_BLOCK = - (Tk_PhotoPutBlock_t)_dfunc(lib, "Tk_PhotoPutBlock")) == NULL); + (TK_PHOTO_PUT_BLOCK = (Tk_PhotoPutBlock_t)_dfunc(lib, "Tk_PhotoPutBlock")) == + NULL); } int diff --git a/src/_imaging.c b/src/_imaging.c index 9b521f552..c565c21bb 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -110,7 +110,7 @@ #define B16(p, i) ((((int)p[(i)]) << 8) + p[(i) + 1]) #define L16(p, i) ((((int)p[(i) + 1]) << 8) + p[(i)]) -#define S16(v) ((v) < 32768 ? (v) : ((v)-65536)) +#define S16(v) ((v) < 32768 ? (v) : ((v) - 65536)) /* -------------------------------------------------------------------- */ /* OBJECT ADMINISTRATION */ @@ -533,7 +533,9 @@ getink(PyObject *color, Imaging im, char *ink) { /* unsigned integer, single layer */ if (rIsInt != 1) { if (tupleSize != 1) { - PyErr_SetString(PyExc_TypeError, "color must be int or single-element tuple"); + PyErr_SetString( + PyExc_TypeError, + "color must be int or single-element tuple"); return NULL; } else if (!PyArg_ParseTuple(color, "L", &r)) { return NULL; @@ -552,7 +554,9 @@ getink(PyObject *color, Imaging im, char *ink) { a = 255; if (im->bands == 2) { if (tupleSize != 1 && tupleSize != 2) { - PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one or two elements"); + PyErr_SetString( + PyExc_TypeError, + "color must be int, or tuple of one or two elements"); return NULL; } else if (!PyArg_ParseTuple(color, "L|i", &r, &a)) { return NULL; @@ -560,7 +564,10 @@ getink(PyObject *color, Imaging im, char *ink) { g = b = r; } else { if (tupleSize != 3 && tupleSize != 4) { - PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one, three or four elements"); + PyErr_SetString( + PyExc_TypeError, + "color must be int, or tuple of one, three or four " + "elements"); return NULL; } else if (!PyArg_ParseTuple(color, "Lii|i", &r, &g, &b, &a)) { return NULL; @@ -599,7 +606,9 @@ getink(PyObject *color, Imaging im, char *ink) { g = (UINT8)(r >> 8); r = (UINT8)r; } else if (tupleSize != 3) { - PyErr_SetString(PyExc_TypeError, "color must be int, or tuple of one or three elements"); + PyErr_SetString( + PyExc_TypeError, + "color must be int, or tuple of one or three elements"); return NULL; } else if (!PyArg_ParseTuple(color, "iiL", &b, &g, &r)) { return NULL; @@ -1537,14 +1546,14 @@ _putdata(ImagingObject *self, PyObject *args) { return NULL; } -#define set_value_to_item(seq, i) \ -op = PySequence_Fast_GET_ITEM(seq, i); \ -if (PySequence_Check(op)) { \ - PyErr_SetString(PyExc_TypeError, "sequence must be flattened"); \ - return NULL; \ -} else { \ - value = PyFloat_AsDouble(op); \ -} +#define set_value_to_item(seq, i) \ + op = PySequence_Fast_GET_ITEM(seq, i); \ + if (PySequence_Check(op)) { \ + PyErr_SetString(PyExc_TypeError, "sequence must be flattened"); \ + return NULL; \ + } else { \ + value = PyFloat_AsDouble(op); \ + } if (image->image8) { if (PyBytes_Check(data)) { unsigned char *p; @@ -1596,8 +1605,10 @@ if (PySequence_Check(op)) { \ value = value * scale + offset; } if (image->type == IMAGING_TYPE_SPECIAL) { - image->image8[y][x * 2 + (bigendian ? 1 : 0)] = CLIP8((int)value % 256); - image->image8[y][x * 2 + (bigendian ? 0 : 1)] = CLIP8((int)value >> 8); + image->image8[y][x * 2 + (bigendian ? 1 : 0)] = + CLIP8((int)value % 256); + image->image8[y][x * 2 + (bigendian ? 0 : 1)] = + CLIP8((int)value >> 8); } else { image->image8[y][x] = (UINT8)CLIP8(value); } @@ -1639,8 +1650,7 @@ if (PySequence_Check(op)) { \ for (i = x = y = 0; i < n; i++) { double value; set_value_to_item(seq, i); - IMAGING_PIXEL_INT32(image, x, y) = - (INT32)(value * scale + offset); + IMAGING_PIXEL_INT32(image, x, y) = (INT32)(value * scale + offset); if (++x >= (int)image->xsize) { x = 0, y++; } @@ -2785,8 +2795,8 @@ _font_getmask(ImagingFontObject *self, PyObject *args) { glyph = &self->glyphs[text[i]]; if (i == 0 || text[i] != text[i - 1]) { ImagingDelete(bitmap); - bitmap = - ImagingCrop(self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1); + bitmap = ImagingCrop( + self->bitmap, glyph->sx0, glyph->sy0, glyph->sx1, glyph->sy1); if (!bitmap) { goto failed; } @@ -3315,7 +3325,8 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) { free(xy); - if (ImagingDrawPolygon(self->image->image, n, ixy, &ink, fill, width, self->blend) < 0) { + if (ImagingDrawPolygon(self->image->image, n, ixy, &ink, fill, width, self->blend) < + 0) { free(ixy); return NULL; } @@ -4411,7 +4422,8 @@ setup_module(PyObject *m) { PyModule_AddObject(m, "HAVE_XCB", have_xcb); PyObject *pillow_version = PyUnicode_FromString(version); - PyDict_SetItemString(d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None); + PyDict_SetItemString( + d, "PILLOW_VERSION", pillow_version ? pillow_version : Py_None); Py_XDECREF(pillow_version); return 0; diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 63d78f84d..ba8c81005 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -213,11 +213,8 @@ cms_transform_dealloc(CmsTransformObject *self) { static cmsUInt32Number findLCMStype(char *PILmode) { - if ( - strcmp(PILmode, "RGB") == 0 || - strcmp(PILmode, "RGBA") == 0 || - strcmp(PILmode, "RGBX") == 0 - ) { + if (strcmp(PILmode, "RGB") == 0 || strcmp(PILmode, "RGBA") == 0 || + strcmp(PILmode, "RGBX") == 0) { return TYPE_RGBA_8; } if (strcmp(PILmode, "RGBA;16B") == 0) { @@ -232,10 +229,7 @@ findLCMStype(char *PILmode) { if (strcmp(PILmode, "L;16B") == 0) { return TYPE_GRAY_16_SE; } - if ( - strcmp(PILmode, "YCCA") == 0 || - strcmp(PILmode, "YCC") == 0 - ) { + if (strcmp(PILmode, "YCCA") == 0 || strcmp(PILmode, "YCC") == 0) { return TYPE_YCbCr_8; } if (strcmp(PILmode, "LAB") == 0) { @@ -393,7 +387,7 @@ _buildTransform( Py_END_ALLOW_THREADS - if (!hTransform) { + if (!hTransform) { PyErr_SetString(PyExc_ValueError, "cannot build transform"); } @@ -427,7 +421,7 @@ _buildProofTransform( Py_END_ALLOW_THREADS - if (!hTransform) { + if (!hTransform) { PyErr_SetString(PyExc_ValueError, "cannot build proof transform"); } diff --git a/src/_imagingft.c b/src/_imagingft.c index 6e24fcf95..e83ddfec1 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -47,17 +47,17 @@ ; #ifdef HAVE_RAQM -# ifdef HAVE_RAQM_SYSTEM -# include -# else -# include "thirdparty/raqm/raqm.h" -# ifdef HAVE_FRIBIDI_SYSTEM -# include -# else -# include "thirdparty/fribidi-shim/fribidi.h" -# include -# endif -# endif +#ifdef HAVE_RAQM_SYSTEM +#include +#else +#include "thirdparty/raqm/raqm.h" +#ifdef HAVE_FRIBIDI_SYSTEM +#include +#else +#include "thirdparty/fribidi-shim/fribidi.h" +#include +#endif +#endif #endif static int have_raqm = 0; @@ -490,8 +490,7 @@ text_layout( size_t count; #ifdef HAVE_RAQM if (have_raqm && self->layout_engine == LAYOUT_RAQM) { - count = text_layout_raqm( - string, self, dir, features, lang, glyph_info); + count = text_layout_raqm(string, self, dir, features, lang, glyph_info); } else #endif { @@ -550,7 +549,17 @@ font_getlength(FontObject *self, PyObject *args) { } static int -bounding_box_and_anchors(FT_Face face, const char *anchor, int horizontal_dir, GlyphInfo *glyph_info, size_t count, int load_flags, int *width, int *height, int *x_offset, int *y_offset) { +bounding_box_and_anchors( + FT_Face face, + const char *anchor, + int horizontal_dir, + GlyphInfo *glyph_info, + size_t count, + int load_flags, + int *width, + int *height, + int *x_offset, + int *y_offset) { int position; /* pen position along primary axis, in 26.6 precision */ int advanced; /* pen position along primary axis, in pixels */ int px, py; /* position of current glyph, in pixels */ @@ -558,8 +567,8 @@ bounding_box_and_anchors(FT_Face face, const char *anchor, int horizontal_dir, G int x_anchor, y_anchor; /* offset of point drawn at (0, 0), in pixels */ int error; FT_Glyph glyph; - FT_BBox bbox; /* glyph bounding box */ - size_t i; /* glyph_info index */ + FT_BBox bbox; /* glyph bounding box */ + size_t i; /* glyph_info index */ /* * text bounds are given by: * - bounding boxes of individual glyphs @@ -654,8 +663,7 @@ bounding_box_and_anchors(FT_Face face, const char *anchor, int horizontal_dir, G break; case 'm': // middle (ascender + descender) / 2 y_anchor = PIXEL( - (face->size->metrics.ascender + - face->size->metrics.descender) / + (face->size->metrics.ascender + face->size->metrics.descender) / 2); break; case 's': // horizontal baseline @@ -719,7 +727,7 @@ bad_anchor: static PyObject * font_getsize(FontObject *self, PyObject *args) { int width, height, x_offset, y_offset; - int load_flags; /* FreeType load_flags parameter */ + int load_flags; /* FreeType load_flags parameter */ int error; GlyphInfo *glyph_info = NULL; /* computed text layout */ size_t count; /* glyph_info length */ @@ -758,7 +766,17 @@ font_getsize(FontObject *self, PyObject *args) { load_flags |= FT_LOAD_COLOR; } - error = bounding_box_and_anchors(self->face, anchor, horizontal_dir, glyph_info, count, load_flags, &width, &height, &x_offset, &y_offset); + error = bounding_box_and_anchors( + self->face, + anchor, + horizontal_dir, + glyph_info, + count, + load_flags, + &width, + &height, + &x_offset, + &y_offset); if (glyph_info) { PyMem_Free(glyph_info); glyph_info = NULL; @@ -767,12 +785,7 @@ font_getsize(FontObject *self, PyObject *args) { return NULL; } - return Py_BuildValue( - "(ii)(ii)", - width, - height, - x_offset, - y_offset); + return Py_BuildValue("(ii)(ii)", width, height, x_offset, y_offset); } static PyObject * @@ -869,7 +882,17 @@ font_render(FontObject *self, PyObject *args) { horizontal_dir = dir && strcmp(dir, "ttb") == 0 ? 0 : 1; - error = bounding_box_and_anchors(self->face, anchor, horizontal_dir, glyph_info, count, load_flags, &width, &height, &x_offset, &y_offset); + error = bounding_box_and_anchors( + self->face, + anchor, + horizontal_dir, + glyph_info, + count, + load_flags, + &width, + &height, + &x_offset, + &y_offset); if (error) { PyMem_Del(glyph_info); return NULL; @@ -1066,17 +1089,26 @@ font_render(FontObject *self, PyObject *args) { /* paste only if source has data */ if (src_alpha > 0) { /* unpremultiply BGRa */ - int src_red = CLIP8((255 * (int)source[k * 4 + 2]) / src_alpha); - int src_green = CLIP8((255 * (int)source[k * 4 + 1]) / src_alpha); - int src_blue = CLIP8((255 * (int)source[k * 4 + 0]) / src_alpha); + int src_red = + CLIP8((255 * (int)source[k * 4 + 2]) / src_alpha); + int src_green = + CLIP8((255 * (int)source[k * 4 + 1]) / src_alpha); + int src_blue = + CLIP8((255 * (int)source[k * 4 + 0]) / src_alpha); /* blend required if target has data */ if (target[k * 4 + 3] > 0) { /* blend RGBA colors */ - target[k * 4 + 0] = BLEND(src_alpha, target[k * 4 + 0], src_red, tmp); - target[k * 4 + 1] = BLEND(src_alpha, target[k * 4 + 1], src_green, tmp); - target[k * 4 + 2] = BLEND(src_alpha, target[k * 4 + 2], src_blue, tmp); - target[k * 4 + 3] = CLIP8(src_alpha + MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp)); + target[k * 4 + 0] = + BLEND(src_alpha, target[k * 4 + 0], src_red, tmp); + target[k * 4 + 1] = + BLEND(src_alpha, target[k * 4 + 1], src_green, tmp); + target[k * 4 + 2] = + BLEND(src_alpha, target[k * 4 + 2], src_blue, tmp); + target[k * 4 + 3] = CLIP8( + src_alpha + + MULDIV255( + target[k * 4 + 3], (255 - src_alpha), tmp)); } else { /* paste unpremultiplied RGBA values */ target[k * 4 + 0] = src_red; @@ -1093,10 +1125,16 @@ font_render(FontObject *self, PyObject *args) { unsigned int src_alpha = source[k] * convert_scale; if (src_alpha > 0) { if (target[k * 4 + 3] > 0) { - target[k * 4 + 0] = BLEND(src_alpha, target[k * 4 + 0], ink[0], tmp); - target[k * 4 + 1] = BLEND(src_alpha, target[k * 4 + 1], ink[1], tmp); - target[k * 4 + 2] = BLEND(src_alpha, target[k * 4 + 2], ink[2], tmp); - target[k * 4 + 3] = CLIP8(src_alpha + MULDIV255(target[k * 4 + 3], (255 - src_alpha), tmp)); + target[k * 4 + 0] = BLEND( + src_alpha, target[k * 4 + 0], ink[0], tmp); + target[k * 4 + 1] = BLEND( + src_alpha, target[k * 4 + 1], ink[1], tmp); + target[k * 4 + 2] = BLEND( + src_alpha, target[k * 4 + 2], ink[2], tmp); + target[k * 4 + 3] = CLIP8( + src_alpha + + MULDIV255( + target[k * 4 + 3], (255 - src_alpha), tmp)); } else { target[k * 4 + 0] = ink[0]; target[k * 4 + 1] = ink[1]; @@ -1109,7 +1147,13 @@ font_render(FontObject *self, PyObject *args) { for (k = x0; k < x1; k++) { unsigned int src_alpha = source[k] * convert_scale; if (src_alpha > 0) { - target[k] = target[k] > 0 ? CLIP8(src_alpha + MULDIV255(target[k], (255 - src_alpha), tmp)) : src_alpha; + target[k] = + target[k] > 0 + ? CLIP8( + src_alpha + + MULDIV255( + target[k], (255 - src_alpha), tmp)) + : src_alpha; } } } @@ -1249,7 +1293,8 @@ font_getvaraxes(FontObject *self) { if (name.name_id == axis.strid) { axis_name = Py_BuildValue("y#", name.string, name.string_len); - PyDict_SetItemString(list_axis, "name", axis_name ? axis_name : Py_None); + PyDict_SetItemString( + list_axis, "name", axis_name ? axis_name : Py_None); Py_XDECREF(axis_name); break; } @@ -1299,7 +1344,7 @@ font_setvaraxes(FontObject *self, PyObject *args) { } num_coords = PyObject_Length(axes); - coords = (FT_Fixed*)malloc(num_coords * sizeof(FT_Fixed)); + coords = (FT_Fixed *)malloc(num_coords * sizeof(FT_Fixed)); if (coords == NULL) { return PyErr_NoMemory(); } diff --git a/src/encode.c b/src/encode.c index c7dd51015..442b5d04f 100644 --- a/src/encode.c +++ b/src/encode.c @@ -1163,8 +1163,10 @@ PyImaging_JpegEncoderNew(PyObject *self, PyObject *args) { ((JPEGENCODERSTATE *)encoder->state.context)->streamtype = streamtype; ((JPEGENCODERSTATE *)encoder->state.context)->xdpi = xdpi; ((JPEGENCODERSTATE *)encoder->state.context)->ydpi = ydpi; - ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_blocks = restart_marker_blocks; - ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_rows = restart_marker_rows; + ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_blocks = + restart_marker_blocks; + ((JPEGENCODERSTATE *)encoder->state.context)->restart_marker_rows = + restart_marker_rows; ((JPEGENCODERSTATE *)encoder->state.context)->comment = comment; ((JPEGENCODERSTATE *)encoder->state.context)->comment_size = comment_size; ((JPEGENCODERSTATE *)encoder->state.context)->extra = extra; @@ -1333,9 +1335,7 @@ PyImaging_Jpeg2KEncoderNew(PyObject *self, PyObject *args) { if (comment && comment_size > 0) { /* Size is stored as as an uint16, subtract 4 bytes for the header */ if (comment_size >= 65532) { - PyErr_SetString( - PyExc_ValueError, - "JPEG 2000 comment is too long"); + PyErr_SetString(PyExc_ValueError, "JPEG 2000 comment is too long"); Py_DECREF(encoder); return NULL; } diff --git a/src/libImaging/Access.c b/src/libImaging/Access.c index 091c84e18..97d21034a 100644 --- a/src/libImaging/Access.c +++ b/src/libImaging/Access.c @@ -191,11 +191,11 @@ put_pixel_32(Imaging im, int x, int y, const void *color) { void ImagingAccessInit() { -#define ADD(mode_, get_pixel_, put_pixel_) \ - { \ - ImagingAccess access = add_item(mode_); \ - access->get_pixel = get_pixel_; \ - access->put_pixel = put_pixel_; \ +#define ADD(mode_, get_pixel_, put_pixel_) \ + { \ + ImagingAccess access = add_item(mode_); \ + access->get_pixel = get_pixel_; \ + access->put_pixel = put_pixel_; \ } /* populate access table */ diff --git a/src/libImaging/BcnDecode.c b/src/libImaging/BcnDecode.c index 5e4296eeb..72f478d8d 100644 --- a/src/libImaging/BcnDecode.c +++ b/src/libImaging/BcnDecode.c @@ -83,7 +83,6 @@ decode_bc1_color(rgba *dst, const UINT8 *src, int separate_alpha) { g1 = p[1].g; b1 = p[1].b; - /* NOTE: BC2 and BC3 reuse BC1 color blocks but always act like c0 > c1 */ if (col.c0 > col.c1 || separate_alpha) { p[2].r = (2 * r0 + 1 * r1) / 3; @@ -354,8 +353,7 @@ decode_bc7_block(rgba *col, const UINT8 *src) { } return; } - while (!(mode & (1 << bit++))) - ; + while (!(mode & (1 << bit++))); mode = bit - 1; info = &bc7_modes[mode]; /* color selection bits: {subset}{endpoint} */ @@ -546,7 +544,7 @@ static const UINT8 bc6_bit_packings[][75] = { 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 52, 10, 112, 113, 114, 115, 64, 65, 66, 67, 26, 176, 160, 161, 162, 163, 80, 81, 82, 83, 42, 177, 128, 129, 130, 131, - 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179}, + 96, 97, 98, 99, 100, 178, 144, 145, 146, 147, 148, 179}, {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 48, 49, 50, 51, 10, 164, 112, 113, 114, 115, 64, 65, 66, 67, 68, @@ -684,7 +682,7 @@ bc6_clamp(float value) { } else if (value > 1.0f) { return 255; } else { - return (UINT8) (value * 255.0f); + return (UINT8)(value * 255.0f); } } @@ -826,7 +824,13 @@ put_block(Imaging im, ImagingCodecState state, const char *col, int sz, int C) { static int decode_bcn( - Imaging im, ImagingCodecState state, const UINT8 *src, int bytes, int N, int C, char *pixel_format) { + Imaging im, + ImagingCodecState state, + const UINT8 *src, + int bytes, + int N, + int C, + char *pixel_format) { int ymax = state->ysize + state->yoff; const UINT8 *ptr = src; switch (N) { @@ -849,8 +853,7 @@ decode_bcn( DECODE_LOOP(2, 16, rgba); DECODE_LOOP(3, 16, rgba); DECODE_LOOP(4, 8, lum); - case 5: - { + case 5: { int sign = strcmp(pixel_format, "BC5S") == 0 ? 1 : 0; while (bytes >= 16) { rgba col[16]; @@ -865,8 +868,7 @@ decode_bcn( } break; } - case 6: - { + case 6: { int sign = strcmp(pixel_format, "BC6HS") == 0 ? 1 : 0; while (bytes >= 16) { rgba col[16]; @@ -880,7 +882,7 @@ decode_bcn( } break; } - DECODE_LOOP(7, 16, rgba); + DECODE_LOOP(7, 16, rgba); #undef DECODE_LOOP } return (int)(ptr - src); diff --git a/src/libImaging/BoxBlur.c b/src/libImaging/BoxBlur.c index adf425d0d..4ea9c7717 100644 --- a/src/libImaging/BoxBlur.c +++ b/src/libImaging/BoxBlur.c @@ -313,12 +313,12 @@ _gaussian_blur_radius(float radius, int passes) { } Imaging -ImagingGaussianBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int passes) { +ImagingGaussianBlur( + Imaging imOut, Imaging imIn, float xradius, float yradius, int passes) { return ImagingBoxBlur( - imOut, - imIn, - _gaussian_blur_radius(xradius, passes), - _gaussian_blur_radius(yradius, passes), - passes - ); + imOut, + imIn, + _gaussian_blur_radius(xradius, passes), + _gaussian_blur_radius(yradius, passes), + passes); } diff --git a/src/libImaging/Convert.c b/src/libImaging/Convert.c index 64840d08c..fcb5f7ad9 100644 --- a/src/libImaging/Convert.c +++ b/src/libImaging/Convert.c @@ -518,8 +518,8 @@ rgba2rgb_(UINT8 *out, const UINT8 *in, int xsize) { /* * Conversion of RGB + single transparent color either to - * RGBA or LA, where any pixel matching the color will have the alpha channel set to 0, or - * RGBa or La, where any pixel matching the color will have all channels set to 0 + * RGBA or LA, where any pixel matching the color will have the alpha channel set to 0, + * or RGBa or La, where any pixel matching the color will have all channels set to 0 */ static void @@ -1676,7 +1676,8 @@ convert( return (Imaging)ImagingError_ValueError("conversion not supported"); #else static char buf[100]; - snprintf(buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode); + snprintf( + buf, 100, "conversion from %.10s to %.10s not supported", imIn->mode, mode); return (Imaging)ImagingError_ValueError(buf); #endif } @@ -1720,25 +1721,24 @@ ImagingConvertTransparent(Imaging imIn, const char *mode, int r, int g, int b) { return (Imaging)ImagingError_ModeError(); } - if (strcmp(imIn->mode, "RGB") == 0 && (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBa") == 0)) { + if (strcmp(imIn->mode, "RGB") == 0 && + (strcmp(mode, "RGBA") == 0 || strcmp(mode, "RGBa") == 0)) { convert = rgb2rgba; if (strcmp(mode, "RGBa") == 0) { premultiplied = 1; } - } else if (strcmp(imIn->mode, "RGB") == 0 && (strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) { + } else if ( + strcmp(imIn->mode, "RGB") == 0 && + (strcmp(mode, "LA") == 0 || strcmp(mode, "La") == 0)) { convert = rgb2la; source_transparency = 1; if (strcmp(mode, "La") == 0) { premultiplied = 1; } - } else if ((strcmp(imIn->mode, "1") == 0 || - strcmp(imIn->mode, "I") == 0 || - strcmp(imIn->mode, "I;16") == 0 || - strcmp(imIn->mode, "L") == 0 - ) && ( - strcmp(mode, "RGBA") == 0 || - strcmp(mode, "LA") == 0 - )) { + } else if ( + (strcmp(imIn->mode, "1") == 0 || strcmp(imIn->mode, "I") == 0 || + strcmp(imIn->mode, "I;16") == 0 || strcmp(imIn->mode, "L") == 0) && + (strcmp(mode, "RGBA") == 0 || strcmp(mode, "LA") == 0)) { if (strcmp(imIn->mode, "1") == 0) { convert = bit2rgb; } else if (strcmp(imIn->mode, "I") == 0) { diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 0ccf22d58..133696dd8 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -48,7 +48,7 @@ * This guarantees that ROUND_UP|DOWN(f) == -ROUND_UP|DOWN(-f) */ #define ROUND_UP(f) ((int)((f) >= 0.0 ? floor((f) + 0.5F) : -floor(fabs(f) + 0.5F))) -#define ROUND_DOWN(f) ((int)((f) >= 0.0 ? ceil((f)-0.5F) : -ceil(fabs(f) - 0.5F))) +#define ROUND_DOWN(f) ((int)((f) >= 0.0 ? ceil((f) - 0.5F) : -ceil(fabs(f) - 0.5F))) /* -------------------------------------------------------------------- */ /* Primitives */ @@ -439,7 +439,14 @@ draw_horizontal_lines( * Filled polygon draw function using scan line algorithm. */ static inline int -polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler hline, int hasAlpha) { +polygon_generic( + Imaging im, + int n, + Edge *e, + int ink, + int eofill, + hline_handler hline, + int hasAlpha) { Edge **edge_table; float *xx; int edge_count = 0; @@ -499,7 +506,7 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler h // Needed to draw consistent polygons xx[j] = xx[j - 1]; j++; - } else if (current->dx != 0 && roundf(xx[j-1]) == xx[j-1]) { + } else if (current->dx != 0 && roundf(xx[j - 1]) == xx[j - 1]) { // Connect discontiguous corners for (k = 0; k < i; k++) { Edge *other_edge = edge_table[k]; @@ -510,23 +517,38 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler h // Check if the two edges join to make a corner if (((ymin == current->ymin && ymin == other_edge->ymin) || (ymin == current->ymax && ymin == other_edge->ymax)) && - xx[j-1] == (ymin - other_edge->y0) * other_edge->dx + other_edge->x0) { + xx[j - 1] == (ymin - other_edge->y0) * other_edge->dx + + other_edge->x0) { // Determine points from the edges on the next row // Or if this is the last row, check the previous row int offset = ymin == ymax ? -1 : 1; - adjacent_line_x = (ymin + offset - current->y0) * current->dx + current->x0; - adjacent_line_x_other_edge = (ymin + offset - other_edge->y0) * other_edge->dx + other_edge->x0; + adjacent_line_x = + (ymin + offset - current->y0) * current->dx + + current->x0; + adjacent_line_x_other_edge = + (ymin + offset - other_edge->y0) * other_edge->dx + + other_edge->x0; if (ymin == current->ymax) { if (current->dx > 0) { - xx[k] = fmax(adjacent_line_x, adjacent_line_x_other_edge) + 1; + xx[k] = fmax( + adjacent_line_x, + adjacent_line_x_other_edge) + + 1; } else { - xx[k] = fmin(adjacent_line_x, adjacent_line_x_other_edge) - 1; + xx[k] = fmin( + adjacent_line_x, + adjacent_line_x_other_edge) - + 1; } } else { if (current->dx > 0) { - xx[k] = fmin(adjacent_line_x, adjacent_line_x_other_edge); + xx[k] = fmin( + adjacent_line_x, adjacent_line_x_other_edge); } else { - xx[k] = fmax(adjacent_line_x, adjacent_line_x_other_edge) + 1; + xx[k] = fmax( + adjacent_line_x, + adjacent_line_x_other_edge) + + 1; } } break; @@ -552,7 +574,8 @@ polygon_generic(Imaging im, int n, Edge *e, int ink, int eofill, hline_handler h int x_start = ROUND_UP(xx[i - 1]); if (x_pos > x_start) { - // Line would be partway through x_pos, so increase the starting point + // Line would be partway through x_pos, so increase the starting + // point x_start = x_pos; if (x_end < x_start) { // Line would now end before it started @@ -776,7 +799,8 @@ ImagingDrawRectangle( } int -ImagingDrawPolygon(Imaging im, int count, int *xy, const void *ink_, int fill, int width, int op) { +ImagingDrawPolygon( + Imaging im, int count, int *xy, const void *ink_, int fill, int width, int op) { int i, n, x0, y0, x1, y1; DRAW *draw; INT32 ink; @@ -803,7 +827,7 @@ ImagingDrawPolygon(Imaging im, int count, int *xy, const void *ink_, int fill, i if (y0 == y1 && i != 0 && y0 == xy[i * 2 - 1]) { // This is a horizontal line, // that immediately follows another horizontal line - Edge *last_e = &e[n-1]; + Edge *last_e = &e[n - 1]; if (x1 > x0 && x0 > xy[i * 2 - 2]) { // They are both increasing in x last_e->xmax = x1; @@ -826,14 +850,24 @@ ImagingDrawPolygon(Imaging im, int count, int *xy, const void *ink_, int fill, i /* Outline */ if (width == 1) { for (i = 0; i < count - 1; i++) { - draw->line(im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink); + draw->line( + im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink); } draw->line(im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink); } else { for (i = 0; i < count - 1; i++) { - ImagingDrawWideLine(im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink_, width, op); + ImagingDrawWideLine( + im, + xy[i * 2], + xy[i * 2 + 1], + xy[i * 2 + 2], + xy[i * 2 + 3], + ink_, + width, + op); } - ImagingDrawWideLine(im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op); + ImagingDrawWideLine( + im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink_, width, op); } } diff --git a/src/libImaging/Filter.c b/src/libImaging/Filter.c index 4dcd368ca..85de77fcb 100644 --- a/src/libImaging/Filter.c +++ b/src/libImaging/Filter.c @@ -106,7 +106,7 @@ ImagingExpand(Imaging imIn, int xmargin, int ymargin) { void ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) { -#define KERNEL1x3(in0, x, kernel, d) \ +#define KERNEL1x3(in0, x, kernel, d) \ (_i2f(in0[x - d]) * (kernel)[0] + _i2f(in0[x]) * (kernel)[1] + \ _i2f(in0[x + d]) * (kernel)[2]) @@ -224,10 +224,9 @@ ImagingFilter3x3(Imaging imOut, Imaging im, const float *kernel, float offset) { void ImagingFilter5x5(Imaging imOut, Imaging im, const float *kernel, float offset) { -#define KERNEL1x5(in0, x, kernel, d) \ - (_i2f(in0[x - d - d]) * (kernel)[0] + \ - _i2f(in0[x - d]) * (kernel)[1] + _i2f(in0[x]) * (kernel)[2] + \ - _i2f(in0[x + d]) * (kernel)[3] + \ +#define KERNEL1x5(in0, x, kernel, d) \ + (_i2f(in0[x - d - d]) * (kernel)[0] + _i2f(in0[x - d]) * (kernel)[1] + \ + _i2f(in0[x]) * (kernel)[2] + _i2f(in0[x + d]) * (kernel)[3] + \ _i2f(in0[x + d + d]) * (kernel)[4]) int x = 0, y = 0; diff --git a/src/libImaging/FliDecode.c b/src/libImaging/FliDecode.c index d6e4ea0ff..debe7ddd8 100644 --- a/src/libImaging/FliDecode.c +++ b/src/libImaging/FliDecode.c @@ -232,7 +232,8 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt /* Note, have to check Data + size, not just ptr + size) */ if (data + (state->xsize * state->ysize) > ptr + bytes) { /* not enough data for frame */ - /* UNDONE Unclear that we're actually going to leave the buffer at the right place. */ + /* UNDONE Unclear that we're actually going to leave the buffer at + * the right place. */ return ptr - buf; /* bytes consumed */ } for (y = 0; y < state->ysize; y++) { @@ -251,7 +252,7 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt return -1; } advance = I32(ptr); - if (advance == 0 ) { + if (advance == 0) { // If there's no advance, we're in an infinite loop state->errcode = IMAGING_CODEC_BROKEN; return -1; diff --git a/src/libImaging/Geometry.c b/src/libImaging/Geometry.c index 0c5915792..cf3bc9979 100644 --- a/src/libImaging/Geometry.c +++ b/src/libImaging/Geometry.c @@ -565,13 +565,13 @@ bilinear_filter32RGB(void *out, Imaging im, double xin, double yin) { #undef BILINEAR_HEAD #undef BILINEAR_BODY -#define BICUBIC(v, v1, v2, v3, v4, d) \ - { \ - double p1 = v2; \ - double p2 = -v1 + v3; \ - double p3 = 2 * (v1 - v2) + v3 - v4; \ - double p4 = -v1 + v2 - v3 + v4; \ - v = p1 + (d) * (p2 + (d) * (p3 + (d)*p4)); \ +#define BICUBIC(v, v1, v2, v3, v4, d) \ + { \ + double p1 = v2; \ + double p2 = -v1 + v3; \ + double p3 = 2 * (v1 - v2) + v3 - v4; \ + double p4 = -v1 + v2 - v3 + v4; \ + v = p1 + (d) * (p2 + (d) * (p3 + (d) * p4)); \ } #define BICUBIC_HEAD(type) \ @@ -966,7 +966,7 @@ affine_fixed( ysize = (int)imIn->ysize; /* use 16.16 fixed point arithmetics */ -#define FIX(v) FLOOR((v)*65536.0 + 0.5) +#define FIX(v) FLOOR((v) * 65536.0 + 0.5) a0 = FIX(a[0]); a1 = FIX(a[1]); diff --git a/src/libImaging/GetBBox.c b/src/libImaging/GetBBox.c index 86c687ca0..bd2a2778c 100644 --- a/src/libImaging/GetBBox.c +++ b/src/libImaging/GetBBox.c @@ -58,11 +58,11 @@ ImagingGetBBox(Imaging im, int bbox[4], int alpha_only) { INT32 mask = 0xffffffff; if (im->bands == 3) { ((UINT8 *)&mask)[3] = 0; - } else if (alpha_only && ( - strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 || - strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 || - strcmp(im->mode, "PA") == 0 - )) { + } else if ( + alpha_only && + (strcmp(im->mode, "RGBa") == 0 || strcmp(im->mode, "RGBA") == 0 || + strcmp(im->mode, "La") == 0 || strcmp(im->mode, "LA") == 0 || + strcmp(im->mode, "PA") == 0)) { #ifdef WORDS_BIGENDIAN mask = 0x000000ff; #else diff --git a/src/libImaging/Gif.h b/src/libImaging/Gif.h index 5d7e2bdaa..8edfbc2ed 100644 --- a/src/libImaging/Gif.h +++ b/src/libImaging/Gif.h @@ -9,10 +9,10 @@ /* Max size for a LZW code word. */ -#define GIFBITS 12 +#define GIFBITS 12 -#define GIFTABLE (1<next_code = st->end_code + 1; st->max_code = 2 * st->clear_code - 1; st->code_width = st->bits + 1; memset(st->codes, 0, sizeof(st->codes)); } -static void glzwe_init(GIFENCODERSTATE *st) { +static void +glzwe_init(GIFENCODERSTATE *st) { st->clear_code = 1 << st->bits; st->end_code = st->clear_code + 1; glzwe_reset(st); @@ -64,156 +72,157 @@ static void glzwe_init(GIFENCODERSTATE *st) { st->code_buffer = 0; } -static int glzwe(GIFENCODERSTATE *st, const UINT8 *in_ptr, UINT8 *out_ptr, - UINT32 *in_avail, UINT32 *out_avail, - UINT32 end_of_data) { +static int +glzwe( + GIFENCODERSTATE *st, + const UINT8 *in_ptr, + UINT8 *out_ptr, + UINT32 *in_avail, + UINT32 *out_avail, + UINT32 end_of_data) { switch (st->entry_state) { - - case LZW_TRY_IN1: -get_first_byte: - if (!*in_avail) { - if (end_of_data) { - goto end_of_data; + case LZW_TRY_IN1: + get_first_byte: + if (!*in_avail) { + if (end_of_data) { + goto end_of_data; + } + st->entry_state = LZW_TRY_IN1; + return GLZW_NO_INPUT_AVAIL; } - st->entry_state = LZW_TRY_IN1; - return GLZW_NO_INPUT_AVAIL; - } - st->head = *in_ptr++; - (*in_avail)--; + st->head = *in_ptr++; + (*in_avail)--; - case LZW_TRY_IN2: -encode_loop: - if (!*in_avail) { - if (end_of_data) { - st->code = st->head; - st->put_state = PUT_LAST_HEAD; - goto put_code; + case LZW_TRY_IN2: + encode_loop: + if (!*in_avail) { + if (end_of_data) { + st->code = st->head; + st->put_state = PUT_LAST_HEAD; + goto put_code; + } + st->entry_state = LZW_TRY_IN2; + return GLZW_NO_INPUT_AVAIL; } - st->entry_state = LZW_TRY_IN2; - return GLZW_NO_INPUT_AVAIL; - } - st->tail = *in_ptr++; - (*in_avail)--; + st->tail = *in_ptr++; + (*in_avail)--; - /* Knuth TAOCP vol 3 sec. 6.4 algorithm D. */ - /* Hash found experimentally to be pretty good. */ - /* This works ONLY with TABLE_SIZE a power of 2. */ - st->probe = ((st->head ^ (st->tail << 6)) * 31) & (TABLE_SIZE - 1); - while (st->codes[st->probe]) { - if ((st->codes[st->probe] & 0xFFFFF) == - ((st->head << 8) | st->tail)) { - st->head = st->codes[st->probe] >> 20; - goto encode_loop; - } else { - /* Reprobe decrement must be non-zero and relatively prime to table - * size. So, any odd positive number for power-of-2 size. */ - if ((st->probe -= ((st->tail << 2) | 1)) < 0) { - st->probe += TABLE_SIZE; + /* Knuth TAOCP vol 3 sec. 6.4 algorithm D. */ + /* Hash found experimentally to be pretty good. */ + /* This works ONLY with TABLE_SIZE a power of 2. */ + st->probe = ((st->head ^ (st->tail << 6)) * 31) & (TABLE_SIZE - 1); + while (st->codes[st->probe]) { + if ((st->codes[st->probe] & 0xFFFFF) == ((st->head << 8) | st->tail)) { + st->head = st->codes[st->probe] >> 20; + goto encode_loop; + } else { + /* Reprobe decrement must be non-zero and relatively prime to table + * size. So, any odd positive number for power-of-2 size. */ + if ((st->probe -= ((st->tail << 2) | 1)) < 0) { + st->probe += TABLE_SIZE; + } } } - } - /* Key not found, probe is at empty slot. */ - st->code = st->head; - st->put_state = PUT_HEAD; - goto put_code; -insert_code_or_clear: /* jump here after put_code */ - if (st->next_code < CODE_LIMIT) { - st->codes[st->probe] = (st->next_code << 20) | - (st->head << 8) | st->tail; - if (st->next_code > st->max_code) { - st->max_code = st->max_code * 2 + 1; - st->code_width++; - } - st->next_code++; - } else { - st->code = st->clear_code; - st->put_state = PUT_CLEAR; + /* Key not found, probe is at empty slot. */ + st->code = st->head; + st->put_state = PUT_HEAD; goto put_code; -reset_after_clear: /* jump here after put_code */ - glzwe_reset(st); - } - st->head = st->tail; - goto encode_loop; - - case LZW_INITIAL: - glzwe_reset(st); - st->code = st->clear_code; - st->put_state = PUT_INIT_CLEAR; -put_code: - st->code_bits_left = st->code_width; -check_buf_bits: - if (!st->buf_bits_left) { /* out buffer full */ - - case LZW_TRY_OUT1: - if (!*out_avail) { - st->entry_state = LZW_TRY_OUT1; - return GLZW_NO_OUTPUT_AVAIL; + insert_code_or_clear: /* jump here after put_code */ + if (st->next_code < CODE_LIMIT) { + st->codes[st->probe] = + (st->next_code << 20) | (st->head << 8) | st->tail; + if (st->next_code > st->max_code) { + st->max_code = st->max_code * 2 + 1; + st->code_width++; + } + st->next_code++; + } else { + st->code = st->clear_code; + st->put_state = PUT_CLEAR; + goto put_code; + reset_after_clear: /* jump here after put_code */ + glzwe_reset(st); } - *out_ptr++ = st->code_buffer; - (*out_avail)--; - st->code_buffer = 0; - st->buf_bits_left = 8; - } - /* code bits to pack */ - UINT32 n = st->buf_bits_left < st->code_bits_left - ? st->buf_bits_left : st->code_bits_left; - st->code_buffer |= - (st->code & ((1 << n) - 1)) << (8 - st->buf_bits_left); - st->code >>= n; - st->buf_bits_left -= n; - st->code_bits_left -= n; - if (st->code_bits_left) { - goto check_buf_bits; - } - switch (st->put_state) { - case PUT_INIT_CLEAR: - goto get_first_byte; - case PUT_HEAD: - goto insert_code_or_clear; - case PUT_CLEAR: - goto reset_after_clear; - case PUT_LAST_HEAD: - goto end_of_data; - case PUT_END: - goto flush_code_buffer; + st->head = st->tail; + goto encode_loop; + + case LZW_INITIAL: + glzwe_reset(st); + st->code = st->clear_code; + st->put_state = PUT_INIT_CLEAR; + put_code: + st->code_bits_left = st->code_width; + check_buf_bits: + if (!st->buf_bits_left) { /* out buffer full */ + + case LZW_TRY_OUT1: + if (!*out_avail) { + st->entry_state = LZW_TRY_OUT1; + return GLZW_NO_OUTPUT_AVAIL; + } + *out_ptr++ = st->code_buffer; + (*out_avail)--; + st->code_buffer = 0; + st->buf_bits_left = 8; + } + /* code bits to pack */ + UINT32 n = st->buf_bits_left < st->code_bits_left ? st->buf_bits_left + : st->code_bits_left; + st->code_buffer |= (st->code & ((1 << n) - 1)) << (8 - st->buf_bits_left); + st->code >>= n; + st->buf_bits_left -= n; + st->code_bits_left -= n; + if (st->code_bits_left) { + goto check_buf_bits; + } + switch (st->put_state) { + case PUT_INIT_CLEAR: + goto get_first_byte; + case PUT_HEAD: + goto insert_code_or_clear; + case PUT_CLEAR: + goto reset_after_clear; + case PUT_LAST_HEAD: + goto end_of_data; + case PUT_END: + goto flush_code_buffer; + default: + return GLZW_INTERNAL_ERROR; + } + + end_of_data: + st->code = st->end_code; + st->put_state = PUT_END; + goto put_code; + flush_code_buffer: /* jump here after put_code */ + if (st->buf_bits_left < 8) { + case LZW_TRY_OUT2: + if (!*out_avail) { + st->entry_state = LZW_TRY_OUT2; + return GLZW_NO_OUTPUT_AVAIL; + } + *out_ptr++ = st->code_buffer; + (*out_avail)--; + } + st->entry_state = LZW_FINISHED; + return GLZW_OK; + + case LZW_FINISHED: + return GLZW_OK; + default: return GLZW_INTERNAL_ERROR; - } - -end_of_data: - st->code = st->end_code; - st->put_state = PUT_END; - goto put_code; -flush_code_buffer: /* jump here after put_code */ - if (st->buf_bits_left < 8) { - - case LZW_TRY_OUT2: - if (!*out_avail) { - st->entry_state = LZW_TRY_OUT2; - return GLZW_NO_OUTPUT_AVAIL; - } - *out_ptr++ = st->code_buffer; - (*out_avail)--; - } - st->entry_state = LZW_FINISHED; - return GLZW_OK; - - case LZW_FINISHED: - return GLZW_OK; - - default: - return GLZW_INTERNAL_ERROR; } } /* -END- GIF LZW encoder. */ int -ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) { - UINT8* ptr; - UINT8* sub_block_ptr; - UINT8* sub_block_limit; - UINT8* buf_limit; - GIFENCODERSTATE *context = (GIFENCODERSTATE*) state->context; +ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { + UINT8 *ptr; + UINT8 *sub_block_ptr; + UINT8 *sub_block_limit; + UINT8 *buf_limit; + GIFENCODERSTATE *context = (GIFENCODERSTATE *)state->context; int r; UINT32 in_avail, in_used; @@ -278,9 +287,9 @@ ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) { return ptr - buf; } sub_block_ptr = ptr; - sub_block_limit = sub_block_ptr + - (256 < buf_limit - sub_block_ptr ? - 256 : buf_limit - sub_block_ptr); + sub_block_limit = + sub_block_ptr + + (256 < buf_limit - sub_block_ptr ? 256 : buf_limit - sub_block_ptr); *ptr++ = 0; } @@ -301,9 +310,9 @@ ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) { /* get another line of data */ state->shuffle( state->buffer, - (UINT8*) im->image[state->y + state->yoff] + - state->xoff * im->pixelsize, state->xsize - ); + (UINT8 *)im->image[state->y + state->yoff] + + state->xoff * im->pixelsize, + state->xsize); state->x = 0; /* step forward, according to the interlace settings */ @@ -331,10 +340,15 @@ ImagingGifEncode(Imaging im, ImagingCodecState state, UINT8* buf, int bytes) { } } - in_avail = state->xsize - state->x; /* bytes left in line */ + in_avail = state->xsize - state->x; /* bytes left in line */ out_avail = sub_block_limit - ptr; /* bytes left in sub-block */ - r = glzwe(context, &state->buffer[state->x], ptr, &in_avail, - &out_avail, state->state == FINISH); + r = glzwe( + context, + &state->buffer[state->x], + ptr, + &in_avail, + &out_avail, + state->state == FINISH); out_used = sub_block_limit - ptr - out_avail; *sub_block_ptr += out_used; ptr += out_used; diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index afcd2229b..1f2c03e93 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -108,15 +108,15 @@ struct ImagingMemoryInstance { #define IMAGING_PIXEL_1(im, x, y) ((im)->image8[(y)][(x)]) #define IMAGING_PIXEL_L(im, x, y) ((im)->image8[(y)][(x)]) -#define IMAGING_PIXEL_LA(im, x, y) ((im)->image[(y)][(x)*4]) +#define IMAGING_PIXEL_LA(im, x, y) ((im)->image[(y)][(x) * 4]) #define IMAGING_PIXEL_P(im, x, y) ((im)->image8[(y)][(x)]) -#define IMAGING_PIXEL_PA(im, x, y) ((im)->image[(y)][(x)*4]) +#define IMAGING_PIXEL_PA(im, x, y) ((im)->image[(y)][(x) * 4]) #define IMAGING_PIXEL_I(im, x, y) ((im)->image32[(y)][(x)]) #define IMAGING_PIXEL_F(im, x, y) (((FLOAT32 *)(im)->image32[y])[x]) -#define IMAGING_PIXEL_RGB(im, x, y) ((im)->image[(y)][(x)*4]) -#define IMAGING_PIXEL_RGBA(im, x, y) ((im)->image[(y)][(x)*4]) -#define IMAGING_PIXEL_CMYK(im, x, y) ((im)->image[(y)][(x)*4]) -#define IMAGING_PIXEL_YCbCr(im, x, y) ((im)->image[(y)][(x)*4]) +#define IMAGING_PIXEL_RGB(im, x, y) ((im)->image[(y)][(x) * 4]) +#define IMAGING_PIXEL_RGBA(im, x, y) ((im)->image[(y)][(x) * 4]) +#define IMAGING_PIXEL_CMYK(im, x, y) ((im)->image[(y)][(x) * 4]) +#define IMAGING_PIXEL_YCbCr(im, x, y) ((im)->image[(y)][(x) * 4]) #define IMAGING_PIXEL_UINT8(im, x, y) ((im)->image8[(y)][(x)]) #define IMAGING_PIXEL_INT32(im, x, y) ((im)->image32[(y)][(x)]) @@ -161,7 +161,7 @@ typedef struct ImagingMemoryArena { int stats_reallocated_blocks; /* Number of blocks which were actually reallocated after retrieving */ int stats_freed_blocks; /* Number of freed blocks */ -} * ImagingMemoryArena; +} *ImagingMemoryArena; /* Objects */ /* ------- */ @@ -309,7 +309,8 @@ ImagingFlipLeftRight(Imaging imOut, Imaging imIn); extern Imaging ImagingFlipTopBottom(Imaging imOut, Imaging imIn); extern Imaging -ImagingGaussianBlur(Imaging imOut, Imaging imIn, float xradius, float yradius, int passes); +ImagingGaussianBlur( + Imaging imOut, Imaging imIn, float xradius, float yradius, int passes); extern Imaging ImagingGetBand(Imaging im, int band); extern Imaging @@ -487,7 +488,8 @@ ImagingDrawPieslice( extern int ImagingDrawPoint(Imaging im, int x, int y, const void *ink, int op); extern int -ImagingDrawPolygon(Imaging im, int points, int *xy, const void *ink, int fill, int width, int op); +ImagingDrawPolygon( + Imaging im, int points, int *xy, const void *ink, int fill, int width, int op); extern int ImagingDrawRectangle( Imaging im, diff --git a/src/libImaging/ImagingUtils.h b/src/libImaging/ImagingUtils.h index 0c0c1eda9..714458ad0 100644 --- a/src/libImaging/ImagingUtils.h +++ b/src/libImaging/ImagingUtils.h @@ -21,7 +21,7 @@ #define DIV255(a, tmp) (tmp = (a) + 128, SHIFTFORDIV255(tmp)) -#define BLEND(mask, in1, in2, tmp1) DIV255(in1 *(255 - mask) + in2 * mask, tmp1) +#define BLEND(mask, in1, in2, tmp1) DIV255(in1 * (255 - mask) + in2 * mask, tmp1) #define PREBLEND(mask, in1, in2, tmp1) (MULDIV255(in1, (255 - mask), tmp1) + in2) diff --git a/src/libImaging/Jpeg2KDecode.c b/src/libImaging/Jpeg2KDecode.c index 78a09bb83..dd066c10b 100644 --- a/src/libImaging/Jpeg2KDecode.c +++ b/src/libImaging/Jpeg2KDecode.c @@ -183,9 +183,9 @@ j2ku_gray_i( UINT16 *row = (UINT16 *)im->image[y0 + y] + x0; for (x = 0; x < w; ++x) { UINT16 pixel = j2ku_shift(offset + *data++, shift); - #ifdef WORDS_BIGENDIAN - pixel = (pixel >> 8) | (pixel << 8); - #endif +#ifdef WORDS_BIGENDIAN + pixel = (pixel >> 8) | (pixel << 8); +#endif *row++ = pixel; } } @@ -778,7 +778,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) { color_space = OPJ_CLRSPC_SYCC; break; } - break; + break; } } @@ -864,7 +864,7 @@ j2k_decode_entry(Imaging im, ImagingCodecState state) { a, and then a malicious file could have a smaller tile_bytes */ - for (n=0; n < tile_info.nb_comps; n++) { + for (n = 0; n < tile_info.nb_comps; n++) { // see csize /acsize calcs int csize = (image->comps[n].prec + 7) >> 3; csize = (csize == 3) ? 4 : csize; diff --git a/src/libImaging/Jpeg2KEncode.c b/src/libImaging/Jpeg2KEncode.c index 3295373fd..7f1aeaddb 100644 --- a/src/libImaging/Jpeg2KEncode.c +++ b/src/libImaging/Jpeg2KEncode.c @@ -383,8 +383,7 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) { float *pq; if (len > 0) { - if ((size_t)len > - sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) { + if ((size_t)len > sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0])) { len = sizeof(params.tcp_rates) / sizeof(params.tcp_rates[0]); } @@ -464,7 +463,8 @@ j2k_encode_entry(Imaging im, ImagingCodecState state) { } if (!context->num_resolutions) { - while (tile_width < (1U << (params.numresolution - 1U)) || tile_height < (1U << (params.numresolution - 1U))) { + while (tile_width < (1U << (params.numresolution - 1U)) || + tile_height < (1U << (params.numresolution - 1U))) { params.numresolution -= 1; } } diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c index 00f3d5f74..bcbe65aa4 100644 --- a/src/libImaging/JpegEncode.c +++ b/src/libImaging/JpegEncode.c @@ -145,8 +145,8 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { case JCS_EXT_RGBX: #endif switch (context->subsampling) { - case -1: /* Default */ - case 0: /* No subsampling */ + case -1: /* Default */ + case 0: /* No subsampling */ break; default: /* Would subsample the green and blue @@ -305,7 +305,11 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { case 4: if (context->comment) { - jpeg_write_marker(&context->cinfo, JPEG_COM, (unsigned char *)context->comment, context->comment_size); + jpeg_write_marker( + &context->cinfo, + JPEG_COM, + (unsigned char *)context->comment, + context->comment_size); } state->state++; @@ -342,7 +346,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } jpeg_finish_compress(&context->cinfo); -cleanup: + cleanup: /* Clean up */ if (context->comment) { free(context->comment); diff --git a/src/libImaging/Paste.c b/src/libImaging/Paste.c index 6684b11ef..a018225b2 100644 --- a/src/libImaging/Paste.c +++ b/src/libImaging/Paste.c @@ -432,11 +432,10 @@ fill_mask_L( } } else { - int alpha_channel = strcmp(imOut->mode, "RGBa") == 0 || - strcmp(imOut->mode, "RGBA") == 0 || - strcmp(imOut->mode, "La") == 0 || - strcmp(imOut->mode, "LA") == 0 || - strcmp(imOut->mode, "PA") == 0; + int alpha_channel = + strcmp(imOut->mode, "RGBa") == 0 || strcmp(imOut->mode, "RGBA") == 0 || + strcmp(imOut->mode, "La") == 0 || strcmp(imOut->mode, "LA") == 0 || + strcmp(imOut->mode, "PA") == 0; for (y = 0; y < ysize; y++) { UINT8 *out = (UINT8 *)imOut->image[y + dy] + dx * pixelsize; UINT8 *mask = (UINT8 *)imMask->image[y + sy] + sx; diff --git a/src/libImaging/Point.c b/src/libImaging/Point.c index 8883578cb..dd06f3940 100644 --- a/src/libImaging/Point.c +++ b/src/libImaging/Point.c @@ -134,7 +134,7 @@ ImagingPoint(Imaging imIn, const char *mode, const void *table) { ImagingSectionCookie cookie; Imaging imOut; im_point_context context; - void (*point)(Imaging imIn, Imaging imOut, im_point_context * context); + void (*point)(Imaging imIn, Imaging imOut, im_point_context *context); if (!imIn) { return (Imaging)ImagingError_ModeError(); diff --git a/src/libImaging/Quant.c b/src/libImaging/Quant.c index 2582830c4..cdc614536 100644 --- a/src/libImaging/Quant.c +++ b/src/libImaging/Quant.c @@ -260,8 +260,7 @@ mergesort_pixels(PixelList *head, int i) { return head; } for (c = t = head; c && t; - c = c->next[i], t = (t->next[i]) ? t->next[i]->next[i] : NULL) - ; + c = c->next[i], t = (t->next[i]) ? t->next[i]->next[i] : NULL); if (c) { if (c->prev[i]) { c->prev[i]->next[i] = NULL; @@ -354,12 +353,10 @@ splitlists( for (_i = 0; _i < 3; _i++) { for (_nextCount[_i] = 0, _nextTest = h[_i]; _nextTest && _nextTest->next[_i]; - _nextTest = _nextTest->next[_i], _nextCount[_i]++) - ; + _nextTest = _nextTest->next[_i], _nextCount[_i]++); for (_prevCount[_i] = 0, _prevTest = t[_i]; _prevTest && _prevTest->prev[_i]; - _prevTest = _prevTest->prev[_i], _prevCount[_i]++) - ; + _prevTest = _prevTest->prev[_i], _prevCount[_i]++); if (_nextTest != t[_i]) { printf("next-list of axis %d does not end at tail\n", _i); exit(1); @@ -368,10 +365,8 @@ splitlists( printf("prev-list of axis %d does not end at head\n", _i); exit(1); } - for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i]) - ; - for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i]) - ; + for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i]); + for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i]); if (_nextTest != h[_i]) { printf("next-list of axis %d does not loop back to head\n", _i); exit(1); @@ -548,22 +543,18 @@ split(BoxNode *node) { for (_i = 0; _i < 3; _i++) { for (_nextCount[_i] = 0, _nextTest = node->head[_i]; _nextTest && _nextTest->next[_i]; - _nextTest = _nextTest->next[_i], _nextCount[_i]++) - ; + _nextTest = _nextTest->next[_i], _nextCount[_i]++); for (_prevCount[_i] = 0, _prevTest = node->tail[_i]; _prevTest && _prevTest->prev[_i]; - _prevTest = _prevTest->prev[_i], _prevCount[_i]++) - ; + _prevTest = _prevTest->prev[_i], _prevCount[_i]++); if (_nextTest != node->tail[_i]) { printf("next-list of axis %d does not end at tail\n", _i); } if (_prevTest != node->head[_i]) { printf("prev-list of axis %d does not end at head\n", _i); } - for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i]) - ; - for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i]) - ; + for (; _nextTest && _nextTest->prev[_i]; _nextTest = _nextTest->prev[_i]); + for (; _prevTest && _prevTest->next[_i]; _prevTest = _prevTest->next[_i]); if (_nextTest != node->head[_i]) { printf("next-list of axis %d does not loop back to head\n", _i); } @@ -668,8 +659,7 @@ median_cut(PixelList *hl[3], uint32_t imPixelCount, int nPixels) { return NULL; } for (i = 0; i < 3; i++) { - for (tl[i] = hl[i]; tl[i] && tl[i]->next[i]; tl[i] = tl[i]->next[i]) - ; + for (tl[i] = hl[i]; tl[i] && tl[i]->next[i]; tl[i] = tl[i]->next[i]); root->head[i] = hl[i]; root->tail[i] = tl[i]; } @@ -832,16 +822,9 @@ build_distance_tables( } for (i = 0; i < nEntries; i++) { for (j = 0; j < nEntries; j++) { - dwi[j] = (DistanceWithIndex){ - &(avgDist[i * nEntries + j]), - j - }; + dwi[j] = (DistanceWithIndex){&(avgDist[i * nEntries + j]), j}; } - qsort( - dwi, - nEntries, - sizeof(DistanceWithIndex), - _distance_index_cmp); + qsort(dwi, nEntries, sizeof(DistanceWithIndex), _distance_index_cmp); for (j = 0; j < nEntries; j++) { avgDistSortKey[i * nEntries + j] = dwi[j].distance; } @@ -1213,7 +1196,7 @@ k_means( compute_palette_from_quantized_pixels( pixelData, nPixels, paletteData, nPaletteEntries, avg, count, qp); if (!build_distance_tables( - avgDist, avgDistSortKey, paletteData, nPaletteEntries)) { + avgDist, avgDistSortKey, paletteData, nPaletteEntries)) { goto error_3; } built = 1; @@ -1452,15 +1435,17 @@ quantize( hashtable_insert(h2, pixelData[i], bestmatch); } if (qp[i] != bestmatch) { - printf ("discrepancy in matching algorithms pixel %d [%d %d] %f %f\n", - i,qp[i],bestmatch, - sqrt((double)(_SQR(pixelData[i].c.r-p[qp[i]].c.r)+ - _SQR(pixelData[i].c.g-p[qp[i]].c.g)+ - _SQR(pixelData[i].c.b-p[qp[i]].c.b))), - sqrt((double)(_SQR(pixelData[i].c.r-p[bestmatch].c.r)+ - _SQR(pixelData[i].c.g-p[bestmatch].c.g)+ - _SQR(pixelData[i].c.b-p[bestmatch].c.b))) - ); + printf( + "discrepancy in matching algorithms pixel %d [%d %d] %f %f\n", + i, + qp[i], + bestmatch, + sqrt((double)(_SQR(pixelData[i].c.r - p[qp[i]].c.r) + + _SQR(pixelData[i].c.g - p[qp[i]].c.g) + + _SQR(pixelData[i].c.b - p[qp[i]].c.b))), + sqrt((double)(_SQR(pixelData[i].c.r - p[bestmatch].c.r) + + _SQR(pixelData[i].c.g - p[bestmatch].c.g) + + _SQR(pixelData[i].c.b - p[bestmatch].c.b)))); } } hashtable_free(h2); diff --git a/src/libImaging/QuantOctree.c b/src/libImaging/QuantOctree.c index 5e79bce35..1331a30ad 100644 --- a/src/libImaging/QuantOctree.c +++ b/src/libImaging/QuantOctree.c @@ -38,7 +38,7 @@ typedef struct _ColorBucket { uint64_t g; uint64_t b; uint64_t a; -} * ColorBucket; +} *ColorBucket; typedef struct _ColorCube { unsigned int rBits, gBits, bBits, aBits; @@ -47,7 +47,7 @@ typedef struct _ColorCube { unsigned long size; ColorBucket buckets; -} * ColorCube; +} *ColorCube; #define MAX(a, b) (a) > (b) ? (a) : (b) diff --git a/src/libImaging/Reduce.c b/src/libImaging/Reduce.c index 60928d2bc..61566f0c5 100644 --- a/src/libImaging/Reduce.c +++ b/src/libImaging/Reduce.c @@ -2,7 +2,7 @@ #include -#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f)-0.5F)) +#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f) - 0.5F)) UINT32 division_UINT32(int divider, int result_bits) { diff --git a/src/libImaging/Resample.c b/src/libImaging/Resample.c index cf79d8a4e..59c27b3f4 100644 --- a/src/libImaging/Resample.c +++ b/src/libImaging/Resample.c @@ -2,7 +2,7 @@ #include -#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f)-0.5F)) +#define ROUND_UP(f) ((int)((f) >= 0.0 ? (f) + 0.5F : (f) - 0.5F)) struct filter { double (*filter)(double x); diff --git a/src/libImaging/SgiRleDecode.c b/src/libImaging/SgiRleDecode.c index 4eef44ba5..89dedb525 100644 --- a/src/libImaging/SgiRleDecode.c +++ b/src/libImaging/SgiRleDecode.c @@ -113,7 +113,8 @@ expandrow(UINT8 *dest, UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer } static int -expandrow2(UINT8 *dest, const UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer) { +expandrow2( + UINT8 *dest, const UINT8 *src, int n, int z, int xsize, UINT8 *end_of_buffer) { UINT8 pixel, count; int x = 0; @@ -197,7 +198,6 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t return -1; } - /* decoder initialization */ state->count = 0; state->y = 0; @@ -252,7 +252,7 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t c->rlelength, im->bands, im->xsize, - &ptr[c->bufsize-1]); + &ptr[c->bufsize - 1]); } else { status = expandrow2( &state->buffer[c->channo * 2], @@ -260,7 +260,7 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t c->rlelength, im->bands, im->xsize, - &ptr[c->bufsize-1]); + &ptr[c->bufsize - 1]); } if (status == -1) { state->errcode = IMAGING_CODEC_OVERRUN; @@ -268,7 +268,6 @@ ImagingSgiRleDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t } else if (status == 1) { goto sgi_finish_decode; } - } /* store decompressed data in image */ diff --git a/src/libImaging/Storage.c b/src/libImaging/Storage.c index b1b03c515..b27195a35 100644 --- a/src/libImaging/Storage.c +++ b/src/libImaging/Storage.c @@ -418,9 +418,8 @@ ImagingAllocateArray(Imaging im, int dirty, int block_size) { } im->blocks[current_block] = block; /* Bulletproof code from libc _int_memalign */ - aligned_ptr = (char *)( - ((size_t) (block.ptr + arena->alignment - 1)) & - -((Py_ssize_t) arena->alignment)); + aligned_ptr = (char *)(((size_t)(block.ptr + arena->alignment - 1)) & + -((Py_ssize_t)arena->alignment)); } im->image[y] = aligned_ptr + aligned_linesize * line_in_block; diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index e3b81590e..858de9332 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -60,7 +60,11 @@ _tiffReadProc(thandle_t hdata, tdata_t buf, tsize_t size) { dump_state(state); if (state->loc > state->eof) { - TIFFError("_tiffReadProc", "Invalid Read at loc %" PRIu64 ", eof: %" PRIu64, state->loc, state->eof); + TIFFError( + "_tiffReadProc", + "Invalid Read at loc %" PRIu64 ", eof: %" PRIu64, + state->loc, + state->eof); return 0; } to_read = min(size, min(state->size, (tsize_t)state->eof) - (tsize_t)state->loc); @@ -217,7 +221,12 @@ ImagingLibTiffInit(ImagingCodecState state, int fp, uint32_t offset) { } int -_pickUnpackers(Imaging im, ImagingCodecState state, TIFF *tiff, uint16_t planarconfig, ImagingShuffler *unpackers) { +_pickUnpackers( + Imaging im, + ImagingCodecState state, + TIFF *tiff, + uint16_t planarconfig, + ImagingShuffler *unpackers) { // if number of bands is 1, there is no difference with contig case if (planarconfig == PLANARCONFIG_SEPARATE && im->bands > 1) { uint16_t bits_per_sample = 8; @@ -232,10 +241,14 @@ _pickUnpackers(Imaging im, ImagingCodecState state, TIFF *tiff, uint16_t planarc // We'll pick appropriate set of unpackers depending on planar_configuration // It does not matter if data is RGB(A), CMYK or LUV really, // we just copy it plane by plane - unpackers[0] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); - unpackers[1] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); - unpackers[2] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); - unpackers[3] = ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); + unpackers[0] = + ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "R;16N" : "R", NULL); + unpackers[1] = + ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "G;16N" : "G", NULL); + unpackers[2] = + ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "B;16N" : "B", NULL); + unpackers[3] = + ImagingFindUnpacker("RGBA", bits_per_sample == 16 ? "A;16N" : "A", NULL); return im->bands; } else { @@ -247,10 +260,10 @@ _pickUnpackers(Imaging im, ImagingCodecState state, TIFF *tiff, uint16_t planarc int _decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) { - // To avoid dealing with YCbCr subsampling and other complications, let libtiff handle it - // Use a TIFFRGBAImage wrapping the tiff image, and let libtiff handle - // all of the conversion. Metadata read from the TIFFRGBAImage could - // be different from the metadata that the base tiff returns. + // To avoid dealing with YCbCr subsampling and other complications, let libtiff + // handle it Use a TIFFRGBAImage wrapping the tiff image, and let libtiff handle all + // of the conversion. Metadata read from the TIFFRGBAImage could be different from + // the metadata that the base tiff returns. INT32 current_row; UINT8 *new_data; @@ -259,17 +272,16 @@ _decodeAsRGBA(Imaging im, ImagingCodecState state, TIFF *tiff) { TIFFRGBAImage img; char emsg[1024] = ""; - // Since using TIFFRGBAImage* functions, we can read whole tiff into rastrr in one call - // Let's select smaller block size. Multiplying image width by (tile length OR rows per strip) - // gives us manageable block size in pixels + // Since using TIFFRGBAImage* functions, we can read whole tiff into rastrr in one + // call Let's select smaller block size. Multiplying image width by (tile length OR + // rows per strip) gives us manageable block size in pixels if (TIFFIsTiled(tiff)) { ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_TILELENGTH, &rows_per_block); - } - else { + } else { ret = TIFFGetFieldDefaulted(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_block); } - if (ret != 1 || rows_per_block==(UINT32)(-1)) { + if (ret != 1 || rows_per_block == (UINT32)(-1)) { rows_per_block = state->ysize; } @@ -357,7 +369,12 @@ decodergba_err: } int -_decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { +_decodeTile( + Imaging im, + ImagingCodecState state, + TIFF *tiff, + int planes, + ImagingShuffler *unpackers) { INT32 x, y, tile_y, current_tile_length, current_tile_width; UINT32 tile_width, tile_length; tsize_t tile_bytes_size, row_byte_size; @@ -396,7 +413,8 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging if (tile_bytes_size > ((tile_length * state->bits / planes + 7) / 8) * tile_width) { // If the tile size as expected by LibTiff isn't what we're expecting, abort. - // man: TIFFTileSize returns the equivalent size for a tile of data as it would be returned in a + // man: TIFFTileSize returns the equivalent size for a tile of data as it + // would be returned in a // call to TIFFReadTile ... state->errcode = IMAGING_CODEC_BROKEN; return -1; @@ -428,19 +446,24 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging TRACE(("Read tile at %dx%d; \n\n", x, y)); - current_tile_width = min((INT32) tile_width, state->xsize - x); - current_tile_length = min((INT32) tile_length, state->ysize - y); + current_tile_width = min((INT32)tile_width, state->xsize - x); + current_tile_length = min((INT32)tile_length, state->ysize - y); // iterate over each line in the tile and stuff data into image for (tile_y = 0; tile_y < current_tile_length; tile_y++) { - TRACE(("Writing tile data at %dx%d using tile_width: %d; \n", tile_y + y, x, current_tile_width)); + TRACE( + ("Writing tile data at %dx%d using tile_width: %d; \n", + tile_y + y, + x, + current_tile_width)); // UINT8 * bbb = state->buffer + tile_y * row_byte_size; - // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); + // TRACE(("chars: %x%x%x%x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], + // ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); - shuffler((UINT8*) im->image[tile_y + y] + x * im->pixelsize, - state->buffer + tile_y * row_byte_size, - current_tile_width - ); + shuffler( + (UINT8 *)im->image[tile_y + y] + x * im->pixelsize, + state->buffer + tile_y * row_byte_size, + current_tile_width); } } } @@ -450,7 +473,12 @@ _decodeTile(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imaging } int -_decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, ImagingShuffler *unpackers) { +_decodeStrip( + Imaging im, + ImagingCodecState state, + TIFF *tiff, + int planes, + ImagingShuffler *unpackers) { INT32 strip_row = 0; UINT8 *new_data; UINT32 rows_per_strip; @@ -458,7 +486,7 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin tsize_t strip_size, row_byte_size, unpacker_row_byte_size; ret = TIFFGetField(tiff, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); - if (ret != 1 || rows_per_strip==(UINT32)(-1)) { + if (ret != 1 || rows_per_strip == (UINT32)(-1)) { rows_per_strip = state->ysize; } @@ -478,7 +506,8 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin unpacker_row_byte_size = (state->xsize * state->bits / planes + 7) / 8; if (strip_size > (unpacker_row_byte_size * rows_per_strip)) { // If the strip size as expected by LibTiff isn't what we're expecting, abort. - // man: TIFFStripSize returns the equivalent size for a strip of data as it would be returned in a + // man: TIFFStripSize returns the equivalent size for a strip of data as it + // would be returned in a // call to TIFFReadEncodedStrip ... state->errcode = IMAGING_CODEC_BROKEN; return -1; @@ -513,8 +542,13 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin int plane; for (plane = 0; plane < planes; plane++) { ImagingShuffler shuffler = unpackers[plane]; - if (TIFFReadEncodedStrip(tiff, TIFFComputeStrip(tiff, state->y, plane), (tdata_t)state->buffer, strip_size) == -1) { - TRACE(("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))); + if (TIFFReadEncodedStrip( + tiff, + TIFFComputeStrip(tiff, state->y, plane), + (tdata_t)state->buffer, + strip_size) == -1) { + TRACE( + ("Decode Error, strip %d\n", TIFFComputeStrip(tiff, state->y, 0))); state->errcode = IMAGING_CODEC_BROKEN; return -1; } @@ -523,16 +557,17 @@ _decodeStrip(Imaging im, ImagingCodecState state, TIFF *tiff, int planes, Imagin // iterate over each row in the strip and stuff data into image for (strip_row = 0; - strip_row < min((INT32) rows_per_strip, state->ysize - state->y); + strip_row < min((INT32)rows_per_strip, state->ysize - state->y); strip_row++) { TRACE(("Writing data into line %d ; \n", state->y + strip_row)); - // UINT8 * bbb = state->buffer + strip_row * (state->bytes / rows_per_strip); - // TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); + // UINT8 * bbb = state->buffer + strip_row * (state->bytes / + // rows_per_strip); TRACE(("chars: %x %x %x %x\n", ((UINT8 *)bbb)[0], + // ((UINT8 *)bbb)[1], ((UINT8 *)bbb)[2], ((UINT8 *)bbb)[3])); shuffler( - (UINT8*) im->image[state->y + state->yoff + strip_row] + - state->xoff * im->pixelsize, + (UINT8 *)im->image[state->y + state->yoff + strip_row] + + state->xoff * im->pixelsize, state->buffer + strip_row * row_byte_size, state->xsize); } @@ -666,7 +701,6 @@ ImagingLibTiffDecode( goto decode_err; } - TIFFGetField(tiff, TIFFTAG_PHOTOMETRIC, &photometric); TIFFGetField(tiff, TIFFTAG_COMPRESSION, &compression); TIFFGetFieldDefaulted(tiff, TIFFTAG_PLANARCONFIG, &planarconfig); @@ -675,16 +709,17 @@ ImagingLibTiffDecode( // Let LibTiff read them as RGBA readAsRGBA = photometric == PHOTOMETRIC_YCBCR; - if (readAsRGBA && compression == COMPRESSION_JPEG && planarconfig == PLANARCONFIG_CONTIG) { - // If using new JPEG compression, let libjpeg do RGB conversion for performance reasons + if (readAsRGBA && compression == COMPRESSION_JPEG && + planarconfig == PLANARCONFIG_CONTIG) { + // If using new JPEG compression, let libjpeg do RGB conversion for performance + // reasons TIFFSetField(tiff, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); readAsRGBA = 0; } if (readAsRGBA) { _decodeAsRGBA(im, state, tiff); - } - else { + } else { planes = _pickUnpackers(im, state, tiff, planarconfig, unpackers); if (planes <= 0) { goto decode_err; @@ -692,8 +727,7 @@ ImagingLibTiffDecode( if (TIFFIsTiled(tiff)) { _decodeTile(im, state, tiff, planes, unpackers); - } - else { + } else { _decodeStrip(im, state, tiff, planes, unpackers); } @@ -702,20 +736,20 @@ ImagingLibTiffDecode( // so we have to convert it to RGBA if (planes > 3 && strcmp(im->mode, "RGBA") == 0) { uint16_t extrasamples; - uint16_t* sampleinfo; + uint16_t *sampleinfo; ImagingShuffler shuffle; INT32 y; - TIFFGetFieldDefaulted(tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo); + TIFFGetFieldDefaulted( + tiff, TIFFTAG_EXTRASAMPLES, &extrasamples, &sampleinfo); - if (extrasamples >= 1 && - (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA) - ) { + if (extrasamples >= 1 && (sampleinfo[0] == EXTRASAMPLE_UNSPECIFIED || + sampleinfo[0] == EXTRASAMPLE_ASSOCALPHA)) { shuffle = ImagingFindUnpacker("RGBA", "RGBa", NULL); for (y = state->yoff; y < state->ysize; y++) { - UINT8* ptr = (UINT8*) im->image[y + state->yoff] + - state->xoff * im->pixelsize; + UINT8 *ptr = (UINT8 *)im->image[y + state->yoff] + + state->xoff * im->pixelsize; shuffle(ptr, ptr, state->xsize); } } @@ -723,7 +757,7 @@ ImagingLibTiffDecode( } } - decode_err: +decode_err: // TIFFClose in libtiff calls tif_closeproc and TIFFCleanup if (clientstate->fp) { // Pillow will manage the closing of the file rather than libtiff diff --git a/src/libImaging/TiffDecode.h b/src/libImaging/TiffDecode.h index 02454ba03..212b7dee6 100644 --- a/src/libImaging/TiffDecode.h +++ b/src/libImaging/TiffDecode.h @@ -30,7 +30,7 @@ typedef struct { * Should be uint32 for libtiff 3.9.x * uint64 for libtiff 4.0.x */ - TIFF *tiff; /* Used in write */ + TIFF *tiff; /* Used in write */ toff_t eof; int flrealloc; /* may we realloc */ } TIFFSTATE; diff --git a/src/libImaging/Unpack.c b/src/libImaging/Unpack.c index e351aa2f1..1b84cd68f 100644 --- a/src/libImaging/Unpack.c +++ b/src/libImaging/Unpack.c @@ -1437,90 +1437,90 @@ band3I(UINT8 *out, const UINT8 *in, int pixels) { } static void -band016B(UINT8* out, const UINT8* in, int pixels) -{ +band016B(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 0 only, big endian */ for (i = 0; i < pixels; i++) { out[0] = in[0]; - out += 4; in += 2; + out += 4; + in += 2; } } static void -band116B(UINT8* out, const UINT8* in, int pixels) -{ +band116B(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 1 only, big endian */ for (i = 0; i < pixels; i++) { out[1] = in[0]; - out += 4; in += 2; + out += 4; + in += 2; } } static void -band216B(UINT8* out, const UINT8* in, int pixels) -{ +band216B(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 2 only, big endian */ for (i = 0; i < pixels; i++) { out[2] = in[0]; - out += 4; in += 2; + out += 4; + in += 2; } } static void -band316B(UINT8* out, const UINT8* in, int pixels) -{ +band316B(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 3 only, big endian */ for (i = 0; i < pixels; i++) { out[3] = in[0]; - out += 4; in += 2; + out += 4; + in += 2; } } static void -band016L(UINT8* out, const UINT8* in, int pixels) -{ +band016L(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 0 only, little endian */ for (i = 0; i < pixels; i++) { out[0] = in[1]; - out += 4; in += 2; + out += 4; + in += 2; } } static void -band116L(UINT8* out, const UINT8* in, int pixels) -{ +band116L(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 1 only, little endian */ for (i = 0; i < pixels; i++) { out[1] = in[1]; - out += 4; in += 2; + out += 4; + in += 2; } } static void -band216L(UINT8* out, const UINT8* in, int pixels) -{ +band216L(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 2 only, little endian */ for (i = 0; i < pixels; i++) { out[2] = in[1]; - out += 4; in += 2; + out += 4; + in += 2; } } static void -band316L(UINT8* out, const UINT8* in, int pixels) -{ +band316L(UINT8 *out, const UINT8 *in, int pixels) { int i; /* band 3 only, little endian */ for (i = 0; i < pixels; i++) { out[3] = in[1]; - out += 4; in += 2; + out += 4; + in += 2; } } @@ -1687,7 +1687,6 @@ static struct { {"RGB", "G;16N", 16, band116L}, {"RGB", "B;16N", 16, band216L}, - {"RGBA", "R;16N", 16, band016L}, {"RGBA", "G;16N", 16, band116L}, {"RGBA", "B;16N", 16, band216L}, diff --git a/src/path.c b/src/path.c index cc0698c4d..6bc90abed 100644 --- a/src/path.c +++ b/src/path.c @@ -162,24 +162,24 @@ PyPath_Flatten(PyObject *data, double **pxy) { return -1; } -#define assign_item_to_array(op, decref) \ -if (PyFloat_Check(op)) { \ - xy[j++] = PyFloat_AS_DOUBLE(op); \ -} else if (PyLong_Check(op)) { \ - xy[j++] = (float)PyLong_AS_LONG(op); \ -} else if (PyNumber_Check(op)) { \ - xy[j++] = PyFloat_AsDouble(op); \ -} else if (PyArg_ParseTuple(op, "dd", &x, &y)) { \ - xy[j++] = x; \ - xy[j++] = y; \ -} else { \ - PyErr_SetString(PyExc_ValueError, "incorrect coordinate type"); \ - if (decref) { \ - Py_DECREF(op); \ - } \ - free(xy); \ - return -1; \ -} +#define assign_item_to_array(op, decref) \ + if (PyFloat_Check(op)) { \ + xy[j++] = PyFloat_AS_DOUBLE(op); \ + } else if (PyLong_Check(op)) { \ + xy[j++] = (float)PyLong_AS_LONG(op); \ + } else if (PyNumber_Check(op)) { \ + xy[j++] = PyFloat_AsDouble(op); \ + } else if (PyArg_ParseTuple(op, "dd", &x, &y)) { \ + xy[j++] = x; \ + xy[j++] = y; \ + } else { \ + PyErr_SetString(PyExc_ValueError, "incorrect coordinate type"); \ + if (decref) { \ + Py_DECREF(op); \ + } \ + free(xy); \ + return -1; \ + } /* Copy table to path array */ if (PyList_Check(data)) { From c0cb417a44ac705e573e56582e7d9979dccf93ec Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 25 Apr 2024 21:08:24 +0300 Subject: [PATCH 08/63] Add semicolons to fix indent --- src/_imagingcms.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_imagingcms.c b/src/_imagingcms.c index ba8c81005..dbf7057c5 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -385,7 +385,7 @@ _buildTransform( iRenderingIntent, cmsFLAGS); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if (!hTransform) { PyErr_SetString(PyExc_ValueError, "cannot build transform"); @@ -419,7 +419,7 @@ _buildProofTransform( iProofIntent, cmsFLAGS); - Py_END_ALLOW_THREADS + Py_END_ALLOW_THREADS; if (!hTransform) { PyErr_SetString(PyExc_ValueError, "cannot build proof transform"); From 1420e725664114680ce72ad5b9542229faa39a0f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 18:08:50 +0000 Subject: [PATCH 09/63] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_imagingcms.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/_imagingcms.c b/src/_imagingcms.c index dbf7057c5..1a18525d0 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -387,7 +387,7 @@ _buildTransform( Py_END_ALLOW_THREADS; - if (!hTransform) { + if (!hTransform) { PyErr_SetString(PyExc_ValueError, "cannot build transform"); } @@ -421,7 +421,7 @@ _buildProofTransform( Py_END_ALLOW_THREADS; - if (!hTransform) { + if (!hTransform) { PyErr_SetString(PyExc_ValueError, "cannot build proof transform"); } From 39da704c61b4ae5ec9c4117d34a8eda8f058a18e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Apr 2024 07:10:15 +1000 Subject: [PATCH 10/63] Updated libimagequant to 4.3.1 --- depends/install_imagequant.sh | 2 +- docs/installation/building-from-source.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/depends/install_imagequant.sh b/depends/install_imagequant.sh index 973b4374f..9dd7742ed 100755 --- a/depends/install_imagequant.sh +++ b/depends/install_imagequant.sh @@ -2,7 +2,7 @@ # install libimagequant archive_name=libimagequant -archive_version=4.3.0 +archive_version=4.3.1 archive=$archive_name-$archive_version diff --git a/docs/installation/building-from-source.rst b/docs/installation/building-from-source.rst index 961312b14..7f7dfa6ff 100644 --- a/docs/installation/building-from-source.rst +++ b/docs/installation/building-from-source.rst @@ -68,7 +68,7 @@ Many of Pillow's features require external libraries: * **libimagequant** provides improved color quantization - * Pillow has been tested with libimagequant **2.6-4.3** + * Pillow has been tested with libimagequant **2.6-4.3.1** * Libimagequant is licensed GPLv3, which is more restrictive than the Pillow license, therefore we will not be distributing binaries with libimagequant support enabled. From fd8c6a629594968d02b845a4fa2fa52540a86c7e Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Apr 2024 13:51:32 +1000 Subject: [PATCH 11/63] Do not indent goto labels --- .clang-format | 1 + src/libImaging/GifEncode.c | 16 ++++++++-------- src/libImaging/JpegEncode.c | 2 +- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/.clang-format b/.clang-format index be32e6d1a..3199e330b 100644 --- a/.clang-format +++ b/.clang-format @@ -9,6 +9,7 @@ BinPackParameters: false BreakBeforeBraces: Attach ColumnLimit: 88 DerivePointerAlignment: false +IndentGotoLabels: false IndentWidth: 4 Language: Cpp PointerAlignment: Right diff --git a/src/libImaging/GifEncode.c b/src/libImaging/GifEncode.c index 831ce432c..9e91944fe 100644 --- a/src/libImaging/GifEncode.c +++ b/src/libImaging/GifEncode.c @@ -82,7 +82,7 @@ glzwe( UINT32 end_of_data) { switch (st->entry_state) { case LZW_TRY_IN1: - get_first_byte: +get_first_byte: if (!*in_avail) { if (end_of_data) { goto end_of_data; @@ -94,7 +94,7 @@ glzwe( (*in_avail)--; case LZW_TRY_IN2: - encode_loop: +encode_loop: if (!*in_avail) { if (end_of_data) { st->code = st->head; @@ -127,7 +127,7 @@ glzwe( st->code = st->head; st->put_state = PUT_HEAD; goto put_code; - insert_code_or_clear: /* jump here after put_code */ +insert_code_or_clear: /* jump here after put_code */ if (st->next_code < CODE_LIMIT) { st->codes[st->probe] = (st->next_code << 20) | (st->head << 8) | st->tail; @@ -140,7 +140,7 @@ glzwe( st->code = st->clear_code; st->put_state = PUT_CLEAR; goto put_code; - reset_after_clear: /* jump here after put_code */ +reset_after_clear: /* jump here after put_code */ glzwe_reset(st); } st->head = st->tail; @@ -150,9 +150,9 @@ glzwe( glzwe_reset(st); st->code = st->clear_code; st->put_state = PUT_INIT_CLEAR; - put_code: +put_code: st->code_bits_left = st->code_width; - check_buf_bits: +check_buf_bits: if (!st->buf_bits_left) { /* out buffer full */ case LZW_TRY_OUT1: @@ -190,11 +190,11 @@ glzwe( return GLZW_INTERNAL_ERROR; } - end_of_data: +end_of_data: st->code = st->end_code; st->put_state = PUT_END; goto put_code; - flush_code_buffer: /* jump here after put_code */ +flush_code_buffer: /* jump here after put_code */ if (st->buf_bits_left < 8) { case LZW_TRY_OUT2: if (!*out_avail) { diff --git a/src/libImaging/JpegEncode.c b/src/libImaging/JpegEncode.c index bcbe65aa4..ba8353c2d 100644 --- a/src/libImaging/JpegEncode.c +++ b/src/libImaging/JpegEncode.c @@ -346,7 +346,7 @@ ImagingJpegEncode(Imaging im, ImagingCodecState state, UINT8 *buf, int bytes) { } jpeg_finish_compress(&context->cinfo); - cleanup: +cleanup: /* Clean up */ if (context->comment) { free(context->comment); From 5597f618a3b5cf8e208dded44a2107313c47122d Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Sun, 28 Apr 2024 02:49:42 -0600 Subject: [PATCH 12/63] Change comment style Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/libImaging/GifEncode.c | 4 ++-- src/libImaging/TiffDecode.c | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/libImaging/GifEncode.c b/src/libImaging/GifEncode.c index 9e91944fe..45b67616d 100644 --- a/src/libImaging/GifEncode.c +++ b/src/libImaging/GifEncode.c @@ -116,8 +116,8 @@ encode_loop: st->head = st->codes[st->probe] >> 20; goto encode_loop; } else { - /* Reprobe decrement must be non-zero and relatively prime to table - * size. So, any odd positive number for power-of-2 size. */ + // Reprobe decrement must be non-zero and relatively prime to table + // size. So, any odd positive number for power-of-2 size. if ((st->probe -= ((st->tail << 2) | 1)) < 0) { st->probe += TABLE_SIZE; } diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 858de9332..e6b57e0a7 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -414,8 +414,7 @@ _decodeTile( if (tile_bytes_size > ((tile_length * state->bits / planes + 7) / 8) * tile_width) { // If the tile size as expected by LibTiff isn't what we're expecting, abort. // man: TIFFTileSize returns the equivalent size for a tile of data as it - // would be returned in a - // call to TIFFReadTile ... + // would be returned in a call to TIFFReadTile ... state->errcode = IMAGING_CODEC_BROKEN; return -1; } @@ -507,8 +506,7 @@ _decodeStrip( if (strip_size > (unpacker_row_byte_size * rows_per_strip)) { // If the strip size as expected by LibTiff isn't what we're expecting, abort. // man: TIFFStripSize returns the equivalent size for a strip of data as it - // would be returned in a - // call to TIFFReadEncodedStrip ... + // would be returned in a call to TIFFReadEncodedStrip ... state->errcode = IMAGING_CODEC_BROKEN; return -1; } From 996c053d8995893bcdac7673f0469091990c8f25 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 28 Apr 2024 22:49:56 +1000 Subject: [PATCH 13/63] Change comment style --- src/libImaging/FliDecode.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/libImaging/FliDecode.c b/src/libImaging/FliDecode.c index debe7ddd8..6b2518d35 100644 --- a/src/libImaging/FliDecode.c +++ b/src/libImaging/FliDecode.c @@ -231,9 +231,9 @@ ImagingFliDecode(Imaging im, ImagingCodecState state, UINT8 *buf, Py_ssize_t byt } /* Note, have to check Data + size, not just ptr + size) */ if (data + (state->xsize * state->ysize) > ptr + bytes) { - /* not enough data for frame */ - /* UNDONE Unclear that we're actually going to leave the buffer at - * the right place. */ + // not enough data for frame + // UNDONE Unclear that we're actually going to leave the buffer at + // the right place. return ptr - buf; /* bytes consumed */ } for (y = 0; y < state->ysize; y++) { From 6036d81d973e7b0a4613bb06d4a2d79310eef799 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 4 May 2024 20:51:54 +1000 Subject: [PATCH 14/63] Added type hints --- src/PIL/BlpImagePlugin.py | 6 +++--- src/PIL/BmpImagePlugin.py | 4 ++-- src/PIL/DcxImagePlugin.py | 4 ++-- src/PIL/DdsImagePlugin.py | 2 +- src/PIL/EpsImagePlugin.py | 4 ++-- src/PIL/FliImagePlugin.py | 4 ++-- src/PIL/GifImagePlugin.py | 12 ++++++------ src/PIL/Hdf5StubImagePlugin.py | 2 +- src/PIL/IcnsImagePlugin.py | 2 +- src/PIL/IcoImagePlugin.py | 2 +- src/PIL/ImImagePlugin.py | 4 ++-- src/PIL/Image.py | 7 +++++-- src/PIL/ImageFile.py | 10 +++++----- src/PIL/ImageFilter.py | 5 ++++- src/PIL/ImageWin.py | 4 ++-- src/PIL/MicImagePlugin.py | 4 ++-- src/PIL/MpoImagePlugin.py | 4 ++-- src/PIL/PngImagePlugin.py | 8 ++++---- src/PIL/PsdImagePlugin.py | 9 ++++----- src/PIL/SpiderImagePlugin.py | 6 +++--- src/PIL/TiffImagePlugin.py | 20 ++++++++++---------- src/PIL/WalImageFile.py | 2 +- src/PIL/WebPImagePlugin.py | 4 ++-- 23 files changed, 67 insertions(+), 62 deletions(-) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 8d351ce91..bdf54baae 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -253,7 +253,7 @@ class BlpImageFile(ImageFile.ImageFile): format = "BLP" format_description = "Blizzard Mipmap Format" - def _open(self): + def _open(self) -> None: self.magic = self.fp.read(4) self.fp.seek(5, os.SEEK_CUR) @@ -333,7 +333,7 @@ class _BLPBaseDecoder(ImageFile.PyDecoder): class BLP1Decoder(_BLPBaseDecoder): - def _load(self): + def _load(self) -> None: if self._blp_compression == Format.JPEG: self._decode_jpeg_stream() @@ -418,7 +418,7 @@ class BLP2Decoder(_BLPBaseDecoder): class BLPEncoder(ImageFile.PyEncoder): _pushes_fd = True - def _write_palette(self): + def _write_palette(self) -> bytes: data = b"" palette = self.im.getpalette("RGBA", "RGBA") for i in range(len(palette) // 4): diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index 9ce0fed88..c5d1cd40d 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -283,7 +283,7 @@ class BmpImageFile(ImageFile.ImageFile): ) ] - def _open(self): + def _open(self) -> None: """Open file, check magic number and read header""" # read 14 bytes: magic number, filesize, reserved, header final offset head_data = self.fp.read(14) @@ -376,7 +376,7 @@ class DibImageFile(BmpImageFile): format = "DIB" format_description = "Windows Bitmap" - def _open(self): + def _open(self) -> None: self._bitmap() diff --git a/src/PIL/DcxImagePlugin.py b/src/PIL/DcxImagePlugin.py index b24c16329..1c455b032 100644 --- a/src/PIL/DcxImagePlugin.py +++ b/src/PIL/DcxImagePlugin.py @@ -63,7 +63,7 @@ class DcxImageFile(PcxImageFile): self.is_animated = self.n_frames > 1 self.seek(0) - def seek(self, frame): + def seek(self, frame: int) -> None: if not self._seek_check(frame): return self.frame = frame @@ -71,7 +71,7 @@ class DcxImageFile(PcxImageFile): self.fp.seek(self._offset[frame]) PcxImageFile._open(self) - def tell(self): + def tell(self) -> int: return self.frame diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 3032e4aec..59ee0f8a0 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -331,7 +331,7 @@ class DdsImageFile(ImageFile.ImageFile): format = "DDS" format_description = "DirectDraw Surface" - def _open(self): + def _open(self) -> None: if not _accept(self.fp.read(4)): msg = "not a DDS file" raise SyntaxError(msg) diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index ec6705742..b57daca56 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -178,7 +178,7 @@ class PSFile: self.char = None self.fp.seek(offset, whence) - def readline(self): + def readline(self) -> str: s = [self.char or b""] self.char = None @@ -212,7 +212,7 @@ class EpsImageFile(ImageFile.ImageFile): mode_map = {1: "L", 2: "LAB", 3: "RGB", 4: "CMYK"} - def _open(self): + def _open(self) -> None: (length, offset) = self._find_offset(self.fp) # go to offset - start of "%!PS" diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index 7a233d015..eea2c0c95 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -123,7 +123,7 @@ class FliImageFile(ImageFile.ImageFile): palette[i] = (r, g, b) i += 1 - def seek(self, frame): + def seek(self, frame: int) -> None: if not self._seek_check(frame): return if frame < self.__frame: @@ -162,7 +162,7 @@ class FliImageFile(ImageFile.ImageFile): self.__offset += framesize - def tell(self): + def tell(self) -> int: return self.__frame diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 93be7fefb..26e595819 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -76,7 +76,7 @@ class GifImageFile(ImageFile.ImageFile): global_palette = None - def data(self): + def data(self) -> bytes | None: s = self.fp.read(1) if s and s[0]: return self.fp.read(s[0]) @@ -88,7 +88,7 @@ class GifImageFile(ImageFile.ImageFile): return True return False - def _open(self): + def _open(self) -> None: # Screen s = self.fp.read(13) if not _accept(s): @@ -147,7 +147,7 @@ class GifImageFile(ImageFile.ImageFile): self.seek(current) return self._is_animated - def seek(self, frame): + def seek(self, frame: int) -> None: if not self._seek_check(frame): return if frame < self.__frame: @@ -417,7 +417,7 @@ class GifImageFile(ImageFile.ImageFile): elif k in self.info: del self.info[k] - def load_prepare(self): + def load_prepare(self) -> None: temp_mode = "P" if self._frame_palette else "L" self._prev_im = None if self.__frame == 0: @@ -437,7 +437,7 @@ class GifImageFile(ImageFile.ImageFile): super().load_prepare() - def load_end(self): + def load_end(self) -> None: if self.__frame == 0: if self.mode == "P" and LOADING_STRATEGY == LoadingStrategy.RGB_ALWAYS: if self._frame_transparency is not None: @@ -463,7 +463,7 @@ class GifImageFile(ImageFile.ImageFile): else: self.im.paste(frame_im, self.dispose_extent) - def tell(self): + def tell(self) -> int: return self.__frame diff --git a/src/PIL/Hdf5StubImagePlugin.py b/src/PIL/Hdf5StubImagePlugin.py index f50e6bf16..afbfd1639 100644 --- a/src/PIL/Hdf5StubImagePlugin.py +++ b/src/PIL/Hdf5StubImagePlugin.py @@ -37,7 +37,7 @@ class HDF5StubImageFile(ImageFile.StubImageFile): format = "HDF5" format_description = "HDF5" - def _open(self): + def _open(self) -> None: offset = self.fp.tell() if not _accept(self.fp.read(8)): diff --git a/src/PIL/IcnsImagePlugin.py b/src/PIL/IcnsImagePlugin.py index c2c950863..0a86ba883 100644 --- a/src/PIL/IcnsImagePlugin.py +++ b/src/PIL/IcnsImagePlugin.py @@ -252,7 +252,7 @@ class IcnsImageFile(ImageFile.ImageFile): format = "ICNS" format_description = "Mac OS icns resource" - def _open(self): + def _open(self) -> None: self.icns = IcnsFile(self.fp) self._mode = "RGBA" self.info["sizes"] = self.icns.itersizes() diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index 82b190eb8..eacffbae6 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -302,7 +302,7 @@ class IcoImageFile(ImageFile.ImageFile): format = "ICO" format_description = "Windows Icon" - def _open(self): + def _open(self) -> None: self.ico = IcoFile(self.fp) self.info["sizes"] = self.ico.sizes() self.size = self.ico.entry[0]["dim"] diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index 4613e40b6..0de7d6492 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -278,7 +278,7 @@ class ImImageFile(ImageFile.ImageFile): def is_animated(self): return self.info[FRAMES] > 1 - def seek(self, frame): + def seek(self, frame: int) -> None: if not self._seek_check(frame): return @@ -296,7 +296,7 @@ class ImImageFile(ImageFile.ImageFile): self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))] - def tell(self): + def tell(self) -> int: return self.frame diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 33b3da9a6..0f2e146bb 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1298,7 +1298,10 @@ class Image: self.load() return self._new(self.im.expand(xmargin, ymargin)) - def filter(self, filter): + if TYPE_CHECKING: + from . import ImageFilter + + def filter(self, filter: ImageFilter.Filter | type[ImageFilter.Filter]) -> Image: """ Filters this image using the given filter. For a list of available filters, see the :py:mod:`~PIL.ImageFilter` module. @@ -1310,7 +1313,7 @@ class Image: self.load() - if isinstance(filter, Callable): + if callable(filter): filter = filter() if not hasattr(filter, "filter"): msg = "filter argument should be ImageFilter.Filter instance or class" diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 27885e654..b93e2ad2c 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -311,7 +311,7 @@ class ImageFile(Image.Image): return Image.Image.load(self) - def load_prepare(self): + def load_prepare(self) -> None: # create image memory if necessary if not self.im or self.im.mode != self.mode or self.im.size != self.size: self.im = Image.core.new(self.mode, self.size) @@ -319,7 +319,7 @@ class ImageFile(Image.Image): if self.mode == "P": Image.Image.load(self) - def load_end(self): + def load_end(self) -> None: # may be overridden pass @@ -390,7 +390,7 @@ class Parser: offset = 0 finished = 0 - def reset(self): + def reset(self) -> None: """ (Consumer) Reset the parser. Note that you can only call this method immediately after you've created a parser; parser @@ -605,7 +605,7 @@ def _safe_read(fp, size): class PyCodecState: - def __init__(self): + def __init__(self) -> None: self.xsize = 0 self.ysize = 0 self.xoff = 0 @@ -634,7 +634,7 @@ class PyCodec: """ self.args = args - def cleanup(self): + def cleanup(self) -> None: """ Override to perform codec specific cleanup diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index b2c4950d6..fa9ebd9de 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -16,11 +16,14 @@ # from __future__ import annotations +import abc import functools class Filter: - pass + @abc.abstractmethod + def filter(self, image): + pass class MultibandFilter(Filter): diff --git a/src/PIL/ImageWin.py b/src/PIL/ImageWin.py index 75910d2d9..2c439038d 100644 --- a/src/PIL/ImageWin.py +++ b/src/PIL/ImageWin.py @@ -204,7 +204,7 @@ class Window: def ui_handle_damage(self, x0, y0, x1, y1): pass - def ui_handle_destroy(self): + def ui_handle_destroy(self) -> None: pass def ui_handle_repair(self, dc, x0, y0, x1, y1): @@ -213,7 +213,7 @@ class Window: def ui_handle_resize(self, width, height): pass - def mainloop(self): + def mainloop(self) -> None: Image.core.eventloop() diff --git a/src/PIL/MicImagePlugin.py b/src/PIL/MicImagePlugin.py index 96de386a8..5aef94dfb 100644 --- a/src/PIL/MicImagePlugin.py +++ b/src/PIL/MicImagePlugin.py @@ -38,7 +38,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): format_description = "Microsoft Image Composer" _close_exclusive_fp_after_loading = False - def _open(self): + def _open(self) -> None: # read the OLE directory and see if this is a likely # to be a Microsoft Image Composer file @@ -88,7 +88,7 @@ class MicImageFile(TiffImagePlugin.TiffImageFile): def tell(self): return self.frame - def close(self): + def close(self) -> None: self.__fp.close() self.ole.close() super().close() diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index ac9820bbf..fb6620e75 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -127,7 +127,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): def load_seek(self, pos): self._fp.seek(pos) - def seek(self, frame): + def seek(self, frame: int) -> None: if not self._seek_check(frame): return self.fp = self._fp @@ -149,7 +149,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): self.tile = [("jpeg", (0, 0) + self.size, self.offset, self.tile[0][-1])] self.__frame = frame - def tell(self): + def tell(self) -> int: return self.__frame @staticmethod diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 012e0b61b..39faa0f78 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -800,7 +800,7 @@ class PngImageFile(ImageFile.ImageFile): self.fp.close() self.fp = None - def seek(self, frame): + def seek(self, frame: int) -> None: if not self._seek_check(frame): return if frame < self.__frame: @@ -909,10 +909,10 @@ class PngImageFile(ImageFile.ImageFile): else: self.dispose = None - def tell(self): + def tell(self) -> int: return self.__frame - def load_prepare(self): + def load_prepare(self) -> None: """internal: prepare to read PNG file""" if self.info.get("interlace"): @@ -954,7 +954,7 @@ class PngImageFile(ImageFile.ImageFile): return self.fp.read(read_bytes) - def load_end(self): + def load_end(self) -> None: """internal: finished reading image data""" if self.__idat != 0: self.fp.read(self.__idat) diff --git a/src/PIL/PsdImagePlugin.py b/src/PIL/PsdImagePlugin.py index b15918313..86c1a6763 100644 --- a/src/PIL/PsdImagePlugin.py +++ b/src/PIL/PsdImagePlugin.py @@ -57,7 +57,7 @@ class PsdImageFile(ImageFile.ImageFile): format_description = "Adobe Photoshop" _close_exclusive_fp_after_loading = False - def _open(self): + def _open(self) -> None: read = self.fp.read # @@ -141,23 +141,22 @@ class PsdImageFile(ImageFile.ImageFile): self.frame = 1 self._min_frame = 1 - def seek(self, layer): + def seek(self, layer: int) -> None: if not self._seek_check(layer): return # seek to given layer (1..max) try: - name, mode, bbox, tile = self.layers[layer - 1] + _, mode, _, tile = self.layers[layer - 1] self._mode = mode self.tile = tile self.frame = layer self.fp = self._fp - return name, bbox except IndexError as e: msg = "no such layer" raise EOFError(msg) from e - def tell(self): + def tell(self) -> int: # return layer number (0=image, 1..max=layers) return self.frame diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index 86582fb12..01a39e97c 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -97,7 +97,7 @@ class SpiderImageFile(ImageFile.ImageFile): format_description = "Spider 2D image" _close_exclusive_fp_after_loading = False - def _open(self): + def _open(self) -> None: # check header n = 27 * 4 # read 27 float values f = self.fp.read(n) @@ -165,13 +165,13 @@ class SpiderImageFile(ImageFile.ImageFile): return self._nimages > 1 # 1st image index is zero (although SPIDER imgnumber starts at 1) - def tell(self): + def tell(self) -> int: if self.imgnumber < 1: return 0 else: return self.imgnumber - 1 - def seek(self, frame): + def seek(self, frame: int) -> None: if self.istack == 0: msg = "attempt to seek in a non-stack file" raise EOFError(msg) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index c78c223b3..1be717de1 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1143,7 +1143,7 @@ class TiffImageFile(ImageFile.ImageFile): self.seek(current) return self._n_frames - def seek(self, frame): + def seek(self, frame: int) -> None: """Select a given frame as current image""" if not self._seek_check(frame): return @@ -1198,7 +1198,7 @@ class TiffImageFile(ImageFile.ImageFile): self.__frame = frame self._setup() - def tell(self): + def tell(self) -> int: """Return the current frame number""" return self.__frame @@ -1237,7 +1237,7 @@ class TiffImageFile(ImageFile.ImageFile): return self._load_libtiff() return super().load() - def load_end(self): + def load_end(self) -> None: # allow closing if we're on the first frame, there's no next # This is the ImageFile.load path only, libtiff specific below. if not self.is_animated: @@ -1942,7 +1942,7 @@ class AppendingTiffWriter: self.beginning = self.f.tell() self.setup() - def setup(self): + def setup(self) -> None: # Reset everything. self.f.seek(self.beginning, os.SEEK_SET) @@ -1967,7 +1967,7 @@ class AppendingTiffWriter: self.skipIFDs() self.goToEnd() - def finalize(self): + def finalize(self) -> None: if self.isFirst: return @@ -1990,7 +1990,7 @@ class AppendingTiffWriter: self.f.seek(ifd_offset) self.fixIFD() - def newFrame(self): + def newFrame(self) -> None: # Call this to finish a frame. self.finalize() self.setup() @@ -2013,7 +2013,7 @@ class AppendingTiffWriter: self.f.seek(offset, whence) return self.tell() - def goToEnd(self): + def goToEnd(self) -> None: self.f.seek(0, os.SEEK_END) pos = self.f.tell() @@ -2029,7 +2029,7 @@ class AppendingTiffWriter: self.shortFmt = self.endian + "H" self.tagFormat = self.endian + "HHL" - def skipIFDs(self): + def skipIFDs(self) -> None: while True: ifd_offset = self.readLong() if ifd_offset == 0: @@ -2084,11 +2084,11 @@ class AppendingTiffWriter: msg = f"wrote only {bytes_written} bytes but wanted 4" raise RuntimeError(msg) - def close(self): + def close(self) -> None: self.finalize() self.f.close() - def fixIFD(self): + def fixIFD(self) -> None: num_tags = self.readShort() for i in range(num_tags): diff --git a/src/PIL/WalImageFile.py b/src/PIL/WalImageFile.py index c5bf3e04c..fbd7be6ed 100644 --- a/src/PIL/WalImageFile.py +++ b/src/PIL/WalImageFile.py @@ -32,7 +32,7 @@ class WalImageFile(ImageFile.ImageFile): format = "WAL" format_description = "Quake2 Texture" - def _open(self): + def _open(self) -> None: self._mode = "P" # read header fields diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 9c8d53336..61ae9eae5 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -109,7 +109,7 @@ class WebPImageFile(ImageFile.ImageFile): """ return self._getxmp(self.info["xmp"]) if "xmp" in self.info else {} - def seek(self, frame): + def seek(self, frame: int) -> None: if not self._seek_check(frame): return @@ -174,7 +174,7 @@ class WebPImageFile(ImageFile.ImageFile): def load_seek(self, pos): pass - def tell(self): + def tell(self) -> int: if not _webp.HAVE_WEBPANIM: return super().tell() From b8e3e0a43059dc2c1a1b410ac87fca0772de3af1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 6 May 2024 17:25:18 +0000 Subject: [PATCH 15/63] [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.3.4 → v0.4.3](https://github.com/astral-sh/ruff-pre-commit/compare/v0.3.4...v0.4.3) - [github.com/psf/black-pre-commit-mirror: 24.3.0 → 24.4.2](https://github.com/psf/black-pre-commit-mirror/compare/24.3.0...24.4.2) - [github.com/pre-commit/pre-commit-hooks: v4.5.0 → v4.6.0](https://github.com/pre-commit/pre-commit-hooks/compare/v4.5.0...v4.6.0) - [github.com/python-jsonschema/check-jsonschema: 0.28.1 → 0.28.2](https://github.com/python-jsonschema/check-jsonschema/compare/0.28.1...0.28.2) - [github.com/tox-dev/pyproject-fmt: 1.7.0 → 1.8.0](https://github.com/tox-dev/pyproject-fmt/compare/1.7.0...1.8.0) --- .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 51625eb4c..1272913c9 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.3.4 + rev: v0.4.3 hooks: - id: ruff args: [--exit-non-zero-on-fix] - repo: https://github.com/psf/black-pre-commit-mirror - rev: 24.3.0 + rev: 24.4.2 hooks: - id: black @@ -29,7 +29,7 @@ repos: - id: rst-backticks - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.5.0 + rev: v4.6.0 hooks: - id: check-executables-have-shebangs - id: check-shebang-scripts-are-executable @@ -43,7 +43,7 @@ repos: exclude: ^.github/.*TEMPLATE|^Tests/(fonts|images)/ - repo: https://github.com/python-jsonschema/check-jsonschema - rev: 0.28.1 + rev: 0.28.2 hooks: - id: check-github-workflows - id: check-readthedocs @@ -55,7 +55,7 @@ repos: - id: sphinx-lint - repo: https://github.com/tox-dev/pyproject-fmt - rev: 1.7.0 + rev: 1.8.0 hooks: - id: pyproject-fmt From b17f1e507b1e44246b89938e5e4b5d53716751f0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 7 May 2024 14:01:08 +1000 Subject: [PATCH 16/63] Use f-strings --- Tests/test_file_eps.py | 4 +--- selftest.py | 4 ++-- src/PIL/IptcImagePlugin.py | 2 +- src/PIL/PdfParser.py | 5 +++-- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/Tests/test_file_eps.py b/Tests/test_file_eps.py index d01884f96..1c21aa8ca 100644 --- a/Tests/test_file_eps.py +++ b/Tests/test_file_eps.py @@ -336,9 +336,7 @@ def test_readline_psfile(tmp_path: Path) -> None: strings = ["something", "else", "baz", "bif"] def _test_readline(t: EpsImagePlugin.PSFile, ending: str) -> None: - ending = "Failure with line ending: %s" % ( - "".join("%s" % ord(s) for s in ending) - ) + ending = f"Failure with line ending: {''.join(str(ord(s)) for s in ending)}" assert t.readline().strip("\r\n") == "something", ending assert t.readline().strip("\r\n") == "else", ending assert t.readline().strip("\r\n") == "baz", ending diff --git a/selftest.py b/selftest.py index 661abcddb..9e049367e 100755 --- a/selftest.py +++ b/selftest.py @@ -165,9 +165,9 @@ if __name__ == "__main__": print("Running selftest:") status = doctest.testmod(sys.modules[__name__]) if status[0]: - print("*** %s tests of %d failed." % status) + print(f"*** {status[0]} tests of {status[1]} failed.") exit_status = 1 else: - print("--- %s tests passed." % status[1]) + print(f"--- {status[1]} tests passed.") sys.exit(exit_status) diff --git a/src/PIL/IptcImagePlugin.py b/src/PIL/IptcImagePlugin.py index 409609434..73df83bfb 100644 --- a/src/PIL/IptcImagePlugin.py +++ b/src/PIL/IptcImagePlugin.py @@ -57,7 +57,7 @@ def dump(c: Sequence[int | bytes]) -> None: """.. deprecated:: 10.2.0""" deprecate("IptcImagePlugin.dump", 12) for i in c: - print("%02x" % _i8(i), end=" ") + print(f"{_i8(i):02x}", end=" ") print() diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index c1ed78797..65db70e13 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -825,8 +825,9 @@ class PdfParser: try: stream_len = int(result[b"Length"]) except (TypeError, KeyError, ValueError) as e: - msg = "bad or missing Length in stream dict (%r)" % result.get( - b"Length", None + msg = ( + "bad or missing Length in stream dict " + f"({result.get(b'Length')})" ) raise PdfFormatError(msg) from e stream_data = data[m.end() : m.end() + stream_len] From 7d81cbd0ede0dd9e516f7c3b5e2a42988dd105b5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 7 May 2024 13:59:30 +1000 Subject: [PATCH 17/63] Do not use percent format --- Tests/test_image_access.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index 02c75073a..f37ae6096 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -415,7 +415,9 @@ class TestEmbeddable: int main(int argc, char* argv[]) { - char *home = "%s"; + char *home = \"""" + + sys.prefix.replace("\\", "\\\\") + + """\"; wchar_t *whome = Py_DecodeLocale(home, NULL); Py_SetPythonHome(whome); @@ -432,7 +434,6 @@ int main(int argc, char* argv[]) return 0; } """ - % sys.prefix.replace("\\", "\\\\") ) compiler = getattr(build_ext, "new_compiler")() From ed0867abecd7f4ce8eb300c4896229fcedeee6c4 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 8 May 2024 06:30:43 +1000 Subject: [PATCH 18/63] Set stream length for later use --- src/PIL/PdfParser.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 65db70e13..c43f2da7b 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -823,12 +823,10 @@ class PdfParser: m = cls.re_stream_start.match(data, offset) if m: try: - stream_len = int(result[b"Length"]) - except (TypeError, KeyError, ValueError) as e: - msg = ( - "bad or missing Length in stream dict " - f"({result.get(b'Length')})" - ) + stream_len_str = result.get(b"Length") + stream_len = int(stream_len_str) + except (TypeError, ValueError) as e: + msg = f"bad or missing Length in stream dict ({stream_len_str})" raise PdfFormatError(msg) from e stream_data = data[m.end() : m.end() + stream_len] m = cls.re_stream_end.match(data, m.end() + stream_len) From a3356879fd5b0685b3741d6f43802626a3f5d2e0 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 8 May 2024 17:57:36 +1000 Subject: [PATCH 19/63] Use f-string --- Tests/test_image_access.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index f37ae6096..e55a4d9c1 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -409,15 +409,14 @@ class TestEmbeddable: from setuptools.command import build_ext with open("embed_pil.c", "w", encoding="utf-8") as fh: + home = sys.prefix.replace("\\", "\\\\") fh.write( - """ + f""" #include "Python.h" int main(int argc, char* argv[]) -{ - char *home = \"""" - + sys.prefix.replace("\\", "\\\\") - + """\"; +{{ + char *home = "{home}"; wchar_t *whome = Py_DecodeLocale(home, NULL); Py_SetPythonHome(whome); @@ -432,7 +431,7 @@ int main(int argc, char* argv[]) PyMem_RawFree(whome); return 0; -} +}} """ ) From 57399ce204d79c74c80612c622bb788e20d786e8 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 10 May 2024 22:43:56 +1000 Subject: [PATCH 20/63] Parse _version contents instead of using exec() --- setup.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/setup.py b/setup.py index 7d8e1c1ee..abdd87ea2 100644 --- a/setup.py +++ b/setup.py @@ -23,8 +23,7 @@ from setuptools.command.build_ext import build_ext def get_version(): version_file = "src/PIL/_version.py" with open(version_file, encoding="utf-8") as f: - exec(compile(f.read(), version_file, "exec")) - return locals()["__version__"] + return f.read().split('"')[1] configuration = {} From 18b87c8515941f7131b764d4293e3cdf638ba2ff Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 11 May 2024 10:48:09 +1000 Subject: [PATCH 21/63] Added type hints --- src/PIL/BufrStubImagePlugin.py | 2 +- src/PIL/CurImagePlugin.py | 2 +- src/PIL/FpxImagePlugin.py | 2 +- src/PIL/FtexImagePlugin.py | 2 +- src/PIL/GbrImagePlugin.py | 2 +- src/PIL/GribStubImagePlugin.py | 2 +- src/PIL/ImImagePlugin.py | 2 +- src/PIL/ImageTk.py | 4 ++-- src/PIL/Jpeg2KImagePlugin.py | 4 ++-- src/PIL/JpegImagePlugin.py | 2 +- src/PIL/MpoImagePlugin.py | 2 +- src/PIL/PSDraw.py | 2 +- src/PIL/PdfParser.py | 16 ++++++++-------- src/PIL/PngImagePlugin.py | 6 +++--- src/PIL/PyAccess.py | 2 +- src/PIL/QoiImagePlugin.py | 2 +- src/PIL/WebPImagePlugin.py | 2 +- src/PIL/WmfImagePlugin.py | 2 +- src/PIL/XpmImagePlugin.py | 2 +- 19 files changed, 30 insertions(+), 30 deletions(-) diff --git a/src/PIL/BufrStubImagePlugin.py b/src/PIL/BufrStubImagePlugin.py index 1cbd50d19..271db7258 100644 --- a/src/PIL/BufrStubImagePlugin.py +++ b/src/PIL/BufrStubImagePlugin.py @@ -37,7 +37,7 @@ class BufrStubImageFile(ImageFile.StubImageFile): format = "BUFR" format_description = "BUFR" - def _open(self): + def _open(self) -> None: offset = self.fp.tell() if not _accept(self.fp.read(4)): diff --git a/src/PIL/CurImagePlugin.py b/src/PIL/CurImagePlugin.py index b8790e209..85e2145e7 100644 --- a/src/PIL/CurImagePlugin.py +++ b/src/PIL/CurImagePlugin.py @@ -37,7 +37,7 @@ class CurImageFile(BmpImagePlugin.BmpImageFile): format = "CUR" format_description = "Windows Cursor" - def _open(self): + def _open(self) -> None: offset = self.fp.tell() # check magic diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index cfaf86239..4ba93bb39 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -237,7 +237,7 @@ class FpxImageFile(ImageFile.ImageFile): return ImageFile.ImageFile.load(self) - def close(self): + def close(self) -> None: self.ole.close() super().close() diff --git a/src/PIL/FtexImagePlugin.py b/src/PIL/FtexImagePlugin.py index a746959a3..7fcf81376 100644 --- a/src/PIL/FtexImagePlugin.py +++ b/src/PIL/FtexImagePlugin.py @@ -71,7 +71,7 @@ class FtexImageFile(ImageFile.ImageFile): format = "FTEX" format_description = "Texture File Format (IW2:EOC)" - def _open(self): + def _open(self) -> None: if not _accept(self.fp.read(4)): msg = "not an FTEX file" raise SyntaxError(msg) diff --git a/src/PIL/GbrImagePlugin.py b/src/PIL/GbrImagePlugin.py index 62197e36c..93e89b1e6 100644 --- a/src/PIL/GbrImagePlugin.py +++ b/src/PIL/GbrImagePlugin.py @@ -41,7 +41,7 @@ class GbrImageFile(ImageFile.ImageFile): format = "GBR" format_description = "GIMP brush file" - def _open(self): + def _open(self) -> None: header_size = i32(self.fp.read(4)) if header_size < 20: msg = "not a GIMP brush" diff --git a/src/PIL/GribStubImagePlugin.py b/src/PIL/GribStubImagePlugin.py index a80fe0a23..13bdfa616 100644 --- a/src/PIL/GribStubImagePlugin.py +++ b/src/PIL/GribStubImagePlugin.py @@ -37,7 +37,7 @@ class GribStubImageFile(ImageFile.StubImageFile): format = "GRIB" format_description = "GRIB" - def _open(self): + def _open(self) -> None: offset = self.fp.tell() if not _accept(self.fp.read(8)): diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index 9c16159e9..a325f8552 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -119,7 +119,7 @@ class ImImageFile(ImageFile.ImageFile): format_description = "IFUNC Image Memory" _close_exclusive_fp_after_loading = False - def _open(self): + def _open(self) -> None: # Quick rejection: if there's not an LF among the first # 100 bytes, this is (probably) not a text header. diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 10b2cc69a..2f9d7f505 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -128,7 +128,7 @@ class PhotoImage: if image: self.paste(image) - def __del__(self): + def __del__(self) -> None: name = self.__photo.name self.__photo.name = None try: @@ -219,7 +219,7 @@ class BitmapImage: kw["data"] = image.tobitmap() self.__photo = tkinter.BitmapImage(**kw) - def __del__(self): + def __del__(self) -> None: name = self.__photo.name self.__photo.name = None try: diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 697bad221..81ef32253 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -215,7 +215,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile): format = "JPEG2000" format_description = "JPEG 2000 (ISO 15444)" - def _open(self): + def _open(self) -> None: sig = self.fp.read(4) if sig == b"\xff\x4f\xff\x51": self.codec = "j2k" @@ -267,7 +267,7 @@ class Jpeg2KImageFile(ImageFile.ImageFile): ) ] - def _parse_comment(self): + def _parse_comment(self) -> None: hdr = self.fp.read(2) length = _binary.i16be(hdr) self.fp.seek(length - 2, os.SEEK_CUR) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 715a358a3..7a3c99b6c 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -462,7 +462,7 @@ class JpegImageFile(ImageFile.ImageFile): box = (0, 0, original_size[0] / scale, original_size[1] / scale) return self.mode, box - def load_djpeg(self): + def load_djpeg(self) -> None: # ALTERNATIVE: handle JPEGs via the IJG command line utilities f, path = tempfile.mkstemp() diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index fb6620e75..eba35fb4d 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -100,7 +100,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): format_description = "MPO (CIPA DC-007)" _close_exclusive_fp_after_loading = False - def _open(self): + def _open(self) -> None: self.fp.seek(0) # prep the fp in order to pass the JPEG test JpegImagePlugin.JpegImageFile._open(self) self._after_jpeg_open() diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py index 848fc2f71..49c06ce13 100644 --- a/src/PIL/PSDraw.py +++ b/src/PIL/PSDraw.py @@ -54,7 +54,7 @@ class PSDraw: self.fp.write(b"%%EndProlog\n") self.isofont = {} - def end_document(self): + def end_document(self) -> None: """Ends printing. (Write PostScript DSC footer.)""" self.fp.write(b"%%EndDocument\nrestore showpage\n%%End\n") if hasattr(self.fp, "flush"): diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index c43f2da7b..077c9ec8b 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -409,28 +409,28 @@ class PdfParser: self.close() return False # do not suppress exceptions - def start_writing(self): + def start_writing(self) -> None: self.close_buf() self.seek_end() - def close_buf(self): + def close_buf(self) -> None: try: self.buf.close() except AttributeError: pass self.buf = None - def close(self): + def close(self) -> None: if self.should_close_buf: self.close_buf() if self.f is not None and self.should_close_file: self.f.close() self.f = None - def seek_end(self): + def seek_end(self) -> None: self.f.seek(0, os.SEEK_END) - def write_header(self): + def write_header(self) -> None: self.f.write(b"%PDF-1.4\n") def write_comment(self, s): @@ -450,7 +450,7 @@ class PdfParser: ) return self.root_ref - def rewrite_pages(self): + def rewrite_pages(self) -> None: pages_tree_nodes_to_delete = [] for i, page_ref in enumerate(self.orig_pages): page_info = self.cached_objects[page_ref] @@ -529,7 +529,7 @@ class PdfParser: f.write(b"endobj\n") return ref - def del_root(self): + def del_root(self) -> None: if self.root_ref is None: return del self.xref_table[self.root_ref.object_id] @@ -547,7 +547,7 @@ class PdfParser: except ValueError: # cannot mmap an empty file return b"" - def read_pdf_info(self): + def read_pdf_info(self) -> None: self.file_size_total = len(self.buf) self.file_size_this = self.file_size_total - self.start_offset self.read_trailer() diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 1547edde5..76e0abc31 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -179,7 +179,7 @@ class ChunkStream: def __exit__(self, *args): self.close() - def close(self): + def close(self) -> None: self.queue = self.fp = None def push(self, cid, pos, length): @@ -370,14 +370,14 @@ class PngStream(ChunkStream): ) raise ValueError(msg) - def save_rewind(self): + def save_rewind(self) -> None: self.rewind_state = { "info": self.im_info.copy(), "tile": self.im_tile, "seq_num": self._seq_num, } - def rewind(self): + def rewind(self) -> None: self.im_info = self.rewind_state["info"].copy() self.im_tile = self.rewind_state["tile"] self._seq_num = self.rewind_state["seq_num"] diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index 2c831913d..a9da90613 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -70,7 +70,7 @@ class PyAccess: # logger.debug("%s", vals) self._post_init() - def _post_init(self): + def _post_init(self) -> None: pass def __setitem__(self, xy, color): diff --git a/src/PIL/QoiImagePlugin.py b/src/PIL/QoiImagePlugin.py index 2875b8d75..cea8b60da 100644 --- a/src/PIL/QoiImagePlugin.py +++ b/src/PIL/QoiImagePlugin.py @@ -21,7 +21,7 @@ class QoiImageFile(ImageFile.ImageFile): format = "QOI" format_description = "Quite OK Image" - def _open(self): + def _open(self) -> None: if not _accept(self.fp.read(4)): msg = "not a QOI file" raise SyntaxError(msg) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 61ae9eae5..052f253cf 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -43,7 +43,7 @@ class WebPImageFile(ImageFile.ImageFile): __loaded = 0 __logical_frame = 0 - def _open(self): + def _open(self) -> None: if not _webp.HAVE_WEBPANIM: # Legacy mode data, width, height, self._mode, icc_profile, exif = _webp.WebPDecode( diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index 7f045ec7d..b0328657b 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -79,7 +79,7 @@ class WmfStubImageFile(ImageFile.StubImageFile): format = "WMF" format_description = "Windows Metafile" - def _open(self): + def _open(self) -> None: self._inch = None # check placable header diff --git a/src/PIL/XpmImagePlugin.py b/src/PIL/XpmImagePlugin.py index a638547af..88d14e9c2 100644 --- a/src/PIL/XpmImagePlugin.py +++ b/src/PIL/XpmImagePlugin.py @@ -36,7 +36,7 @@ class XpmImageFile(ImageFile.ImageFile): format = "XPM" format_description = "X11 Pixel Map" - def _open(self): + def _open(self) -> None: if not _accept(self.fp.read(9)): msg = "not an XPM file" raise SyntaxError(msg) From db4714c280c96ea4b61f1b3360c55e730ffaf62d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 12 May 2024 21:20:46 +1000 Subject: [PATCH 22/63] Removed helper.py modes --- Tests/helper.py | 27 --------------------------- Tests/test_image.py | 9 ++++----- Tests/test_image_access.py | 13 +++++++------ 3 files changed, 11 insertions(+), 38 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 1297c1c43..5fd4fe332 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -29,33 +29,6 @@ elif "GITHUB_ACTIONS" in os.environ: uploader = "github_actions" -modes = ( - "1", - "L", - "LA", - "La", - "P", - "PA", - "F", - "I", - "I;16", - "I;16L", - "I;16B", - "I;16N", - "RGB", - "RGBA", - "RGBa", - "RGBX", - "BGR;15", - "BGR;16", - "BGR;24", - "CMYK", - "YCbCr", - "HSV", - "LAB", -) - - def upload(a: Image.Image, b: Image.Image) -> str | None: if uploader == "show": # local img.show for errors. diff --git a/Tests/test_image.py b/Tests/test_image.py index e1490d6a0..742d0dfe4 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -31,7 +31,6 @@ from .helper import ( is_big_endian, is_win32, mark_if_feature_version, - modes, skip_unless_feature, ) @@ -46,7 +45,7 @@ def helper_image_new(mode: str, size: tuple[int, int]) -> Image.Image: class TestImage: - @pytest.mark.parametrize("mode", modes) + @pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"]) def test_image_modes_success(self, mode: str) -> None: helper_image_new(mode, (1, 1)) @@ -1027,7 +1026,7 @@ class TestImage: class TestImageBytes: - @pytest.mark.parametrize("mode", modes) + @pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"]) def test_roundtrip_bytes_constructor(self, mode: str) -> None: im = hopper(mode) source_bytes = im.tobytes() @@ -1039,7 +1038,7 @@ class TestImageBytes: reloaded = Image.frombytes(mode, im.size, source_bytes) assert reloaded.tobytes() == source_bytes - @pytest.mark.parametrize("mode", modes) + @pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"]) def test_roundtrip_bytes_method(self, mode: str) -> None: im = hopper(mode) source_bytes = im.tobytes() @@ -1048,7 +1047,7 @@ class TestImageBytes: reloaded.frombytes(source_bytes) assert reloaded.tobytes() == source_bytes - @pytest.mark.parametrize("mode", modes) + @pytest.mark.parametrize("mode", Image.MODES + ["BGR;15", "BGR;16", "BGR;24"]) def test_getdata_putdata(self, mode: str) -> None: if is_big_endian() and mode == "BGR;15": pytest.xfail("Known failure of BGR;15 on big-endian") diff --git a/Tests/test_image_access.py b/Tests/test_image_access.py index e55a4d9c1..9d6006679 100644 --- a/Tests/test_image_access.py +++ b/Tests/test_image_access.py @@ -10,7 +10,7 @@ import pytest from PIL import Image -from .helper import assert_image_equal, hopper, is_win32, modes +from .helper import assert_image_equal, hopper, is_win32 # CFFI imports pycparser which doesn't support PYTHONOPTIMIZE=2 # https://github.com/eliben/pycparser/pull/198#issuecomment-317001670 @@ -205,12 +205,13 @@ class TestImageGetPixel(AccessTest): with pytest.raises(error): im.getpixel((-1, -1)) - @pytest.mark.parametrize("mode", modes) + @pytest.mark.parametrize("mode", Image.MODES) def test_basic(self, mode: str) -> None: - if mode.startswith("BGR;"): - with pytest.warns(DeprecationWarning): - self.check(mode) - else: + self.check(mode) + + @pytest.mark.parametrize("mode", ("BGR;15", "BGR;16", "BGR;24")) + def test_deprecated(self, mode: str) -> None: + with pytest.warns(DeprecationWarning): self.check(mode) def test_list(self) -> None: From 00e5e43da42ec9cd4da6c80ae269536a975f1217 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Sun, 12 May 2024 11:43:08 +0000 Subject: [PATCH 23/63] chore(deps): update dependency cibuildwheel to v2.18.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 45c2af975..8d39ea9bb 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==2.17.0 +cibuildwheel==2.18.0 From a8d154877d92d549f1d18411383441d1c9238e7c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 13 May 2024 18:47:51 +1000 Subject: [PATCH 24/63] Added type hints --- src/PIL/FliImagePlugin.py | 2 +- src/PIL/GifImagePlugin.py | 10 +++++----- src/PIL/GimpPaletteFile.py | 2 +- src/PIL/ImImagePlugin.py | 4 ++-- src/PIL/ImageDraw.py | 7 +++++-- src/PIL/ImageFilter.py | 2 +- src/PIL/ImagePalette.py | 6 +++--- src/PIL/ImageTk.py | 12 ++++++------ src/PIL/Jpeg2KImagePlugin.py | 4 ++-- src/PIL/PdfParser.py | 20 ++++++++++---------- src/PIL/SpiderImagePlugin.py | 10 +++++++--- src/PIL/TiffImagePlugin.py | 14 +++++++------- src/PIL/WebPImagePlugin.py | 2 +- 13 files changed, 51 insertions(+), 44 deletions(-) diff --git a/src/PIL/FliImagePlugin.py b/src/PIL/FliImagePlugin.py index eea2c0c95..dceb83927 100644 --- a/src/PIL/FliImagePlugin.py +++ b/src/PIL/FliImagePlugin.py @@ -132,7 +132,7 @@ class FliImageFile(ImageFile.ImageFile): for f in range(self.__frame + 1, frame + 1): self._seek(f) - def _seek(self, frame): + def _seek(self, frame: int) -> None: if frame == 0: self.__frame = -1 self._fp.seek(self.__rewind) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 26e595819..eede41549 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -82,7 +82,7 @@ class GifImageFile(ImageFile.ImageFile): return self.fp.read(s[0]) return None - def _is_palette_needed(self, p): + def _is_palette_needed(self, p: bytes) -> bool: for i in range(0, len(p), 3): if not (i // 3 == p[i] == p[i + 1] == p[i + 2]): return True @@ -474,7 +474,7 @@ class GifImageFile(ImageFile.ImageFile): RAWMODE = {"1": "L", "L": "L", "P": "P"} -def _normalize_mode(im): +def _normalize_mode(im: Image.Image) -> Image.Image: """ Takes an image (or frame), returns an image in a mode that is appropriate for saving in a Gif. @@ -887,7 +887,7 @@ def _get_optimize(im, info): return used_palette_colors -def _get_color_table_size(palette_bytes): +def _get_color_table_size(palette_bytes: bytes) -> int: # calculate the palette size for the header if not palette_bytes: return 0 @@ -897,7 +897,7 @@ def _get_color_table_size(palette_bytes): return math.ceil(math.log(len(palette_bytes) // 3, 2)) - 1 -def _get_header_palette(palette_bytes): +def _get_header_palette(palette_bytes: bytes) -> bytes: """ Returns the palette, null padded to the next power of 2 (*3) bytes suitable for direct inclusion in the GIF header @@ -915,7 +915,7 @@ def _get_header_palette(palette_bytes): return palette_bytes -def _get_palette_bytes(im): +def _get_palette_bytes(im: Image.Image) -> bytes: """ Gets the palette for inclusion in the gif header diff --git a/src/PIL/GimpPaletteFile.py b/src/PIL/GimpPaletteFile.py index a3109ebaa..2274f1a8b 100644 --- a/src/PIL/GimpPaletteFile.py +++ b/src/PIL/GimpPaletteFile.py @@ -53,5 +53,5 @@ class GimpPaletteFile: self.palette = b"".join(self.palette) - def getpalette(self): + def getpalette(self) -> tuple[bytes, str]: return self.palette, self.rawmode diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index a325f8552..8e949ebaf 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -271,11 +271,11 @@ class ImImageFile(ImageFile.ImageFile): self.tile = [("raw", (0, 0) + self.size, offs, (self.rawmode, 0, -1))] @property - def n_frames(self): + def n_frames(self) -> int: return self.info[FRAMES] @property - def is_animated(self): + def is_animated(self) -> bool: return self.info[FRAMES] > 1 def seek(self, frame: int) -> None: diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index d3efe6486..42f2ee8c7 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -34,7 +34,7 @@ from __future__ import annotations import math import numbers import struct -from typing import Sequence, cast +from typing import TYPE_CHECKING, Sequence, cast from . import Image, ImageColor from ._typing import Coords @@ -92,7 +92,10 @@ class ImageDraw: self.fontmode = "L" # aliasing is okay for other modes self.fill = False - def getfont(self): + if TYPE_CHECKING: + from . import ImageFont + + def getfont(self) -> ImageFont.FreeTypeFont | ImageFont.ImageFont: """ Get the current default font. diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index fa9ebd9de..678bd29a2 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -544,7 +544,7 @@ class Color3DLUT(MultibandFilter): _copy_table=False, ) - def __repr__(self): + def __repr__(self) -> str: r = [ f"{self.__class__.__name__} from {self.table.__class__.__name__}", "size={:d}x{:d}x{:d}".format(*self.size), diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index 770d10025..ae5c5dec0 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -66,7 +66,7 @@ class ImagePalette: def colors(self, colors): self._colors = colors - def copy(self): + def copy(self) -> ImagePalette: new = ImagePalette() new.mode = self.mode @@ -77,7 +77,7 @@ class ImagePalette: return new - def getdata(self): + def getdata(self) -> tuple[str, bytes]: """ Get palette contents in format suitable for the low-level ``im.putpalette`` primitive. @@ -88,7 +88,7 @@ class ImagePalette: return self.rawmode, self.palette return self.mode, self.tobytes() - def tobytes(self): + def tobytes(self) -> bytes: """Convert palette to bytes. .. warning:: This method is experimental. diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 2f9d7f505..6e2e7db1e 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -136,7 +136,7 @@ class PhotoImage: except Exception: pass # ignore internal errors - def __str__(self): + def __str__(self) -> str: """ Get the Tkinter photo image identifier. This method is automatically called by Tkinter whenever a PhotoImage object is passed to a Tkinter @@ -146,7 +146,7 @@ class PhotoImage: """ return str(self.__photo) - def width(self): + def width(self) -> int: """ Get the width of the image. @@ -154,7 +154,7 @@ class PhotoImage: """ return self.__size[0] - def height(self): + def height(self) -> int: """ Get the height of the image. @@ -227,7 +227,7 @@ class BitmapImage: except Exception: pass # ignore internal errors - def width(self): + def width(self) -> int: """ Get the width of the image. @@ -235,7 +235,7 @@ class BitmapImage: """ return self.__size[0] - def height(self): + def height(self) -> int: """ Get the height of the image. @@ -243,7 +243,7 @@ class BitmapImage: """ return self.__size[1] - def __str__(self): + def __str__(self) -> str: """ Get the Tkinter bitmap image identifier. This method is automatically called by Tkinter whenever a BitmapImage object is passed to a Tkinter diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index 81ef32253..ce6342bdb 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -63,12 +63,12 @@ class BoxReader: data = self._read_bytes(size) return struct.unpack(field_format, data) - def read_boxes(self): + def read_boxes(self) -> BoxReader: size = self.remaining_in_box data = self._read_bytes(size) return BoxReader(io.BytesIO(data), size) - def has_next_box(self): + def has_next_box(self) -> bool: if self.has_length: return self.fp.tell() + self.remaining_in_box < self.length else: diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 077c9ec8b..68501d625 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -87,10 +87,10 @@ class IndirectReferenceTuple(NamedTuple): class IndirectReference(IndirectReferenceTuple): - def __str__(self): + def __str__(self) -> str: return f"{self.object_id} {self.generation} R" - def __bytes__(self): + def __bytes__(self) -> bytes: return self.__str__().encode("us-ascii") def __eq__(self, other): @@ -108,7 +108,7 @@ class IndirectReference(IndirectReferenceTuple): class IndirectObjectDef(IndirectReference): - def __str__(self): + def __str__(self) -> str: return f"{self.object_id} {self.generation} obj" @@ -150,7 +150,7 @@ class XrefTable: def __contains__(self, key): return key in self.existing_entries or key in self.new_entries - def __len__(self): + def __len__(self) -> int: return len( set(self.existing_entries.keys()) | set(self.new_entries.keys()) @@ -211,7 +211,7 @@ class PdfName: else: self.name = name.encode("us-ascii") - def name_as_str(self): + def name_as_str(self) -> str: return self.name.decode("us-ascii") def __eq__(self, other): @@ -222,7 +222,7 @@ class PdfName: def __hash__(self): return hash(self.name) - def __repr__(self): + def __repr__(self) -> str: return f"{self.__class__.__name__}({repr(self.name)})" @classmethod @@ -231,7 +231,7 @@ class PdfName: allowed_chars = set(range(33, 127)) - {ord(c) for c in "#%/()<>[]{}"} - def __bytes__(self): + def __bytes__(self) -> bytes: result = bytearray(b"/") for b in self.name: if b in self.allowed_chars: @@ -242,7 +242,7 @@ class PdfName: class PdfArray(List[Any]): - def __bytes__(self): + def __bytes__(self) -> bytes: return b"[ " + b" ".join(pdf_repr(x) for x in self) + b" ]" @@ -286,7 +286,7 @@ class PdfDict(_DictBase): value = time.gmtime(calendar.timegm(value) + offset) return value - def __bytes__(self): + def __bytes__(self) -> bytes: out = bytearray(b"<<") for key, value in self.items(): if value is None: @@ -304,7 +304,7 @@ class PdfBinary: def __init__(self, data): self.data = data - def __bytes__(self): + def __bytes__(self) -> bytes: return b"<%s>" % b"".join(b"%02X" % b for b in self.data) diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index 21509b2d9..5b8ad47f0 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -37,6 +37,7 @@ from __future__ import annotations import os import struct import sys +from typing import TYPE_CHECKING from . import Image, ImageFile @@ -157,11 +158,11 @@ class SpiderImageFile(ImageFile.ImageFile): self._fp = self.fp # FIXME: hack @property - def n_frames(self): + def n_frames(self) -> int: return self._nimages @property - def is_animated(self): + def is_animated(self) -> bool: return self._nimages > 1 # 1st image index is zero (although SPIDER imgnumber starts at 1) @@ -191,8 +192,11 @@ class SpiderImageFile(ImageFile.ImageFile): b = -m * minimum return self.point(lambda i, m=m, b=b: i * m + b).convert("L") + if TYPE_CHECKING: + from . import ImageTk + # returns a ImageTk.PhotoImage object, after rescaling to 0..255 - def tkPhotoImage(self): + def tkPhotoImage(self) -> ImageTk.PhotoImage: from . import ImageTk return ImageTk.PhotoImage(self.convert2byte(), palette=256) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index a7a7e28bd..54faa59c5 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -381,7 +381,7 @@ class IFDRational(Rational): f = self._val.limit_denominator(max_denominator) return f.numerator, f.denominator - def __repr__(self): + def __repr__(self) -> str: return str(float(self._val)) def __hash__(self): @@ -603,7 +603,7 @@ class ImageFileDirectory_v2(_IFDv2Base): self._next = None self._offset = None - def __str__(self): + def __str__(self) -> str: return str(dict(self)) def named(self): @@ -617,7 +617,7 @@ class ImageFileDirectory_v2(_IFDv2Base): for code, value in self.items() } - def __len__(self): + def __len__(self) -> int: return len(set(self._tagdata) | set(self._tags_v2)) def __getitem__(self, tag): @@ -1041,7 +1041,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): ifd.next = original.next # an indicator for multipage tiffs return ifd - def to_v2(self): + def to_v2(self) -> ImageFileDirectory_v2: """Returns an :py:class:`~PIL.TiffImagePlugin.ImageFileDirectory_v2` instance with the same data as is contained in the original @@ -1061,7 +1061,7 @@ class ImageFileDirectory_v1(ImageFileDirectory_v2): def __contains__(self, tag): return tag in self._tags_v1 or tag in self._tagdata - def __len__(self): + def __len__(self) -> int: return len(set(self._tagdata) | set(self._tags_v1)) def __iter__(self): @@ -1154,7 +1154,7 @@ class TiffImageFile(ImageFile.ImageFile): Image._decompression_bomb_check(self.size) self.im = Image.core.new(self.mode, self.size) - def _seek(self, frame): + def _seek(self, frame: int) -> None: self.fp = self._fp # reset buffered io handle in case fp @@ -2003,7 +2003,7 @@ class AppendingTiffWriter: self.close() return False - def tell(self): + def tell(self) -> int: return self.f.tell() - self.offsetOfNewPage def seek(self, offset, whence=io.SEEK_SET): diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 052f253cf..4b8cfe65c 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -144,7 +144,7 @@ class WebPImageFile(ImageFile.ImageFile): timestamp -= duration return data, timestamp, duration - def _seek(self, frame): + def _seek(self, frame: int) -> None: if self.__physical_frame == frame: return # Nothing to do if frame < self.__physical_frame: From e9b15f8091431de34d1a5f82cc0cf954d2cf2d6c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 14 May 2024 10:09:44 +1000 Subject: [PATCH 25/63] Updated harfbuzz to 8.5.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 0d45d5a20..c5b279a33 100755 --- a/.github/workflows/wheels-dependencies.sh +++ b/.github/workflows/wheels-dependencies.sh @@ -16,7 +16,7 @@ ARCHIVE_SDIR=pillow-depends-main # Package versions for fresh source builds FREETYPE_VERSION=2.13.2 -HARFBUZZ_VERSION=8.4.0 +HARFBUZZ_VERSION=8.5.0 LIBPNG_VERSION=1.6.43 JPEGTURBO_VERSION=3.0.2 OPENJPEG_VERSION=2.5.2 diff --git a/winbuild/build_prepare.py b/winbuild/build_prepare.py index 0d6da7754..b654ee8da 100644 --- a/winbuild/build_prepare.py +++ b/winbuild/build_prepare.py @@ -113,7 +113,7 @@ V = { "BROTLI": "1.1.0", "FREETYPE": "2.13.2", "FRIBIDI": "1.0.13", - "HARFBUZZ": "8.4.0", + "HARFBUZZ": "8.5.0", "JPEGTURBO": "3.0.2", "LCMS2": "2.16", "LIBPNG": "1.6.43", From e419fd7ab4a71bc269a9c624c92d8955b372aaed Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 15 May 2024 20:19:09 +1000 Subject: [PATCH 26/63] Added type hints --- src/PIL/DdsImagePlugin.py | 2 +- src/PIL/EpsImagePlugin.py | 4 +-- src/PIL/FtexImagePlugin.py | 2 +- src/PIL/IcoImagePlugin.py | 2 +- src/PIL/ImageFile.py | 4 +-- src/PIL/JpegImagePlugin.py | 2 +- src/PIL/MpoImagePlugin.py | 2 +- src/PIL/PngImagePlugin.py | 74 +++++++++++++++++++++----------------- src/PIL/WebPImagePlugin.py | 2 +- src/PIL/XpmImagePlugin.py | 7 ++-- src/PIL/features.py | 27 +++++++------- 11 files changed, 65 insertions(+), 63 deletions(-) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index b2ddbe44f..1575f2d88 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -472,7 +472,7 @@ class DdsImageFile(ImageFile.ImageFile): else: self.tile = [ImageFile._Tile("raw", extents, 0, rawmode or self.mode)] - def load_seek(self, pos): + def load_seek(self, pos: int) -> None: pass diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index b57daca56..5a44baa49 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -42,7 +42,7 @@ gs_binary: str | bool | None = None gs_windows_binary = None -def has_ghostscript(): +def has_ghostscript() -> bool: global gs_binary, gs_windows_binary if gs_binary is None: if sys.platform.startswith("win"): @@ -404,7 +404,7 @@ class EpsImageFile(ImageFile.ImageFile): self.tile = [] return Image.Image.load(self) - def load_seek(self, pos): + def load_seek(self, pos: int) -> None: # we can't incrementally load, so force ImageFile.parser to # use our custom load method by defining this method. pass diff --git a/src/PIL/FtexImagePlugin.py b/src/PIL/FtexImagePlugin.py index 7fcf81376..5acbb4912 100644 --- a/src/PIL/FtexImagePlugin.py +++ b/src/PIL/FtexImagePlugin.py @@ -103,7 +103,7 @@ class FtexImageFile(ImageFile.ImageFile): self.fp.close() self.fp = BytesIO(data) - def load_seek(self, pos): + def load_seek(self, pos: int) -> None: pass diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index eacffbae6..cea093f9c 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -341,7 +341,7 @@ class IcoImageFile(ImageFile.ImageFile): self.size = im.size - def load_seek(self, pos): + def load_seek(self, pos: int) -> None: # Flag the ImageFile.Parser so that it # just does all the decode at the end. pass diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index b93e2ad2c..33467fc4f 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -324,11 +324,11 @@ class ImageFile(Image.Image): pass # may be defined for contained formats - # def load_seek(self, pos): + # def load_seek(self, pos: int) -> None: # pass # may be defined for blocked formats (e.g. PNG) - # def load_read(self, read_bytes): + # def load_read(self, read_bytes: int) -> bytes: # pass def _seek_check(self, frame): diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 7a3c99b6c..909911dfe 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -408,7 +408,7 @@ class JpegImageFile(ImageFile.ImageFile): msg = "no marker found" raise SyntaxError(msg) - def load_read(self, read_bytes): + def load_read(self, read_bytes: int) -> bytes: """ internal: read more image data For premature EOF and LOAD_TRUNCATED_IMAGES adds EOI marker diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index eba35fb4d..766e1290c 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -124,7 +124,7 @@ class MpoImageFile(JpegImagePlugin.JpegImageFile): # for now we can only handle reading and individual frame extraction self.readonly = 1 - def load_seek(self, pos): + def load_seek(self, pos: int) -> None: self._fp.seek(pos) def seek(self, frame: int) -> None: diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 76e0abc31..c74cbccf1 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -39,6 +39,7 @@ import struct import warnings import zlib from enum import IntEnum +from typing import IO from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence from ._binary import i16be as i16 @@ -149,14 +150,15 @@ def _crc32(data, seed=0): class ChunkStream: - def __init__(self, fp): - self.fp = fp - self.queue = [] + def __init__(self, fp: IO[bytes]) -> None: + self.fp: IO[bytes] | None = fp + self.queue: list[tuple[bytes, int, int]] | None = [] - def read(self): + def read(self) -> tuple[bytes, int, int]: """Fetch a new chunk. Returns header information.""" cid = None + assert self.fp is not None if self.queue: cid, pos, length = self.queue.pop() self.fp.seek(pos) @@ -173,7 +175,7 @@ class ChunkStream: return cid, pos, length - def __enter__(self): + def __enter__(self) -> ChunkStream: return self def __exit__(self, *args): @@ -182,7 +184,8 @@ class ChunkStream: def close(self) -> None: self.queue = self.fp = None - def push(self, cid, pos, length): + def push(self, cid: bytes, pos: int, length: int) -> None: + assert self.queue is not None self.queue.append((cid, pos, length)) def call(self, cid, pos, length): @@ -191,7 +194,7 @@ class ChunkStream: logger.debug("STREAM %r %s %s", cid, pos, length) return getattr(self, f"chunk_{cid.decode('ascii')}")(pos, length) - def crc(self, cid, data): + def crc(self, cid: bytes, data: bytes) -> None: """Read and verify checksum""" # Skip CRC checks for ancillary chunks if allowed to load truncated @@ -201,6 +204,7 @@ class ChunkStream: self.crc_skip(cid, data) return + assert self.fp is not None try: crc1 = _crc32(data, _crc32(cid)) crc2 = i32(self.fp.read(4)) @@ -211,12 +215,13 @@ class ChunkStream: msg = f"broken PNG file (incomplete checksum in {repr(cid)})" raise SyntaxError(msg) from e - def crc_skip(self, cid, data): + def crc_skip(self, cid: bytes, data: bytes) -> None: """Read checksum""" + assert self.fp is not None self.fp.read(4) - def verify(self, endchunk=b"IEND"): + def verify(self, endchunk: bytes = b"IEND") -> list[bytes]: # Simple approach; just calculate checksum for all remaining # blocks. Must be called directly after open. @@ -361,7 +366,7 @@ class PngStream(ChunkStream): self.text_memory = 0 - def check_text_memory(self, chunklen): + def check_text_memory(self, chunklen: int) -> None: self.text_memory += chunklen if self.text_memory > MAX_TEXT_MEMORY: msg = ( @@ -382,7 +387,7 @@ class PngStream(ChunkStream): self.im_tile = self.rewind_state["tile"] self._seq_num = self.rewind_state["seq_num"] - def chunk_iCCP(self, pos, length): + def chunk_iCCP(self, pos: int, length: int) -> bytes: # ICC profile s = ImageFile._safe_read(self.fp, length) # according to PNG spec, the iCCP chunk contains: @@ -409,7 +414,7 @@ class PngStream(ChunkStream): self.im_info["icc_profile"] = icc_profile return s - def chunk_IHDR(self, pos, length): + def chunk_IHDR(self, pos: int, length: int) -> bytes: # image header s = ImageFile._safe_read(self.fp, length) if length < 13: @@ -446,14 +451,14 @@ class PngStream(ChunkStream): msg = "end of PNG image" raise EOFError(msg) - def chunk_PLTE(self, pos, length): + def chunk_PLTE(self, pos: int, length: int) -> bytes: # palette s = ImageFile._safe_read(self.fp, length) if self.im_mode == "P": self.im_palette = "RGB", s return s - def chunk_tRNS(self, pos, length): + def chunk_tRNS(self, pos: int, length: int) -> bytes: # transparency s = ImageFile._safe_read(self.fp, length) if self.im_mode == "P": @@ -473,13 +478,13 @@ class PngStream(ChunkStream): self.im_info["transparency"] = i16(s), i16(s, 2), i16(s, 4) return s - def chunk_gAMA(self, pos, length): + def chunk_gAMA(self, pos: int, length: int) -> bytes: # gamma setting s = ImageFile._safe_read(self.fp, length) self.im_info["gamma"] = i32(s) / 100000.0 return s - def chunk_cHRM(self, pos, length): + def chunk_cHRM(self, pos: int, length: int) -> bytes: # chromaticity, 8 unsigned ints, actual value is scaled by 100,000 # WP x,y, Red x,y, Green x,y Blue x,y @@ -488,7 +493,7 @@ class PngStream(ChunkStream): self.im_info["chromaticity"] = tuple(elt / 100000.0 for elt in raw_vals) return s - def chunk_sRGB(self, pos, length): + def chunk_sRGB(self, pos: int, length: int) -> bytes: # srgb rendering intent, 1 byte # 0 perceptual # 1 relative colorimetric @@ -504,7 +509,7 @@ class PngStream(ChunkStream): self.im_info["srgb"] = s[0] return s - def chunk_pHYs(self, pos, length): + def chunk_pHYs(self, pos: int, length: int) -> bytes: # pixels per unit s = ImageFile._safe_read(self.fp, length) if length < 9: @@ -521,7 +526,7 @@ class PngStream(ChunkStream): self.im_info["aspect"] = px, py return s - def chunk_tEXt(self, pos, length): + def chunk_tEXt(self, pos: int, length: int) -> bytes: # text s = ImageFile._safe_read(self.fp, length) try: @@ -540,7 +545,7 @@ class PngStream(ChunkStream): return s - def chunk_zTXt(self, pos, length): + def chunk_zTXt(self, pos: int, length: int) -> bytes: # compressed text s = ImageFile._safe_read(self.fp, length) try: @@ -574,7 +579,7 @@ class PngStream(ChunkStream): return s - def chunk_iTXt(self, pos, length): + def chunk_iTXt(self, pos: int, length: int) -> bytes: # international text r = s = ImageFile._safe_read(self.fp, length) try: @@ -614,13 +619,13 @@ class PngStream(ChunkStream): return s - def chunk_eXIf(self, pos, length): + def chunk_eXIf(self, pos: int, length: int) -> bytes: s = ImageFile._safe_read(self.fp, length) self.im_info["exif"] = b"Exif\x00\x00" + s return s # APNG chunks - def chunk_acTL(self, pos, length): + def chunk_acTL(self, pos: int, length: int) -> bytes: s = ImageFile._safe_read(self.fp, length) if length < 8: if ImageFile.LOAD_TRUNCATED_IMAGES: @@ -640,7 +645,7 @@ class PngStream(ChunkStream): self.im_custom_mimetype = "image/apng" return s - def chunk_fcTL(self, pos, length): + def chunk_fcTL(self, pos: int, length: int) -> bytes: s = ImageFile._safe_read(self.fp, length) if length < 26: if ImageFile.LOAD_TRUNCATED_IMAGES: @@ -669,7 +674,7 @@ class PngStream(ChunkStream): self.im_info["blend"] = s[25] return s - def chunk_fdAT(self, pos, length): + def chunk_fdAT(self, pos: int, length: int) -> bytes: if length < 4: if ImageFile.LOAD_TRUNCATED_IMAGES: s = ImageFile._safe_read(self.fp, length) @@ -701,7 +706,7 @@ class PngImageFile(ImageFile.ImageFile): format = "PNG" format_description = "Portable network graphics" - def _open(self): + def _open(self) -> None: if not _accept(self.fp.read(8)): msg = "not a PNG file" raise SyntaxError(msg) @@ -711,8 +716,8 @@ class PngImageFile(ImageFile.ImageFile): # # Parse headers up to the first IDAT or fDAT chunk - self.private_chunks = [] - self.png = PngStream(self.fp) + self.private_chunks: list[tuple[bytes, bytes] | tuple[bytes, bytes, bool]] = [] + self.png: PngStream | None = PngStream(self.fp) while True: # @@ -793,6 +798,7 @@ class PngImageFile(ImageFile.ImageFile): # back up to beginning of IDAT block self.fp.seek(self.tile[0][2] - 8) + assert self.png is not None self.png.verify() self.png.close() @@ -921,9 +927,10 @@ class PngImageFile(ImageFile.ImageFile): self.__idat = self.__prepare_idat # used by load_read() ImageFile.ImageFile.load_prepare(self) - def load_read(self, read_bytes): + def load_read(self, read_bytes: int) -> bytes: """internal: read more image data""" + assert self.png is not None while self.__idat == 0: # end of chunk, skip forward to next one @@ -956,6 +963,7 @@ class PngImageFile(ImageFile.ImageFile): def load_end(self) -> None: """internal: finished reading image data""" + assert self.png is not None if self.__idat != 0: self.fp.read(self.__idat) while True: @@ -1079,7 +1087,7 @@ class _idat: self.fp = fp self.chunk = chunk - def write(self, data): + def write(self, data: bytes) -> None: self.chunk(self.fp, b"IDAT", data) @@ -1091,7 +1099,7 @@ class _fdat: self.chunk = chunk self.seq_num = seq_num - def write(self, data): + def write(self, data: bytes) -> None: self.chunk(self.fp, b"fdAT", o32(self.seq_num), data) self.seq_num += 1 @@ -1436,10 +1444,10 @@ def getchunks(im, **params): class collector: data = [] - def write(self, data): + def write(self, data: bytes) -> None: pass - def append(self, chunk): + def append(self, chunk: bytes) -> None: self.data.append(chunk) def append(fp, cid, *data): diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index 4b8cfe65c..cae124e9f 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -171,7 +171,7 @@ class WebPImageFile(ImageFile.ImageFile): return super().load() - def load_seek(self, pos): + def load_seek(self, pos: int) -> None: pass def tell(self) -> int: diff --git a/src/PIL/XpmImagePlugin.py b/src/PIL/XpmImagePlugin.py index 88d14e9c2..8d56331e6 100644 --- a/src/PIL/XpmImagePlugin.py +++ b/src/PIL/XpmImagePlugin.py @@ -103,16 +103,13 @@ class XpmImageFile(ImageFile.ImageFile): self.tile = [("raw", (0, 0) + self.size, self.fp.tell(), ("P", 0, 1))] - def load_read(self, read_bytes): + def load_read(self, read_bytes: int) -> bytes: # # load all image data in one chunk xsize, ysize = self.size - s = [None] * ysize - - for i in range(ysize): - s[i] = self.fp.readline()[1 : xsize + 1].ljust(xsize) + s = [self.fp.readline()[1 : xsize + 1].ljust(xsize) for i in range(ysize)] return b"".join(s) diff --git a/src/PIL/features.py b/src/PIL/features.py index 95c6c84cc..16c749f14 100644 --- a/src/PIL/features.py +++ b/src/PIL/features.py @@ -18,7 +18,7 @@ modules = { } -def check_module(feature): +def check_module(feature: str) -> bool: """ Checks if a module is available. @@ -42,7 +42,7 @@ def check_module(feature): return False -def version_module(feature): +def version_module(feature: str) -> str | None: """ :param feature: The module to check for. :returns: @@ -54,13 +54,10 @@ def version_module(feature): module, ver = modules[feature] - if ver is None: - return None - return getattr(__import__(module, fromlist=[ver]), ver) -def get_supported_modules(): +def get_supported_modules() -> list[str]: """ :returns: A list of all supported modules. """ @@ -75,7 +72,7 @@ codecs = { } -def check_codec(feature): +def check_codec(feature: str) -> bool: """ Checks if a codec is available. @@ -92,7 +89,7 @@ def check_codec(feature): return f"{codec}_encoder" in dir(Image.core) -def version_codec(feature): +def version_codec(feature: str) -> str | None: """ :param feature: The codec to check for. :returns: @@ -113,7 +110,7 @@ def version_codec(feature): return version -def get_supported_codecs(): +def get_supported_codecs() -> list[str]: """ :returns: A list of all supported codecs. """ @@ -133,7 +130,7 @@ features = { } -def check_feature(feature): +def check_feature(feature: str) -> bool | None: """ Checks if a feature is available. @@ -157,7 +154,7 @@ def check_feature(feature): return None -def version_feature(feature): +def version_feature(feature: str) -> str | None: """ :param feature: The feature to check for. :returns: The version number as a string, or ``None`` if not available. @@ -174,14 +171,14 @@ def version_feature(feature): return getattr(__import__(module, fromlist=[ver]), ver) -def get_supported_features(): +def get_supported_features() -> list[str]: """ :returns: A list of all supported features. """ return [f for f in features if check_feature(f)] -def check(feature): +def check(feature: str) -> bool | None: """ :param feature: A module, codec, or feature name. :returns: @@ -199,7 +196,7 @@ def check(feature): return False -def version(feature): +def version(feature: str) -> str | None: """ :param feature: The module, codec, or feature to check for. @@ -215,7 +212,7 @@ def version(feature): return None -def get_supported(): +def get_supported() -> list[str]: """ :returns: A list of all supported modules, features, and codecs. """ From 3062ec4dd2343258e91c240645d20d1c5b6cecf1 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 16 May 2024 22:55:03 +1000 Subject: [PATCH 27/63] Fix type errors --- src/_imagingcms.c | 2 +- src/display.c | 2 +- src/libImaging/Dib.c | 2 +- src/libImaging/TiffDecode.c | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 63d78f84d..4b0e21d7a 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -622,7 +622,7 @@ cms_profile_is_intent_supported(CmsProfileObject *self, PyObject *args) { static PyObject * cms_get_display_profile_win32(PyObject *self, PyObject *args) { char filename[MAX_PATH]; - cmsUInt32Number filename_size; + DWORD filename_size; BOOL ok; HANDLE handle = 0; diff --git a/src/display.c b/src/display.c index 6b66ddafb..abf94f1e1 100644 --- a/src/display.c +++ b/src/display.c @@ -716,7 +716,7 @@ PyImaging_DrawWmf(PyObject *self, PyObject *args) { HDC dc; RECT rect; PyObject *buffer = NULL; - char *ptr; + void *ptr; char *data; Py_ssize_t datasize; diff --git a/src/libImaging/Dib.c b/src/libImaging/Dib.c index 1b5bfe132..5194bfca3 100644 --- a/src/libImaging/Dib.c +++ b/src/libImaging/Dib.c @@ -95,7 +95,7 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) { } dib->bitmap = - CreateDIBSection(dib->dc, dib->info, DIB_RGB_COLORS, &dib->bits, NULL, 0); + CreateDIBSection(dib->dc, dib->info, DIB_RGB_COLORS, (void **)&dib->bits, NULL, 0); if (!dib->bitmap) { free(dib->info); free(dib); diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index e3b81590e..4874dd26a 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -973,7 +973,7 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt } if (state->state == 1 && !clientstate->fp) { - int read = (int)_tiffReadProc(clientstate, (tdata_t)buffer, (tsize_t)bytes); + int read = (int)_tiffReadProc((thandle_t)clientstate, (tdata_t)buffer, (tsize_t)bytes); TRACE( ("Buffer: %p: %c%c%c%c\n", buffer, From 8a3a72e51d57360df6e91f6f73e73707f181069f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 18 May 2024 16:06:50 +1000 Subject: [PATCH 28/63] Added type hints --- docs/reference/ImageFile.rst | 4 ++++ src/PIL/BlpImagePlugin.py | 22 +++++++++--------- src/PIL/BufrStubImagePlugin.py | 4 ++-- src/PIL/DcxImagePlugin.py | 4 ++-- src/PIL/GimpPaletteFile.py | 9 ++++---- src/PIL/GribStubImagePlugin.py | 4 ++-- src/PIL/Hdf5StubImagePlugin.py | 8 ++++--- src/PIL/Image.py | 4 +++- src/PIL/ImageFile.py | 10 +++++++++ src/PIL/ImageFilter.py | 17 ++++++++------ src/PIL/ImageFont.py | 4 ++-- src/PIL/ImagePalette.py | 14 ++++++------ src/PIL/ImageWin.py | 22 ++++++++++-------- src/PIL/JpegImagePlugin.py | 15 +++++++------ src/PIL/PSDraw.py | 41 ++++++++++++++++++++-------------- src/PIL/PdfParser.py | 10 ++++----- src/PIL/PngImagePlugin.py | 8 +++---- src/PIL/PyAccess.py | 12 ++++++---- src/PIL/TiffImagePlugin.py | 2 +- src/PIL/WebPImagePlugin.py | 5 +++-- src/PIL/WmfImagePlugin.py | 10 ++++----- 21 files changed, 135 insertions(+), 94 deletions(-) diff --git a/docs/reference/ImageFile.rst b/docs/reference/ImageFile.rst index 047990f1c..e59c7311a 100644 --- a/docs/reference/ImageFile.rst +++ b/docs/reference/ImageFile.rst @@ -57,6 +57,10 @@ Classes :undoc-members: :show-inheritance: +.. autoclass:: PIL.ImageFile.StubHandler() + :members: + :show-inheritance: + .. autoclass:: PIL.ImageFile.StubImageFile() :members: :show-inheritance: diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index bdf54baae..782e28cf5 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -55,7 +55,7 @@ class AlphaEncoding(IntEnum): DXT5 = 7 -def unpack_565(i): +def unpack_565(i: int) -> tuple[int, int, int]: return ((i >> 11) & 0x1F) << 3, ((i >> 5) & 0x3F) << 2, (i & 0x1F) << 3 @@ -284,7 +284,8 @@ class _BLPBaseDecoder(ImageFile.PyDecoder): raise OSError(msg) from e return -1, 0 - def _read_blp_header(self): + def _read_blp_header(self) -> None: + assert self.fd is not None self.fd.seek(4) (self._blp_compression,) = struct.unpack(" bytes: return ImageFile._safe_read(self.fd, length) - def _read_palette(self): + def _read_palette(self) -> list[tuple[int, int, int, int]]: ret = [] for i in range(256): try: @@ -349,29 +350,30 @@ class BLP1Decoder(_BLPBaseDecoder): msg = f"Unsupported BLP compression {repr(self._blp_encoding)}" raise BLPFormatError(msg) - def _decode_jpeg_stream(self): + def _decode_jpeg_stream(self) -> None: from .JpegImagePlugin import JpegImageFile (jpeg_header_size,) = struct.unpack(" None: palette = self._read_palette() + assert self.fd is not None self.fd.seek(self._blp_offsets[0]) if self._blp_compression == 1: diff --git a/src/PIL/BufrStubImagePlugin.py b/src/PIL/BufrStubImagePlugin.py index 271db7258..826e89daf 100644 --- a/src/PIL/BufrStubImagePlugin.py +++ b/src/PIL/BufrStubImagePlugin.py @@ -15,7 +15,7 @@ from . import Image, ImageFile _handler = None -def register_handler(handler): +def register_handler(handler: ImageFile.StubHandler) -> None: """ Install application-specific BUFR image handler. @@ -54,7 +54,7 @@ class BufrStubImageFile(ImageFile.StubImageFile): if loader: loader.open(self) - def _load(self): + def _load(self) -> ImageFile.StubHandler | None: return _handler diff --git a/src/PIL/DcxImagePlugin.py b/src/PIL/DcxImagePlugin.py index 1c455b032..f67f27d73 100644 --- a/src/PIL/DcxImagePlugin.py +++ b/src/PIL/DcxImagePlugin.py @@ -42,7 +42,7 @@ class DcxImageFile(PcxImageFile): format_description = "Intel DCX" _close_exclusive_fp_after_loading = False - def _open(self): + def _open(self) -> None: # Header s = self.fp.read(4) if not _accept(s): @@ -58,7 +58,7 @@ class DcxImageFile(PcxImageFile): self._offset.append(offset) self._fp = self.fp - self.frame = None + self.frame = -1 self.n_frames = len(self._offset) self.is_animated = self.n_frames > 1 self.seek(0) diff --git a/src/PIL/GimpPaletteFile.py b/src/PIL/GimpPaletteFile.py index 2274f1a8b..4cad0ebee 100644 --- a/src/PIL/GimpPaletteFile.py +++ b/src/PIL/GimpPaletteFile.py @@ -16,6 +16,7 @@ from __future__ import annotations import re +from typing import IO from ._binary import o8 @@ -25,8 +26,8 @@ class GimpPaletteFile: rawmode = "RGB" - def __init__(self, fp): - self.palette = [o8(i) * 3 for i in range(256)] + def __init__(self, fp: IO[bytes]) -> None: + palette = [o8(i) * 3 for i in range(256)] if fp.readline()[:12] != b"GIMP Palette": msg = "not a GIMP palette file" @@ -49,9 +50,9 @@ class GimpPaletteFile: msg = "bad palette entry" raise ValueError(msg) - self.palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2]) + palette[i] = o8(v[0]) + o8(v[1]) + o8(v[2]) - self.palette = b"".join(self.palette) + self.palette = b"".join(palette) def getpalette(self) -> tuple[bytes, str]: return self.palette, self.rawmode diff --git a/src/PIL/GribStubImagePlugin.py b/src/PIL/GribStubImagePlugin.py index 13bdfa616..c27cffab6 100644 --- a/src/PIL/GribStubImagePlugin.py +++ b/src/PIL/GribStubImagePlugin.py @@ -15,7 +15,7 @@ from . import Image, ImageFile _handler = None -def register_handler(handler): +def register_handler(handler: ImageFile.StubHandler) -> None: """ Install application-specific GRIB image handler. @@ -54,7 +54,7 @@ class GribStubImageFile(ImageFile.StubImageFile): if loader: loader.open(self) - def _load(self): + def _load(self) -> ImageFile.StubHandler | None: return _handler diff --git a/src/PIL/Hdf5StubImagePlugin.py b/src/PIL/Hdf5StubImagePlugin.py index afbfd1639..c8d7866a3 100644 --- a/src/PIL/Hdf5StubImagePlugin.py +++ b/src/PIL/Hdf5StubImagePlugin.py @@ -10,12 +10,14 @@ # from __future__ import annotations +from typing import IO + from . import Image, ImageFile _handler = None -def register_handler(handler): +def register_handler(handler: ImageFile.StubHandler) -> None: """ Install application-specific HDF5 image handler. @@ -54,11 +56,11 @@ class HDF5StubImageFile(ImageFile.StubImageFile): if loader: loader.open(self) - def _load(self): + def _load(self) -> ImageFile.StubHandler | None: return _handler -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: if _handler is None or not hasattr(_handler, "save"): msg = "HDF5 save handler not installed" raise OSError(msg) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 958b95e3b..38ff0bfe4 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1948,7 +1948,9 @@ class Image: self.im.putband(alpha.im, band) - def putdata(self, data, scale=1.0, offset=0.0): + def putdata( + self, data: Sequence[float], scale: float = 1.0, offset: float = 0.0 + ) -> None: """ Copies pixel data from a flattened sequence object into the image. The values should start at the upper left corner (0, 0), continue to the diff --git a/src/PIL/ImageFile.py b/src/PIL/ImageFile.py index 33467fc4f..f0e492387 100644 --- a/src/PIL/ImageFile.py +++ b/src/PIL/ImageFile.py @@ -28,6 +28,7 @@ # from __future__ import annotations +import abc import io import itertools import struct @@ -347,6 +348,15 @@ class ImageFile(Image.Image): return self.tell() != frame +class StubHandler: + def open(self, im: StubImageFile) -> None: + pass + + @abc.abstractmethod + def load(self, im: StubImageFile) -> Image.Image: + pass + + class StubImageFile(ImageFile): """ Base class for stub image loaders. diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 678bd29a2..43e700b7b 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -18,6 +18,7 @@ from __future__ import annotations import abc import functools +from typing import Sequence class Filter: @@ -79,7 +80,7 @@ class RankFilter(Filter): name = "Rank" - def __init__(self, size, rank): + def __init__(self, size: int, rank: int) -> None: self.size = size self.rank = rank @@ -101,7 +102,7 @@ class MedianFilter(RankFilter): name = "Median" - def __init__(self, size=3): + def __init__(self, size: int = 3) -> None: self.size = size self.rank = size * size // 2 @@ -116,7 +117,7 @@ class MinFilter(RankFilter): name = "Min" - def __init__(self, size=3): + def __init__(self, size: int = 3) -> None: self.size = size self.rank = 0 @@ -131,7 +132,7 @@ class MaxFilter(RankFilter): name = "Max" - def __init__(self, size=3): + def __init__(self, size: int = 3) -> None: self.size = size self.rank = size * size - 1 @@ -147,7 +148,7 @@ class ModeFilter(Filter): name = "Mode" - def __init__(self, size=3): + def __init__(self, size: int = 3) -> None: self.size = size def filter(self, image): @@ -165,7 +166,7 @@ class GaussianBlur(MultibandFilter): name = "GaussianBlur" - def __init__(self, radius=2): + def __init__(self, radius: float | Sequence[float] = 2) -> None: self.radius = radius def filter(self, image): @@ -228,7 +229,9 @@ class UnsharpMask(MultibandFilter): name = "UnsharpMask" - def __init__(self, radius=2, percent=150, threshold=3): + def __init__( + self, radius: float = 2, percent: int = 150, threshold: int = 3 + ) -> None: self.radius = radius self.percent = percent self.threshold = threshold diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 256c581df..5446bc0c0 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -261,7 +261,7 @@ class FreeTypeFont: """ return self.font.family, self.font.style - def getmetrics(self): + def getmetrics(self) -> tuple[int, int]: """ :return: A tuple of the font ascent (the distance from the baseline to the highest outline point) and descent (the distance from the @@ -628,7 +628,7 @@ class FreeTypeFont: layout_engine=layout_engine or self.layout_engine, ) - def get_variation_names(self): + def get_variation_names(self) -> list[bytes]: """ :returns: A list of the named styles in a variation font. :exception OSError: If the font is not a variation font. diff --git a/src/PIL/ImagePalette.py b/src/PIL/ImagePalette.py index ae5c5dec0..057ccd1d7 100644 --- a/src/PIL/ImagePalette.py +++ b/src/PIL/ImagePalette.py @@ -18,7 +18,7 @@ from __future__ import annotations import array -from typing import Sequence +from typing import IO, Sequence from . import GimpGradientFile, GimpPaletteFile, ImageColor, PaletteFile @@ -166,7 +166,7 @@ class ImagePalette: msg = f"unknown color specifier: {repr(color)}" raise ValueError(msg) - def save(self, fp): + def save(self, fp: str | IO[str]) -> None: """Save palette to text file. .. warning:: This method is experimental. @@ -213,29 +213,29 @@ def make_linear_lut(black, white): raise NotImplementedError(msg) # FIXME -def make_gamma_lut(exp): +def make_gamma_lut(exp: float) -> list[int]: return [int(((i / 255.0) ** exp) * 255.0 + 0.5) for i in range(256)] -def negative(mode="RGB"): +def negative(mode: str = "RGB") -> ImagePalette: palette = list(range(256 * len(mode))) palette.reverse() return ImagePalette(mode, [i // len(mode) for i in palette]) -def random(mode="RGB"): +def random(mode: str = "RGB") -> ImagePalette: from random import randint palette = [randint(0, 255) for _ in range(256 * len(mode))] return ImagePalette(mode, palette) -def sepia(white="#fff0c0"): +def sepia(white: str = "#fff0c0") -> ImagePalette: bands = [make_linear_lut(0, band) for band in ImageColor.getrgb(white)] return ImagePalette("RGB", [bands[i % 3][i // 3] for i in range(256 * 3)]) -def wedge(mode="RGB"): +def wedge(mode: str = "RGB") -> ImagePalette: palette = list(range(256 * len(mode))) return ImagePalette(mode, [i // len(mode) for i in palette]) diff --git a/src/PIL/ImageWin.py b/src/PIL/ImageWin.py index 77e57a415..6c29e2590 100644 --- a/src/PIL/ImageWin.py +++ b/src/PIL/ImageWin.py @@ -28,10 +28,10 @@ class HDC: methods. """ - def __init__(self, dc): + def __init__(self, dc: int) -> None: self.dc = dc - def __int__(self): + def __int__(self) -> int: return self.dc @@ -42,10 +42,10 @@ class HWND: methods, instead of a DC. """ - def __init__(self, wnd): + def __init__(self, wnd: int) -> None: self.wnd = wnd - def __int__(self): + def __int__(self) -> int: return self.wnd @@ -149,7 +149,9 @@ class Dib: result = self.image.query_palette(handle) return result - def paste(self, im, box=None): + def paste( + self, im: Image.Image, box: tuple[int, int, int, int] | None = None + ) -> None: """ Paste a PIL image into the bitmap image. @@ -169,16 +171,16 @@ class Dib: else: self.image.paste(im.im) - def frombytes(self, buffer): + def frombytes(self, buffer: bytes) -> None: """ Load display memory contents from byte data. :param buffer: A buffer containing display data (usually data returned from :py:func:`~PIL.ImageWin.Dib.tobytes`) """ - return self.image.frombytes(buffer) + self.image.frombytes(buffer) - def tobytes(self): + def tobytes(self) -> bytes: """ Copy display memory contents to bytes object. @@ -190,7 +192,9 @@ class Dib: class Window: """Create a Window with the given title size.""" - def __init__(self, title="PIL", width=None, height=None): + def __init__( + self, title: str = "PIL", width: int | None = None, height: int | None = None + ) -> None: self.hwnd = Image.core.createwindow( title, self.__dispatcher, width or 0, height or 0 ) diff --git a/src/PIL/JpegImagePlugin.py b/src/PIL/JpegImagePlugin.py index 909911dfe..9fea4e7d1 100644 --- a/src/PIL/JpegImagePlugin.py +++ b/src/PIL/JpegImagePlugin.py @@ -42,6 +42,7 @@ import subprocess import sys import tempfile import warnings +from typing import Any from . import Image, ImageFile from ._binary import i16be as i16 @@ -54,7 +55,7 @@ from .JpegPresets import presets # Parser -def Skip(self, marker): +def Skip(self: JpegImageFile, marker: int) -> None: n = i16(self.fp.read(2)) - 2 ImageFile._safe_read(self.fp, n) @@ -191,7 +192,7 @@ def APP(self, marker): self.info["dpi"] = 72, 72 -def COM(self, marker): +def COM(self: JpegImageFile, marker: int) -> None: # # Comment marker. Store these in the APP dictionary. n = i16(self.fp.read(2)) - 2 @@ -202,7 +203,7 @@ def COM(self, marker): self.applist.append(("COM", s)) -def SOF(self, marker): +def SOF(self: JpegImageFile, marker: int) -> None: # # Start of frame marker. Defines the size and mode of the # image. JPEG is colour blind, so we use some simple @@ -250,7 +251,7 @@ def SOF(self, marker): self.layer.append((t[0], t[1] // 16, t[1] & 15, t[2])) -def DQT(self, marker): +def DQT(self: JpegImageFile, marker: int) -> None: # # Define quantization table. Note that there might be more # than one table in each marker. @@ -493,13 +494,13 @@ class JpegImageFile(ImageFile.ImageFile): self.tile = [] - def _getexif(self): + def _getexif(self) -> dict[str, Any] | None: return _getexif(self) def _getmp(self): return _getmp(self) - def getxmp(self): + def getxmp(self) -> dict[str, Any]: """ Returns a dictionary containing the XMP tags. Requires defusedxml to be installed. @@ -515,7 +516,7 @@ class JpegImageFile(ImageFile.ImageFile): return {} -def _getexif(self): +def _getexif(self) -> dict[str, Any] | None: if "exif" not in self.info: return None return self.getexif()._get_merged_dict() diff --git a/src/PIL/PSDraw.py b/src/PIL/PSDraw.py index 49c06ce13..4e2b9788e 100644 --- a/src/PIL/PSDraw.py +++ b/src/PIL/PSDraw.py @@ -17,6 +17,7 @@ from __future__ import annotations import sys +from typing import TYPE_CHECKING from . import EpsImagePlugin @@ -38,7 +39,7 @@ class PSDraw: fp = sys.stdout self.fp = fp - def begin_document(self, id=None): + def begin_document(self, id: str | None = None) -> None: """Set up printing of a document. (Write PostScript DSC header.)""" # FIXME: incomplete self.fp.write( @@ -52,7 +53,7 @@ class PSDraw: self.fp.write(EDROFF_PS) self.fp.write(VDI_PS) self.fp.write(b"%%EndProlog\n") - self.isofont = {} + self.isofont: dict[bytes, int] = {} def end_document(self) -> None: """Ends printing. (Write PostScript DSC footer.)""" @@ -60,22 +61,24 @@ class PSDraw: if hasattr(self.fp, "flush"): self.fp.flush() - def setfont(self, font, size): + def setfont(self, font: str, size: int) -> None: """ Selects which font to use. :param font: A PostScript font name :param size: Size in points. """ - font = bytes(font, "UTF-8") - if font not in self.isofont: + font_bytes = bytes(font, "UTF-8") + if font_bytes not in self.isofont: # reencode font - self.fp.write(b"/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font, font)) - self.isofont[font] = 1 + self.fp.write( + b"/PSDraw-%s ISOLatin1Encoding /%s E\n" % (font_bytes, font_bytes) + ) + self.isofont[font_bytes] = 1 # rough - self.fp.write(b"/F0 %d /PSDraw-%s F\n" % (size, font)) + self.fp.write(b"/F0 %d /PSDraw-%s F\n" % (size, font_bytes)) - def line(self, xy0, xy1): + def line(self, xy0: tuple[int, int], xy1: tuple[int, int]) -> None: """ Draws a line between the two points. Coordinates are given in PostScript point coordinates (72 points per inch, (0, 0) is the lower @@ -83,7 +86,7 @@ class PSDraw: """ self.fp.write(b"%d %d %d %d Vl\n" % (*xy0, *xy1)) - def rectangle(self, box): + def rectangle(self, box: tuple[int, int, int, int]) -> None: """ Draws a rectangle. @@ -92,18 +95,22 @@ class PSDraw: """ self.fp.write(b"%d %d M 0 %d %d Vr\n" % box) - def text(self, xy, text): + def text(self, xy: tuple[int, int], text: str) -> None: """ Draws text at the given position. You must use :py:meth:`~PIL.PSDraw.PSDraw.setfont` before calling this method. """ - text = bytes(text, "UTF-8") - text = b"\\(".join(text.split(b"(")) - text = b"\\)".join(text.split(b")")) - xy += (text,) - self.fp.write(b"%d %d M (%s) S\n" % xy) + text_bytes = bytes(text, "UTF-8") + text_bytes = b"\\(".join(text_bytes.split(b"(")) + text_bytes = b"\\)".join(text_bytes.split(b")")) + self.fp.write(b"%d %d M (%s) S\n" % (xy + (text_bytes,))) - def image(self, box, im, dpi=None): + if TYPE_CHECKING: + from . import Image + + def image( + self, box: tuple[int, int, int, int], im: Image.Image, dpi: int | None = None + ) -> None: """Draw a PIL image, centered in the given box.""" # default resolution depends on mode if not dpi: diff --git a/src/PIL/PdfParser.py b/src/PIL/PdfParser.py index 68501d625..a6c24e671 100644 --- a/src/PIL/PdfParser.py +++ b/src/PIL/PdfParser.py @@ -13,7 +13,7 @@ from typing import TYPE_CHECKING, Any, List, NamedTuple, Union # see 7.9.2.2 Text String Type on page 86 and D.3 PDFDocEncoding Character Set # on page 656 -def encode_text(s): +def encode_text(s: str) -> bytes: return codecs.BOM_UTF16_BE + s.encode("utf_16_be") @@ -103,7 +103,7 @@ class IndirectReference(IndirectReferenceTuple): def __ne__(self, other): return not (self == other) - def __hash__(self): + def __hash__(self) -> int: return hash((self.object_id, self.generation)) @@ -219,7 +219,7 @@ class PdfName: isinstance(other, PdfName) and other.name == self.name ) or other == self.name - def __hash__(self): + def __hash__(self) -> int: return hash(self.name) def __repr__(self) -> str: @@ -402,7 +402,7 @@ class PdfParser: if f: self.seek_end() - def __enter__(self): + def __enter__(self) -> PdfParser: return self def __exit__(self, exc_type, exc_value, traceback): @@ -436,7 +436,7 @@ class PdfParser: def write_comment(self, s): self.f.write(f"% {s}\n".encode()) - def write_catalog(self): + def write_catalog(self) -> IndirectReference: self.del_root() self.root_ref = self.next_object_id(self.f.tell()) self.pages_ref = self.next_object_id(0) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index c74cbccf1..f7ccc8381 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -39,7 +39,7 @@ import struct import warnings import zlib from enum import IntEnum -from typing import IO +from typing import IO, Any from . import Image, ImageChops, ImageFile, ImagePalette, ImageSequence from ._binary import i16be as i16 @@ -1019,7 +1019,7 @@ class PngImageFile(ImageFile.ImageFile): if self.pyaccess: self.pyaccess = None - def _getexif(self): + def _getexif(self) -> dict[str, Any] | None: if "exif" not in self.info: self.load() if "exif" not in self.info and "Raw profile type exif" not in self.info: @@ -1032,7 +1032,7 @@ class PngImageFile(ImageFile.ImageFile): return super().getexif() - def getxmp(self): + def getxmp(self) -> dict[str, Any]: """ Returns a dictionary containing the XMP tags. Requires defusedxml to be installed. @@ -1234,7 +1234,7 @@ def _write_multiple_frames(im, fp, chunk, rawmode, default_image, append_images) seq_num = fdat_chunks.seq_num -def _save_all(im, fp, filename): +def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None: _save(im, fp, filename, save_all=True) diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index a9da90613..f476713ca 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -22,6 +22,7 @@ from __future__ import annotations import logging import sys +from typing import TYPE_CHECKING from ._deprecate import deprecate @@ -48,9 +49,12 @@ except ImportError as ex: logger = logging.getLogger(__name__) +if TYPE_CHECKING: + from . import Image + class PyAccess: - def __init__(self, img, readonly=False): + def __init__(self, img: Image.Image, readonly: bool = False) -> None: deprecate("PyAccess", 11) vals = dict(img.im.unsafe_ptrs) self.readonly = readonly @@ -130,7 +134,7 @@ class PyAccess: putpixel = __setitem__ getpixel = __getitem__ - def check_xy(self, xy): + def check_xy(self, xy: tuple[int, int]) -> tuple[int, int]: (x, y) = xy if not (0 <= x < self.xsize and 0 <= y < self.ysize): msg = "pixel location out of range" @@ -161,7 +165,7 @@ class _PyAccess32_3(PyAccess): def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) - def get_pixel(self, x, y): + def get_pixel(self, x: int, y: int) -> tuple[int, int, int]: pixel = self.pixels[y][x] return pixel.r, pixel.g, pixel.b @@ -180,7 +184,7 @@ class _PyAccess32_4(PyAccess): def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) - def get_pixel(self, x, y): + def get_pixel(self, x: int, y: int) -> tuple[int, int, int, int]: pixel = self.pixels[y][x] return pixel.r, pixel.g, pixel.b, pixel.a diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index 54faa59c5..f3fa3c24c 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1202,7 +1202,7 @@ class TiffImageFile(ImageFile.ImageFile): """Return the current frame number""" return self.__frame - def getxmp(self): + def getxmp(self) -> dict[str, Any]: """ Returns a dictionary containing the XMP tags. Requires defusedxml to be installed. diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index cae124e9f..ff7402dca 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -1,6 +1,7 @@ from __future__ import annotations from io import BytesIO +from typing import Any from . import Image, ImageFile @@ -95,12 +96,12 @@ class WebPImageFile(ImageFile.ImageFile): # Initialize seek state self._reset(reset=False) - def _getexif(self): + def _getexif(self) -> dict[str, Any] | None: if "exif" not in self.info: return None return self.getexif()._get_merged_dict() - def getxmp(self): + def getxmp(self) -> dict[str, Any]: """ Returns a dictionary containing the XMP tags. Requires defusedxml to be installed. diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index b0328657b..fab3e26c5 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -28,7 +28,7 @@ from ._binary import si32le as _long _handler = None -def register_handler(handler): +def register_handler(handler: ImageFile.StubHandler) -> None: """ Install application-specific WMF image handler. @@ -41,12 +41,12 @@ def register_handler(handler): if hasattr(Image.core, "drawwmf"): # install default handler (windows only) - class WmfHandler: - def open(self, im): + class WmfHandler(ImageFile.StubHandler): + def open(self, im: ImageFile.StubImageFile) -> None: im._mode = "RGB" self.bbox = im.info["wmf_bbox"] - def load(self, im): + def load(self, im: ImageFile.StubImageFile) -> Image.Image: im.fp.seek(0) # rewind return Image.frombytes( "RGB", @@ -147,7 +147,7 @@ class WmfStubImageFile(ImageFile.StubImageFile): if loader: loader.open(self) - def _load(self): + def _load(self) -> ImageFile.StubHandler | None: return _handler def load(self, dpi=None): From 82910a5e4f1f12472dd85ce31090e7abeefe9814 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 19 May 2024 22:00:45 +1000 Subject: [PATCH 29/63] Lint fixes --- src/libImaging/Dib.c | 4 ++-- src/libImaging/TiffDecode.c | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libImaging/Dib.c b/src/libImaging/Dib.c index 5194bfca3..269be1058 100644 --- a/src/libImaging/Dib.c +++ b/src/libImaging/Dib.c @@ -94,8 +94,8 @@ ImagingNewDIB(const char *mode, int xsize, int ysize) { return (ImagingDIB)ImagingError_MemoryError(); } - dib->bitmap = - CreateDIBSection(dib->dc, dib->info, DIB_RGB_COLORS, (void **)&dib->bits, NULL, 0); + dib->bitmap = CreateDIBSection( + dib->dc, dib->info, DIB_RGB_COLORS, (void **)&dib->bits, NULL, 0); if (!dib->bitmap) { free(dib->info); free(dib); diff --git a/src/libImaging/TiffDecode.c b/src/libImaging/TiffDecode.c index 8b4d7aeac..abffdeabc 100644 --- a/src/libImaging/TiffDecode.c +++ b/src/libImaging/TiffDecode.c @@ -1005,7 +1005,8 @@ ImagingLibTiffEncode(Imaging im, ImagingCodecState state, UINT8 *buffer, int byt } if (state->state == 1 && !clientstate->fp) { - int read = (int)_tiffReadProc((thandle_t)clientstate, (tdata_t)buffer, (tsize_t)bytes); + int read = + (int)_tiffReadProc((thandle_t)clientstate, (tdata_t)buffer, (tsize_t)bytes); TRACE( ("Buffer: %p: %c%c%c%c\n", buffer, From ea9dc1e4a5c5c508bbb5c7cbd29f207836e77007 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 21:30:03 +0000 Subject: [PATCH 30/63] chore(deps): update dependency cibuildwheel to v2.18.1 --- .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 8d39ea9bb..7e257b75c 100644 --- a/.ci/requirements-cibw.txt +++ b/.ci/requirements-cibw.txt @@ -1 +1 @@ -cibuildwheel==2.18.0 +cibuildwheel==2.18.1 From 3cc26e9ea614def5a5efdcbcfd646ff50e3936e2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 21 May 2024 12:55:48 +1000 Subject: [PATCH 31/63] Added Python 3.13 wheels --- .github/workflows/wheels-test.sh | 2 +- .github/workflows/wheels.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/wheels-test.sh b/.github/workflows/wheels-test.sh index 3fbf3be69..a3376ac92 100755 --- a/.github/workflows/wheels-test.sh +++ b/.github/workflows/wheels-test.sh @@ -12,7 +12,7 @@ elif [ "${AUDITWHEEL_POLICY::9}" == "musllinux" ]; then else yum install -y fribidi fi -if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ]; then +if [ "${AUDITWHEEL_POLICY::9}" != "musllinux" ] && !([[ "$OSTYPE" == "darwin"* ]] && [[ $(python3 --version) == *"3.13."* ]]); then python3 -m pip install numpy fi diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index b2fbd3140..3d6099c1c 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -46,6 +46,7 @@ jobs: - cp310 - cp311 - cp312 + - cp313 spec: - manylinux2014 - manylinux_2_28 @@ -80,6 +81,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 # Extra options for manylinux. CIBW_MANYLINUX_AARCH64_IMAGE: ${{ matrix.spec }} CIBW_MANYLINUX_PYPY_AARCH64_IMAGE: ${{ matrix.spec }} @@ -133,6 +135,7 @@ jobs: CIBW_BUILD: ${{ matrix.build }} CIBW_MANYLINUX_PYPY_X86_64_IMAGE: ${{ matrix.manylinux }} CIBW_MANYLINUX_X86_64_IMAGE: ${{ matrix.manylinux }} + CIBW_PRERELEASE_PYTHONS: True CIBW_SKIP: pp38-* CIBW_TEST_SKIP: cp38-macosx_arm64 MACOSX_DEPLOYMENT_TARGET: ${{ matrix.macosx_deployment_target }} @@ -204,6 +207,7 @@ jobs: CIBW_ARCHS: ${{ matrix.cibw_arch }} CIBW_BEFORE_ALL: "{package}\\winbuild\\build\\build_dep_all.cmd" CIBW_CACHE_PATH: "C:\\cibw" + CIBW_PRERELEASE_PYTHONS: True CIBW_SKIP: pp38-* CIBW_TEST_SKIP: "*-win_arm64" CIBW_TEST_COMMAND: 'docker run --rm From d461ff8cef83116abffbd24145459ee1300c520d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 22 May 2024 11:50:10 +1000 Subject: [PATCH 32/63] Added release notes --- docs/releasenotes/10.4.0.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/releasenotes/10.4.0.rst b/docs/releasenotes/10.4.0.rst index 3150bf4e0..41f33102f 100644 --- a/docs/releasenotes/10.4.0.rst +++ b/docs/releasenotes/10.4.0.rst @@ -53,7 +53,9 @@ TODO Other Changes ============= -TODO -^^^^ +Python 3.13 beta +^^^^^^^^^^^^^^^^ -TODO +To help others prepare for Python 3.13, wheels have been built against the 3.13 beta as +a preview. This is not official support for Python 3.13, but simply an opportunity for +users to test how Pillow works with the beta and report any problems. From 33e304ed66851ed7cbd6e9018764ec9ec089c25f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 22 May 2024 19:43:00 +1000 Subject: [PATCH 33/63] Use @cached_property --- src/PIL/GifImagePlugin.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index eede41549..962a92834 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -30,6 +30,7 @@ import math import os import subprocess from enum import IntEnum +from functools import cached_property from . import ( Image, @@ -112,8 +113,7 @@ class GifImageFile(ImageFile.ImageFile): self._fp = self.fp # FIXME: hack self.__rewind = self.fp.tell() - self._n_frames = None - self._is_animated = None + self._n_frames: int | None = None self._seek(0) # get ready to read first frame @property @@ -128,24 +128,23 @@ class GifImageFile(ImageFile.ImageFile): self.seek(current) return self._n_frames - @property - def is_animated(self): - if self._is_animated is None: - if self._n_frames is not None: - self._is_animated = self._n_frames != 1 - else: - current = self.tell() - if current: - self._is_animated = True - else: - try: - self._seek(1, False) - self._is_animated = True - except EOFError: - self._is_animated = False + @cached_property + def is_animated(self) -> bool: + if self._n_frames is not None: + return self._n_frames != 1 - self.seek(current) - return self._is_animated + current = self.tell() + if current: + return True + + try: + self._seek(1, False) + is_animated = True + except EOFError: + is_animated = False + + self.seek(current) + return is_animated def seek(self, frame: int) -> None: if not self._seek_check(frame): From 92d1879a776be3b109a26786d6611cab9ef6181c Mon Sep 17 00:00:00 2001 From: Yay295 Date: Thu, 23 May 2024 13:27:53 -0500 Subject: [PATCH 34/63] add mypy task to makefile --- Makefile | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Makefile b/Makefile index 1f9b4a370..94f7565d8 100644 --- a/Makefile +++ b/Makefile @@ -118,3 +118,8 @@ lint-fix: python3 -m black . python3 -c "import ruff" > /dev/null 2>&1 || python3 -m pip install ruff python3 -m ruff --fix . + +.PHONY: mypy +mypy: + python3 -c "import tox" > /dev/null 2>&1 || python3 -m pip install tox + python3 -m tox -e mypy From 16cd3584548d8b5f4cd81d8460805b6604530a6f Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Fri, 24 May 2024 20:32:52 +1000 Subject: [PATCH 35/63] Update CHANGES.rst [ci skip] --- CHANGES.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index c5df1f8f7..8a531d1e2 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,12 @@ Changelog (Pillow) 10.4.0 (unreleased) ------------------- +- Add mypy target to Makefile #8077 + [Yay295] + +- Added more modes to Image.MODES #7984 + [radarhere] + - Deprecate BGR;15, BGR;16 and BGR;24 modes #7978 [radarhere, hugovk] From 2c9b5f03607d083665f5880506197405197f34ae Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 25 May 2024 06:20:03 +1000 Subject: [PATCH 36/63] Updated Ghostscript to 10.3.1 --- .appveyor.yml | 2 +- .github/workflows/test-windows.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.appveyor.yml b/.appveyor.yml index 57a8fa5a0..6470dbc4c 100644 --- a/.appveyor.yml +++ b/.appveyor.yml @@ -34,7 +34,7 @@ install: - xcopy /S /Y c:\test-images-main\* c:\pillow\tests\images - curl -fsSL -o nasm-win64.zip https://raw.githubusercontent.com/python-pillow/pillow-depends/main/nasm-2.16.01-win64.zip - 7z x nasm-win64.zip -oc:\ -- choco install ghostscript --version=10.3.0 +- choco install ghostscript --version=10.3.1 - path c:\nasm-2.16.01;C:\Program Files\gs\gs10.00.0\bin;%PATH% - cd c:\pillow\winbuild\ - ps: | diff --git a/.github/workflows/test-windows.yml b/.github/workflows/test-windows.yml index 9edc15173..ee265774b 100644 --- a/.github/workflows/test-windows.yml +++ b/.github/workflows/test-windows.yml @@ -86,7 +86,7 @@ jobs: choco install nasm --no-progress echo "C:\Program Files\NASM" >> $env:GITHUB_PATH - choco install ghostscript --version=10.3.0 --no-progress + choco install ghostscript --version=10.3.1 --no-progress echo "C:\Program Files\gs\gs10.00.0\bin" >> $env:GITHUB_PATH # Install extra test images From 1a6b0bb6b5423dba1607dfee6d4ea437a58cc246 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 25 May 2024 19:30:20 +1000 Subject: [PATCH 37/63] Removed documentation of unused argument --- src/PIL/ImageFont.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/PIL/ImageFont.py b/src/PIL/ImageFont.py index 256c581df..ad5f75459 100644 --- a/src/PIL/ImageFont.py +++ b/src/PIL/ImageFont.py @@ -160,10 +160,6 @@ class ImageFont: .. versionadded:: 9.2.0 :param text: Text to render. - :param mode: Used by some graphics drivers to indicate what mode the - driver prefers; if empty, the renderer may return either - mode. Note that the mode is always a string, to simplify - C-level implementations. :return: ``(left, top, right, bottom)`` bounding box """ From 1b878189d857bcc823f887cf94650a5f0eaa182a Mon Sep 17 00:00:00 2001 From: Yay295 Date: Sun, 26 May 2024 02:25:45 -0500 Subject: [PATCH 38/63] don't reuse variable name --- src/PIL/PngImagePlugin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index c74cbccf1..31706cef9 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1294,7 +1294,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): # get the corresponding PNG mode try: - rawmode, mode = _OUTMODES[mode] + rawmode, rawmode_depth_type = _OUTMODES[mode] except KeyError as e: msg = f"cannot write mode {mode} as PNG" raise OSError(msg) from e @@ -1309,7 +1309,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): b"IHDR", o32(size[0]), # 0: size o32(size[1]), - mode, # 8: depth/type + rawmode_depth_type, # 8: depth/type b"\0", # 10: compression b"\0", # 11: filter category b"\0", # 12: interlace flag From 82d992690572a270f4a8280cf48318fb8f70f2c2 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 26 May 2024 21:18:14 +1000 Subject: [PATCH 39/63] Split depth/type into bit depth and color type --- src/PIL/PngImagePlugin.py | 37 +++++++++++++++++++------------------ 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/src/PIL/PngImagePlugin.py b/src/PIL/PngImagePlugin.py index 31706cef9..0d5751962 100644 --- a/src/PIL/PngImagePlugin.py +++ b/src/PIL/PngImagePlugin.py @@ -1050,22 +1050,22 @@ class PngImageFile(ImageFile.ImageFile): # PNG writer _OUTMODES = { - # supported PIL modes, and corresponding rawmodes/bits/color combinations - "1": ("1", b"\x01\x00"), - "L;1": ("L;1", b"\x01\x00"), - "L;2": ("L;2", b"\x02\x00"), - "L;4": ("L;4", b"\x04\x00"), - "L": ("L", b"\x08\x00"), - "LA": ("LA", b"\x08\x04"), - "I": ("I;16B", b"\x10\x00"), - "I;16": ("I;16B", b"\x10\x00"), - "I;16B": ("I;16B", b"\x10\x00"), - "P;1": ("P;1", b"\x01\x03"), - "P;2": ("P;2", b"\x02\x03"), - "P;4": ("P;4", b"\x04\x03"), - "P": ("P", b"\x08\x03"), - "RGB": ("RGB", b"\x08\x02"), - "RGBA": ("RGBA", b"\x08\x06"), + # supported PIL modes, and corresponding rawmode, bit depth and color type + "1": ("1", b"\x01", b"\x00"), + "L;1": ("L;1", b"\x01", b"\x00"), + "L;2": ("L;2", b"\x02", b"\x00"), + "L;4": ("L;4", b"\x04", b"\x00"), + "L": ("L", b"\x08", b"\x00"), + "LA": ("LA", b"\x08", b"\x04"), + "I": ("I;16B", b"\x10", b"\x00"), + "I;16": ("I;16B", b"\x10", b"\x00"), + "I;16B": ("I;16B", b"\x10", b"\x00"), + "P;1": ("P;1", b"\x01", b"\x03"), + "P;2": ("P;2", b"\x02", b"\x03"), + "P;4": ("P;4", b"\x04", b"\x03"), + "P": ("P", b"\x08", b"\x03"), + "RGB": ("RGB", b"\x08", b"\x02"), + "RGBA": ("RGBA", b"\x08", b"\x06"), } @@ -1294,7 +1294,7 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): # get the corresponding PNG mode try: - rawmode, rawmode_depth_type = _OUTMODES[mode] + rawmode, bit_depth, color_type = _OUTMODES[mode] except KeyError as e: msg = f"cannot write mode {mode} as PNG" raise OSError(msg) from e @@ -1309,7 +1309,8 @@ def _save(im, fp, filename, chunk=putchunk, save_all=False): b"IHDR", o32(size[0]), # 0: size o32(size[1]), - rawmode_depth_type, # 8: depth/type + bit_depth, + color_type, b"\0", # 10: compression b"\0", # 11: filter category b"\0", # 12: interlace flag From 2c4a6e1179a7e2460d3dae9f4558173c547996a7 Mon Sep 17 00:00:00 2001 From: void4 Date: Mon, 27 May 2024 00:23:16 +0200 Subject: [PATCH 40/63] Add function and documentation to draw circle --- docs/reference/ImageDraw.rst | 13 +++++++++++++ src/PIL/ImageDraw.py | 9 +++++++++ 2 files changed, 22 insertions(+) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 4ccfacae7..6987adc88 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -240,6 +240,19 @@ Methods .. versionadded:: 5.3.0 +.. py:method:: ImageDraw.circle(xy, radius, fill=None, outline=None, width=1) + + Draws a circle given the center coordinates and a radius. + + :param xy: One point to define the circle center. Sequence: + ``[x, y]`` + :param radius: Radius of the circle + :param outline: Color to use for the outline. + :param fill: Color to use for the fill. + :param width: The line width, in pixels. + + .. versionadded:: ?.?.? + .. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None) Draws a line between the coordinates in the ``xy`` list. diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 42f2ee8c7..4b42b32d0 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -181,6 +181,15 @@ class ImageDraw: if ink is not None and ink != fill and width != 0: self.draw.draw_ellipse(xy, ink, 0, width) + def circle(self, xy: Coords, radius, fill=None, outline=None, width=1) -> None: + """Draw a circle given center coordinates and a radius.""" + ink, fill = self._getink(outline, fill) + ellipse_xy = (xy[0]-radius, xy[1]-radius, xy[0]+radius, xy[1]+radius) + if fill is not None: + self.draw.draw_ellipse(ellipse_xy, fill, 1) + if ink is not None and ink != fill and width != 0: + self.draw.draw_ellipse(ellipse_xy, ink, 0, width) + def line(self, xy: Coords, fill=None, width=0, joint=None) -> None: """Draw a line, or a connected sequence of line segments.""" ink = self._getink(fill)[0] From 2ee3cef50ef6b1917ea3c03ad06849a2a599ee8a Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Sun, 26 May 2024 22:25:13 +0000 Subject: [PATCH 41/63] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/PIL/ImageDraw.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 4b42b32d0..03f2637ae 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -184,7 +184,7 @@ class ImageDraw: def circle(self, xy: Coords, radius, fill=None, outline=None, width=1) -> None: """Draw a circle given center coordinates and a radius.""" ink, fill = self._getink(outline, fill) - ellipse_xy = (xy[0]-radius, xy[1]-radius, xy[0]+radius, xy[1]+radius) + ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius) if fill is not None: self.draw.draw_ellipse(ellipse_xy, fill, 1) if ink is not None and ink != fill and width != 0: From 8d9a4dda980f02ff93fa2301d906ef32602c5e07 Mon Sep 17 00:00:00 2001 From: void4 Date: Mon, 27 May 2024 12:57:50 +0200 Subject: [PATCH 42/63] Update docs/reference/ImageDraw.rst - Set versionadded Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- docs/reference/ImageDraw.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 6987adc88..51d5965db 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -251,7 +251,7 @@ Methods :param fill: Color to use for the fill. :param width: The line width, in pixels. - .. versionadded:: ?.?.? + .. versionadded:: 10.4.0 .. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None) From 38e6913579c3c7c04bb40384cd7eb797130c5dd8 Mon Sep 17 00:00:00 2001 From: void4 Date: Mon, 27 May 2024 13:01:10 +0200 Subject: [PATCH 43/63] Simplify circle() by reusing ellipse() --- src/PIL/ImageDraw.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 03f2637ae..b270cc6ba 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -183,12 +183,8 @@ class ImageDraw: def circle(self, xy: Coords, radius, fill=None, outline=None, width=1) -> None: """Draw a circle given center coordinates and a radius.""" - ink, fill = self._getink(outline, fill) ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius) - if fill is not None: - self.draw.draw_ellipse(ellipse_xy, fill, 1) - if ink is not None and ink != fill and width != 0: - self.draw.draw_ellipse(ellipse_xy, ink, 0, width) + self.ellipse(ellipse_xy, fill, outline, width) def line(self, xy: Coords, fill=None, width=0, joint=None) -> None: """Draw a line, or a connected sequence of line segments.""" From 35a700a1d4b2de51c8e13d4b60736f765b8b816b Mon Sep 17 00:00:00 2001 From: void4 Date: Mon, 27 May 2024 13:14:04 +0200 Subject: [PATCH 44/63] Update 10.4.0.rst - Add PIL.ImageDraw.circle() API addition --- docs/releasenotes/10.4.0.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/releasenotes/10.4.0.rst b/docs/releasenotes/10.4.0.rst index 41f33102f..3c4258801 100644 --- a/docs/releasenotes/10.4.0.rst +++ b/docs/releasenotes/10.4.0.rst @@ -45,6 +45,10 @@ TODO API Additions ============= +Added PIL.ImageDraw.circle() +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Given the circle center coordinate pair and a radius, plots a circle using PIL.ImageDraw.ellipse() + TODO ^^^^ From 773ff20b762247a9fa717683f5e89349830def7e Mon Sep 17 00:00:00 2001 From: void4 Date: Mon, 27 May 2024 13:18:47 +0200 Subject: [PATCH 45/63] Update docs/reference/ImageDraw.rst - move circle method up to indicate it is new Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- docs/reference/ImageDraw.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 51d5965db..ed762c8d6 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -243,6 +243,8 @@ Methods .. py:method:: ImageDraw.circle(xy, radius, fill=None, outline=None, width=1) Draws a circle given the center coordinates and a radius. + + .. versionadded:: 10.4.0 :param xy: One point to define the circle center. Sequence: ``[x, y]`` From 034f3cbed522b862b07bf938bbce0a599018bc57 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 27 May 2024 11:19:09 +0000 Subject: [PATCH 46/63] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- docs/reference/ImageDraw.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index ed762c8d6..4996acf71 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -243,7 +243,7 @@ Methods .. py:method:: ImageDraw.circle(xy, radius, fill=None, outline=None, width=1) Draws a circle given the center coordinates and a radius. - + .. versionadded:: 10.4.0 :param xy: One point to define the circle center. Sequence: From 9b7556228e0f045495f6b3dd2fd22e84b65ab0eb Mon Sep 17 00:00:00 2001 From: void4 Date: Mon, 27 May 2024 13:21:41 +0200 Subject: [PATCH 47/63] Update docs/reference/ImageDraw.rst - move versionadded Co-authored-by: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> --- docs/reference/ImageDraw.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 4996acf71..c4ce76cc0 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -253,8 +253,6 @@ Methods :param fill: Color to use for the fill. :param width: The line width, in pixels. - .. versionadded:: 10.4.0 - .. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None) Draws a line between the coordinates in the ``xy`` list. From 8db5fbead1955ed6f1f4810e3b36c46483dcc62e Mon Sep 17 00:00:00 2001 From: void4 Date: Mon, 27 May 2024 13:27:56 +0200 Subject: [PATCH 48/63] Update src/PIL/ImageDraw.py - set circle argument xy to type Sequence[float] instead of Coords, radius to float Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/ImageDraw.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index b270cc6ba..17c176430 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -181,7 +181,9 @@ class ImageDraw: if ink is not None and ink != fill and width != 0: self.draw.draw_ellipse(xy, ink, 0, width) - def circle(self, xy: Coords, radius, fill=None, outline=None, width=1) -> None: + def circle( + self, xy: Sequence[float], radius: float, fill=None, outline=None, width=1 + ) -> None: """Draw a circle given center coordinates and a radius.""" ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius) self.ellipse(ellipse_xy, fill, outline, width) From 12cefd798e7b28d05af4a39abd601bc862081881 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 27 May 2024 21:48:38 +1000 Subject: [PATCH 49/63] Added method links to release notes --- docs/reference/ImageDraw.rst | 25 ++++++++++++------------- docs/releasenotes/10.4.0.rst | 9 ++++++--- 2 files changed, 18 insertions(+), 16 deletions(-) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index c4ce76cc0..1404869ca 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -227,6 +227,18 @@ Methods .. versionadded:: 5.3.0 +.. py:method:: ImageDraw.circle(xy, radius, fill=None, outline=None, width=1) + + Draws a circle with a given radius centering on a point. + + .. versionadded:: 10.4.0 + + :param xy: The point for the center of the circle, e.g. ``(x, y)``. + :param radius: Radius of the circle. + :param outline: Color to use for the outline. + :param fill: Color to use for the fill. + :param width: The line width, in pixels. + .. py:method:: ImageDraw.ellipse(xy, fill=None, outline=None, width=1) Draws an ellipse inside the given bounding box. @@ -240,19 +252,6 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: ImageDraw.circle(xy, radius, fill=None, outline=None, width=1) - - Draws a circle given the center coordinates and a radius. - - .. versionadded:: 10.4.0 - - :param xy: One point to define the circle center. Sequence: - ``[x, y]`` - :param radius: Radius of the circle - :param outline: Color to use for the outline. - :param fill: Color to use for the fill. - :param width: The line width, in pixels. - .. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None) Draws a line between the coordinates in the ``xy`` list. diff --git a/docs/releasenotes/10.4.0.rst b/docs/releasenotes/10.4.0.rst index 3c4258801..e0d695a8b 100644 --- a/docs/releasenotes/10.4.0.rst +++ b/docs/releasenotes/10.4.0.rst @@ -45,9 +45,12 @@ TODO API Additions ============= -Added PIL.ImageDraw.circle() -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Given the circle center coordinate pair and a radius, plots a circle using PIL.ImageDraw.ellipse() +ImageDraw.circle +^^^^^^^^^^^^^^^^ + +Added :py:meth:`~PIL.ImageDraw.ImageDraw.circle`. It provides the same functionality as +:py:meth:`~PIL.ImageDraw.ImageDraw.ellipse`, but instead of taking a bounding box, it +takes a center point and radius. TODO ^^^^ From cac1a04329a2bb0864075f0aca4ff6e2c48a45cf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 27 May 2024 21:59:32 +1000 Subject: [PATCH 50/63] Added test --- Tests/test_imagedraw.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 0a699e2ab..69d09e03d 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -2,6 +2,7 @@ from __future__ import annotations import contextlib import os.path +from typing import Sequence import pytest @@ -265,6 +266,21 @@ def test_chord_too_fat() -> None: assert_image_equal_tofile(im, "Tests/images/imagedraw_chord_too_fat.png") +@pytest.mark.parametrize("mode", ("RGB", "L")) +@pytest.mark.parametrize("xy", ((W / 2, H / 2), [W / 2, H / 2])) +def test_circle(mode: str, xy: Sequence[float]) -> None: + # Arrange + im = Image.new(mode, (W, H)) + draw = ImageDraw.Draw(im) + expected = f"Tests/images/imagedraw_ellipse_{mode}.png" + + # Act + draw.circle(xy, 25, fill="green", outline="blue") + + # Assert + assert_image_similar_tofile(im, expected, 1) + + @pytest.mark.parametrize("mode", ("RGB", "L")) @pytest.mark.parametrize("bbox", BBOX) def test_ellipse(mode: str, bbox: Coords) -> None: From 759ab28757430d0661cdbd32410c30f663b4ca9c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 28 May 2024 22:16:04 +1000 Subject: [PATCH 51/63] Update CHANGES.rst [ci skip] --- CHANGES.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGES.rst b/CHANGES.rst index 8a531d1e2..dc4016d76 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -5,6 +5,9 @@ Changelog (Pillow) 10.4.0 (unreleased) ------------------- +- Added ImageDraw circle() #8085 + [void4, hugovk, radarhere] + - Add mypy target to Makefile #8077 [Yay295] From a6d1daeb4b5e2797a393492d82333870ba7660a7 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 29 May 2024 22:51:02 +1000 Subject: [PATCH 52/63] Added type hints --- Tests/helper.py | 25 +++++++++++-------------- Tests/test_file_webp_animated.py | 10 ++++++---- Tests/test_imagecms.py | 19 ++++++++++++++----- Tests/test_imagefile.py | 6 ++++++ Tests/test_imagefont.py | 23 +++++++++++++++-------- 5 files changed, 52 insertions(+), 31 deletions(-) diff --git a/Tests/helper.py b/Tests/helper.py index 5fd4fe332..fe337c09f 100644 --- a/Tests/helper.py +++ b/Tests/helper.py @@ -174,12 +174,13 @@ def skip_unless_feature(feature: str) -> pytest.MarkDecorator: def skip_unless_feature_version( feature: str, required: str, reason: str | None = None ) -> pytest.MarkDecorator: - if not features.check(feature): + version = features.version(feature) + if version is None: return pytest.mark.skip(f"{feature} not available") if reason is None: reason = f"{feature} is older than {required}" version_required = parse_version(required) - version_available = parse_version(features.version(feature)) + version_available = parse_version(version) return pytest.mark.skipif(version_available < version_required, reason=reason) @@ -189,12 +190,13 @@ def mark_if_feature_version( version_blacklist: str, reason: str | None = None, ) -> pytest.MarkDecorator: - if not features.check(feature): + version = features.version(feature) + if version is None: return pytest.mark.pil_noop_mark() if reason is None: reason = f"{feature} is {version_blacklist}" version_required = parse_version(version_blacklist) - version_available = parse_version(features.version(feature)) + version_available = parse_version(version) if ( version_available.major == version_required.major and version_available.minor == version_required.minor @@ -220,16 +222,11 @@ class PillowLeakTestCase: from resource import RUSAGE_SELF, getrusage mem = getrusage(RUSAGE_SELF).ru_maxrss - if sys.platform == "darwin": - # man 2 getrusage: - # ru_maxrss - # This is the maximum resident set size utilized (in bytes). - return mem / 1024 # Kb - # linux - # man 2 getrusage - # ru_maxrss (since Linux 2.6.32) - # This is the maximum resident set size used (in kilobytes). - return mem # Kb + # man 2 getrusage: + # ru_maxrss + # This is the maximum resident set size utilized + # in bytes on macOS, in kilobytes on Linux + return mem / 1024 if sys.platform == "darwin" else mem def _test_leak(self, core: Callable[[], None]) -> None: start_mem = self._get_mem_usage() diff --git a/Tests/test_file_webp_animated.py b/Tests/test_file_webp_animated.py index 6a9337fa5..ba931f864 100644 --- a/Tests/test_file_webp_animated.py +++ b/Tests/test_file_webp_animated.py @@ -52,8 +52,9 @@ def test_write_animation_L(tmp_path: Path) -> None: assert_image_similar(im, orig.convert("RGBA"), 32.9) if is_big_endian(): - webp = parse_version(features.version_module("webp")) - if webp < parse_version("1.2.2"): + version = features.version_module("webp") + assert version is not None + if parse_version(version) < parse_version("1.2.2"): pytest.skip("Fails with libwebp earlier than 1.2.2") orig.seek(orig.n_frames - 1) im.seek(im.n_frames - 1) @@ -78,8 +79,9 @@ def test_write_animation_RGB(tmp_path: Path) -> None: # Compare second frame to original if is_big_endian(): - webp = parse_version(features.version_module("webp")) - if webp < parse_version("1.2.2"): + version = features.version_module("webp") + assert version is not None + if parse_version(version) < parse_version("1.2.2"): pytest.skip("Fails with libwebp earlier than 1.2.2") im.seek(1) im.load() diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index bf629fa79..8d2029c21 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -60,10 +60,13 @@ def test_sanity() -> None: assert list(map(type, v)) == [str, str, str, str] # internal version number - assert re.search(r"\d+\.\d+(\.\d+)?$", features.version_module("littlecms2")) + version = features.version_module("littlecms2") + assert version is not None + assert re.search(r"\d+\.\d+(\.\d+)?$", version) skip_missing() i = ImageCms.profileToProfile(hopper(), SRGB, SRGB) + assert i is not None assert_image(i, "RGB", (128, 128)) i = hopper() @@ -72,23 +75,27 @@ def test_sanity() -> None: t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") i = ImageCms.applyTransform(hopper(), t) + assert i is not None assert_image(i, "RGB", (128, 128)) with hopper() as i: t = ImageCms.buildTransform(SRGB, SRGB, "RGB", "RGB") ImageCms.applyTransform(hopper(), t, inPlace=True) + assert i is not None assert_image(i, "RGB", (128, 128)) p = ImageCms.createProfile("sRGB") o = ImageCms.getOpenProfile(SRGB) t = ImageCms.buildTransformFromOpenProfiles(p, o, "RGB", "RGB") i = ImageCms.applyTransform(hopper(), t) + assert i is not None assert_image(i, "RGB", (128, 128)) t = ImageCms.buildProofTransform(SRGB, SRGB, SRGB, "RGB", "RGB") assert t.inputMode == "RGB" assert t.outputMode == "RGB" i = ImageCms.applyTransform(hopper(), t) + assert i is not None assert_image(i, "RGB", (128, 128)) # test PointTransform convenience API @@ -260,7 +267,7 @@ def test_simple_lab() -> None: t = ImageCms.buildTransform(psRGB, pLab, "RGB", "LAB") i_lab = ImageCms.applyTransform(i, t) - + assert i_lab is not None assert i_lab.mode == "LAB" k = i_lab.getpixel((0, 0)) @@ -284,6 +291,7 @@ def test_lab_color() -> None: # Need to add a type mapping for some PIL type to TYPE_Lab_8 in findLCMSType, and # have that mapping work back to a PIL mode (likely RGB). i = ImageCms.applyTransform(hopper(), t) + assert i is not None assert_image(i, "LAB", (128, 128)) # i.save('temp.lab.tif') # visually verified vs PS. @@ -298,6 +306,7 @@ def test_lab_srgb() -> None: with Image.open("Tests/images/hopper.Lab.tif") as img: img_srgb = ImageCms.applyTransform(img, t) + assert img_srgb is not None # img_srgb.save('temp.srgb.tif') # visually verified vs ps. @@ -317,11 +326,11 @@ def test_lab_roundtrip() -> None: t2 = ImageCms.buildTransform(pLab, psRGB, "LAB", "RGB") i = ImageCms.applyTransform(hopper(), t) - + assert i is not None assert i.info["icc_profile"] == ImageCmsProfile(pLab).tobytes() out = ImageCms.applyTransform(i, t2) - + assert out is not None assert_image_similar(hopper(), out, 2) @@ -657,7 +666,7 @@ def test_auxiliary_channels_isolated() -> None: reference_image = ImageCms.applyTransform( source_image.convert(src_format[2]), reference_transform ) - + assert reference_image is not None assert_image_equal(test_image.convert(dst_format[2]), reference_image) diff --git a/Tests/test_imagefile.py b/Tests/test_imagefile.py index ddcae80d6..c9dba2943 100644 --- a/Tests/test_imagefile.py +++ b/Tests/test_imagefile.py @@ -202,6 +202,8 @@ class TestImageFile: class MockPyDecoder(ImageFile.PyDecoder): + last: MockPyDecoder + def __init__(self, mode: str, *args: Any) -> None: MockPyDecoder.last = self @@ -213,6 +215,8 @@ class MockPyDecoder(ImageFile.PyDecoder): class MockPyEncoder(ImageFile.PyEncoder): + last: MockPyEncoder | None + def __init__(self, mode: str, *args: Any) -> None: MockPyEncoder.last = self @@ -315,6 +319,7 @@ class TestPyEncoder(CodecsTest): im, fp, [("MOCK", (xoff, yoff, xoff + xsize, yoff + ysize), 0, "RGB")] ) + assert MockPyEncoder.last assert MockPyEncoder.last.state.xoff == xoff assert MockPyEncoder.last.state.yoff == yoff assert MockPyEncoder.last.state.xsize == xsize @@ -329,6 +334,7 @@ class TestPyEncoder(CodecsTest): fp = BytesIO() ImageFile._save(im, fp, [("MOCK", None, 0, "RGB")]) + assert MockPyEncoder.last assert MockPyEncoder.last.state.xoff == 0 assert MockPyEncoder.last.state.yoff == 0 assert MockPyEncoder.last.state.xsize == 200 diff --git a/Tests/test_imagefont.py b/Tests/test_imagefont.py index 05b5d4716..4398f8a30 100644 --- a/Tests/test_imagefont.py +++ b/Tests/test_imagefont.py @@ -34,7 +34,9 @@ pytestmark = skip_unless_feature("freetype2") def test_sanity() -> None: - assert re.search(r"\d+\.\d+\.\d+$", features.version_module("freetype2")) + version = features.version_module("freetype2") + assert version is not None + assert re.search(r"\d+\.\d+\.\d+$", version) @pytest.fixture( @@ -547,11 +549,10 @@ def test_find_font( def loadable_font( filepath: str, size: int, index: int, encoding: str, *args: Any ): + _freeTypeFont = getattr(ImageFont, "_FreeTypeFont") if filepath == path_to_fake: - return ImageFont._FreeTypeFont( - FONT_PATH, size, index, encoding, *args - ) - return ImageFont._FreeTypeFont(filepath, size, index, encoding, *args) + return _freeTypeFont(FONT_PATH, size, index, encoding, *args) + return _freeTypeFont(filepath, size, index, encoding, *args) m.setattr(ImageFont, "FreeTypeFont", loadable_font) font = ImageFont.truetype(fontname) @@ -630,7 +631,9 @@ def test_complex_font_settings() -> None: def test_variation_get(font: ImageFont.FreeTypeFont) -> None: - freetype = parse_version(features.version_module("freetype2")) + version = features.version_module("freetype2") + assert version is not None + freetype = parse_version(version) if freetype < parse_version("2.9.1"): with pytest.raises(NotImplementedError): font.get_variation_names() @@ -700,7 +703,9 @@ def _check_text(font: ImageFont.FreeTypeFont, path: str, epsilon: float) -> None def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None: - freetype = parse_version(features.version_module("freetype2")) + version = features.version_module("freetype2") + assert version is not None + freetype = parse_version(version) if freetype < parse_version("2.9.1"): with pytest.raises(NotImplementedError): font.set_variation_by_name("Bold") @@ -725,7 +730,9 @@ def test_variation_set_by_name(font: ImageFont.FreeTypeFont) -> None: def test_variation_set_by_axes(font: ImageFont.FreeTypeFont) -> None: - freetype = parse_version(features.version_module("freetype2")) + version = features.version_module("freetype2") + assert version is not None + freetype = parse_version(version) if freetype < parse_version("2.9.1"): with pytest.raises(NotImplementedError): font.set_variation_by_axes([100]) From e68cec640a0b0698fbfea9493089727edaf03379 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 30 May 2024 12:00:50 +1000 Subject: [PATCH 53/63] Added type hints --- Tests/oss-fuzz/test_fuzzers.py | 5 +++-- Tests/test_features.py | 10 +++++++--- Tests/test_file_jpeg.py | 4 +++- Tests/test_file_jpeg2k.py | 4 +++- Tests/test_file_libtiff.py | 4 +++- Tests/test_file_png.py | 6 +++--- Tests/test_file_webp.py | 4 +++- Tests/test_image_quantize.py | 5 +++-- Tests/test_image_reduce.py | 2 +- Tests/test_imageops_usm.py | 14 +++++++------- 10 files changed, 36 insertions(+), 22 deletions(-) diff --git a/Tests/oss-fuzz/test_fuzzers.py b/Tests/oss-fuzz/test_fuzzers.py index 58d0213e8..90eb8713a 100644 --- a/Tests/oss-fuzz/test_fuzzers.py +++ b/Tests/oss-fuzz/test_fuzzers.py @@ -12,8 +12,9 @@ from Tests.helper import skip_unless_feature if sys.platform.startswith("win32"): pytest.skip("Fuzzer is linux only", allow_module_level=True) -if features.check("libjpeg_turbo"): - version = packaging.version.parse(features.version("libjpeg_turbo")) +libjpeg_turbo_version = features.version("libjpeg_turbo") +if libjpeg_turbo_version is not None: + version = packaging.version.parse(libjpeg_turbo_version) if version.major == 2 and version.minor == 0: pytestmark = pytest.mark.valgrind_known_error( reason="Known failing with libjpeg_turbo 2.0" diff --git a/Tests/test_features.py b/Tests/test_features.py index 2d402ca91..59fb49809 100644 --- a/Tests/test_features.py +++ b/Tests/test_features.py @@ -30,7 +30,7 @@ def test_version() -> None: # Check the correctness of the convenience function # and the format of version numbers - def test(name: str, function: Callable[[str], bool]) -> None: + def test(name: str, function: Callable[[str], str | None]) -> None: version = features.version(name) if not features.check(name): assert version is None @@ -67,12 +67,16 @@ def test_webp_anim() -> None: @skip_unless_feature("libjpeg_turbo") def test_libjpeg_turbo_version() -> None: - assert re.search(r"\d+\.\d+\.\d+$", features.version("libjpeg_turbo")) + version = features.version("libjpeg_turbo") + assert version is not None + assert re.search(r"\d+\.\d+\.\d+$", version) @skip_unless_feature("libimagequant") def test_libimagequant_version() -> None: - assert re.search(r"\d+\.\d+\.\d+$", features.version("libimagequant")) + version = features.version("libimagequant") + assert version is not None + assert re.search(r"\d+\.\d+\.\d+$", version) @pytest.mark.parametrize("feature", features.modules) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index 5d2157651..f24faecaa 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -70,7 +70,9 @@ class TestFileJpeg: def test_sanity(self) -> None: # internal version number - assert re.search(r"\d+\.\d+$", features.version_codec("jpg")) + version = features.version_codec("jpg") + assert version is not None + assert re.search(r"\d+\.\d+$", version) with Image.open(TEST_FILE) as im: im.load() diff --git a/Tests/test_file_jpeg2k.py b/Tests/test_file_jpeg2k.py index a7cae563a..5a208739f 100644 --- a/Tests/test_file_jpeg2k.py +++ b/Tests/test_file_jpeg2k.py @@ -48,7 +48,9 @@ def roundtrip(im: Image.Image, **options: Any) -> Image.Image: def test_sanity() -> None: # Internal version number - assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("jpg_2000")) + version = features.version_codec("jpg_2000") + assert version is not None + assert re.search(r"\d+\.\d+\.\d+$", version) with Image.open("Tests/images/test-card-lossless.jp2") as im: px = im.load() diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 11883ad24..6c13999a5 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -52,7 +52,9 @@ class LibTiffTestCase: class TestFileLibTiff(LibTiffTestCase): def test_version(self) -> None: - assert re.search(r"\d+\.\d+\.\d+$", features.version_codec("libtiff")) + version = features.version_codec("libtiff") + assert version is not None + assert re.search(r"\d+\.\d+\.\d+$", version) def test_g4_tiff(self, tmp_path: Path) -> None: """Test the ordinary file path load path""" diff --git a/Tests/test_file_png.py b/Tests/test_file_png.py index 19462dcb5..c7c9f6fab 100644 --- a/Tests/test_file_png.py +++ b/Tests/test_file_png.py @@ -85,9 +85,9 @@ class TestFilePng: def test_sanity(self, tmp_path: Path) -> None: # internal version number - assert re.search( - r"\d+(\.\d+){1,3}(\.zlib\-ng)?$", features.version_codec("zlib") - ) + version = features.version_codec("zlib") + assert version is not None + assert re.search(r"\d+(\.\d+){1,3}(\.zlib\-ng)?$", version) test_file = str(tmp_path / "temp.png") diff --git a/Tests/test_file_webp.py b/Tests/test_file_webp.py index 249846da4..e2de84c71 100644 --- a/Tests/test_file_webp.py +++ b/Tests/test_file_webp.py @@ -49,7 +49,9 @@ class TestFileWebp: def test_version(self) -> None: _webp.WebPDecoderVersion() _webp.WebPDecoderBuggyAlpha() - assert re.search(r"\d+\.\d+\.\d+$", features.version_module("webp")) + version = features.version_module("webp") + assert version is not None + assert re.search(r"\d+\.\d+\.\d+$", version) def test_read_rgb(self) -> None: """ diff --git a/Tests/test_image_quantize.py b/Tests/test_image_quantize.py index e1aa6252b..2daaf5c3c 100644 --- a/Tests/test_image_quantize.py +++ b/Tests/test_image_quantize.py @@ -24,8 +24,9 @@ def test_sanity() -> None: def test_libimagequant_quantize() -> None: image = hopper() if is_ppc64le(): - libimagequant = parse_version(features.version_feature("libimagequant")) - if libimagequant < parse_version("4"): + version = features.version_feature("libimagequant") + assert version is not None + if parse_version(version) < parse_version("4"): pytest.skip("Fails with libimagequant earlier than 4.0.0 on ppc64le") converted = image.quantize(100, Image.Quantize.LIBIMAGEQUANT) assert converted.mode == "P" diff --git a/Tests/test_image_reduce.py b/Tests/test_image_reduce.py index fcf671daa..f6609a1a0 100644 --- a/Tests/test_image_reduce.py +++ b/Tests/test_image_reduce.py @@ -102,7 +102,7 @@ def test_unsupported_modes(mode: str) -> None: def get_image(mode: str) -> Image.Image: mode_info = ImageMode.getmode(mode) if mode_info.basetype == "L": - bands = [gradients_image] + bands: list[Image.Image] = [gradients_image] for _ in mode_info.bands[1:]: # rotate previous image band = bands[-1].transpose(Image.Transpose.ROTATE_90) diff --git a/Tests/test_imageops_usm.py b/Tests/test_imageops_usm.py index c15907a55..104c620de 100644 --- a/Tests/test_imageops_usm.py +++ b/Tests/test_imageops_usm.py @@ -4,11 +4,11 @@ from typing import Generator import pytest -from PIL import Image, ImageFilter +from PIL import Image, ImageFile, ImageFilter @pytest.fixture -def test_images() -> Generator[dict[str, Image.Image], None, None]: +def test_images() -> Generator[dict[str, ImageFile.ImageFile], None, None]: ims = { "im": Image.open("Tests/images/hopper.ppm"), "snakes": Image.open("Tests/images/color_snakes.png"), @@ -20,7 +20,7 @@ def test_images() -> Generator[dict[str, Image.Image], None, None]: im.close() -def test_filter_api(test_images: dict[str, Image.Image]) -> None: +def test_filter_api(test_images: dict[str, ImageFile.ImageFile]) -> None: im = test_images["im"] test_filter = ImageFilter.GaussianBlur(2.0) @@ -34,7 +34,7 @@ def test_filter_api(test_images: dict[str, Image.Image]) -> None: assert i.size == (128, 128) -def test_usm_formats(test_images: dict[str, Image.Image]) -> None: +def test_usm_formats(test_images: dict[str, ImageFile.ImageFile]) -> None: im = test_images["im"] usm = ImageFilter.UnsharpMask @@ -52,7 +52,7 @@ def test_usm_formats(test_images: dict[str, Image.Image]) -> None: im.convert("YCbCr").filter(usm) -def test_blur_formats(test_images: dict[str, Image.Image]) -> None: +def test_blur_formats(test_images: dict[str, ImageFile.ImageFile]) -> None: im = test_images["im"] blur = ImageFilter.GaussianBlur @@ -70,7 +70,7 @@ def test_blur_formats(test_images: dict[str, Image.Image]) -> None: im.convert("YCbCr").filter(blur) -def test_usm_accuracy(test_images: dict[str, Image.Image]) -> None: +def test_usm_accuracy(test_images: dict[str, ImageFile.ImageFile]) -> None: snakes = test_images["snakes"] src = snakes.convert("RGB") @@ -79,7 +79,7 @@ def test_usm_accuracy(test_images: dict[str, Image.Image]) -> None: assert i.tobytes() == src.tobytes() -def test_blur_accuracy(test_images: dict[str, Image.Image]) -> None: +def test_blur_accuracy(test_images: dict[str, ImageFile.ImageFile]) -> None: snakes = test_images["snakes"] i = snakes.filter(ImageFilter.GaussianBlur(0.4)) From afc7d8d0b012b8be86e00411c5cb69de62478ee5 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Thu, 30 May 2024 17:17:22 +1000 Subject: [PATCH 54/63] Added type hints --- Tests/test_font_leaks.py | 2 +- Tests/test_image.py | 20 +++++++++++++++----- Tests/test_imagecms.py | 26 ++++++++++++++------------ Tests/test_imagestat.py | 4 ++-- 4 files changed, 32 insertions(+), 20 deletions(-) diff --git a/Tests/test_font_leaks.py b/Tests/test_font_leaks.py index 08a0e7431..3fb92a62e 100644 --- a/Tests/test_font_leaks.py +++ b/Tests/test_font_leaks.py @@ -12,7 +12,7 @@ class TestTTypeFontLeak(PillowLeakTestCase): iterations = 10 mem_limit = 4096 # k - def _test_font(self, font: ImageFont.FreeTypeFont) -> None: + def _test_font(self, font: ImageFont.FreeTypeFont | ImageFont.ImageFont) -> None: im = Image.new("RGB", (255, 255), "white") draw = ImageDraw.ImageDraw(im) self._test_leak( diff --git a/Tests/test_image.py b/Tests/test_image.py index 742d0dfe4..c7694a0ef 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -99,10 +99,18 @@ class TestImage: JPGFILE = "Tests/images/hopper.jpg" with pytest.raises(TypeError): - with Image.open(PNGFILE, formats=123): + with Image.open(PNGFILE, formats=123): # type: ignore[arg-type] pass - for formats in [["JPEG"], ("JPEG",), ["jpeg"], ["Jpeg"], ["jPeG"], ["JpEg"]]: + format_list: list[list[str] | tuple[str, ...]] = [ + ["JPEG"], + ("JPEG",), + ["jpeg"], + ["Jpeg"], + ["jPeG"], + ["JpEg"], + ] + for formats in format_list: with pytest.raises(UnidentifiedImageError): with Image.open(PNGFILE, formats=formats): pass @@ -138,7 +146,7 @@ class TestImage: def test_bad_mode(self) -> None: with pytest.raises(ValueError): - with Image.open("filename", "bad mode"): + with Image.open("filename", "bad mode"): # type: ignore[arg-type] pass def test_stringio(self) -> None: @@ -497,9 +505,11 @@ class TestImage: def test_check_size(self) -> None: # Checking that the _check_size function throws value errors when we want it to with pytest.raises(ValueError): - Image.new("RGB", 0) # not a tuple + # not a tuple + Image.new("RGB", 0) # type: ignore[arg-type] with pytest.raises(ValueError): - Image.new("RGB", (0,)) # Tuple too short + # tuple too short + Image.new("RGB", (0,)) # type: ignore[arg-type] with pytest.raises(ValueError): Image.new("RGB", (-1, -1)) # w,h < 0 diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 8d2029c21..082eb8162 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -7,7 +7,7 @@ import shutil import sys from io import BytesIO from pathlib import Path -from typing import Any +from typing import Any, Literal, cast import pytest @@ -209,13 +209,13 @@ def test_exceptions() -> None: ImageCms.buildTransform("foo", "bar", "RGB", "RGB") with pytest.raises(ImageCms.PyCMSError, match="Invalid type for Profile"): - ImageCms.getProfileName(None) + ImageCms.getProfileName(None) # type: ignore[arg-type] skip_missing() # Python <= 3.9: "an integer is required (got type NoneType)" # Python > 3.9: "'NoneType' object cannot be interpreted as an integer" with pytest.raises(ImageCms.PyCMSError, match="integer"): - ImageCms.isIntentSupported(SRGB, None, None) + ImageCms.isIntentSupported(SRGB, None, None) # type: ignore[arg-type] def test_display_profile() -> None: @@ -239,7 +239,7 @@ def test_unsupported_color_space() -> None: "Color space not supported for on-the-fly profile creation (unsupported)" ), ): - ImageCms.createProfile("unsupported") + ImageCms.createProfile("unsupported") # type: ignore[arg-type] def test_invalid_color_temperature() -> None: @@ -352,7 +352,7 @@ def test_extended_information() -> None: p = o.profile def assert_truncated_tuple_equal( - tup1: tuple[Any, ...], tup2: tuple[Any, ...], digits: int = 10 + tup1: tuple[Any, ...] | None, tup2: tuple[Any, ...], digits: int = 10 ) -> None: # Helper function to reduce precision of tuples of floats # recursively and then check equality. @@ -368,6 +368,7 @@ def test_extended_information() -> None: for val in tuple_value ) + assert tup1 is not None assert truncate_tuple(tup1) == truncate_tuple(tup2) assert p.attributes == 4294967296 @@ -513,22 +514,22 @@ def test_non_ascii_path(tmp_path: Path) -> None: def test_profile_typesafety() -> None: # does not segfault with pytest.raises(TypeError, match="Invalid type for Profile"): - ImageCms.ImageCmsProfile(0).tobytes() + ImageCms.ImageCmsProfile(0) # type: ignore[arg-type] with pytest.raises(TypeError, match="Invalid type for Profile"): - ImageCms.ImageCmsProfile(1).tobytes() + ImageCms.ImageCmsProfile(1) # type: ignore[arg-type] # also check core function with pytest.raises(TypeError): - ImageCms.core.profile_tobytes(0) + ImageCms.core.profile_tobytes(0) # type: ignore[arg-type] with pytest.raises(TypeError): - ImageCms.core.profile_tobytes(1) + ImageCms.core.profile_tobytes(1) # type: ignore[arg-type] if not is_pypy(): # core profile should not be directly instantiable with pytest.raises(TypeError): ImageCms.core.CmsProfile() with pytest.raises(TypeError): - ImageCms.core.CmsProfile(0) + ImageCms.core.CmsProfile(0) # type: ignore[call-arg] @pytest.mark.skipif(is_pypy(), reason="fails on PyPy") @@ -537,7 +538,7 @@ def test_transform_typesafety() -> None: with pytest.raises(TypeError): ImageCms.core.CmsTransform() with pytest.raises(TypeError): - ImageCms.core.CmsTransform(0) + ImageCms.core.CmsTransform(0) # type: ignore[call-arg] def assert_aux_channel_preserved( @@ -637,7 +638,8 @@ def test_auxiliary_channels_isolated() -> None: continue # convert with and without AUX data, test colors are equal - source_profile = ImageCms.createProfile(src_format[1]) + src_colorSpace = cast(Literal["LAB", "XYZ", "sRGB"], src_format[1]) + source_profile = ImageCms.createProfile(src_colorSpace) destination_profile = ImageCms.createProfile(dst_format[1]) source_image = src_format[3] test_transform = ImageCms.buildTransform( diff --git a/Tests/test_imagestat.py b/Tests/test_imagestat.py index b1c1306c1..0dfbc5a2a 100644 --- a/Tests/test_imagestat.py +++ b/Tests/test_imagestat.py @@ -25,10 +25,10 @@ def test_sanity() -> None: st.stddev with pytest.raises(AttributeError): - st.spam() + st.spam() # type: ignore[attr-defined] with pytest.raises(TypeError): - ImageStat.Stat(1) + ImageStat.Stat(1) # type: ignore[arg-type] def test_hopper() -> None: From 66ab7e0de22251b32eccd8938ac7f985579dc0ac Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 1 Jun 2024 21:31:53 +1000 Subject: [PATCH 55/63] Added type hints --- Tests/test_file_gif.py | 5 +++-- Tests/test_file_jpeg.py | 4 ++-- Tests/test_file_libtiff.py | 3 ++- Tests/test_image.py | 4 +++- Tests/test_image_array.py | 2 +- Tests/test_image_crop.py | 26 +++++++++++++------------- Tests/test_image_filter.py | 4 ++-- Tests/test_image_getextrema.py | 2 +- Tests/test_imagecms.py | 8 ++++++-- Tests/test_imagedraw.py | 2 +- 10 files changed, 34 insertions(+), 26 deletions(-) diff --git a/Tests/test_file_gif.py b/Tests/test_file_gif.py index 48c70db8a..4e790926b 100644 --- a/Tests/test_file_gif.py +++ b/Tests/test_file_gif.py @@ -1252,10 +1252,11 @@ def test_palette_save_L(tmp_path: Path) -> None: im = hopper("P") im_l = Image.frombytes("L", im.size, im.tobytes()) - palette = bytes(im.getpalette()) + palette = im.getpalette() + assert palette is not None out = str(tmp_path / "temp.gif") - im_l.save(out, palette=palette) + im_l.save(out, palette=bytes(palette)) with Image.open(out) as reloaded: assert_image_equal(reloaded.convert("RGB"), im.convert("RGB")) diff --git a/Tests/test_file_jpeg.py b/Tests/test_file_jpeg.py index f24faecaa..33f9ce00e 100644 --- a/Tests/test_file_jpeg.py +++ b/Tests/test_file_jpeg.py @@ -154,7 +154,7 @@ class TestFileJpeg: assert k > 0.9 def test_rgb(self) -> None: - def getchannels(im: Image.Image) -> tuple[int, int, int]: + def getchannels(im: JpegImagePlugin.JpegImageFile) -> tuple[int, int, int]: return tuple(v[0] for v in im.layer) im = hopper() @@ -443,7 +443,7 @@ class TestFileJpeg: assert_image(im1, im2.mode, im2.size) def test_subsampling(self) -> None: - def getsampling(im: Image.Image): + def getsampling(im: JpegImagePlugin.JpegImageFile): layer = im.layer return layer[0][1:3] + layer[1][1:3] + layer[2][1:3] diff --git a/Tests/test_file_libtiff.py b/Tests/test_file_libtiff.py index 6c13999a5..22bcd2856 100644 --- a/Tests/test_file_libtiff.py +++ b/Tests/test_file_libtiff.py @@ -668,7 +668,8 @@ class TestFileLibTiff(LibTiffTestCase): pilim.save(buffer_io, format="tiff", compression=compression) buffer_io.seek(0) - assert_image_similar_tofile(pilim, buffer_io, 0) + with Image.open(buffer_io) as saved_im: + assert_image_similar(pilim, saved_im, 0) save_bytesio() save_bytesio("raw") diff --git a/Tests/test_image.py b/Tests/test_image.py index c7694a0ef..d6a739c79 100644 --- a/Tests/test_image.py +++ b/Tests/test_image.py @@ -25,6 +25,7 @@ from PIL import ( from .helper import ( assert_image_equal, assert_image_equal_tofile, + assert_image_similar, assert_image_similar_tofile, assert_not_all_same, hopper, @@ -193,7 +194,8 @@ class TestImage: with tempfile.TemporaryFile() as fp: im.save(fp, "JPEG") fp.seek(0) - assert_image_similar_tofile(im, fp, 20) + with Image.open(fp) as reloaded: + assert_image_similar(im, reloaded, 20) def test_unknown_extension(self, tmp_path: Path) -> None: im = hopper() diff --git a/Tests/test_image_array.py b/Tests/test_image_array.py index 342bd8654..d7e6c562c 100644 --- a/Tests/test_image_array.py +++ b/Tests/test_image_array.py @@ -86,8 +86,8 @@ def test_fromarray() -> None: assert test("RGBX") == ("RGBA", (128, 100), True) # Test mode is None with no "typestr" in the array interface + wrapped = Wrapper(hopper("L"), {"shape": (100, 128)}) with pytest.raises(TypeError): - wrapped = Wrapper(test("L"), {"shape": (100, 128)}) Image.fromarray(wrapped) diff --git a/Tests/test_image_crop.py b/Tests/test_image_crop.py index d095364ba..07fec2e64 100644 --- a/Tests/test_image_crop.py +++ b/Tests/test_image_crop.py @@ -18,7 +18,7 @@ def test_crop(mode: str) -> None: def test_wide_crop() -> None: - def crop(*bbox: int) -> tuple[int, ...]: + def crop(bbox: tuple[int, int, int, int]) -> tuple[int, ...]: i = im.crop(bbox) h = i.histogram() while h and not h[-1]: @@ -27,23 +27,23 @@ def test_wide_crop() -> None: im = Image.new("L", (100, 100), 1) - assert crop(0, 0, 100, 100) == (0, 10000) - assert crop(25, 25, 75, 75) == (0, 2500) + assert crop((0, 0, 100, 100)) == (0, 10000) + assert crop((25, 25, 75, 75)) == (0, 2500) # sides - assert crop(-25, 0, 25, 50) == (1250, 1250) - assert crop(0, -25, 50, 25) == (1250, 1250) - assert crop(75, 0, 125, 50) == (1250, 1250) - assert crop(0, 75, 50, 125) == (1250, 1250) + assert crop((-25, 0, 25, 50)) == (1250, 1250) + assert crop((0, -25, 50, 25)) == (1250, 1250) + assert crop((75, 0, 125, 50)) == (1250, 1250) + assert crop((0, 75, 50, 125)) == (1250, 1250) - assert crop(-25, 25, 125, 75) == (2500, 5000) - assert crop(25, -25, 75, 125) == (2500, 5000) + assert crop((-25, 25, 125, 75)) == (2500, 5000) + assert crop((25, -25, 75, 125)) == (2500, 5000) # corners - assert crop(-25, -25, 25, 25) == (1875, 625) - assert crop(75, -25, 125, 25) == (1875, 625) - assert crop(75, 75, 125, 125) == (1875, 625) - assert crop(-25, 75, 25, 125) == (1875, 625) + assert crop((-25, -25, 25, 25)) == (1875, 625) + assert crop((75, -25, 125, 25)) == (1875, 625) + assert crop((75, 75, 125, 125)) == (1875, 625) + assert crop((-25, 75, 25, 125)) == (1875, 625) @pytest.mark.parametrize("box", ((8, 2, 2, 8), (2, 8, 8, 2), (8, 8, 2, 2))) diff --git a/Tests/test_image_filter.py b/Tests/test_image_filter.py index 47f9ffa3d..1f0644471 100644 --- a/Tests/test_image_filter.py +++ b/Tests/test_image_filter.py @@ -46,9 +46,9 @@ def test_sanity(filter_to_apply: ImageFilter.Filter, mode: str) -> None: @pytest.mark.parametrize("mode", ("L", "I", "RGB", "CMYK")) def test_sanity_error(mode: str) -> None: + im = hopper(mode) with pytest.raises(TypeError): - im = hopper(mode) - im.filter("hello") + im.filter("hello") # type: ignore[arg-type] # crashes on small images diff --git a/Tests/test_image_getextrema.py b/Tests/test_image_getextrema.py index a5b974459..de5956f3e 100644 --- a/Tests/test_image_getextrema.py +++ b/Tests/test_image_getextrema.py @@ -6,7 +6,7 @@ from .helper import hopper def test_extrema() -> None: - def extrema(mode: str) -> tuple[int, int] | tuple[tuple[int, int], ...]: + def extrema(mode: str) -> tuple[float, float] | tuple[tuple[int, int], ...]: return hopper(mode).getextrema() assert extrema("1") == (0, 255) diff --git a/Tests/test_imagecms.py b/Tests/test_imagecms.py index 082eb8162..55f72c3b9 100644 --- a/Tests/test_imagecms.py +++ b/Tests/test_imagecms.py @@ -247,7 +247,7 @@ def test_invalid_color_temperature() -> None: ImageCms.PyCMSError, match='Color temperature must be numeric, "invalid" not valid', ): - ImageCms.createProfile("LAB", "invalid") + ImageCms.createProfile("LAB", "invalid") # type: ignore[arg-type] @pytest.mark.parametrize("flag", ("my string", -1)) @@ -256,7 +256,7 @@ def test_invalid_flag(flag: str | int) -> None: with pytest.raises( ImageCms.PyCMSError, match="flags must be an integer between 0 and " ): - ImageCms.profileToProfile(im, "foo", "bar", flags=flag) + ImageCms.profileToProfile(im, "foo", "bar", flags=flag) # type: ignore[arg-type] def test_simple_lab() -> None: @@ -588,11 +588,13 @@ def assert_aux_channel_preserved( ) # apply transform + result_image: Image.Image | None if transform_in_place: ImageCms.applyTransform(source_image, t, inPlace=True) result_image = source_image else: result_image = ImageCms.applyTransform(source_image, t, inPlace=False) + assert result_image is not None result_image_aux = result_image.getchannel(preserved_channel) assert_image_equal(source_image_aux, result_image_aux) @@ -650,6 +652,7 @@ def test_auxiliary_channels_isolated() -> None: ) # test conversion from aux-ful source + test_image: Image.Image | None if transform_in_place: test_image = source_image.copy() ImageCms.applyTransform(test_image, test_transform, inPlace=True) @@ -657,6 +660,7 @@ def test_auxiliary_channels_isolated() -> None: test_image = ImageCms.applyTransform( source_image, test_transform, inPlace=False ) + assert test_image is not None # reference conversion from aux-less source reference_transform = ImageCms.buildTransform( diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 69d09e03d..c221fe008 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1083,8 +1083,8 @@ def test_line_horizontal() -> None: ) +@pytest.mark.xfail(reason="failing test") def test_line_h_s1_w2() -> None: - pytest.skip("failing") img, draw = create_base_image_draw((20, 20)) draw.line((5, 5, 14, 6), BLACK, 2) assert_image_equal_tofile( From 54150f2061fa87cb5f629f28958d53c293ea4b90 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 2 Jun 2024 16:26:35 +1000 Subject: [PATCH 56/63] Corrected docstring --- src/PIL/ImageCms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 5f5c5df54..5915cc944 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -777,7 +777,7 @@ def createProfile( :param colorSpace: String, the color space of the profile you wish to create. Currently only "LAB", "XYZ", and "sRGB" are supported. - :param colorTemp: Positive integer for the white point for the profile, in + :param colorTemp: Positive number for the white point for the profile, in degrees Kelvin (i.e. 5000, 6500, 9600, etc.). The default is for D50 illuminant if omitted (5000k). colorTemp is ONLY applied to LAB profiles, and is ignored for XYZ and sRGB. From 4aba0b8238db501b5f498fee9335f380a7d08f88 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 2 Jun 2024 16:27:05 +1000 Subject: [PATCH 57/63] Changed default colorTemp --- src/PIL/ImageCms.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 5915cc944..ec9471f84 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -754,7 +754,7 @@ def applyTransform( def createProfile( - colorSpace: Literal["LAB", "XYZ", "sRGB"], colorTemp: SupportsFloat = -1 + colorSpace: Literal["LAB", "XYZ", "sRGB"], colorTemp: SupportsFloat = 0 ) -> core.CmsProfile: """ (pyCMS) Creates a profile. From 8dae9b618f39b8afd9ae2177dcce6a02ea227006 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sun, 2 Jun 2024 17:53:52 +1000 Subject: [PATCH 58/63] Corrected type hint --- src/PIL/ImageCms.py | 2 +- src/PIL/_imagingcms.pyi | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index ec9471f84..19a79facc 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -1089,7 +1089,7 @@ def isIntentSupported( raise PyCMSError(v) from v -def versions() -> tuple[str, str, str, str]: +def versions() -> tuple[str, str | None, str, str]: """ (pyCMS) Fetches versions. """ diff --git a/src/PIL/_imagingcms.pyi b/src/PIL/_imagingcms.pyi index f704047be..2abd6d0f7 100644 --- a/src/PIL/_imagingcms.pyi +++ b/src/PIL/_imagingcms.pyi @@ -2,7 +2,7 @@ import datetime import sys from typing import Literal, SupportsFloat, TypedDict -littlecms_version: str +littlecms_version: str | None _Tuple3f = tuple[float, float, float] _Tuple2x3f = tuple[_Tuple3f, _Tuple3f] From f5da04adb07f19bd79aededb2be7291d445e4f96 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 3 Jun 2024 21:58:02 +1000 Subject: [PATCH 59/63] Added type hints Co-authored-by: Nulano --- src/PIL/Image.py | 10 ++++------ src/PIL/ImageDraw.py | 8 +++++++- src/PIL/PyAccess.py | 41 ++++++++++++++++++++++++++--------------- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 958b95e3b..dd6984020 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -1511,7 +1511,7 @@ class Image: self._exif._loaded = False self.getexif() - def get_child_images(self): + def get_child_images(self) -> list[ImageFile.ImageFile]: child_images = [] exif = self.getexif() ifds = [] @@ -1535,10 +1535,7 @@ class Image: fp = self.fp thumbnail_offset = ifd.get(513) if thumbnail_offset is not None: - try: - thumbnail_offset += self._exif_offset - except AttributeError: - pass + thumbnail_offset += getattr(self, "_exif_offset", 0) self.fp.seek(thumbnail_offset) data = self.fp.read(ifd.get(514)) fp = io.BytesIO(data) @@ -1604,7 +1601,7 @@ class Image: or "transparency" in self.info ) - def apply_transparency(self): + def apply_transparency(self) -> None: """ If a P mode image has a "transparency" key in the info dictionary, remove the key and instead apply the transparency to the palette. @@ -1616,6 +1613,7 @@ class Image: from . import ImagePalette palette = self.getpalette("RGBA") + assert palette is not None transparency = self.info["transparency"] if isinstance(transparency, bytes): for i, alpha in enumerate(transparency): diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 17c176430..8fe179dd5 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -908,7 +908,13 @@ def getdraw(im=None, hints=None): return im, handler -def floodfill(image: Image.Image, xy, value, border=None, thresh=0) -> None: +def floodfill( + image: Image.Image, + xy: tuple[int, int], + value: float | tuple[int, ...], + border: float | tuple[int, ...] | None = None, + thresh: float = 0, +) -> None: """ (experimental) Fills a bounded region with a given color. diff --git a/src/PIL/PyAccess.py b/src/PIL/PyAccess.py index a9da90613..fe12cb641 100644 --- a/src/PIL/PyAccess.py +++ b/src/PIL/PyAccess.py @@ -22,6 +22,7 @@ from __future__ import annotations import logging import sys +from typing import TYPE_CHECKING from ._deprecate import deprecate @@ -48,9 +49,12 @@ except ImportError as ex: logger = logging.getLogger(__name__) +if TYPE_CHECKING: + from . import Image + class PyAccess: - def __init__(self, img, readonly=False): + def __init__(self, img: Image.Image, readonly: bool = False) -> None: deprecate("PyAccess", 11) vals = dict(img.im.unsafe_ptrs) self.readonly = readonly @@ -77,7 +81,8 @@ class PyAccess: """ Modifies the pixel at x,y. The color is given as a single numerical value for single band images, and a tuple for - multi-band images + multi-band images. In addition to this, RGB and RGBA tuples + are accepted for P and PA images. :param xy: The pixel coordinate, given as (x, y). See :ref:`coordinate-system`. @@ -108,7 +113,7 @@ class PyAccess: return self.set_pixel(x, y, color) - def __getitem__(self, xy): + def __getitem__(self, xy: tuple[int, int]) -> float | tuple[int, ...]: """ Returns the pixel at x,y. The pixel is returned as a single value for single band images or a tuple for multiple band @@ -130,13 +135,19 @@ class PyAccess: putpixel = __setitem__ getpixel = __getitem__ - def check_xy(self, xy): + def check_xy(self, xy: tuple[int, int]) -> tuple[int, int]: (x, y) = xy if not (0 <= x < self.xsize and 0 <= y < self.ysize): msg = "pixel location out of range" raise ValueError(msg) return xy + def get_pixel(self, x: int, y: int) -> float | tuple[int, ...]: + raise NotImplementedError() + + def set_pixel(self, x: int, y: int, color: float | tuple[int, ...]) -> None: + raise NotImplementedError() + class _PyAccess32_2(PyAccess): """PA, LA, stored in first and last bytes of a 32 bit word""" @@ -144,7 +155,7 @@ class _PyAccess32_2(PyAccess): def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) - def get_pixel(self, x, y): + def get_pixel(self, x: int, y: int) -> tuple[int, int]: pixel = self.pixels[y][x] return pixel.r, pixel.a @@ -161,7 +172,7 @@ class _PyAccess32_3(PyAccess): def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) - def get_pixel(self, x, y): + def get_pixel(self, x: int, y: int) -> tuple[int, int, int]: pixel = self.pixels[y][x] return pixel.r, pixel.g, pixel.b @@ -180,7 +191,7 @@ class _PyAccess32_4(PyAccess): def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_RGBA **", self.image32) - def get_pixel(self, x, y): + def get_pixel(self, x: int, y: int) -> tuple[int, int, int, int]: pixel = self.pixels[y][x] return pixel.r, pixel.g, pixel.b, pixel.a @@ -199,7 +210,7 @@ class _PyAccess8(PyAccess): def _post_init(self, *args, **kwargs): self.pixels = self.image8 - def get_pixel(self, x, y): + def get_pixel(self, x: int, y: int) -> int: return self.pixels[y][x] def set_pixel(self, x, y, color): @@ -217,7 +228,7 @@ class _PyAccessI16_N(PyAccess): def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("unsigned short **", self.image) - def get_pixel(self, x, y): + def get_pixel(self, x: int, y: int) -> int: return self.pixels[y][x] def set_pixel(self, x, y, color): @@ -235,7 +246,7 @@ class _PyAccessI16_L(PyAccess): def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_I16 **", self.image) - def get_pixel(self, x, y): + def get_pixel(self, x: int, y: int) -> int: pixel = self.pixels[y][x] return pixel.l + pixel.r * 256 @@ -256,7 +267,7 @@ class _PyAccessI16_B(PyAccess): def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("struct Pixel_I16 **", self.image) - def get_pixel(self, x, y): + def get_pixel(self, x: int, y: int) -> int: pixel = self.pixels[y][x] return pixel.l * 256 + pixel.r @@ -277,7 +288,7 @@ class _PyAccessI32_N(PyAccess): def _post_init(self, *args, **kwargs): self.pixels = self.image32 - def get_pixel(self, x, y): + def get_pixel(self, x: int, y: int) -> int: return self.pixels[y][x] def set_pixel(self, x, y, color): @@ -296,7 +307,7 @@ class _PyAccessI32_Swap(PyAccess): chars[0], chars[1], chars[2], chars[3] = chars[3], chars[2], chars[1], chars[0] return ffi.cast("int *", chars)[0] - def get_pixel(self, x, y): + def get_pixel(self, x: int, y: int) -> int: return self.reverse(self.pixels[y][x]) def set_pixel(self, x, y, color): @@ -309,7 +320,7 @@ class _PyAccessF(PyAccess): def _post_init(self, *args, **kwargs): self.pixels = ffi.cast("float **", self.image32) - def get_pixel(self, x, y): + def get_pixel(self, x: int, y: int) -> float: return self.pixels[y][x] def set_pixel(self, x, y, color): @@ -357,7 +368,7 @@ else: mode_map["I;32B"] = _PyAccessI32_N -def new(img, readonly=False): +def new(img: Image.Image, readonly: bool = False) -> PyAccess | None: access_type = mode_map.get(img.mode, None) if not access_type: logger.debug("PyAccess Not Implemented: %s", img.mode) From 322814d7ce8d48bcbf45ac912aecef445f6743b2 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 3 Jun 2024 17:24:10 +0000 Subject: [PATCH 60/63] [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.4.3 → v0.4.7](https://github.com/astral-sh/ruff-pre-commit/compare/v0.4.3...v0.4.7) - [github.com/pre-commit/mirrors-clang-format: v18.1.4 → v18.1.5](https://github.com/pre-commit/mirrors-clang-format/compare/v18.1.4...v18.1.5) - [github.com/python-jsonschema/check-jsonschema: 0.28.2 → 0.28.4](https://github.com/python-jsonschema/check-jsonschema/compare/0.28.2...0.28.4) - [github.com/abravalheri/validate-pyproject: v0.16 → v0.18](https://github.com/abravalheri/validate-pyproject/compare/v0.16...v0.18) --- .pre-commit-config.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e848eb670..6a76e8c00 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.4.3 + rev: v0.4.7 hooks: - id: ruff args: [--exit-non-zero-on-fix] @@ -24,7 +24,7 @@ repos: exclude: (Makefile$|\.bat$|\.cmake$|\.eps$|\.fits$|\.gd$|\.opt$) - repo: https://github.com/pre-commit/mirrors-clang-format - rev: v18.1.4 + rev: v18.1.5 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.28.2 + rev: 0.28.4 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.16 + rev: v0.18 hooks: - id: validate-pyproject From 6e40601f69875e0734dce467e4a3b969649f65bf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Tue, 4 Jun 2024 20:37:09 +1000 Subject: [PATCH 61/63] Added type hints --- src/PIL/BlpImagePlugin.py | 3 ++- src/PIL/EpsImagePlugin.py | 2 +- src/PIL/FpxImagePlugin.py | 6 +++--- src/PIL/ImageEnhance.py | 13 ++++++++----- src/PIL/ImageFilter.py | 25 ++++++++++++++++--------- src/PIL/ImageMorph.py | 6 +++--- src/PIL/Jpeg2KImagePlugin.py | 6 +++--- src/PIL/PaletteFile.py | 2 +- src/PIL/QoiImagePlugin.py | 2 +- src/PIL/SpiderImagePlugin.py | 2 +- src/PIL/WebPImagePlugin.py | 2 +- 11 files changed, 40 insertions(+), 29 deletions(-) diff --git a/src/PIL/BlpImagePlugin.py b/src/PIL/BlpImagePlugin.py index 782e28cf5..2db115ccc 100644 --- a/src/PIL/BlpImagePlugin.py +++ b/src/PIL/BlpImagePlugin.py @@ -35,6 +35,7 @@ import os import struct from enum import IntEnum from io import BytesIO +from typing import IO from . import Image, ImageFile @@ -448,7 +449,7 @@ class BLPEncoder(ImageFile.PyEncoder): return len(data), 0, data -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: if im.mode != "P": msg = "Unsupported BLP image mode" raise ValueError(msg) diff --git a/src/PIL/EpsImagePlugin.py b/src/PIL/EpsImagePlugin.py index 5a44baa49..d24a2ba80 100644 --- a/src/PIL/EpsImagePlugin.py +++ b/src/PIL/EpsImagePlugin.py @@ -228,7 +228,7 @@ class EpsImageFile(ImageFile.ImageFile): reading_trailer_comments = False trailer_reached = False - def check_required_header_comments(): + def check_required_header_comments() -> None: if "PS-Adobe" not in self.info: msg = 'EPS header missing "%!PS-Adobe" comment' raise SyntaxError(msg) diff --git a/src/PIL/FpxImagePlugin.py b/src/PIL/FpxImagePlugin.py index 4ba93bb39..b3e6c6e36 100644 --- a/src/PIL/FpxImagePlugin.py +++ b/src/PIL/FpxImagePlugin.py @@ -70,7 +70,7 @@ class FpxImageFile(ImageFile.ImageFile): self._open_index(1) - def _open_index(self, index=1): + def _open_index(self, index: int = 1) -> None: # # get the Image Contents Property Set @@ -85,7 +85,7 @@ class FpxImageFile(ImageFile.ImageFile): size = max(self.size) i = 1 while size > 64: - size = size / 2 + size = size // 2 i += 1 self.maxid = i - 1 @@ -118,7 +118,7 @@ class FpxImageFile(ImageFile.ImageFile): self._open_subimage(1, self.maxid) - def _open_subimage(self, index=1, subimage=0): + def _open_subimage(self, index: int = 1, subimage: int = 0) -> None: # # setup tile descriptors for a given subimage diff --git a/src/PIL/ImageEnhance.py b/src/PIL/ImageEnhance.py index 93a50d2a2..d7e99a968 100644 --- a/src/PIL/ImageEnhance.py +++ b/src/PIL/ImageEnhance.py @@ -23,7 +23,10 @@ from . import Image, ImageFilter, ImageStat class _Enhance: - def enhance(self, factor): + image: Image.Image + degenerate: Image.Image + + def enhance(self, factor: float) -> Image.Image: """ Returns an enhanced image. @@ -46,7 +49,7 @@ class Color(_Enhance): the original image. """ - def __init__(self, image): + def __init__(self, image: Image.Image) -> None: self.image = image self.intermediate_mode = "L" if "A" in image.getbands(): @@ -63,7 +66,7 @@ class Contrast(_Enhance): gives a solid gray image. A factor of 1.0 gives the original image. """ - def __init__(self, image): + def __init__(self, image: Image.Image) -> None: self.image = image mean = int(ImageStat.Stat(image.convert("L")).mean[0] + 0.5) self.degenerate = Image.new("L", image.size, mean).convert(image.mode) @@ -80,7 +83,7 @@ class Brightness(_Enhance): original image. """ - def __init__(self, image): + def __init__(self, image: Image.Image) -> None: self.image = image self.degenerate = Image.new(image.mode, image.size, 0) @@ -96,7 +99,7 @@ class Sharpness(_Enhance): original image, and a factor of 2.0 gives a sharpened image. """ - def __init__(self, image): + def __init__(self, image: Image.Image) -> None: self.image = image self.degenerate = image.filter(ImageFilter.SMOOTH) diff --git a/src/PIL/ImageFilter.py b/src/PIL/ImageFilter.py index 43e700b7b..02288e135 100644 --- a/src/PIL/ImageFilter.py +++ b/src/PIL/ImageFilter.py @@ -18,7 +18,8 @@ from __future__ import annotations import abc import functools -from typing import Sequence +from types import ModuleType +from typing import Any, Sequence class Filter: @@ -57,7 +58,13 @@ class Kernel(BuiltinFilter): name = "Kernel" - def __init__(self, size, kernel, scale=None, offset=0): + def __init__( + self, + size: tuple[int, int], + kernel: Sequence[float], + scale: float | None = None, + offset: float = 0, + ) -> None: if scale is None: # default scale is sum of kernel scale = functools.reduce(lambda a, b: a + b, kernel) @@ -194,10 +201,8 @@ class BoxBlur(MultibandFilter): name = "BoxBlur" - def __init__(self, radius): - xy = radius - if not isinstance(xy, (tuple, list)): - xy = (xy, xy) + def __init__(self, radius: float | Sequence[float]) -> None: + xy = radius if isinstance(radius, (tuple, list)) else (radius, radius) if xy[0] < 0 or xy[1] < 0: msg = "radius must be >= 0" raise ValueError(msg) @@ -381,7 +386,9 @@ class Color3DLUT(MultibandFilter): name = "Color 3D LUT" - def __init__(self, size, table, channels=3, target_mode=None, **kwargs): + def __init__( + self, size, table, channels: int = 3, target_mode: str | None = None, **kwargs + ): if channels not in (3, 4): msg = "Only 3 or 4 output channels are supported" raise ValueError(msg) @@ -395,7 +402,7 @@ class Color3DLUT(MultibandFilter): items = size[0] * size[1] * size[2] wrong_size = False - numpy = None + numpy: ModuleType | None = None if hasattr(table, "shape"): try: import numpy @@ -442,7 +449,7 @@ class Color3DLUT(MultibandFilter): self.table = table @staticmethod - def _check_size(size): + def _check_size(size: Any) -> list[int]: try: _, _, _ = size except ValueError as e: diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py index 6ee8c4f25..6a43983d3 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -200,7 +200,7 @@ class MorphOp: elif patterns is not None: self.lut = LutBuilder(patterns=patterns).build_lut() - def apply(self, image: Image.Image): + def apply(self, image: Image.Image) -> tuple[int, Image.Image]: """Run a single morphological operation on an image Returns a tuple of the number of changed pixels and the @@ -216,7 +216,7 @@ class MorphOp: count = _imagingmorph.apply(bytes(self.lut), image.im.id, outimage.im.id) return count, outimage - def match(self, image: Image.Image): + def match(self, image: Image.Image) -> list[tuple[int, int]]: """Get a list of coordinates matching the morphological operation on an image. @@ -231,7 +231,7 @@ class MorphOp: raise ValueError(msg) return _imagingmorph.match(bytes(self.lut), image.im.id) - def get_on_pixels(self, image: Image.Image): + def get_on_pixels(self, image: Image.Image) -> list[tuple[int, int]]: """Get a list of all turned on pixels in a binary image Returns a list of tuples of (x,y) coordinates diff --git a/src/PIL/Jpeg2KImagePlugin.py b/src/PIL/Jpeg2KImagePlugin.py index ce6342bdb..e6395b1cb 100644 --- a/src/PIL/Jpeg2KImagePlugin.py +++ b/src/PIL/Jpeg2KImagePlugin.py @@ -34,7 +34,7 @@ class BoxReader: self.length = length self.remaining_in_box = -1 - def _can_read(self, num_bytes): + def _can_read(self, num_bytes: int) -> bool: if self.has_length and self.fp.tell() + num_bytes > self.length: # Outside box: ensure we don't read past the known file length return False @@ -44,7 +44,7 @@ class BoxReader: else: return True # No length known, just read - def _read_bytes(self, num_bytes): + def _read_bytes(self, num_bytes: int) -> bytes: if not self._can_read(num_bytes): msg = "Not enough data in header" raise SyntaxError(msg) @@ -74,7 +74,7 @@ class BoxReader: else: return True - def next_box_type(self): + def next_box_type(self) -> bytes: # Skip the rest of the box if it has not been read if self.remaining_in_box > 0: self.fp.seek(self.remaining_in_box, os.SEEK_CUR) diff --git a/src/PIL/PaletteFile.py b/src/PIL/PaletteFile.py index dc3175402..eaed5367c 100644 --- a/src/PIL/PaletteFile.py +++ b/src/PIL/PaletteFile.py @@ -48,5 +48,5 @@ class PaletteFile: self.palette = b"".join(self.palette) - def getpalette(self): + def getpalette(self) -> tuple[bytes, str]: return self.palette, self.rawmode diff --git a/src/PIL/QoiImagePlugin.py b/src/PIL/QoiImagePlugin.py index cea8b60da..f2cf06d0d 100644 --- a/src/PIL/QoiImagePlugin.py +++ b/src/PIL/QoiImagePlugin.py @@ -38,7 +38,7 @@ class QoiImageFile(ImageFile.ImageFile): class QoiDecoder(ImageFile.PyDecoder): _pulls_fd = True - def _add_to_previous_pixels(self, value): + def _add_to_previous_pixels(self, value: bytes | bytearray) -> None: self._previous_pixel = value r, g, b, a = value diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index 5b8ad47f0..e5242395f 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -233,7 +233,7 @@ def loadImageSeries(filelist=None): # For saving images in Spider format -def makeSpiderHeader(im): +def makeSpiderHeader(im: Image.Image) -> list[bytes]: nsam, nrow = im.size lenbyt = nsam * 4 # There are labrec records in the header labrec = int(1024 / lenbyt) diff --git a/src/PIL/WebPImagePlugin.py b/src/PIL/WebPImagePlugin.py index ff7402dca..463d6a623 100644 --- a/src/PIL/WebPImagePlugin.py +++ b/src/PIL/WebPImagePlugin.py @@ -117,7 +117,7 @@ class WebPImageFile(ImageFile.ImageFile): # Set logical frame to requested position self.__logical_frame = frame - def _reset(self, reset=True): + def _reset(self, reset: bool = True) -> None: if reset: self._decoder.reset() self.__physical_frame = 0 From b3c534cc9aa1acb7d84d0be83c2919072e46af95 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Wed, 5 Jun 2024 08:29:28 +1000 Subject: [PATCH 62/63] Added type hints --- src/PIL/BmpImagePlugin.py | 9 ++++++--- src/PIL/BufrStubImagePlugin.py | 4 +++- src/PIL/DdsImagePlugin.py | 3 ++- src/PIL/GifImagePlugin.py | 16 +++++++++------- src/PIL/GribStubImagePlugin.py | 4 +++- src/PIL/IcoImagePlugin.py | 6 ++++-- src/PIL/ImImagePlugin.py | 5 +++-- src/PIL/Image.py | 20 ++++++++++---------- src/PIL/MpoImagePlugin.py | 3 ++- src/PIL/PdfImagePlugin.py | 3 ++- src/PIL/SpiderImagePlugin.py | 6 +++--- src/PIL/TiffImagePlugin.py | 4 ++-- src/PIL/WmfImagePlugin.py | 4 +++- 13 files changed, 52 insertions(+), 35 deletions(-) diff --git a/src/PIL/BmpImagePlugin.py b/src/PIL/BmpImagePlugin.py index c5d1cd40d..2df1d8d33 100644 --- a/src/PIL/BmpImagePlugin.py +++ b/src/PIL/BmpImagePlugin.py @@ -25,6 +25,7 @@ from __future__ import annotations import os +from typing import IO from . import Image, ImageFile, ImagePalette from ._binary import i16le as i16 @@ -52,7 +53,7 @@ def _accept(prefix: bytes) -> bool: return prefix[:2] == b"BM" -def _dib_accept(prefix): +def _dib_accept(prefix: bytes) -> bool: return i32(prefix) in [12, 40, 52, 56, 64, 108, 124] @@ -394,11 +395,13 @@ SAVE = { } -def _dib_save(im, fp, filename): +def _dib_save(im: Image.Image, fp: IO[bytes], filename: str) -> None: _save(im, fp, filename, False) -def _save(im, fp, filename, bitmap_header=True): +def _save( + im: Image.Image, fp: IO[bytes], filename: str, bitmap_header: bool = True +) -> None: try: rawmode, bits, colors = SAVE[im.mode] except KeyError as e: diff --git a/src/PIL/BufrStubImagePlugin.py b/src/PIL/BufrStubImagePlugin.py index 826e89daf..6f52204b8 100644 --- a/src/PIL/BufrStubImagePlugin.py +++ b/src/PIL/BufrStubImagePlugin.py @@ -10,6 +10,8 @@ # from __future__ import annotations +from typing import IO + from . import Image, ImageFile _handler = None @@ -58,7 +60,7 @@ class BufrStubImageFile(ImageFile.StubImageFile): return _handler -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: if _handler is None or not hasattr(_handler, "save"): msg = "BUFR save handler not installed" raise OSError(msg) diff --git a/src/PIL/DdsImagePlugin.py b/src/PIL/DdsImagePlugin.py index 1575f2d88..a3efadb03 100644 --- a/src/PIL/DdsImagePlugin.py +++ b/src/PIL/DdsImagePlugin.py @@ -16,6 +16,7 @@ import io import struct import sys from enum import IntEnum, IntFlag +from typing import IO from . import Image, ImageFile, ImagePalette from ._binary import i32le as i32 @@ -510,7 +511,7 @@ class DdsRgbDecoder(ImageFile.PyDecoder): return -1, 0 -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: if im.mode not in ("RGB", "RGBA", "L", "LA"): msg = f"cannot write mode {im.mode} as DDS" raise OSError(msg) diff --git a/src/PIL/GifImagePlugin.py b/src/PIL/GifImagePlugin.py index 962a92834..e62852db3 100644 --- a/src/PIL/GifImagePlugin.py +++ b/src/PIL/GifImagePlugin.py @@ -31,6 +31,7 @@ import os import subprocess from enum import IntEnum from functools import cached_property +from typing import IO from . import ( Image, @@ -336,14 +337,13 @@ class GifImageFile(ImageFile.ImageFile): self._mode = "RGB" self.im = self.im.convert("RGB", Image.Dither.FLOYDSTEINBERG) - def _rgb(color): + def _rgb(color: int) -> tuple[int, int, int]: if self._frame_palette: if color * 3 + 3 > len(self._frame_palette.palette): color = 0 - color = tuple(self._frame_palette.palette[color * 3 : color * 3 + 3]) + return tuple(self._frame_palette.palette[color * 3 : color * 3 + 3]) else: - color = (color, color, color) - return color + return (color, color, color) self.dispose_extent = frame_dispose_extent try: @@ -709,11 +709,13 @@ def _write_multiple_frames(im, fp, palette): return True -def _save_all(im, fp, filename): +def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None: _save(im, fp, filename, save_all=True) -def _save(im, fp, filename, save_all=False): +def _save( + im: Image.Image, fp: IO[bytes], filename: str, save_all: bool = False +) -> None: # header if "palette" in im.encoderinfo or "palette" in im.info: palette = im.encoderinfo.get("palette", im.info.get("palette")) @@ -730,7 +732,7 @@ def _save(im, fp, filename, save_all=False): fp.flush() -def get_interlace(im): +def get_interlace(im: Image.Image) -> int: interlace = im.encoderinfo.get("interlace", 1) # workaround for @PIL153 diff --git a/src/PIL/GribStubImagePlugin.py b/src/PIL/GribStubImagePlugin.py index c27cffab6..b24dcded2 100644 --- a/src/PIL/GribStubImagePlugin.py +++ b/src/PIL/GribStubImagePlugin.py @@ -10,6 +10,8 @@ # from __future__ import annotations +from typing import IO + from . import Image, ImageFile _handler = None @@ -58,7 +60,7 @@ class GribStubImageFile(ImageFile.StubImageFile): return _handler -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: if _handler is None or not hasattr(_handler, "save"): msg = "GRIB save handler not installed" raise OSError(msg) diff --git a/src/PIL/IcoImagePlugin.py b/src/PIL/IcoImagePlugin.py index cea093f9c..af94e5a2e 100644 --- a/src/PIL/IcoImagePlugin.py +++ b/src/PIL/IcoImagePlugin.py @@ -25,6 +25,7 @@ from __future__ import annotations import warnings from io import BytesIO from math import ceil, log +from typing import IO from . import BmpImagePlugin, Image, ImageFile, PngImagePlugin from ._binary import i16le as i16 @@ -39,7 +40,7 @@ from ._binary import o32le as o32 _MAGIC = b"\0\0\1\0" -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: fp.write(_MAGIC) # (2+2) bmp = im.encoderinfo.get("bitmap_format") == "bmp" sizes = im.encoderinfo.get( @@ -194,7 +195,7 @@ class IcoFile: """ return self.frame(self.getentryindex(size, bpp)) - def frame(self, idx): + def frame(self, idx: int) -> Image.Image: """ Get an image from frame idx """ @@ -205,6 +206,7 @@ class IcoFile: data = self.buf.read(8) self.buf.seek(header["offset"]) + im: Image.Image if data[:8] == PngImagePlugin._MAGIC: # png frame im = PngImagePlugin.PngImageFile(self.buf) diff --git a/src/PIL/ImImagePlugin.py b/src/PIL/ImImagePlugin.py index 8e949ebaf..c98cfb098 100644 --- a/src/PIL/ImImagePlugin.py +++ b/src/PIL/ImImagePlugin.py @@ -28,6 +28,7 @@ from __future__ import annotations import os import re +from typing import IO, Any from . import Image, ImageFile, ImagePalette @@ -103,7 +104,7 @@ for j in range(2, 33): split = re.compile(rb"^([A-Za-z][^:]*):[ \t]*(.*)[ \t]*$") -def number(s): +def number(s: Any) -> float: try: return int(s) except ValueError: @@ -325,7 +326,7 @@ SAVE = { } -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: try: image_type, rawmode = SAVE[im.mode] except KeyError as e: diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 6385f204b..8cb4b7e32 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -2875,7 +2875,7 @@ class Image: self.load() return self._new(self.im.transpose(method)) - def effect_spread(self, distance): + def effect_spread(self, distance: int) -> Image: """ Randomly spread pixels in an image. @@ -3012,7 +3012,7 @@ def new( return im._new(core.fill(mode, size, color)) -def frombytes(mode, size, data, decoder_name="raw", *args) -> Image: +def frombytes(mode, size, data, decoder_name: str = "raw", *args) -> Image: """ Creates a copy of an image memory from pixel data in a buffer. @@ -3051,7 +3051,7 @@ def frombytes(mode, size, data, decoder_name="raw", *args) -> Image: return im -def frombuffer(mode, size, data, decoder_name="raw", *args) -> Image: +def frombuffer(mode: str, size, data, decoder_name: str = "raw", *args) -> Image: """ Creates an image memory referencing pixel data in a byte buffer. @@ -3553,7 +3553,7 @@ def register_save(id: str, driver) -> None: SAVE[id.upper()] = driver -def register_save_all(id, driver) -> None: +def register_save_all(id: str, driver) -> None: """ Registers an image function to save all the frames of a multiframe format. This function should not be @@ -3565,7 +3565,7 @@ def register_save_all(id, driver) -> None: SAVE_ALL[id.upper()] = driver -def register_extension(id, extension) -> None: +def register_extension(id: str, extension: str) -> None: """ Registers an image extension. This function should not be used in application code. @@ -3576,7 +3576,7 @@ def register_extension(id, extension) -> None: EXTENSION[extension.lower()] = id.upper() -def register_extensions(id, extensions) -> None: +def register_extensions(id: str, extensions: list[str]) -> None: """ Registers image extensions. This function should not be used in application code. @@ -3588,7 +3588,7 @@ def register_extensions(id, extensions) -> None: register_extension(id, extension) -def registered_extensions(): +def registered_extensions() -> dict[str, str]: """ Returns a dictionary containing all file extensions belonging to registered plugins @@ -3650,7 +3650,7 @@ def effect_mandelbrot(size, extent, quality): return Image()._new(core.effect_mandelbrot(size, extent, quality)) -def effect_noise(size, sigma): +def effect_noise(size: tuple[int, int], sigma: float) -> Image: """ Generate Gaussian noise centered around 128. @@ -3661,7 +3661,7 @@ def effect_noise(size, sigma): return Image()._new(core.effect_noise(size, sigma)) -def linear_gradient(mode): +def linear_gradient(mode: str) -> Image: """ Generate 256x256 linear gradient from black to white, top to bottom. @@ -3670,7 +3670,7 @@ def linear_gradient(mode): return Image()._new(core.linear_gradient(mode)) -def radial_gradient(mode): +def radial_gradient(mode: str) -> Image: """ Generate 256x256 radial gradient from black to white, centre to edge. diff --git a/src/PIL/MpoImagePlugin.py b/src/PIL/MpoImagePlugin.py index 766e1290c..6716722f2 100644 --- a/src/PIL/MpoImagePlugin.py +++ b/src/PIL/MpoImagePlugin.py @@ -22,6 +22,7 @@ from __future__ import annotations import itertools import os import struct +from typing import IO from . import ( Image, @@ -32,7 +33,7 @@ from . import ( from ._binary import o32le -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: JpegImagePlugin._save(im, fp, filename) diff --git a/src/PIL/PdfImagePlugin.py b/src/PIL/PdfImagePlugin.py index 1777f1f20..ccd28f343 100644 --- a/src/PIL/PdfImagePlugin.py +++ b/src/PIL/PdfImagePlugin.py @@ -25,6 +25,7 @@ import io import math import os import time +from typing import IO from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features @@ -39,7 +40,7 @@ from . import Image, ImageFile, ImageSequence, PdfParser, __version__, features # 5. page contents -def _save_all(im, fp, filename): +def _save_all(im: Image.Image, fp: IO[bytes], filename: str) -> None: _save(im, fp, filename, save_all=True) diff --git a/src/PIL/SpiderImagePlugin.py b/src/PIL/SpiderImagePlugin.py index e5242395f..98dd91c0e 100644 --- a/src/PIL/SpiderImagePlugin.py +++ b/src/PIL/SpiderImagePlugin.py @@ -37,7 +37,7 @@ from __future__ import annotations import os import struct import sys -from typing import TYPE_CHECKING +from typing import IO, TYPE_CHECKING from . import Image, ImageFile @@ -263,7 +263,7 @@ def makeSpiderHeader(im: Image.Image) -> list[bytes]: return [struct.pack("f", v) for v in hdr] -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: if im.mode[0] != "F": im = im.convert("F") @@ -279,7 +279,7 @@ def _save(im, fp, filename): ImageFile._save(im, fp, [("raw", (0, 0) + im.size, 0, (rawmode, 0, 1))]) -def _save_spider(im, fp, filename): +def _save_spider(im: Image.Image, fp: IO[bytes], filename: str) -> None: # get the filename extension and register it with Image ext = os.path.splitext(filename)[1] Image.register_extension(SpiderImageFile.format, ext) diff --git a/src/PIL/TiffImagePlugin.py b/src/PIL/TiffImagePlugin.py index f3fa3c24c..04f36744b 100644 --- a/src/PIL/TiffImagePlugin.py +++ b/src/PIL/TiffImagePlugin.py @@ -1995,7 +1995,7 @@ class AppendingTiffWriter: self.finalize() self.setup() - def __enter__(self): + def __enter__(self) -> AppendingTiffWriter: return self def __exit__(self, exc_type, exc_value, traceback): @@ -2023,7 +2023,7 @@ class AppendingTiffWriter: self.f.write(bytes(pad_bytes)) self.offsetOfNewPage = self.f.tell() - def setEndian(self, endian): + def setEndian(self, endian: str) -> None: self.endian = endian self.longFmt = f"{self.endian}L" self.shortFmt = f"{self.endian}H" diff --git a/src/PIL/WmfImagePlugin.py b/src/PIL/WmfImagePlugin.py index fab3e26c5..25a4545db 100644 --- a/src/PIL/WmfImagePlugin.py +++ b/src/PIL/WmfImagePlugin.py @@ -20,6 +20,8 @@ # http://wvware.sourceforge.net/caolan/ora-wmf.html from __future__ import annotations +from typing import IO + from . import Image, ImageFile from ._binary import i16le as word from ._binary import si16le as short @@ -161,7 +163,7 @@ class WmfStubImageFile(ImageFile.StubImageFile): return super().load() -def _save(im, fp, filename): +def _save(im: Image.Image, fp: IO[bytes], filename: str) -> None: if _handler is None or not hasattr(_handler, "save"): msg = "WMF save handler not installed" raise OSError(msg) From 148f0d345f92261e12d28ebbbd3b92d027d667ca Mon Sep 17 00:00:00 2001 From: Hugo van Kemenade <1324225+hugovk@users.noreply.github.com> Date: Thu, 6 Jun 2024 09:38:38 +0300 Subject: [PATCH 63/63] Use Sphinx long options in Makefile --- docs/Makefile | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index 6495e5866..8f13f1aea 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -9,9 +9,9 @@ PAPER = BUILDDIR = _build # Internal variables. -PAPEROPT_a4 = -D latex_paper_size=a4 -PAPEROPT_letter = -D latex_paper_size=letter -ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +PAPEROPT_a4 = --define latex_paper_size=a4 +PAPEROPT_letter = --define latex_paper_size=letter +ALLSPHINXOPTS = --doctree-dir $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . @@ -51,42 +51,42 @@ install-sphinx: .PHONY: html html: $(MAKE) install-sphinx - $(SPHINXBUILD) -b html -W --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html + $(SPHINXBUILD) --builder html --fail-on-warning --keep-going $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." .PHONY: dirhtml dirhtml: $(MAKE) install-sphinx - $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + $(SPHINXBUILD) --builder dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." .PHONY: singlehtml singlehtml: $(MAKE) install-sphinx - $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + $(SPHINXBUILD) --builder singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." .PHONY: pickle pickle: $(MAKE) install-sphinx - $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + $(SPHINXBUILD) --builder pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." .PHONY: json json: $(MAKE) install-sphinx - $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + $(SPHINXBUILD) --builder json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." .PHONY: htmlhelp htmlhelp: $(MAKE) install-sphinx - $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + $(SPHINXBUILD) --builder htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." @@ -94,7 +94,7 @@ htmlhelp: .PHONY: qthelp qthelp: $(MAKE) install-sphinx - $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + $(SPHINXBUILD) --builder qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @@ -105,7 +105,7 @@ qthelp: .PHONY: devhelp devhelp: $(MAKE) install-sphinx - $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + $(SPHINXBUILD) --builder devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @@ -116,14 +116,14 @@ devhelp: .PHONY: epub epub: $(MAKE) install-sphinx - $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + $(SPHINXBUILD) --builder epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." .PHONY: latex latex: $(MAKE) install-sphinx - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + $(SPHINXBUILD) --builder latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ @@ -132,7 +132,7 @@ latex: .PHONY: latexpdf latexpdf: $(MAKE) install-sphinx - $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + $(SPHINXBUILD) --builder latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." @@ -140,21 +140,21 @@ latexpdf: .PHONY: text text: $(MAKE) install-sphinx - $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + $(SPHINXBUILD) --builder text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." .PHONY: man man: $(MAKE) install-sphinx - $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + $(SPHINXBUILD) --builder man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." .PHONY: texinfo texinfo: $(MAKE) install-sphinx - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + $(SPHINXBUILD) --builder texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ @@ -163,7 +163,7 @@ texinfo: .PHONY: info info: $(MAKE) install-sphinx - $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + $(SPHINXBUILD) --builder texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." @@ -171,21 +171,21 @@ info: .PHONY: gettext gettext: $(MAKE) install-sphinx - $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + $(SPHINXBUILD) --builder gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." .PHONY: changes changes: $(MAKE) install-sphinx - $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + $(SPHINXBUILD) --builder changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." .PHONY: linkcheck linkcheck: $(MAKE) install-sphinx - $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck -j auto + $(SPHINXBUILD) --builder linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck -j auto @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." @@ -193,7 +193,7 @@ linkcheck: .PHONY: doctest doctest: $(MAKE) install-sphinx - $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + $(SPHINXBUILD) --builder doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt."