diff --git a/Tests/benchmarks.py b/Tests/benchmarks.py index 11ed3cf743b..7bada08ba0c 100644 --- a/Tests/benchmarks.py +++ b/Tests/benchmarks.py @@ -898,3 +898,15 @@ def test_quantize_to_palette( result = bench(lambda: im._new(im.im.convert(output_mode, dither, palette.im))) assert result.mode == output_mode benchmark_save(result) + + +@pytest.mark.benchmark +@pytest.mark.parametrize("mode", MODES) +@pytest.mark.parametrize("size", SIZES, ids=_format_size) +def test_get_flattened_data( + bench: BenchmarkFixture, + mode: str, + size: tuple[int, int], +) -> None: + im = make_pillow_image(mode, size) + bench(im.get_flattened_data) diff --git a/src/_imaging.c b/src/_imaging.c index 9bdb6328782..bbaf83f307f 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -523,6 +523,27 @@ float16tofloat32(const FLOAT16 in) { return out[0]; } +static inline PyObject * +make_pixel_tuple(const UINT8 *b, Py_ssize_t bands) { + PyObject *tuple = PyTuple_New(bands); + if (tuple == NULL) { + return NULL; + } + for (Py_ssize_t i = 0; i < bands; i++) { + PyObject *v = PyLong_FromLong(b[i]); + if (v == NULL) { + Py_DECREF(tuple); + return NULL; + } + PyTuple_SET_ITEM(tuple, i, v); + } + // We know these tuples will only have small integers, + // so we can tell the garbage collector to not look inside + // for cycles. + PyObject_GC_UnTrack(tuple); + return tuple; +} + static inline PyObject * getpixel(Imaging im, ImagingAccess access, int x, int y) { union { @@ -552,13 +573,11 @@ getpixel(Imaging im, ImagingAccess access, int x, int y) { case 1: return PyLong_FromLong(pixel.b[0]); case 2: - return Py_BuildValue("BB", pixel.b[0], pixel.b[1]); + return make_pixel_tuple(pixel.b, 2); case 3: - return Py_BuildValue("BBB", pixel.b[0], pixel.b[1], pixel.b[2]); + return make_pixel_tuple(pixel.b, 3); case 4: - return Py_BuildValue( - "BBBB", pixel.b[0], pixel.b[1], pixel.b[2], pixel.b[3] - ); + return make_pixel_tuple(pixel.b, 4); } break; case IMAGING_TYPE_INT32: