Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions Tests/benchmarks.py
Original file line number Diff line number Diff line change
Expand Up @@ -431,6 +431,15 @@ def test_offset(bench: BenchmarkFixture, mode: str, size: tuple[int, int]) -> No
bench(ImageChops.offset, im, 123, 45)


@pytest.mark.benchmark(group="extrema")
@pytest.mark.parametrize("mode", MODES)
@pytest.mark.parametrize("size", SIZES, ids=_format_size)
def test_getextrema(bench: BenchmarkFixture, mode: str, size: tuple[int, int]) -> None:
im = make_pillow_image(mode, size)
bench.extra_info["label"] = [f"extrema {mode}"]
bench(im.getextrema)


@pytest.mark.benchmark(group="histogram")
@pytest.mark.parametrize("mode", MODES)
@pytest.mark.parametrize("size", SIZES, ids=_format_size)
Expand Down
2 changes: 0 additions & 2 deletions src/PIL/Image.py
Original file line number Diff line number Diff line change
Expand Up @@ -1573,8 +1573,6 @@ def getextrema(self) -> tuple[float, float] | tuple[tuple[int, int], ...]:
"""

self.load()
if self.im.bands > 1:
return tuple(self.im.getband(i).getextrema() for i in range(self.im.bands))
return self.im.getextrema()

def getxmp(self) -> dict[str, Any]:
Expand Down
30 changes: 27 additions & 3 deletions src/_imaging.c
Original file line number Diff line number Diff line change
Expand Up @@ -2335,15 +2335,39 @@ _getcolors(ImagingObject *self, PyObject *args) {

static PyObject *
_getextrema(ImagingObject *self, PyObject *args) {
if (self->image->type == IMAGING_TYPE_UINT8 && self->image->bands > 1 &&
self->image->bands <= 4) {
// Fast per-band extrema for 8-bit multiband images (RGB, RGBA, etc.)
UINT8 mb[2 * 4];
int bands = self->image->bands;
int nb = ImagingGetExtremaMultiband(self->image, mb);
if (nb < 0) {
return NULL;
}
PyObject *result = PyTuple_New(bands);
if (!result) {
return NULL;
}
for (int b = 0; b < bands; b++) {
// nb == 0 for an empty image: report None per band.
PyObject *item =
nb ? Py_BuildValue("BB", mb[b * 2], mb[b * 2 + 1]) : Py_NewRef(Py_None);
if (!item) {
Py_DECREF(result);
return NULL;
}
PyTuple_SET_ITEM(result, b, item);
}
return result;
}

union {
UINT8 u[2];
INT32 i[2];
FLOAT32 f[2];
UINT16 s[2];
} extrema;
int status;

status = ImagingGetExtrema(self->image, &extrema);
int status = ImagingGetExtrema(self->image, &extrema);
if (status < 0) {
return NULL;
}
Expand Down
87 changes: 67 additions & 20 deletions src/libImaging/GetBBox.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ ImagingGetProjection(Imaging im, UINT8 *xproj, UINT8 *yproj) {

int
ImagingGetExtrema(Imaging im, void *extrema) {
int x, y;
int xsize = im->xsize, ysize = im->ysize;
INT32 imin, imax;
FLOAT32 fmin, fmax;

Expand All @@ -156,21 +156,18 @@ ImagingGetExtrema(Imaging im, void *extrema) {
return -1; /* mismatch */
}

if (!im->xsize || !im->ysize) {
if (!xsize || !ysize) {
return 0; /* zero size */
}

switch (im->type) {
case IMAGING_TYPE_UINT8:
imin = imax = im->image8[0][0];
for (y = 0; y < im->ysize; y++) {
for (int y = 0; y < ysize; y++) {
UINT8 *in = im->image8[y];
for (x = 0; x < im->xsize; x++) {
if (imin > in[x]) {
imin = in[x];
} else if (imax < in[x]) {
imax = in[x];
}
for (int x = 0; x < xsize; x++) {
imin = imin < in[x] ? imin : in[x];
imax = imax > in[x] ? imax : in[x];
}
if (imin == 0 && imax == 255) {
break;
Expand All @@ -181,24 +178,23 @@ ImagingGetExtrema(Imaging im, void *extrema) {
break;
case IMAGING_TYPE_INT32:
imin = imax = im->image32[0][0];
for (y = 0; y < im->ysize; y++) {
for (int y = 0; y < ysize; y++) {
INT32 *in = im->image32[y];
for (x = 0; x < im->xsize; x++) {
if (imin > in[x]) {
imin = in[x];
} else if (imax < in[x]) {
imax = in[x];
}
for (int x = 0; x < xsize; x++) {
imin = imin < in[x] ? imin : in[x];
imax = imax > in[x] ? imax : in[x];
}
}
memcpy(extrema, &imin, sizeof(imin));
memcpy(((char *)extrema) + sizeof(imin), &imax, sizeof(imax));
break;
case IMAGING_TYPE_FLOAT32:
fmin = fmax = ((FLOAT32 *)im->image32[0])[0];
for (y = 0; y < im->ysize; y++) {
for (int y = 0; y < ysize; y++) {
FLOAT32 *in = (FLOAT32 *)im->image32[y];
for (x = 0; x < im->xsize; x++) {
for (int x = 0; x < xsize; x++) {
// Kept as if/else (unlike the integer branches above),
// since float min/max isn't vectorisable due to NaN semantics.
if (fmin > in[x]) {
fmin = in[x];
} else if (fmax < in[x]) {
Expand All @@ -219,8 +215,8 @@ ImagingGetExtrema(Imaging im, void *extrema) {
memcpy(&v, pixel, sizeof(v));
#endif
imin = imax = v;
for (y = 0; y < im->ysize; y++) {
for (x = 0; x < im->xsize; x++) {
for (int y = 0; y < ysize; y++) {
for (int x = 0; x < xsize; x++) {
pixel = (UINT8 *)im->image[y] + x * sizeof(v);
#ifdef WORDS_BIGENDIAN
v = pixel[0] + ((UINT16)pixel[1] << 8);
Expand Down Expand Up @@ -248,6 +244,57 @@ ImagingGetExtrema(Imaging im, void *extrema) {
return 1; /* ok */
}

int
ImagingGetExtremaMultiband(Imaging im, UINT8 extrema[8]) {
// Per-band min/max for interleaved 8-bit images (up to 4 bands).
// Writes 2 * im->bands bytes to extrema as [min0, max0, min1, max1, ...].
// Returns the band count on success, 0 for an empty image, or -1 on error.
int bands = im->bands, xsize = im->xsize, ysize = im->ysize;
UINT8 vmin[4], vmax[4];

if (im->type != IMAGING_TYPE_UINT8 || im->image8 || bands < 2 || bands > 4) {
// `_getextrema` should have checked these, but best be sure,
// in case someone ends up calling this directly.
(void)ImagingError_ModeError();
return -1;
}

if (!xsize || !ysize) {
return 0; /* zero size */
}

UINT8 *restrict in = (UINT8 *)im->image[0];
for (int b = 0; b < 4; b++) {
vmin[b] = vmax[b] = in[b];
}

for (int y = 0; y < ysize; y++) {
UINT8 *restrict in = (UINT8 *)im->image[y];
for (int x = 0; x < xsize; x++, in += 4) {
for (int b = 0; b < 4; b++) {
if (in[b] < vmin[b]) {
vmin[b] = in[b];
}
if (in[b] > vmax[b]) {
vmax[b] = in[b];
}
}
}
}

// The second band of a two-band image is stored in the fourth byte
// (mirrors the special case in ImagingGetBand).
if (bands == 2) {
vmin[1] = vmin[3];
vmax[1] = vmax[3];
}
for (int b = 0; b < bands; b++) {
extrema[b * 2 + 0] = vmin[b];
extrema[b * 2 + 1] = vmax[b];
}
return bands;
}

/* static ImagingColorItem* getcolors8(Imaging im, int maxcolors, int* size);*/
static ImagingColorItem *
getcolors32(Imaging im, int maxcolors, int *size);
Expand Down
2 changes: 2 additions & 0 deletions src/libImaging/Imaging.h
Original file line number Diff line number Diff line change
Expand Up @@ -354,6 +354,8 @@ ImagingGetColors(Imaging im, int maxcolors, int *colors);
extern int
ImagingGetExtrema(Imaging im, void *extrema);
extern int
ImagingGetExtremaMultiband(Imaging im, UINT8 extrema[8]);
extern int
ImagingGetProjection(Imaging im, UINT8 *xproj, UINT8 *yproj);
extern ImagingHistogram
ImagingGetHistogram(Imaging im, Imaging mask, void *extrema);
Expand Down
Loading