From 2303c063cb9660c8fdd4a5d757e1d971cd47dcf8 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 01:24:51 +0400 Subject: [PATCH 01/31] Use PyCapsule in _imagingmath --- src/PIL/ImageMath.py | 4 +- src/_imagingmath.c | 153 +++++++++++++++++++++++++++---------------- 2 files changed, 98 insertions(+), 59 deletions(-) diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index 191cc2a5f88..b32742657ba 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -65,7 +65,7 @@ def apply( except AttributeError as e: msg = f"bad operand type for '{op}'" raise TypeError(msg) from e - _imagingmath.unop(op, out.im.id, im_1.im.id) + _imagingmath.unop(op, out.im.ptr, im_1.im.ptr) else: # binary operation im_2 = self.__fixup(im2) @@ -93,7 +93,7 @@ def apply( except AttributeError as e: msg = f"bad operand type for '{op}'" raise TypeError(msg) from e - _imagingmath.binop(op, out.im.id, im_1.im.id, im_2.im.id) + _imagingmath.binop(op, out.im.ptr, im_1.im.ptr, im_2.im.ptr) return _Operand(out) # unary operators diff --git a/src/_imagingmath.c b/src/_imagingmath.c index 550a1090382..eafcf1444e9 100644 --- a/src/_imagingmath.c +++ b/src/_imagingmath.c @@ -23,6 +23,9 @@ #define MAX_INT32 2147483647.0 #define MIN_INT32 -2147483648.0 +#define MATH_FUNC_UNOP_MAGIC "Pillow Math unary func" +#define MATH_FUNC_BINOP_MAGIC "Pillow Math binary func" + #define UNOP(name, op, type) \ void name(Imaging out, Imaging im1) { \ int x, y; \ @@ -168,15 +171,28 @@ _unop(PyObject *self, PyObject *args) { Imaging im1; void (*unop)(Imaging, Imaging); - Py_ssize_t op, i0, i1; - if (!PyArg_ParseTuple(args, "nnn", &op, &i0, &i1)) { + PyObject *op, *i0, *i1; + if (!PyArg_ParseTuple(args, "OOO", &op, &i0, &i1)) { return NULL; } - out = (Imaging)i0; - im1 = (Imaging)i1; + if (!PyCapsule_IsValid(op, MATH_FUNC_UNOP_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", MATH_FUNC_UNOP_MAGIC + ); + return NULL; + } + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || + !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); + return NULL; + } - unop = (void *)op; + unop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_UNOP_MAGIC); + out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC); unop(out, im1); @@ -191,16 +207,30 @@ _binop(PyObject *self, PyObject *args) { Imaging im2; void (*binop)(Imaging, Imaging, Imaging); - Py_ssize_t op, i0, i1, i2; - if (!PyArg_ParseTuple(args, "nnnn", &op, &i0, &i1, &i2)) { + PyObject *op, *i0, *i1, *i2; + if (!PyArg_ParseTuple(args, "OOOO", &op, &i0, &i1, &i2)) { return NULL; } - out = (Imaging)i0; - im1 = (Imaging)i1; - im2 = (Imaging)i2; + if (!PyCapsule_IsValid(op, MATH_FUNC_BINOP_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", MATH_FUNC_BINOP_MAGIC + ); + return NULL; + } + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || + !PyCapsule_IsValid(i1, IMAGING_MAGIC) || + !PyCapsule_IsValid(i2, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); + return NULL; + } - binop = (void *)op; + binop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_BINOP_MAGIC); + out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC); + im2 = (Imaging)PyCapsule_GetPointer(i2, IMAGING_MAGIC); binop(out, im1, im2); @@ -213,8 +243,17 @@ static PyMethodDef _functions[] = { }; static void -install(PyObject *d, char *name, void *value) { - PyObject *v = PyLong_FromSsize_t((Py_ssize_t)value); +install_unary(PyObject *d, char *name, void *func) { + PyObject *v = PyCapsule_New(func, MATH_FUNC_UNOP_MAGIC, NULL); + if (!v || PyDict_SetItemString(d, name, v)) { + PyErr_Clear(); + } + Py_XDECREF(v); +} + +static void +install_binary(PyObject *d, char *name, void *func) { + PyObject *v = PyCapsule_New(func, MATH_FUNC_BINOP_MAGIC, NULL); if (!v || PyDict_SetItemString(d, name, v)) { PyErr_Clear(); } @@ -225,50 +264,50 @@ static int setup_module(PyObject *m) { PyObject *d = PyModule_GetDict(m); - install(d, "abs_I", abs_I); - install(d, "neg_I", neg_I); - install(d, "add_I", add_I); - install(d, "sub_I", sub_I); - install(d, "diff_I", diff_I); - install(d, "mul_I", mul_I); - install(d, "div_I", div_I); - install(d, "mod_I", mod_I); - install(d, "min_I", min_I); - install(d, "max_I", max_I); - install(d, "pow_I", pow_I); - - install(d, "invert_I", invert_I); - install(d, "and_I", and_I); - install(d, "or_I", or_I); - install(d, "xor_I", xor_I); - install(d, "lshift_I", lshift_I); - install(d, "rshift_I", rshift_I); - - install(d, "eq_I", eq_I); - install(d, "ne_I", ne_I); - install(d, "lt_I", lt_I); - install(d, "le_I", le_I); - install(d, "gt_I", gt_I); - install(d, "ge_I", ge_I); - - install(d, "abs_F", abs_F); - install(d, "neg_F", neg_F); - install(d, "add_F", add_F); - install(d, "sub_F", sub_F); - install(d, "diff_F", diff_F); - install(d, "mul_F", mul_F); - install(d, "div_F", div_F); - install(d, "mod_F", mod_F); - install(d, "min_F", min_F); - install(d, "max_F", max_F); - install(d, "pow_F", pow_F); - - install(d, "eq_F", eq_F); - install(d, "ne_F", ne_F); - install(d, "lt_F", lt_F); - install(d, "le_F", le_F); - install(d, "gt_F", gt_F); - install(d, "ge_F", ge_F); + install_unary(d, "abs_I", abs_I); + install_unary(d, "neg_I", neg_I); + install_binary(d, "add_I", add_I); + install_binary(d, "sub_I", sub_I); + install_binary(d, "diff_I", diff_I); + install_binary(d, "mul_I", mul_I); + install_binary(d, "div_I", div_I); + install_binary(d, "mod_I", mod_I); + install_binary(d, "min_I", min_I); + install_binary(d, "max_I", max_I); + install_binary(d, "pow_I", pow_I); + + install_unary(d, "invert_I", invert_I); + install_binary(d, "and_I", and_I); + install_binary(d, "or_I", or_I); + install_binary(d, "xor_I", xor_I); + install_binary(d, "lshift_I", lshift_I); + install_binary(d, "rshift_I", rshift_I); + + install_binary(d, "eq_I", eq_I); + install_binary(d, "ne_I", ne_I); + install_binary(d, "lt_I", lt_I); + install_binary(d, "le_I", le_I); + install_binary(d, "gt_I", gt_I); + install_binary(d, "ge_I", ge_I); + + install_unary(d, "abs_F", abs_F); + install_unary(d, "neg_F", neg_F); + install_binary(d, "add_F", add_F); + install_binary(d, "sub_F", sub_F); + install_binary(d, "diff_F", diff_F); + install_binary(d, "mul_F", mul_F); + install_binary(d, "div_F", div_F); + install_binary(d, "mod_F", mod_F); + install_binary(d, "min_F", min_F); + install_binary(d, "max_F", max_F); + install_binary(d, "pow_F", pow_F); + + install_binary(d, "eq_F", eq_F); + install_binary(d, "ne_F", ne_F); + install_binary(d, "lt_F", lt_F); + install_binary(d, "le_F", le_F); + install_binary(d, "gt_F", gt_F); + install_binary(d, "ge_F", ge_F); return 0; } From a9798e78af90674d2983c749c2bb63af9a9e5a6f Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 01:32:28 +0400 Subject: [PATCH 02/31] Use PyCapsule in _imagingcms --- src/PIL/ImageCms.py | 4 ++-- src/_imagingcms.c | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index ec10230f12b..5e256b0ab7f 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -352,7 +352,7 @@ def apply(self, im: Image.Image, imOut: Image.Image | None = None) -> Image.Imag im.load() if imOut is None: imOut = Image.new(self.output_mode, im.size, None) - self.transform.apply(im.im.id, imOut.im.id) + self.transform.apply(im.im.ptr, imOut.im.ptr) imOut.info["icc_profile"] = self.output_profile.tobytes() return imOut @@ -361,7 +361,7 @@ def apply_in_place(self, im: Image.Image) -> Image.Image: if im.mode != self.output_mode: msg = "mode mismatch" raise ValueError(msg) # wrong output mode - self.transform.apply(im.im.id, im.im.id) + self.transform.apply(im.im.ptr, im.im.ptr) im.info["icc_profile"] = self.output_profile.tobytes() return im diff --git a/src/_imagingcms.c b/src/_imagingcms.c index bafe787a743..444b6a60237 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -531,23 +531,26 @@ buildProofTransform(PyObject *self, PyObject *args) { static PyObject * cms_transform_apply(CmsTransformObject *self, PyObject *args) { - Py_ssize_t idIn; - Py_ssize_t idOut; + PyObject *i0, *i1; Imaging im; Imaging imOut; - int result; - - if (!PyArg_ParseTuple(args, "nn:apply", &idIn, &idOut)) { + if (!PyArg_ParseTuple(args, "OO:apply", &i0, &i1)) { return NULL; } - im = (Imaging)idIn; - imOut = (Imaging)idOut; + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || + !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); + return NULL; + } - result = pyCMSdoTransform(im, imOut, self->transform); + im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + imOut = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC); - return Py_BuildValue("i", result); + return Py_BuildValue("i", pyCMSdoTransform(im, imOut, self->transform)); } /* -------------------------------------------------------------------- */ From 2fcab263d3e3aae6156a18a0180e00954f032153 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 01:43:38 +0400 Subject: [PATCH 03/31] Use PyCapsule in _imagingmorph --- Tests/test_imagemorph.py | 8 +++---- src/PIL/ImageMorph.py | 6 ++--- src/_imagingmorph.c | 48 ++++++++++++++++++++++++++++++---------- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 80d8c38155b..04860fd616c 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -328,13 +328,13 @@ def test_wrong_mode() -> None: iml = Image.new("L", (10, 10)) with pytest.raises(RuntimeError): - _imagingmorph.apply(bytes(lut), imrgb.im.id, iml.im.id) + _imagingmorph.apply(bytes(lut), imrgb.im.ptr, iml.im.ptr) with pytest.raises(RuntimeError): - _imagingmorph.apply(bytes(lut), iml.im.id, imrgb.im.id) + _imagingmorph.apply(bytes(lut), iml.im.ptr, imrgb.im.ptr) with pytest.raises(RuntimeError): - _imagingmorph.match(bytes(lut), imrgb.im.id) + _imagingmorph.match(bytes(lut), imrgb.im.ptr) # Should not raise - _imagingmorph.match(bytes(lut), iml.im.id) + _imagingmorph.match(bytes(lut), iml.im.ptr) diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py index 6a43983d33d..bc7ed66d3fb 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -213,7 +213,7 @@ def apply(self, image: Image.Image) -> tuple[int, Image.Image]: msg = "Image mode must be L" raise ValueError(msg) outimage = Image.new(image.mode, image.size, None) - count = _imagingmorph.apply(bytes(self.lut), image.im.id, outimage.im.id) + count = _imagingmorph.apply(bytes(self.lut), image.im.ptr, outimage.im.ptr) return count, outimage def match(self, image: Image.Image) -> list[tuple[int, int]]: @@ -229,7 +229,7 @@ def match(self, image: Image.Image) -> list[tuple[int, int]]: if image.mode != "L": msg = "Image mode must be L" raise ValueError(msg) - return _imagingmorph.match(bytes(self.lut), image.im.id) + return _imagingmorph.match(bytes(self.lut), image.im.ptr) def get_on_pixels(self, image: Image.Image) -> list[tuple[int, int]]: """Get a list of all turned on pixels in a binary image @@ -240,7 +240,7 @@ def get_on_pixels(self, image: Image.Image) -> list[tuple[int, int]]: if image.mode != "L": msg = "Image mode must be L" raise ValueError(msg) - return _imagingmorph.get_on_pixels(image.im.id) + return _imagingmorph.get_on_pixels(image.im.ptr) def load_lut(self, filename: str) -> None: """Load an operator from an mrl file""" diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index 614dfbe7fe7..c7933cc7d5e 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -30,15 +30,15 @@ static PyObject * apply(PyObject *self, PyObject *args) { const char *lut; - PyObject *py_lut; - Py_ssize_t lut_len, i0, i1; + PyObject *py_lut, *i0, *i1; + Py_ssize_t lut_len; Imaging imgin, imgout; int width, height; int row_idx, col_idx; UINT8 **inrows, **outrows; int num_changed_pixels = 0; - if (!PyArg_ParseTuple(args, "Onn", &py_lut, &i0, &i1)) { + if (!PyArg_ParseTuple(args, "OOO", &py_lut, &i0, &i1)) { PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); return NULL; } @@ -57,8 +57,16 @@ apply(PyObject *self, PyObject *args) { lut = PyBytes_AsString(py_lut); - imgin = (Imaging)i0; - imgout = (Imaging)i1; + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || + !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); + return NULL; + } + + imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + imgout = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC); width = imgin->xsize; height = imgin->ysize; @@ -129,8 +137,8 @@ apply(PyObject *self, PyObject *args) { static PyObject * match(PyObject *self, PyObject *args) { const char *lut; - PyObject *py_lut; - Py_ssize_t lut_len, i0; + PyObject *py_lut, *i0; + Py_ssize_t lut_len; Imaging imgin; int width, height; int row_idx, col_idx; @@ -140,7 +148,7 @@ match(PyObject *self, PyObject *args) { return NULL; } - if (!PyArg_ParseTuple(args, "On", &py_lut, &i0)) { + if (!PyArg_ParseTuple(args, "OO", &py_lut, &i0)) { Py_DECREF(ret); PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); return NULL; @@ -161,7 +169,15 @@ match(PyObject *self, PyObject *args) { } lut = PyBytes_AsString(py_lut); - imgin = (Imaging)i0; + + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); + return NULL; + } + + imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) { Py_DECREF(ret); @@ -215,7 +231,7 @@ match(PyObject *self, PyObject *args) { */ static PyObject * get_on_pixels(PyObject *self, PyObject *args) { - Py_ssize_t i0; + PyObject *i0; Imaging img; UINT8 **rows; int row_idx, col_idx; @@ -225,12 +241,20 @@ get_on_pixels(PyObject *self, PyObject *args) { return NULL; } - if (!PyArg_ParseTuple(args, "n", &i0)) { + if (!PyArg_ParseTuple(args, "O", &i0)) { Py_DECREF(ret); PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); return NULL; } - img = (Imaging)i0; + + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); + return NULL; + } + + img = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); rows = img->image8; width = img->xsize; height = img->ysize; From f246be7b13867d6c85f01e38b5d14b093ace66c7 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 02:00:07 +0400 Subject: [PATCH 04/31] Use s# in PyArg_ParseTuple --- src/_imagingmorph.c | 53 ++++++++++--------------------------- src/libImaging/ImPlatform.h | 1 + 2 files changed, 15 insertions(+), 39 deletions(-) diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index c7933cc7d5e..715cccf5142 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -11,7 +11,6 @@ * See the README file for information on usage and redistribution. */ -#include "Python.h" #include "libImaging/Imaging.h" #define LUT_SIZE (1 << 9) @@ -30,7 +29,7 @@ static PyObject * apply(PyObject *self, PyObject *args) { const char *lut; - PyObject *py_lut, *i0, *i1; + PyObject *i0, *i1; Py_ssize_t lut_len; Imaging imgin, imgout; int width, height; @@ -38,25 +37,15 @@ apply(PyObject *self, PyObject *args) { UINT8 **inrows, **outrows; int num_changed_pixels = 0; - if (!PyArg_ParseTuple(args, "OOO", &py_lut, &i0, &i1)) { - PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); + if (!PyArg_ParseTuple(args, "s#OO", &lut, &lut_len, &i0, &i1)) { return NULL; } - if (!PyBytes_Check(py_lut)) { - PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a bytes object"); - return NULL; - } - - lut_len = PyBytes_Size(py_lut); - if (lut_len < LUT_SIZE) { PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size"); return NULL; } - lut = PyBytes_AsString(py_lut); - if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { PyErr_Format( @@ -137,39 +126,22 @@ apply(PyObject *self, PyObject *args) { static PyObject * match(PyObject *self, PyObject *args) { const char *lut; - PyObject *py_lut, *i0; + PyObject *i0; Py_ssize_t lut_len; Imaging imgin; int width, height; int row_idx, col_idx; UINT8 **inrows; - PyObject *ret = PyList_New(0); - if (ret == NULL) { - return NULL; - } - if (!PyArg_ParseTuple(args, "OO", &py_lut, &i0)) { - Py_DECREF(ret); - PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); + if (!PyArg_ParseTuple(args, "s#O", &lut, &lut_len, &i0)) { return NULL; } - if (!PyBytes_Check(py_lut)) { - Py_DECREF(ret); - PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a bytes object"); - return NULL; - } - - lut_len = PyBytes_Size(py_lut); - if (lut_len < LUT_SIZE) { - Py_DECREF(ret); PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size"); return NULL; } - lut = PyBytes_AsString(py_lut); - if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) { PyErr_Format( PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC @@ -180,11 +152,15 @@ match(PyObject *self, PyObject *args) { imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) { - Py_DECREF(ret); PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); return NULL; } + PyObject *ret = PyList_New(0); + if (ret == NULL) { + return NULL; + } + inrows = imgin->image8; width = imgin->xsize; height = imgin->ysize; @@ -236,14 +212,8 @@ get_on_pixels(PyObject *self, PyObject *args) { UINT8 **rows; int row_idx, col_idx; int width, height; - PyObject *ret = PyList_New(0); - if (ret == NULL) { - return NULL; - } if (!PyArg_ParseTuple(args, "O", &i0)) { - Py_DECREF(ret); - PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); return NULL; } @@ -259,6 +229,11 @@ get_on_pixels(PyObject *self, PyObject *args) { width = img->xsize; height = img->ysize; + PyObject *ret = PyList_New(0); + if (ret == NULL) { + return NULL; + } + for (row_idx = 0; row_idx < height; row_idx++) { UINT8 *row = rows[row_idx]; for (col_idx = 0; col_idx < width; col_idx++) { diff --git a/src/libImaging/ImPlatform.h b/src/libImaging/ImPlatform.h index f6e7fb6b921..c9b7e43b425 100644 --- a/src/libImaging/ImPlatform.h +++ b/src/libImaging/ImPlatform.h @@ -7,6 +7,7 @@ * Copyright (c) Fredrik Lundh 1995-2003. */ +#define PY_SSIZE_T_CLEAN #include "Python.h" /* Check that we have an ANSI compliant compiler */ From 7435a06cbbd0dd98319f8c55752794eb6f11aa17 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 02:19:44 +0400 Subject: [PATCH 05/31] Deprecate ImageCore.id and ImageCore.unsafe_ptrs --- Tests/test_image_getim.py | 12 ++++++++++-- docs/deprecations.rst | 10 ++++++++++ src/_imaging.c | 14 ++++++++++++++ src/libImaging/Imaging.h | 2 +- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_getim.py b/Tests/test_image_getim.py index 9afa02b0a8b..75d97cd4451 100644 --- a/Tests/test_image_getim.py +++ b/Tests/test_image_getim.py @@ -1,11 +1,19 @@ from __future__ import annotations +import pytest + from .helper import hopper def test_sanity() -> None: im = hopper() - type_repr = repr(type(im.getim())) + type_repr = repr(type(im.getim())) assert "PyCapsule" in type_repr - assert isinstance(im.im.id, int) + + with pytest.warns(DeprecationWarning): + assert isinstance(im.im.id, int) + + with pytest.warns(DeprecationWarning): + ptrs = dict(im.im.unsafe_ptrs) + assert all(k in ptrs for k in ["image8", "image32", "image"]) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index a9498d5ed5e..5bb0ebf588a 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -136,6 +136,16 @@ Specific WebP Feature Checks ``True`` if the WebP module is installed, until they are removed in Pillow 12.0.0 (2025-10-15). +Get Internal Pointers to Objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 11.0.0 + +``Image.core.ImageCore.id`` and ``Image.core.ImageCore.unsafe_ptrs`` +have been deprecated and will be removed in Pillow 12 (2025-10-15). +They were used for obtaining raw pointers to ``ImageCore`` internals. To interact with +C code, you can use ``Image.core.ImageCore.ptr``, which returns a ``PyCapsule`` object. + Removed features ---------------- diff --git a/src/_imaging.c b/src/_imaging.c index 07d9a64ccff..a455c873b15 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3707,6 +3707,13 @@ _getattr_bands(ImagingObject *self, void *closure) { static PyObject * _getattr_id(ImagingObject *self, void *closure) { + if (PyErr_WarnEx( + PyExc_DeprecationWarning, + "id property is deprecated and will be removed in Pillow 12.0", + 1 + ) < 0) { + return NULL; + } return PyLong_FromSsize_t((Py_ssize_t)self->image); } @@ -3717,6 +3724,13 @@ _getattr_ptr(ImagingObject *self, void *closure) { static PyObject * _getattr_unsafe_ptrs(ImagingObject *self, void *closure) { + if (PyErr_WarnEx( + PyExc_DeprecationWarning, + "unsafe_ptrs property is deprecated and will be removed in Pillow 12.0", + 1 + ) < 0) { + return NULL; + } return Py_BuildValue( "(sn)(sn)(sn)", "image8", diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 321dce988c3..870f5ed971a 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -61,7 +61,7 @@ typedef struct ImagingOutlineInstance *ImagingOutline; typedef struct ImagingPaletteInstance *ImagingPalette; /* handle magics (used with PyCObject). */ -#define IMAGING_MAGIC "PIL Imaging" +#define IMAGING_MAGIC "Pillow Imaging" /* pixel types */ #define IMAGING_TYPE_UINT8 0 From c69ad03af0216f1cce2d9f1c1653bec6c295e6a6 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 03:45:18 +0400 Subject: [PATCH 06/31] Remove legacy 1-bit api, fix AttributeError PytestUnraisableExceptionWarning: Exception ignored in: AttributeError: 'PhotoImage' object has no attribute '_PhotoImage__photo' --- src/PIL/ImageTk.py | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index a788c9d1f13..d546989eeec 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -35,20 +35,6 @@ # -------------------------------------------------------------------- # Check for Tkinter interface hooks -_pilbitmap_ok = None - - -def _pilbitmap_check() -> int: - global _pilbitmap_ok - if _pilbitmap_ok is None: - try: - im = Image.new("1", (1, 1)) - tkinter.BitmapImage(data=f"PIL:{im.im.id}") - _pilbitmap_ok = 1 - except tkinter.TclError: - _pilbitmap_ok = 0 - return _pilbitmap_ok - def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None: source = None @@ -142,6 +128,8 @@ def __init__( self.paste(image) def __del__(self) -> None: + if not hasattr(self, "__photo"): + return name = self.__photo.name self.__photo.name = None try: @@ -225,17 +213,11 @@ def __init__(self, image: Image.Image | None = None, **kw: Any) -> None: self.__mode = image.mode self.__size = image.size - if _pilbitmap_check(): - # fast way (requires the pilbitmap booster patch) - image.load() - kw["data"] = f"PIL:{image.im.id}" - self.__im = image # must keep a reference - else: - # slow but safe way - kw["data"] = image.tobitmap() - self.__photo = tkinter.BitmapImage(**kw) + self.__photo = tkinter.BitmapImage(data=image.tobitmap(), **kw) def __del__(self) -> None: + if not hasattr(self, "__photo"): + return name = self.__photo.name self.__photo.name = None try: From 920c4ac4479ee12636f90e281e14e8bfc89b0580 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 04:29:34 +0400 Subject: [PATCH 07/31] Use PyCapsule in _imagingtk --- src/PIL/ImageTk.py | 11 +++++------ src/Tk/tkImaging.c | 35 ++++++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index d546989eeec..279a21716b2 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -48,18 +48,18 @@ def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None: def _pyimagingtkcall( - command: str, photo: PhotoImage | tkinter.PhotoImage, id: int + command: str, photo: PhotoImage | tkinter.PhotoImage, ptr: object ) -> None: tk = photo.tk try: - tk.call(command, photo, id) + tk.call(command, photo, repr(ptr)) except tkinter.TclError: # activate Tkinter hook # may raise an error if it cannot attach to Tkinter from . import _imagingtk _imagingtk.tkinit(tk.interpaddr()) - tk.call(command, photo, id) + tk.call(command, photo, repr(ptr)) # -------------------------------------------------------------------- @@ -181,7 +181,7 @@ def paste(self, im: Image.Image) -> None: block = image.new_block(self.__mode, im.size) image.convert2(block, image) # convert directly between buffers - _pyimagingtkcall("PyImagingPhoto", self.__photo, block.id) + _pyimagingtkcall("PyImagingPhoto", self.__photo, block.ptr) # -------------------------------------------------------------------- @@ -255,9 +255,8 @@ def __str__(self) -> str: def getimage(photo: PhotoImage) -> Image.Image: """Copies the contents of a PhotoImage to a PIL image memory.""" im = Image.new("RGBA", (photo.width(), photo.height())) - block = im.im - _pyimagingtkcall("PyImagingPhotoGet", photo, block.id) + _pyimagingtkcall("PyImagingPhotoGet", photo, im.im.ptr) return im diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index 727ee6bed51..b33a47d3007 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -56,19 +56,36 @@ static Tk_PhotoPutBlock_t TK_PHOTO_PUT_BLOCK; static Imaging ImagingFind(const char *name) { - Py_ssize_t id; + PyObject *capsule; + int direct_pointer = 0; + const char *expected = "capsule object \"" IMAGING_MAGIC "\" at 0x"; - /* FIXME: use CObject instead? */ -#if defined(_WIN64) - id = _atoi64(name); -#else - id = atol(name); -#endif - if (!id) { + if (name[0] == '<') { + name++; + } else { + // Special case for PyPy, where the string representation of a Capsule + // refers directly to the pointer itself, not to the PyCapsule object. + direct_pointer = 1; + } + + if (strncmp(name, expected, strlen(expected))) { + return NULL; + } + + capsule = (PyObject *)strtoull(name + strlen(expected), NULL, 16); + + if (direct_pointer) { + return (Imaging)capsule; + } + + if (!PyCapsule_IsValid(capsule, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); return NULL; } - return (Imaging)id; + return (Imaging)PyCapsule_GetPointer(capsule, IMAGING_MAGIC); } static int From 147f75ee9376688560942f814a66ea0c2ad03361 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 13:04:57 +0400 Subject: [PATCH 08/31] Use PyCapsule in _imagingft --- src/_imagingft.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index f8143e0cc32..af5d80f87ce 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -824,7 +824,6 @@ font_render(FontObject *self, PyObject *args) { unsigned char convert_scale; /* scale factor for non-8bpp bitmaps */ PyObject *image; Imaging im; - Py_ssize_t id; int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ int color = 0; /* is FT_LOAD_COLOR enabled? */ int stroke_width = 0; @@ -923,10 +922,9 @@ font_render(FontObject *self, PyObject *args) { PyMem_Del(glyph_info); return NULL; } - PyObject *imageId = PyObject_GetAttrString(image, "id"); - id = PyLong_AsSsize_t(imageId); - Py_XDECREF(imageId); - im = (Imaging)id; + PyObject *imagePtr = PyObject_GetAttrString(image, "ptr"); + im = (Imaging)PyCapsule_GetPointer(imagePtr, IMAGING_MAGIC); + Py_XDECREF(imagePtr); x_offset -= stroke_width; y_offset -= stroke_width; From bf11639626373bd27a80f790d56101d4b4e2f1c6 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Tue, 3 Sep 2024 11:54:24 +0400 Subject: [PATCH 09/31] rename PyCapsule -> Capsule --- docs/deprecations.rst | 2 +- src/Tk/tkImaging.c | 4 +--- src/_imagingcms.c | 4 +--- src/_imagingmath.c | 16 ++++------------ src/_imagingmorph.c | 12 +++--------- 5 files changed, 10 insertions(+), 28 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index 5bb0ebf588a..93bd51ef13e 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -144,7 +144,7 @@ Get Internal Pointers to Objects ``Image.core.ImageCore.id`` and ``Image.core.ImageCore.unsafe_ptrs`` have been deprecated and will be removed in Pillow 12 (2025-10-15). They were used for obtaining raw pointers to ``ImageCore`` internals. To interact with -C code, you can use ``Image.core.ImageCore.ptr``, which returns a ``PyCapsule`` object. +C code, you can use ``Image.core.ImageCore.ptr``, which returns a ``Capsule`` object. Removed features ---------------- diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index b33a47d3007..a36c3e0bdc4 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -79,9 +79,7 @@ ImagingFind(const char *name) { } if (!PyCapsule_IsValid(capsule, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 444b6a60237..1823bcf0371 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -541,9 +541,7 @@ cms_transform_apply(CmsTransformObject *self, PyObject *args) { if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } diff --git a/src/_imagingmath.c b/src/_imagingmath.c index eafcf1444e9..dbe636707f4 100644 --- a/src/_imagingmath.c +++ b/src/_imagingmath.c @@ -177,16 +177,12 @@ _unop(PyObject *self, PyObject *args) { } if (!PyCapsule_IsValid(op, MATH_FUNC_UNOP_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", MATH_FUNC_UNOP_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", MATH_FUNC_UNOP_MAGIC); return NULL; } if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } @@ -213,17 +209,13 @@ _binop(PyObject *self, PyObject *args) { } if (!PyCapsule_IsValid(op, MATH_FUNC_BINOP_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", MATH_FUNC_BINOP_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", MATH_FUNC_BINOP_MAGIC); return NULL; } if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || !PyCapsule_IsValid(i1, IMAGING_MAGIC) || !PyCapsule_IsValid(i2, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index 715cccf5142..637295be710 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -48,9 +48,7 @@ apply(PyObject *self, PyObject *args) { if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } @@ -143,9 +141,7 @@ match(PyObject *self, PyObject *args) { } if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } @@ -218,9 +214,7 @@ get_on_pixels(PyObject *self, PyObject *args) { } if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } From 8833548e55977493fcd21b8270ba515e0503a3df Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Thu, 5 Sep 2024 01:04:35 +0400 Subject: [PATCH 10/31] Move new_block to module --- src/PIL/ImageTk.py | 2 +- src/_imaging.c | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 279a21716b2..5bad9b5462d 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -178,7 +178,7 @@ def paste(self, im: Image.Image) -> None: if image.isblock() and im.mode == self.__mode: block = image else: - block = image.new_block(self.__mode, im.size) + block = Image.core.new_block(self.__mode, im.size) image.convert2(block, image) # convert directly between buffers _pyimagingtkcall("PyImagingPhoto", self.__photo, block.ptr) diff --git a/src/_imaging.c b/src/_imaging.c index a455c873b15..e227d8efc51 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3674,15 +3674,12 @@ static struct PyMethodDef methods[] = { /* Unsharpmask extension */ {"gaussian_blur", (PyCFunction)_gaussian_blur, METH_VARARGS}, {"unsharp_mask", (PyCFunction)_unsharp_mask, METH_VARARGS}, - {"box_blur", (PyCFunction)_box_blur, METH_VARARGS}, /* Special effects */ {"effect_spread", (PyCFunction)_effect_spread, METH_VARARGS}, /* Misc. */ - {"new_block", (PyCFunction)_new_block, METH_VARARGS}, - {"save_ppm", (PyCFunction)_save_ppm, METH_VARARGS}, {NULL, NULL} /* sentinel */ @@ -4212,6 +4209,7 @@ static PyMethodDef functions[] = { {"blend", (PyCFunction)_blend, METH_VARARGS}, {"fill", (PyCFunction)_fill, METH_VARARGS}, {"new", (PyCFunction)_new, METH_VARARGS}, + {"new_block", (PyCFunction)_new_block, METH_VARARGS}, {"merge", (PyCFunction)_merge, METH_VARARGS}, /* Functions */ From 6f9128bfaf4c91a32e075847fa2c629d826c7eaf Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 7 Sep 2024 14:28:36 +1000 Subject: [PATCH 11/31] Updated type hint --- src/PIL/Image.py | 7 +------ src/PIL/ImageTk.py | 5 ++++- src/PIL/_typing.py | 5 +++++ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/PIL/Image.py b/src/PIL/Image.py index 1c0cf293633..871d38a83f9 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -224,12 +224,7 @@ class Quantize(IntEnum): from IPython.lib.pretty import PrettyPrinter from . import ImageFile, ImageFilter, ImagePalette, ImageQt, TiffImagePlugin - from ._typing import NumpyArray, StrOrBytesPath, TypeGuard - - if sys.version_info >= (3, 13): - from types import CapsuleType - else: - CapsuleType = object + from ._typing import CapsuleType, NumpyArray, StrOrBytesPath, TypeGuard ID: list[str] = [] OPEN: dict[ str, diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 5bad9b5462d..d01af364bb5 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -32,6 +32,9 @@ from . import Image, ImageFile +if TYPE_CHECKING: + from ._typing import CapsuleType + # -------------------------------------------------------------------- # Check for Tkinter interface hooks @@ -48,7 +51,7 @@ def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None: def _pyimagingtkcall( - command: str, photo: PhotoImage | tkinter.PhotoImage, ptr: object + command: str, photo: PhotoImage | tkinter.PhotoImage, ptr: CapsuleType ) -> None: tk = photo.tk try: diff --git a/src/PIL/_typing.py b/src/PIL/_typing.py index 093778464da..dd08cdd5042 100644 --- a/src/PIL/_typing.py +++ b/src/PIL/_typing.py @@ -15,6 +15,11 @@ except (ImportError, AttributeError): pass +if sys.version_info >= (3, 13): + from types import CapsuleType +else: + CapsuleType = object + if sys.version_info >= (3, 10): from typing import TypeGuard else: From fe002a72606ff33279ce798f77154f5bf8dcef17 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 01:24:51 +0400 Subject: [PATCH 12/31] Use PyCapsule in _imagingmath --- src/PIL/ImageMath.py | 4 +- src/_imagingmath.c | 153 +++++++++++++++++++++++++++---------------- 2 files changed, 98 insertions(+), 59 deletions(-) diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index 191cc2a5f88..b32742657ba 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -65,7 +65,7 @@ def apply( except AttributeError as e: msg = f"bad operand type for '{op}'" raise TypeError(msg) from e - _imagingmath.unop(op, out.im.id, im_1.im.id) + _imagingmath.unop(op, out.im.ptr, im_1.im.ptr) else: # binary operation im_2 = self.__fixup(im2) @@ -93,7 +93,7 @@ def apply( except AttributeError as e: msg = f"bad operand type for '{op}'" raise TypeError(msg) from e - _imagingmath.binop(op, out.im.id, im_1.im.id, im_2.im.id) + _imagingmath.binop(op, out.im.ptr, im_1.im.ptr, im_2.im.ptr) return _Operand(out) # unary operators diff --git a/src/_imagingmath.c b/src/_imagingmath.c index 550a1090382..eafcf1444e9 100644 --- a/src/_imagingmath.c +++ b/src/_imagingmath.c @@ -23,6 +23,9 @@ #define MAX_INT32 2147483647.0 #define MIN_INT32 -2147483648.0 +#define MATH_FUNC_UNOP_MAGIC "Pillow Math unary func" +#define MATH_FUNC_BINOP_MAGIC "Pillow Math binary func" + #define UNOP(name, op, type) \ void name(Imaging out, Imaging im1) { \ int x, y; \ @@ -168,15 +171,28 @@ _unop(PyObject *self, PyObject *args) { Imaging im1; void (*unop)(Imaging, Imaging); - Py_ssize_t op, i0, i1; - if (!PyArg_ParseTuple(args, "nnn", &op, &i0, &i1)) { + PyObject *op, *i0, *i1; + if (!PyArg_ParseTuple(args, "OOO", &op, &i0, &i1)) { return NULL; } - out = (Imaging)i0; - im1 = (Imaging)i1; + if (!PyCapsule_IsValid(op, MATH_FUNC_UNOP_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", MATH_FUNC_UNOP_MAGIC + ); + return NULL; + } + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || + !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); + return NULL; + } - unop = (void *)op; + unop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_UNOP_MAGIC); + out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC); unop(out, im1); @@ -191,16 +207,30 @@ _binop(PyObject *self, PyObject *args) { Imaging im2; void (*binop)(Imaging, Imaging, Imaging); - Py_ssize_t op, i0, i1, i2; - if (!PyArg_ParseTuple(args, "nnnn", &op, &i0, &i1, &i2)) { + PyObject *op, *i0, *i1, *i2; + if (!PyArg_ParseTuple(args, "OOOO", &op, &i0, &i1, &i2)) { return NULL; } - out = (Imaging)i0; - im1 = (Imaging)i1; - im2 = (Imaging)i2; + if (!PyCapsule_IsValid(op, MATH_FUNC_BINOP_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", MATH_FUNC_BINOP_MAGIC + ); + return NULL; + } + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || + !PyCapsule_IsValid(i1, IMAGING_MAGIC) || + !PyCapsule_IsValid(i2, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); + return NULL; + } - binop = (void *)op; + binop = (void *)PyCapsule_GetPointer(op, MATH_FUNC_BINOP_MAGIC); + out = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + im1 = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC); + im2 = (Imaging)PyCapsule_GetPointer(i2, IMAGING_MAGIC); binop(out, im1, im2); @@ -213,8 +243,17 @@ static PyMethodDef _functions[] = { }; static void -install(PyObject *d, char *name, void *value) { - PyObject *v = PyLong_FromSsize_t((Py_ssize_t)value); +install_unary(PyObject *d, char *name, void *func) { + PyObject *v = PyCapsule_New(func, MATH_FUNC_UNOP_MAGIC, NULL); + if (!v || PyDict_SetItemString(d, name, v)) { + PyErr_Clear(); + } + Py_XDECREF(v); +} + +static void +install_binary(PyObject *d, char *name, void *func) { + PyObject *v = PyCapsule_New(func, MATH_FUNC_BINOP_MAGIC, NULL); if (!v || PyDict_SetItemString(d, name, v)) { PyErr_Clear(); } @@ -225,50 +264,50 @@ static int setup_module(PyObject *m) { PyObject *d = PyModule_GetDict(m); - install(d, "abs_I", abs_I); - install(d, "neg_I", neg_I); - install(d, "add_I", add_I); - install(d, "sub_I", sub_I); - install(d, "diff_I", diff_I); - install(d, "mul_I", mul_I); - install(d, "div_I", div_I); - install(d, "mod_I", mod_I); - install(d, "min_I", min_I); - install(d, "max_I", max_I); - install(d, "pow_I", pow_I); - - install(d, "invert_I", invert_I); - install(d, "and_I", and_I); - install(d, "or_I", or_I); - install(d, "xor_I", xor_I); - install(d, "lshift_I", lshift_I); - install(d, "rshift_I", rshift_I); - - install(d, "eq_I", eq_I); - install(d, "ne_I", ne_I); - install(d, "lt_I", lt_I); - install(d, "le_I", le_I); - install(d, "gt_I", gt_I); - install(d, "ge_I", ge_I); - - install(d, "abs_F", abs_F); - install(d, "neg_F", neg_F); - install(d, "add_F", add_F); - install(d, "sub_F", sub_F); - install(d, "diff_F", diff_F); - install(d, "mul_F", mul_F); - install(d, "div_F", div_F); - install(d, "mod_F", mod_F); - install(d, "min_F", min_F); - install(d, "max_F", max_F); - install(d, "pow_F", pow_F); - - install(d, "eq_F", eq_F); - install(d, "ne_F", ne_F); - install(d, "lt_F", lt_F); - install(d, "le_F", le_F); - install(d, "gt_F", gt_F); - install(d, "ge_F", ge_F); + install_unary(d, "abs_I", abs_I); + install_unary(d, "neg_I", neg_I); + install_binary(d, "add_I", add_I); + install_binary(d, "sub_I", sub_I); + install_binary(d, "diff_I", diff_I); + install_binary(d, "mul_I", mul_I); + install_binary(d, "div_I", div_I); + install_binary(d, "mod_I", mod_I); + install_binary(d, "min_I", min_I); + install_binary(d, "max_I", max_I); + install_binary(d, "pow_I", pow_I); + + install_unary(d, "invert_I", invert_I); + install_binary(d, "and_I", and_I); + install_binary(d, "or_I", or_I); + install_binary(d, "xor_I", xor_I); + install_binary(d, "lshift_I", lshift_I); + install_binary(d, "rshift_I", rshift_I); + + install_binary(d, "eq_I", eq_I); + install_binary(d, "ne_I", ne_I); + install_binary(d, "lt_I", lt_I); + install_binary(d, "le_I", le_I); + install_binary(d, "gt_I", gt_I); + install_binary(d, "ge_I", ge_I); + + install_unary(d, "abs_F", abs_F); + install_unary(d, "neg_F", neg_F); + install_binary(d, "add_F", add_F); + install_binary(d, "sub_F", sub_F); + install_binary(d, "diff_F", diff_F); + install_binary(d, "mul_F", mul_F); + install_binary(d, "div_F", div_F); + install_binary(d, "mod_F", mod_F); + install_binary(d, "min_F", min_F); + install_binary(d, "max_F", max_F); + install_binary(d, "pow_F", pow_F); + + install_binary(d, "eq_F", eq_F); + install_binary(d, "ne_F", ne_F); + install_binary(d, "lt_F", lt_F); + install_binary(d, "le_F", le_F); + install_binary(d, "gt_F", gt_F); + install_binary(d, "ge_F", ge_F); return 0; } From 56bc6a1a71da0d51666689c9d89a21615edfc757 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 01:32:28 +0400 Subject: [PATCH 13/31] Use PyCapsule in _imagingcms --- src/PIL/ImageCms.py | 4 ++-- src/_imagingcms.c | 21 ++++++++++++--------- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index ec10230f12b..5e256b0ab7f 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -352,7 +352,7 @@ def apply(self, im: Image.Image, imOut: Image.Image | None = None) -> Image.Imag im.load() if imOut is None: imOut = Image.new(self.output_mode, im.size, None) - self.transform.apply(im.im.id, imOut.im.id) + self.transform.apply(im.im.ptr, imOut.im.ptr) imOut.info["icc_profile"] = self.output_profile.tobytes() return imOut @@ -361,7 +361,7 @@ def apply_in_place(self, im: Image.Image) -> Image.Image: if im.mode != self.output_mode: msg = "mode mismatch" raise ValueError(msg) # wrong output mode - self.transform.apply(im.im.id, im.im.id) + self.transform.apply(im.im.ptr, im.im.ptr) im.info["icc_profile"] = self.output_profile.tobytes() return im diff --git a/src/_imagingcms.c b/src/_imagingcms.c index bafe787a743..444b6a60237 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -531,23 +531,26 @@ buildProofTransform(PyObject *self, PyObject *args) { static PyObject * cms_transform_apply(CmsTransformObject *self, PyObject *args) { - Py_ssize_t idIn; - Py_ssize_t idOut; + PyObject *i0, *i1; Imaging im; Imaging imOut; - int result; - - if (!PyArg_ParseTuple(args, "nn:apply", &idIn, &idOut)) { + if (!PyArg_ParseTuple(args, "OO:apply", &i0, &i1)) { return NULL; } - im = (Imaging)idIn; - imOut = (Imaging)idOut; + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || + !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); + return NULL; + } - result = pyCMSdoTransform(im, imOut, self->transform); + im = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + imOut = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC); - return Py_BuildValue("i", result); + return Py_BuildValue("i", pyCMSdoTransform(im, imOut, self->transform)); } /* -------------------------------------------------------------------- */ From f916b5dc87664b89603f413c179f1ccc6aeab95a Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 01:43:38 +0400 Subject: [PATCH 14/31] Use PyCapsule in _imagingmorph --- Tests/test_imagemorph.py | 8 +++---- src/PIL/ImageMorph.py | 6 ++--- src/_imagingmorph.c | 48 ++++++++++++++++++++++++++++++---------- 3 files changed, 43 insertions(+), 19 deletions(-) diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 80d8c38155b..04860fd616c 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -328,13 +328,13 @@ def test_wrong_mode() -> None: iml = Image.new("L", (10, 10)) with pytest.raises(RuntimeError): - _imagingmorph.apply(bytes(lut), imrgb.im.id, iml.im.id) + _imagingmorph.apply(bytes(lut), imrgb.im.ptr, iml.im.ptr) with pytest.raises(RuntimeError): - _imagingmorph.apply(bytes(lut), iml.im.id, imrgb.im.id) + _imagingmorph.apply(bytes(lut), iml.im.ptr, imrgb.im.ptr) with pytest.raises(RuntimeError): - _imagingmorph.match(bytes(lut), imrgb.im.id) + _imagingmorph.match(bytes(lut), imrgb.im.ptr) # Should not raise - _imagingmorph.match(bytes(lut), iml.im.id) + _imagingmorph.match(bytes(lut), iml.im.ptr) diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py index 6a43983d33d..bc7ed66d3fb 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -213,7 +213,7 @@ def apply(self, image: Image.Image) -> tuple[int, Image.Image]: msg = "Image mode must be L" raise ValueError(msg) outimage = Image.new(image.mode, image.size, None) - count = _imagingmorph.apply(bytes(self.lut), image.im.id, outimage.im.id) + count = _imagingmorph.apply(bytes(self.lut), image.im.ptr, outimage.im.ptr) return count, outimage def match(self, image: Image.Image) -> list[tuple[int, int]]: @@ -229,7 +229,7 @@ def match(self, image: Image.Image) -> list[tuple[int, int]]: if image.mode != "L": msg = "Image mode must be L" raise ValueError(msg) - return _imagingmorph.match(bytes(self.lut), image.im.id) + return _imagingmorph.match(bytes(self.lut), image.im.ptr) def get_on_pixels(self, image: Image.Image) -> list[tuple[int, int]]: """Get a list of all turned on pixels in a binary image @@ -240,7 +240,7 @@ def get_on_pixels(self, image: Image.Image) -> list[tuple[int, int]]: if image.mode != "L": msg = "Image mode must be L" raise ValueError(msg) - return _imagingmorph.get_on_pixels(image.im.id) + return _imagingmorph.get_on_pixels(image.im.ptr) def load_lut(self, filename: str) -> None: """Load an operator from an mrl file""" diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index 614dfbe7fe7..c7933cc7d5e 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -30,15 +30,15 @@ static PyObject * apply(PyObject *self, PyObject *args) { const char *lut; - PyObject *py_lut; - Py_ssize_t lut_len, i0, i1; + PyObject *py_lut, *i0, *i1; + Py_ssize_t lut_len; Imaging imgin, imgout; int width, height; int row_idx, col_idx; UINT8 **inrows, **outrows; int num_changed_pixels = 0; - if (!PyArg_ParseTuple(args, "Onn", &py_lut, &i0, &i1)) { + if (!PyArg_ParseTuple(args, "OOO", &py_lut, &i0, &i1)) { PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); return NULL; } @@ -57,8 +57,16 @@ apply(PyObject *self, PyObject *args) { lut = PyBytes_AsString(py_lut); - imgin = (Imaging)i0; - imgout = (Imaging)i1; + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || + !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); + return NULL; + } + + imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); + imgout = (Imaging)PyCapsule_GetPointer(i1, IMAGING_MAGIC); width = imgin->xsize; height = imgin->ysize; @@ -129,8 +137,8 @@ apply(PyObject *self, PyObject *args) { static PyObject * match(PyObject *self, PyObject *args) { const char *lut; - PyObject *py_lut; - Py_ssize_t lut_len, i0; + PyObject *py_lut, *i0; + Py_ssize_t lut_len; Imaging imgin; int width, height; int row_idx, col_idx; @@ -140,7 +148,7 @@ match(PyObject *self, PyObject *args) { return NULL; } - if (!PyArg_ParseTuple(args, "On", &py_lut, &i0)) { + if (!PyArg_ParseTuple(args, "OO", &py_lut, &i0)) { Py_DECREF(ret); PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); return NULL; @@ -161,7 +169,15 @@ match(PyObject *self, PyObject *args) { } lut = PyBytes_AsString(py_lut); - imgin = (Imaging)i0; + + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); + return NULL; + } + + imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) { Py_DECREF(ret); @@ -215,7 +231,7 @@ match(PyObject *self, PyObject *args) { */ static PyObject * get_on_pixels(PyObject *self, PyObject *args) { - Py_ssize_t i0; + PyObject *i0; Imaging img; UINT8 **rows; int row_idx, col_idx; @@ -225,12 +241,20 @@ get_on_pixels(PyObject *self, PyObject *args) { return NULL; } - if (!PyArg_ParseTuple(args, "n", &i0)) { + if (!PyArg_ParseTuple(args, "O", &i0)) { Py_DECREF(ret); PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); return NULL; } - img = (Imaging)i0; + + if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); + return NULL; + } + + img = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); rows = img->image8; width = img->xsize; height = img->ysize; From 7f48567002a74a9f102dc3b183f872846e2925b3 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 02:00:07 +0400 Subject: [PATCH 15/31] Use s# in PyArg_ParseTuple --- src/_imagingmorph.c | 53 ++++++++++--------------------------- src/libImaging/ImPlatform.h | 1 + 2 files changed, 15 insertions(+), 39 deletions(-) diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index c7933cc7d5e..715cccf5142 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -11,7 +11,6 @@ * See the README file for information on usage and redistribution. */ -#include "Python.h" #include "libImaging/Imaging.h" #define LUT_SIZE (1 << 9) @@ -30,7 +29,7 @@ static PyObject * apply(PyObject *self, PyObject *args) { const char *lut; - PyObject *py_lut, *i0, *i1; + PyObject *i0, *i1; Py_ssize_t lut_len; Imaging imgin, imgout; int width, height; @@ -38,25 +37,15 @@ apply(PyObject *self, PyObject *args) { UINT8 **inrows, **outrows; int num_changed_pixels = 0; - if (!PyArg_ParseTuple(args, "OOO", &py_lut, &i0, &i1)) { - PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); + if (!PyArg_ParseTuple(args, "s#OO", &lut, &lut_len, &i0, &i1)) { return NULL; } - if (!PyBytes_Check(py_lut)) { - PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a bytes object"); - return NULL; - } - - lut_len = PyBytes_Size(py_lut); - if (lut_len < LUT_SIZE) { PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size"); return NULL; } - lut = PyBytes_AsString(py_lut); - if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { PyErr_Format( @@ -137,39 +126,22 @@ apply(PyObject *self, PyObject *args) { static PyObject * match(PyObject *self, PyObject *args) { const char *lut; - PyObject *py_lut, *i0; + PyObject *i0; Py_ssize_t lut_len; Imaging imgin; int width, height; int row_idx, col_idx; UINT8 **inrows; - PyObject *ret = PyList_New(0); - if (ret == NULL) { - return NULL; - } - if (!PyArg_ParseTuple(args, "OO", &py_lut, &i0)) { - Py_DECREF(ret); - PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); + if (!PyArg_ParseTuple(args, "s#O", &lut, &lut_len, &i0)) { return NULL; } - if (!PyBytes_Check(py_lut)) { - Py_DECREF(ret); - PyErr_SetString(PyExc_RuntimeError, "The morphology LUT is not a bytes object"); - return NULL; - } - - lut_len = PyBytes_Size(py_lut); - if (lut_len < LUT_SIZE) { - Py_DECREF(ret); PyErr_SetString(PyExc_RuntimeError, "The morphology LUT has the wrong size"); return NULL; } - lut = PyBytes_AsString(py_lut); - if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) { PyErr_Format( PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC @@ -180,11 +152,15 @@ match(PyObject *self, PyObject *args) { imgin = (Imaging)PyCapsule_GetPointer(i0, IMAGING_MAGIC); if (imgin->type != IMAGING_TYPE_UINT8 || imgin->bands != 1) { - Py_DECREF(ret); PyErr_SetString(PyExc_RuntimeError, "Unsupported image type"); return NULL; } + PyObject *ret = PyList_New(0); + if (ret == NULL) { + return NULL; + } + inrows = imgin->image8; width = imgin->xsize; height = imgin->ysize; @@ -236,14 +212,8 @@ get_on_pixels(PyObject *self, PyObject *args) { UINT8 **rows; int row_idx, col_idx; int width, height; - PyObject *ret = PyList_New(0); - if (ret == NULL) { - return NULL; - } if (!PyArg_ParseTuple(args, "O", &i0)) { - Py_DECREF(ret); - PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); return NULL; } @@ -259,6 +229,11 @@ get_on_pixels(PyObject *self, PyObject *args) { width = img->xsize; height = img->ysize; + PyObject *ret = PyList_New(0); + if (ret == NULL) { + return NULL; + } + for (row_idx = 0; row_idx < height; row_idx++) { UINT8 *row = rows[row_idx]; for (col_idx = 0; col_idx < width; col_idx++) { diff --git a/src/libImaging/ImPlatform.h b/src/libImaging/ImPlatform.h index f6e7fb6b921..c9b7e43b425 100644 --- a/src/libImaging/ImPlatform.h +++ b/src/libImaging/ImPlatform.h @@ -7,6 +7,7 @@ * Copyright (c) Fredrik Lundh 1995-2003. */ +#define PY_SSIZE_T_CLEAN #include "Python.h" /* Check that we have an ANSI compliant compiler */ From 5428e3568e09d69241b03827eecb08d7b5dc9ea9 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 02:19:44 +0400 Subject: [PATCH 16/31] Deprecate ImageCore.id and ImageCore.unsafe_ptrs --- Tests/test_image_getim.py | 12 ++++++++++-- docs/deprecations.rst | 10 ++++++++++ src/_imaging.c | 14 ++++++++++++++ src/libImaging/Imaging.h | 2 +- 4 files changed, 35 insertions(+), 3 deletions(-) diff --git a/Tests/test_image_getim.py b/Tests/test_image_getim.py index 9afa02b0a8b..75d97cd4451 100644 --- a/Tests/test_image_getim.py +++ b/Tests/test_image_getim.py @@ -1,11 +1,19 @@ from __future__ import annotations +import pytest + from .helper import hopper def test_sanity() -> None: im = hopper() - type_repr = repr(type(im.getim())) + type_repr = repr(type(im.getim())) assert "PyCapsule" in type_repr - assert isinstance(im.im.id, int) + + with pytest.warns(DeprecationWarning): + assert isinstance(im.im.id, int) + + with pytest.warns(DeprecationWarning): + ptrs = dict(im.im.unsafe_ptrs) + assert all(k in ptrs for k in ["image8", "image32", "image"]) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index cafda38032a..abb902c88eb 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -136,6 +136,16 @@ Specific WebP Feature Checks ``True`` if the WebP module is installed, until they are removed in Pillow 12.0.0 (2025-10-15). +Get Internal Pointers to Objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 11.0.0 + +``Image.core.ImageCore.id`` and ``Image.core.ImageCore.unsafe_ptrs`` +have been deprecated and will be removed in Pillow 12 (2025-10-15). +They were used for obtaining raw pointers to ``ImageCore`` internals. To interact with +C code, you can use ``Image.core.ImageCore.ptr``, which returns a ``PyCapsule`` object. + Removed features ---------------- diff --git a/src/_imaging.c b/src/_imaging.c index 07d9a64ccff..a455c873b15 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3707,6 +3707,13 @@ _getattr_bands(ImagingObject *self, void *closure) { static PyObject * _getattr_id(ImagingObject *self, void *closure) { + if (PyErr_WarnEx( + PyExc_DeprecationWarning, + "id property is deprecated and will be removed in Pillow 12.0", + 1 + ) < 0) { + return NULL; + } return PyLong_FromSsize_t((Py_ssize_t)self->image); } @@ -3717,6 +3724,13 @@ _getattr_ptr(ImagingObject *self, void *closure) { static PyObject * _getattr_unsafe_ptrs(ImagingObject *self, void *closure) { + if (PyErr_WarnEx( + PyExc_DeprecationWarning, + "unsafe_ptrs property is deprecated and will be removed in Pillow 12.0", + 1 + ) < 0) { + return NULL; + } return Py_BuildValue( "(sn)(sn)(sn)", "image8", diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 321dce988c3..870f5ed971a 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -61,7 +61,7 @@ typedef struct ImagingOutlineInstance *ImagingOutline; typedef struct ImagingPaletteInstance *ImagingPalette; /* handle magics (used with PyCObject). */ -#define IMAGING_MAGIC "PIL Imaging" +#define IMAGING_MAGIC "Pillow Imaging" /* pixel types */ #define IMAGING_TYPE_UINT8 0 From cb3a4e6a00fd41e42afa78b4289e5e1056fc0e81 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 03:45:18 +0400 Subject: [PATCH 17/31] Remove legacy 1-bit api, fix AttributeError PytestUnraisableExceptionWarning: Exception ignored in: AttributeError: 'PhotoImage' object has no attribute '_PhotoImage__photo' --- src/PIL/ImageTk.py | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index a788c9d1f13..d546989eeec 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -35,20 +35,6 @@ # -------------------------------------------------------------------- # Check for Tkinter interface hooks -_pilbitmap_ok = None - - -def _pilbitmap_check() -> int: - global _pilbitmap_ok - if _pilbitmap_ok is None: - try: - im = Image.new("1", (1, 1)) - tkinter.BitmapImage(data=f"PIL:{im.im.id}") - _pilbitmap_ok = 1 - except tkinter.TclError: - _pilbitmap_ok = 0 - return _pilbitmap_ok - def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None: source = None @@ -142,6 +128,8 @@ def __init__( self.paste(image) def __del__(self) -> None: + if not hasattr(self, "__photo"): + return name = self.__photo.name self.__photo.name = None try: @@ -225,17 +213,11 @@ def __init__(self, image: Image.Image | None = None, **kw: Any) -> None: self.__mode = image.mode self.__size = image.size - if _pilbitmap_check(): - # fast way (requires the pilbitmap booster patch) - image.load() - kw["data"] = f"PIL:{image.im.id}" - self.__im = image # must keep a reference - else: - # slow but safe way - kw["data"] = image.tobitmap() - self.__photo = tkinter.BitmapImage(**kw) + self.__photo = tkinter.BitmapImage(data=image.tobitmap(), **kw) def __del__(self) -> None: + if not hasattr(self, "__photo"): + return name = self.__photo.name self.__photo.name = None try: From ee65b305b1fd041b679382003b4ce727bb03392d Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 04:29:34 +0400 Subject: [PATCH 18/31] Use PyCapsule in _imagingtk --- src/PIL/ImageTk.py | 11 +++++------ src/Tk/tkImaging.c | 35 ++++++++++++++++++++++++++--------- 2 files changed, 31 insertions(+), 15 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index d546989eeec..279a21716b2 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -48,18 +48,18 @@ def _get_image_from_kw(kw: dict[str, Any]) -> ImageFile.ImageFile | None: def _pyimagingtkcall( - command: str, photo: PhotoImage | tkinter.PhotoImage, id: int + command: str, photo: PhotoImage | tkinter.PhotoImage, ptr: object ) -> None: tk = photo.tk try: - tk.call(command, photo, id) + tk.call(command, photo, repr(ptr)) except tkinter.TclError: # activate Tkinter hook # may raise an error if it cannot attach to Tkinter from . import _imagingtk _imagingtk.tkinit(tk.interpaddr()) - tk.call(command, photo, id) + tk.call(command, photo, repr(ptr)) # -------------------------------------------------------------------- @@ -181,7 +181,7 @@ def paste(self, im: Image.Image) -> None: block = image.new_block(self.__mode, im.size) image.convert2(block, image) # convert directly between buffers - _pyimagingtkcall("PyImagingPhoto", self.__photo, block.id) + _pyimagingtkcall("PyImagingPhoto", self.__photo, block.ptr) # -------------------------------------------------------------------- @@ -255,9 +255,8 @@ def __str__(self) -> str: def getimage(photo: PhotoImage) -> Image.Image: """Copies the contents of a PhotoImage to a PIL image memory.""" im = Image.new("RGBA", (photo.width(), photo.height())) - block = im.im - _pyimagingtkcall("PyImagingPhotoGet", photo, block.id) + _pyimagingtkcall("PyImagingPhotoGet", photo, im.im.ptr) return im diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index 727ee6bed51..b33a47d3007 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -56,19 +56,36 @@ static Tk_PhotoPutBlock_t TK_PHOTO_PUT_BLOCK; static Imaging ImagingFind(const char *name) { - Py_ssize_t id; + PyObject *capsule; + int direct_pointer = 0; + const char *expected = "capsule object \"" IMAGING_MAGIC "\" at 0x"; - /* FIXME: use CObject instead? */ -#if defined(_WIN64) - id = _atoi64(name); -#else - id = atol(name); -#endif - if (!id) { + if (name[0] == '<') { + name++; + } else { + // Special case for PyPy, where the string representation of a Capsule + // refers directly to the pointer itself, not to the PyCapsule object. + direct_pointer = 1; + } + + if (strncmp(name, expected, strlen(expected))) { + return NULL; + } + + capsule = (PyObject *)strtoull(name + strlen(expected), NULL, 16); + + if (direct_pointer) { + return (Imaging)capsule; + } + + if (!PyCapsule_IsValid(capsule, IMAGING_MAGIC)) { + PyErr_Format( + PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC + ); return NULL; } - return (Imaging)id; + return (Imaging)PyCapsule_GetPointer(capsule, IMAGING_MAGIC); } static int From 882ac783eb67a13c1ff8afba9369d1603328718e Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 13:04:57 +0400 Subject: [PATCH 19/31] Use PyCapsule in _imagingft --- src/_imagingft.c | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/_imagingft.c b/src/_imagingft.c index 5c446e641f5..c539ec2672c 100644 --- a/src/_imagingft.c +++ b/src/_imagingft.c @@ -830,7 +830,6 @@ font_render(FontObject *self, PyObject *args) { unsigned char convert_scale; /* scale factor for non-8bpp bitmaps */ PyObject *image; Imaging im; - Py_ssize_t id; int mask = 0; /* is FT_LOAD_TARGET_MONO enabled? */ int color = 0; /* is FT_LOAD_COLOR enabled? */ int stroke_width = 0; @@ -929,10 +928,9 @@ font_render(FontObject *self, PyObject *args) { PyMem_Del(glyph_info); return NULL; } - PyObject *imageId = PyObject_GetAttrString(image, "id"); - id = PyLong_AsSsize_t(imageId); - Py_XDECREF(imageId); - im = (Imaging)id; + PyObject *imagePtr = PyObject_GetAttrString(image, "ptr"); + im = (Imaging)PyCapsule_GetPointer(imagePtr, IMAGING_MAGIC); + Py_XDECREF(imagePtr); x_offset -= stroke_width; y_offset -= stroke_width; From 934ae12ed5e98b7d2ec14d1946fad3bce6a42924 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Tue, 3 Sep 2024 11:54:24 +0400 Subject: [PATCH 20/31] rename PyCapsule -> Capsule --- docs/deprecations.rst | 2 +- src/Tk/tkImaging.c | 4 +--- src/_imagingcms.c | 4 +--- src/_imagingmath.c | 16 ++++------------ src/_imagingmorph.c | 12 +++--------- 5 files changed, 10 insertions(+), 28 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index abb902c88eb..dd271a97f7c 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -144,7 +144,7 @@ Get Internal Pointers to Objects ``Image.core.ImageCore.id`` and ``Image.core.ImageCore.unsafe_ptrs`` have been deprecated and will be removed in Pillow 12 (2025-10-15). They were used for obtaining raw pointers to ``ImageCore`` internals. To interact with -C code, you can use ``Image.core.ImageCore.ptr``, which returns a ``PyCapsule`` object. +C code, you can use ``Image.core.ImageCore.ptr``, which returns a ``Capsule`` object. Removed features ---------------- diff --git a/src/Tk/tkImaging.c b/src/Tk/tkImaging.c index b33a47d3007..a36c3e0bdc4 100644 --- a/src/Tk/tkImaging.c +++ b/src/Tk/tkImaging.c @@ -79,9 +79,7 @@ ImagingFind(const char *name) { } if (!PyCapsule_IsValid(capsule, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } diff --git a/src/_imagingcms.c b/src/_imagingcms.c index 444b6a60237..1823bcf0371 100644 --- a/src/_imagingcms.c +++ b/src/_imagingcms.c @@ -541,9 +541,7 @@ cms_transform_apply(CmsTransformObject *self, PyObject *args) { if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } diff --git a/src/_imagingmath.c b/src/_imagingmath.c index eafcf1444e9..dbe636707f4 100644 --- a/src/_imagingmath.c +++ b/src/_imagingmath.c @@ -177,16 +177,12 @@ _unop(PyObject *self, PyObject *args) { } if (!PyCapsule_IsValid(op, MATH_FUNC_UNOP_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", MATH_FUNC_UNOP_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", MATH_FUNC_UNOP_MAGIC); return NULL; } if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } @@ -213,17 +209,13 @@ _binop(PyObject *self, PyObject *args) { } if (!PyCapsule_IsValid(op, MATH_FUNC_BINOP_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", MATH_FUNC_BINOP_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", MATH_FUNC_BINOP_MAGIC); return NULL; } if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || !PyCapsule_IsValid(i1, IMAGING_MAGIC) || !PyCapsule_IsValid(i2, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index 715cccf5142..637295be710 100644 --- a/src/_imagingmorph.c +++ b/src/_imagingmorph.c @@ -48,9 +48,7 @@ apply(PyObject *self, PyObject *args) { if (!PyCapsule_IsValid(i0, IMAGING_MAGIC) || !PyCapsule_IsValid(i1, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } @@ -143,9 +141,7 @@ match(PyObject *self, PyObject *args) { } if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } @@ -218,9 +214,7 @@ get_on_pixels(PyObject *self, PyObject *args) { } if (!PyCapsule_IsValid(i0, IMAGING_MAGIC)) { - PyErr_Format( - PyExc_TypeError, "Expected PyCapsule with '%s' name.", IMAGING_MAGIC - ); + PyErr_Format(PyExc_TypeError, "Expected '%s' Capsule", IMAGING_MAGIC); return NULL; } From d29fa73ea608449f86082dc312eb132a10901fd0 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Thu, 5 Sep 2024 01:04:35 +0400 Subject: [PATCH 21/31] Move new_block to module --- src/PIL/ImageTk.py | 2 +- src/_imaging.c | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 279a21716b2..5bad9b5462d 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -178,7 +178,7 @@ def paste(self, im: Image.Image) -> None: if image.isblock() and im.mode == self.__mode: block = image else: - block = image.new_block(self.__mode, im.size) + block = Image.core.new_block(self.__mode, im.size) image.convert2(block, image) # convert directly between buffers _pyimagingtkcall("PyImagingPhoto", self.__photo, block.ptr) diff --git a/src/_imaging.c b/src/_imaging.c index a455c873b15..e227d8efc51 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3674,15 +3674,12 @@ static struct PyMethodDef methods[] = { /* Unsharpmask extension */ {"gaussian_blur", (PyCFunction)_gaussian_blur, METH_VARARGS}, {"unsharp_mask", (PyCFunction)_unsharp_mask, METH_VARARGS}, - {"box_blur", (PyCFunction)_box_blur, METH_VARARGS}, /* Special effects */ {"effect_spread", (PyCFunction)_effect_spread, METH_VARARGS}, /* Misc. */ - {"new_block", (PyCFunction)_new_block, METH_VARARGS}, - {"save_ppm", (PyCFunction)_save_ppm, METH_VARARGS}, {NULL, NULL} /* sentinel */ @@ -4212,6 +4209,7 @@ static PyMethodDef functions[] = { {"blend", (PyCFunction)_blend, METH_VARARGS}, {"fill", (PyCFunction)_fill, METH_VARARGS}, {"new", (PyCFunction)_new, METH_VARARGS}, + {"new_block", (PyCFunction)_new_block, METH_VARARGS}, {"merge", (PyCFunction)_merge, METH_VARARGS}, /* Functions */ From a2988da0d2e0057726687c8030b5f2ea2b007f8f Mon Sep 17 00:00:00 2001 From: Alexander Karpinsky Date: Wed, 11 Sep 2024 19:55:07 +0400 Subject: [PATCH 22/31] =?UTF-8?q?ImageCore=20=E2=86=92=20ImagingCore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- docs/deprecations.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index dd271a97f7c..af6de2f927e 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -141,10 +141,10 @@ Get Internal Pointers to Objects .. deprecated:: 11.0.0 -``Image.core.ImageCore.id`` and ``Image.core.ImageCore.unsafe_ptrs`` -have been deprecated and will be removed in Pillow 12 (2025-10-15). -They were used for obtaining raw pointers to ``ImageCore`` internals. To interact with -C code, you can use ``Image.core.ImageCore.ptr``, which returns a ``Capsule`` object. +``Image.core.ImagingCore.id`` and ``Image.core.ImagingCore.unsafe_ptrs`` have been +deprecated and will be removed in Pillow 12 (2025-10-15). They were used for obtaining +raw pointers to ``ImagingCore`` internals. To interact with C code, you can use +``Image.core.ImagingCore.ptr``, which returns a ``Capsule`` object. Removed features ---------------- From 6921f83629242d199eaab87af55da6279588a8e0 Mon Sep 17 00:00:00 2001 From: Alexander Karpinsky Date: Fri, 13 Sep 2024 22:54:16 +0200 Subject: [PATCH 23/31] Update docs/deprecations.rst Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- docs/deprecations.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/deprecations.rst b/docs/deprecations.rst index af6de2f927e..bfe9849da25 100644 --- a/docs/deprecations.rst +++ b/docs/deprecations.rst @@ -144,7 +144,7 @@ Get Internal Pointers to Objects ``Image.core.ImagingCore.id`` and ``Image.core.ImagingCore.unsafe_ptrs`` have been deprecated and will be removed in Pillow 12 (2025-10-15). They were used for obtaining raw pointers to ``ImagingCore`` internals. To interact with C code, you can use -``Image.core.ImagingCore.ptr``, which returns a ``Capsule`` object. +``Image.Image.getim()``, which returns a ``Capsule`` object. Removed features ---------------- From 1f3fe6f73350aa9630b236a9f16370aa0c71258d Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Mon, 16 Sep 2024 10:21:17 +1000 Subject: [PATCH 24/31] Use getim() --- Tests/test_imagemorph.py | 8 ++++---- src/PIL/ImageCms.py | 4 ++-- src/PIL/ImageMath.py | 5 ++--- src/PIL/ImageMorph.py | 6 +++--- src/PIL/ImageTk.py | 2 +- src/PIL/_imagingcms.pyi | 4 +++- 6 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 04860fd616c..9da32aa0124 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -328,13 +328,13 @@ def test_wrong_mode() -> None: iml = Image.new("L", (10, 10)) with pytest.raises(RuntimeError): - _imagingmorph.apply(bytes(lut), imrgb.im.ptr, iml.im.ptr) + _imagingmorph.apply(bytes(lut), imrgb.getim(), iml.getim()) with pytest.raises(RuntimeError): - _imagingmorph.apply(bytes(lut), iml.im.ptr, imrgb.im.ptr) + _imagingmorph.apply(bytes(lut), iml.getim(), imrgb.getim()) with pytest.raises(RuntimeError): - _imagingmorph.match(bytes(lut), imrgb.im.ptr) + _imagingmorph.match(bytes(lut), imrgb.getim()) # Should not raise - _imagingmorph.match(bytes(lut), iml.im.ptr) + _imagingmorph.match(bytes(lut), iml.getim()) diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index 5e256b0ab7f..f028f177533 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -352,7 +352,7 @@ def apply(self, im: Image.Image, imOut: Image.Image | None = None) -> Image.Imag im.load() if imOut is None: imOut = Image.new(self.output_mode, im.size, None) - self.transform.apply(im.im.ptr, imOut.im.ptr) + self.transform.apply(im.getim(), imOut.getim()) imOut.info["icc_profile"] = self.output_profile.tobytes() return imOut @@ -361,7 +361,7 @@ def apply_in_place(self, im: Image.Image) -> Image.Image: if im.mode != self.output_mode: msg = "mode mismatch" raise ValueError(msg) # wrong output mode - self.transform.apply(im.im.ptr, im.im.ptr) + self.transform.apply(im.getim(), im.getim()) im.info["icc_profile"] = self.output_profile.tobytes() return im diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index b32742657ba..ff3014a8d5a 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -65,7 +65,7 @@ def apply( except AttributeError as e: msg = f"bad operand type for '{op}'" raise TypeError(msg) from e - _imagingmath.unop(op, out.im.ptr, im_1.im.ptr) + _imagingmath.unop(op, out.getim(), im_1.getim()) else: # binary operation im_2 = self.__fixup(im2) @@ -87,13 +87,12 @@ def apply( im_2 = im_2.crop((0, 0) + size) out = Image.new(mode or im_1.mode, im_1.size, None) im_1.load() - im_2.load() try: op = getattr(_imagingmath, f"{op}_{im_1.mode}") except AttributeError as e: msg = f"bad operand type for '{op}'" raise TypeError(msg) from e - _imagingmath.binop(op, out.im.ptr, im_1.im.ptr, im_2.im.ptr) + _imagingmath.binop(op, out.getim(), im_1.getim(), im_2.getim()) return _Operand(out) # unary operators diff --git a/src/PIL/ImageMorph.py b/src/PIL/ImageMorph.py index bc7ed66d3fb..f0a066b5bd8 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -213,7 +213,7 @@ def apply(self, image: Image.Image) -> tuple[int, Image.Image]: msg = "Image mode must be L" raise ValueError(msg) outimage = Image.new(image.mode, image.size, None) - count = _imagingmorph.apply(bytes(self.lut), image.im.ptr, outimage.im.ptr) + count = _imagingmorph.apply(bytes(self.lut), image.getim(), outimage.getim()) return count, outimage def match(self, image: Image.Image) -> list[tuple[int, int]]: @@ -229,7 +229,7 @@ def match(self, image: Image.Image) -> list[tuple[int, int]]: if image.mode != "L": msg = "Image mode must be L" raise ValueError(msg) - return _imagingmorph.match(bytes(self.lut), image.im.ptr) + return _imagingmorph.match(bytes(self.lut), image.getim()) def get_on_pixels(self, image: Image.Image) -> list[tuple[int, int]]: """Get a list of all turned on pixels in a binary image @@ -240,7 +240,7 @@ def get_on_pixels(self, image: Image.Image) -> list[tuple[int, int]]: if image.mode != "L": msg = "Image mode must be L" raise ValueError(msg) - return _imagingmorph.get_on_pixels(image.im.ptr) + return _imagingmorph.get_on_pixels(image.getim()) def load_lut(self, filename: str) -> None: """Load an operator from an mrl file""" diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index d01af364bb5..c36cf9b8473 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -259,7 +259,7 @@ def getimage(photo: PhotoImage) -> Image.Image: """Copies the contents of a PhotoImage to a PIL image memory.""" im = Image.new("RGBA", (photo.width(), photo.height())) - _pyimagingtkcall("PyImagingPhotoGet", photo, im.im.ptr) + _pyimagingtkcall("PyImagingPhotoGet", photo, im.getim()) return im diff --git a/src/PIL/_imagingcms.pyi b/src/PIL/_imagingcms.pyi index 2abd6d0f7a9..ddcf93ab1eb 100644 --- a/src/PIL/_imagingcms.pyi +++ b/src/PIL/_imagingcms.pyi @@ -2,6 +2,8 @@ import datetime import sys from typing import Literal, SupportsFloat, TypedDict +from ._typing import CapsuleType + littlecms_version: str | None _Tuple3f = tuple[float, float, float] @@ -108,7 +110,7 @@ class CmsProfile: def is_intent_supported(self, intent: int, direction: int, /) -> int: ... class CmsTransform: - def apply(self, id_in: int, id_out: int) -> int: ... + def apply(self, id_in: CapsuleType, id_out: CapsuleType) -> int: ... def profile_open(profile: str, /) -> CmsProfile: ... def profile_frombytes(profile: bytes, /) -> CmsProfile: ... From d8ef314205ff65e6aeeefe3092414cc45e7349eb Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 16 Sep 2024 10:39:14 +0200 Subject: [PATCH 25/31] Remove extra load() calls --- src/PIL/ImageCms.py | 2 -- src/PIL/ImageMath.py | 2 -- 2 files changed, 4 deletions(-) diff --git a/src/PIL/ImageCms.py b/src/PIL/ImageCms.py index f028f177533..b6c5de5b36f 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -349,7 +349,6 @@ def point(self, im: Image.Image) -> Image.Image: return self.apply(im) def apply(self, im: Image.Image, imOut: Image.Image | None = None) -> Image.Image: - im.load() if imOut is None: imOut = Image.new(self.output_mode, im.size, None) self.transform.apply(im.getim(), imOut.getim()) @@ -357,7 +356,6 @@ def apply(self, im: Image.Image, imOut: Image.Image | None = None) -> Image.Imag return imOut def apply_in_place(self, im: Image.Image) -> Image.Image: - im.load() if im.mode != self.output_mode: msg = "mode mismatch" raise ValueError(msg) # wrong output mode diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index ff3014a8d5a..773147b5343 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -59,7 +59,6 @@ def apply( if im2 is None: # unary operation out = Image.new(mode or im_1.mode, im_1.size, None) - im_1.load() try: op = getattr(_imagingmath, f"{op}_{im_1.mode}") except AttributeError as e: @@ -86,7 +85,6 @@ def apply( if im_2.size != size: im_2 = im_2.crop((0, 0) + size) out = Image.new(mode or im_1.mode, im_1.size, None) - im_1.load() try: op = getattr(_imagingmath, f"{op}_{im_1.mode}") except AttributeError as e: From bc973690397a075b94f694ee1cb3f3392fff9cdc Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 16 Sep 2024 15:45:34 +0200 Subject: [PATCH 26/31] Increase reference to the image while capsule is alive --- src/_imaging.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/_imaging.c b/src/_imaging.c index e227d8efc51..9422c42bfaf 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3714,9 +3714,18 @@ _getattr_id(ImagingObject *self, void *closure) { return PyLong_FromSsize_t((Py_ssize_t)self->image); } +static void +_ptr_destructor(PyObject *capsule) { + PyObject *self = (PyObject *)PyCapsule_GetContext(capsule); + Py_DECREF(self); +} + static PyObject * _getattr_ptr(ImagingObject *self, void *closure) { - return PyCapsule_New(self->image, IMAGING_MAGIC, NULL); + PyObject *capsule = PyCapsule_New(self->image, IMAGING_MAGIC, _ptr_destructor); + Py_INCREF(self); + PyCapsule_SetContext(capsule, self); + return capsule; } static PyObject * From aa22b241690e0c0084523bc7e4ea137d3596ee93 Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Sep 2024 16:22:50 +1000 Subject: [PATCH 27/31] Load before trying to catch exceptions --- Tests/test_imagemorph.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Tests/test_imagemorph.py b/Tests/test_imagemorph.py index 9da32aa0124..6180a7b5d90 100644 --- a/Tests/test_imagemorph.py +++ b/Tests/test_imagemorph.py @@ -324,17 +324,17 @@ def test_set_lut() -> None: def test_wrong_mode() -> None: lut = ImageMorph.LutBuilder(op_name="corner").build_lut() - imrgb = Image.new("RGB", (10, 10)) - iml = Image.new("L", (10, 10)) + imrgb_ptr = Image.new("RGB", (10, 10)).getim() + iml_ptr = Image.new("L", (10, 10)).getim() with pytest.raises(RuntimeError): - _imagingmorph.apply(bytes(lut), imrgb.getim(), iml.getim()) + _imagingmorph.apply(bytes(lut), imrgb_ptr, iml_ptr) with pytest.raises(RuntimeError): - _imagingmorph.apply(bytes(lut), iml.getim(), imrgb.getim()) + _imagingmorph.apply(bytes(lut), iml_ptr, imrgb_ptr) with pytest.raises(RuntimeError): - _imagingmorph.match(bytes(lut), imrgb.getim()) + _imagingmorph.match(bytes(lut), imrgb_ptr) # Should not raise - _imagingmorph.match(bytes(lut), iml.getim()) + _imagingmorph.match(bytes(lut), iml_ptr) From 5d430eab25a7695cdba79ddf250c40e1e10832da Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Sep 2024 16:24:54 +1000 Subject: [PATCH 28/31] Added release notes --- docs/releasenotes/11.0.0.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/releasenotes/11.0.0.rst b/docs/releasenotes/11.0.0.rst index 36334e39f5e..3194f1a5299 100644 --- a/docs/releasenotes/11.0.0.rst +++ b/docs/releasenotes/11.0.0.rst @@ -73,6 +73,16 @@ vulnerability introduced in FreeType 2.6 (:cve:`2020-15999`). .. _2.10.4: https://sourceforge.net/projects/freetype/files/freetype2/2.10.4/ +Get Internal Pointers to Objects +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. deprecated:: 11.0.0 + +``Image.core.ImagingCore.id`` and ``Image.core.ImagingCore.unsafe_ptrs`` have been +deprecated and will be removed in Pillow 12 (2025-10-15). They were used for obtaining +raw pointers to ``ImagingCore`` internals. To interact with C code, you can use +``Image.Image.getim()``, which returns a ``Capsule`` object. + ICNS (width, height, scale) sizes ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ From 9f409e823b0914b18b0a5447e93bb5f40c0e0c4c Mon Sep 17 00:00:00 2001 From: Andrew Murray Date: Sat, 21 Sep 2024 16:44:39 +1000 Subject: [PATCH 29/31] Use getim() --- src/PIL/ImageTk.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index c36cf9b8473..72fa798ebd4 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -176,15 +176,14 @@ def paste(self, im: Image.Image) -> None: the bitmap image. """ # convert to blittable - im.load() + ptr = im.getim() image = im.im - if image.isblock() and im.mode == self.__mode: - block = image - else: + if not image.isblock() or im.mode != self.__mode: block = Image.core.new_block(self.__mode, im.size) image.convert2(block, image) # convert directly between buffers + ptr = block.ptr - _pyimagingtkcall("PyImagingPhoto", self.__photo, block.ptr) + _pyimagingtkcall("PyImagingPhoto", self.__photo, ptr) # -------------------------------------------------------------------- From 11bcd5aaa9c35d7f8557b0f5abf899b0adf0cecf Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Sun, 22 Sep 2024 13:39:22 +0400 Subject: [PATCH 30/31] Fix hasattr for ImageTk.PhotoImage.__del__ --- src/PIL/ImageTk.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index 72fa798ebd4..c817ab09ced 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -131,9 +131,10 @@ def __init__( self.paste(image) def __del__(self) -> None: - if not hasattr(self, "__photo"): + try: + name = self.__photo.name + except AttributeError: return - name = self.__photo.name self.__photo.name = None try: self.__photo.tk.call("image", "delete", name) From b9d176856135b581bd503e04ab2407318cbe7689 Mon Sep 17 00:00:00 2001 From: Alexander Karpinsky Date: Thu, 26 Sep 2024 16:27:55 +0400 Subject: [PATCH 31/31] Catch AttributeError for BitmapImage.__photo Co-authored-by: Andrew Murray <3112309+radarhere@users.noreply.github.com> --- src/PIL/ImageTk.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/PIL/ImageTk.py b/src/PIL/ImageTk.py index c817ab09ced..bf29fdba54c 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -219,9 +219,10 @@ def __init__(self, image: Image.Image | None = None, **kw: Any) -> None: self.__photo = tkinter.BitmapImage(data=image.tobitmap(), **kw) def __del__(self) -> None: - if not hasattr(self, "__photo"): + try: + name = self.__photo.name + except AttributeError: return - name = self.__photo.name self.__photo.name = None try: self.__photo.tk.call("image", "delete", name)