From 2303c063cb9660c8fdd4a5d757e1d971cd47dcf8 Mon Sep 17 00:00:00 2001 From: Aleksandr Karpinskii Date: Mon, 2 Sep 2024 01:24:51 +0400 Subject: [PATCH 01/11] Use PyCapsule in _imagingmath --- src/PIL/ImageMath.py | 4 +- src/_imagingmath.c | 145 +++++++++++++++++++++++++++---------------- 2 files changed, 94 insertions(+), 55 deletions(-) diff --git a/src/PIL/ImageMath.py b/src/PIL/ImageMath.py index 191cc2a5f..b32742657 100644 --- a/src/PIL/ImageMath.py +++ b/src/PIL/ImageMath.py @@ -65,7 +65,7 @@ class _Operand: 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 @@ class _Operand: 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 550a10903..eafcf1444 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_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(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_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(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_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(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_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(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_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/11] 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 ec10230f1..5e256b0ab 100644 --- a/src/PIL/ImageCms.py +++ b/src/PIL/ImageCms.py @@ -352,7 +352,7 @@ class ImageCmsTransform(Image.ImagePointHandler): 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 @@ class ImageCmsTransform(Image.ImagePointHandler): 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 bafe787a7..444b6a602 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/11] 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 80d8c3815..04860fd61 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 6a43983d3..bc7ed66d3 100644 --- a/src/PIL/ImageMorph.py +++ b/src/PIL/ImageMorph.py @@ -213,7 +213,7 @@ class MorphOp: 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 @@ class MorphOp: 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 @@ class MorphOp: 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 614dfbe7f..c7933cc7d 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/11] Use s# in PyArg_ParseTuple --- src/_imagingmorph.c | 55 ++++++++++--------------------------- src/libImaging/ImPlatform.h | 1 + 2 files changed, 16 insertions(+), 40 deletions(-) diff --git a/src/_imagingmorph.c b/src/_imagingmorph.c index c7933cc7d..715cccf51 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) { + + if (!PyArg_ParseTuple(args, "s#O", &lut, &lut_len, &i0)) { return NULL; } - if (!PyArg_ParseTuple(args, "OO", &py_lut, &i0)) { - Py_DECREF(ret); - PyErr_SetString(PyExc_RuntimeError, "Argument parsing problem"); - 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 f6e7fb6b9..c9b7e43b4 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/11] 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 9afa02b0a..75d97cd44 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 a9498d5ed..5bb0ebf58 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 07d9a64cc..a455c873b 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 321dce988..870f5ed97 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/11] 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 a788c9d1f..d546989ee 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -35,20 +35,6 @@ from . import Image, ImageFile # -------------------------------------------------------------------- # 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 @@ class PhotoImage: 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 @@ class BitmapImage: 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/11] 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 d546989ee..279a21716 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 @@ class PhotoImage: 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 @@ class BitmapImage: 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 727ee6bed..b33a47d30 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; } - return (Imaging)id; + 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)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/11] 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 f8143e0cc..af5d80f87 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/11] 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 5bb0ebf58..93bd51ef1 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 b33a47d30..a36c3e0bd 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 444b6a602..1823bcf03 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 eafcf1444..dbe636707 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 715cccf51..637295be7 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/11] 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 279a21716..5bad9b546 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -178,7 +178,7 @@ class PhotoImage: 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 a455c873b..e227d8efc 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/11] 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 1c0cf2936..871d38a83 100644 --- a/src/PIL/Image.py +++ b/src/PIL/Image.py @@ -224,12 +224,7 @@ if TYPE_CHECKING: 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 5bad9b546..d01af364b 100644 --- a/src/PIL/ImageTk.py +++ b/src/PIL/ImageTk.py @@ -32,6 +32,9 @@ from typing import TYPE_CHECKING, Any, cast 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 093778464..dd08cdd50 100644 --- a/src/PIL/_typing.py +++ b/src/PIL/_typing.py @@ -15,6 +15,11 @@ if TYPE_CHECKING: 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: