Skip to content

Commit d9f1e04

Browse files
committed
Add fast-path for getextrema on 8-bit multichannel images
1 parent c3ca615 commit d9f1e04

4 files changed

Lines changed: 80 additions & 5 deletions

File tree

src/PIL/Image.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1568,8 +1568,6 @@ def getextrema(self) -> tuple[float, float] | tuple[tuple[int, int], ...]:
15681568
"""
15691569

15701570
self.load()
1571-
if self.im.bands > 1:
1572-
return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands))
15731571
return self.im.getextrema()
15741572

15751573
def getxmp(self) -> dict[str, Any]:

src/_imaging.c

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2337,15 +2337,39 @@ _getcolors(ImagingObject *self, PyObject *args) {
23372337

23382338
static PyObject *
23392339
_getextrema(ImagingObject *self, PyObject *args) {
2340+
if (self->image->type == IMAGING_TYPE_UINT8 && self->image->bands > 1 &&
2341+
self->image->bands <= 4) {
2342+
// Fast per-band extrema for 8-bit multiband images (RGB, RGBA, etc.)
2343+
UINT8 mb[2 * 4];
2344+
int bands = self->image->bands;
2345+
int nb = ImagingGetExtremaMultiband(self->image, mb);
2346+
if (nb < 0) {
2347+
return NULL;
2348+
}
2349+
PyObject *result = PyTuple_New(bands);
2350+
if (!result) {
2351+
return NULL;
2352+
}
2353+
for (int b = 0; b < bands; b++) {
2354+
// nb == 0 for an empty image: report None per band.
2355+
PyObject *item =
2356+
nb ? Py_BuildValue("BB", mb[b * 2], mb[b * 2 + 1]) : Py_NewRef(Py_None);
2357+
if (!item) {
2358+
Py_DECREF(result);
2359+
return NULL;
2360+
}
2361+
PyTuple_SET_ITEM(result, b, item);
2362+
}
2363+
return result;
2364+
}
2365+
23402366
union {
23412367
UINT8 u[2];
23422368
INT32 i[2];
23432369
FLOAT32 f[2];
23442370
UINT16 s[2];
23452371
} extrema;
2346-
int status;
2347-
2348-
status = ImagingGetExtrema(self->image, &extrema);
2372+
int status = ImagingGetExtrema(self->image, &extrema);
23492373
if (status < 0) {
23502374
return NULL;
23512375
}

src/libImaging/GetBBox.c

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,6 +248,57 @@ ImagingGetExtrema(Imaging im, void *extrema) {
248248
return 1; /* ok */
249249
}
250250

251+
int
252+
ImagingGetExtremaMultiband(Imaging im, UINT8 extrema[8]) {
253+
// Per-band min/max for interleaved 8-bit images (up to 4 bands).
254+
// Writes 2 * im->bands bytes to extrema as [min0, max0, min1, max1, ...].
255+
// Returns the band count on success, 0 for an empty image, or -1 on error.
256+
int bands = im->bands, xsize = im->xsize, ysize = im->ysize;
257+
UINT8 vmin[4], vmax[4];
258+
259+
if (im->type != IMAGING_TYPE_UINT8 || im->image8 || bands < 2 || bands > 4) {
260+
// `_getextrema` should have checked these, but best be sure,
261+
// in case someone ends up calling this directly.
262+
(void)ImagingError_ModeError();
263+
return -1;
264+
}
265+
266+
if (!xsize || !ysize) {
267+
return 0; /* zero size */
268+
}
269+
270+
UINT8 *restrict in = (UINT8 *)im->image[0];
271+
for (int b = 0; b < 4; b++) {
272+
vmin[b] = vmax[b] = in[b];
273+
}
274+
275+
for (int y = 0; y < ysize; y++) {
276+
UINT8 *restrict in = (UINT8 *)im->image[y];
277+
for (int x = 0; x < xsize; x++, in += 4) {
278+
for (int b = 0; b < 4; b++) {
279+
if (in[b] < vmin[b]) {
280+
vmin[b] = in[b];
281+
}
282+
if (in[b] > vmax[b]) {
283+
vmax[b] = in[b];
284+
}
285+
}
286+
}
287+
}
288+
289+
// The second band of a two-band image is stored in the fourth byte
290+
// (mirrors the special case in ImagingGetBand).
291+
if (bands == 2) {
292+
vmin[1] = vmin[3];
293+
vmax[1] = vmax[3];
294+
}
295+
for (int b = 0; b < bands; b++) {
296+
extrema[b * 2 + 0] = vmin[b];
297+
extrema[b * 2 + 1] = vmax[b];
298+
}
299+
return bands;
300+
}
301+
251302
/* static ImagingColorItem* getcolors8(Imaging im, int maxcolors, int* size);*/
252303
static ImagingColorItem *
253304
getcolors32(Imaging im, int maxcolors, int *size);

src/libImaging/Imaging.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,8 @@ ImagingGetColors(Imaging im, int maxcolors, int *colors);
354354
extern int
355355
ImagingGetExtrema(Imaging im, void *extrema);
356356
extern int
357+
ImagingGetExtremaMultiband(Imaging im, UINT8 extrema[8]);
358+
extern int
357359
ImagingGetProjection(Imaging im, UINT8 *xproj, UINT8 *yproj);
358360
extern ImagingHistogram
359361
ImagingGetHistogram(Imaging im, Imaging mask, void *extrema);

0 commit comments

Comments
 (0)