diff --git a/Tests/benchmarks.py b/Tests/benchmarks.py index 9d7439d321b..cf692f98d4a 100644 --- a/Tests/benchmarks.py +++ b/Tests/benchmarks.py @@ -135,17 +135,28 @@ def test_box_blur( @pytest.mark.benchmark(group="composition") +@pytest.mark.parametrize("alpha", ["opaque", "transparent", "mixed"]) @pytest.mark.parametrize("mode", ALPHA_MODES) @pytest.mark.parametrize("size", SIZES, ids=_format_size) -def test_alpha_composition( +def test_alpha_composite( bench: BenchmarkFixture, mode: str, size: tuple[int, int], + alpha: str, ) -> None: - im = make_pillow_image(mode, size) - second = im.copy() - bench.extra_info["label"] = ["Composition"] - bench(Image.alpha_composite, im, second) + im1 = make_pillow_image(mode, size) + im2 = make_pillow_image(mode, size, pattern_offset=1024) + if alpha == "opaque": + im2.putalpha(255) + elif alpha == "transparent": + im2.putalpha(0) + else: # "mixed" + width, height = size + gradient = bytes((x * 256) // width for x in range(width)) * height + im2.putalpha(Image.frombytes("L", size, gradient)) + bench.extra_info["label"] = ["Composition", alpha] + result = bench(Image.alpha_composite, im1, im2) + assert result.size == im1.size @pytest.mark.benchmark(group="convert") diff --git a/src/libImaging/AlphaComposite.c b/src/libImaging/AlphaComposite.c index 280277e835a..d1a1e7ad34a 100644 --- a/src/libImaging/AlphaComposite.c +++ b/src/libImaging/AlphaComposite.c @@ -22,7 +22,6 @@ typedef struct { Imaging ImagingAlphaComposite(Imaging imDst, Imaging imSrc) { Imaging imOut; - int x, y; /* Check arguments */ if (!imDst || !imSrc || @@ -40,12 +39,17 @@ ImagingAlphaComposite(Imaging imDst, Imaging imSrc) { return NULL; } - for (y = 0; y < imDst->ysize; y++) { - rgba8 *dst = (rgba8 *)imDst->image[y]; - rgba8 *src = (rgba8 *)imSrc->image[y]; - rgba8 *out = (rgba8 *)imOut->image[y]; + // Invariant over the loop. + int xsize = imDst->xsize; + int ysize = imDst->ysize; - for (x = 0; x < imDst->xsize; x++) { + for (int y = 0; y < ysize; y++) { + // restrict safe: imDst/imSrc are read-only, imOut is a fresh allocation. + rgba8 *restrict dst = (rgba8 *)imDst->image[y]; + rgba8 *restrict src = (rgba8 *)imSrc->image[y]; + rgba8 *restrict out = (rgba8 *)imOut->image[y]; + + for (int x = 0; x < xsize; x++) { if (src->a == 0) { // Copy 4 bytes at once. *out = *dst; diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 713eab48b81..472bda5d0fd 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -307,7 +307,7 @@ ImagingConvert(Imaging im, ModeID mode, ImagingPalette palette, int dither); extern Imaging ImagingConvertInPlace(Imaging im, ModeID mode); extern Imaging -ImagingConvertMatrix(Imaging im, ModeID mode, float m[]); +ImagingConvertMatrix(Imaging im, ModeID mode, const float m[12]); extern Imaging ImagingConvertTransparent(Imaging im, ModeID mode, int r, int g, int b); extern Imaging diff --git a/src/libImaging/Matrix.c b/src/libImaging/Matrix.c index d63ba8be880..02676c9e31a 100644 --- a/src/libImaging/Matrix.c +++ b/src/libImaging/Matrix.c @@ -18,9 +18,8 @@ #define CLIPF(v) ((v <= 0.0) ? 0 : (v >= 255.0F) ? 255 : (UINT8)v) Imaging -ImagingConvertMatrix(Imaging im, const ModeID mode, float m[]) { +ImagingConvertMatrix(Imaging im, const ModeID mode, const float m[12]) { Imaging imOut; - int x, y; ImagingSectionCookie cookie; /* Assume there's enough data in the buffer */ @@ -34,12 +33,16 @@ ImagingConvertMatrix(Imaging im, const ModeID mode, float m[]) { return NULL; } + // Invariant over the loop. + int xsize = im->xsize, ysize = im->ysize; + ImagingSectionEnter(&cookie); - for (y = 0; y < im->ysize; y++) { - UINT8 *in = (UINT8 *)im->image[y]; - UINT8 *out = (UINT8 *)imOut->image[y]; + for (int y = 0; y < ysize; y++) { + // restrict safe: im is read-only, imOut is a fresh allocation. + UINT8 *restrict in = (UINT8 *)im->image[y]; + UINT8 *restrict out = (UINT8 *)imOut->image[y]; - for (x = 0; x < im->xsize; x++) { + for (int x = 0; x < xsize; x++) { float v = m[0] * in[0] + m[1] * in[1] + m[2] * in[2] + m[3] + 0.5; out[x] = CLIPF(v); in += 4; @@ -52,12 +55,16 @@ ImagingConvertMatrix(Imaging im, const ModeID mode, float m[]) { return NULL; } - for (y = 0; y < im->ysize; y++) { - UINT8 *in = (UINT8 *)im->image[y]; - UINT8 *out = (UINT8 *)imOut->image[y]; + // Invariant over the loop. + int xsize = im->xsize, ysize = im->ysize; + + for (int y = 0; y < ysize; y++) { + // restrict safe: im is read-only, imOut is a fresh allocation. + UINT8 *restrict in = (UINT8 *)im->image[y]; + UINT8 *restrict out = (UINT8 *)imOut->image[y]; ImagingSectionEnter(&cookie); - for (x = 0; x < im->xsize; x++) { + for (int x = 0; x < xsize; x++) { float v0 = m[0] * in[0] + m[1] * in[1] + m[2] * in[2] + m[3] + 0.5; float v1 = m[4] * in[0] + m[5] * in[1] + m[6] * in[2] + m[7] + 0.5; float v2 = m[8] * in[0] + m[9] * in[1] + m[10] * in[2] + m[11] + 0.5; diff --git a/src/libImaging/Negative.c b/src/libImaging/Negative.c index 70b96c39772..114e8608e87 100644 --- a/src/libImaging/Negative.c +++ b/src/libImaging/Negative.c @@ -32,9 +32,15 @@ ImagingNegative(Imaging im) { return NULL; } - for (y = 0; y < im->ysize; y++) { - for (x = 0; x < im->linesize; x++) { - imOut->image[y][x] = ~im->image[y][x]; + // ysize and linesize are loop-invariant. + int ysize = im->ysize, linesize = im->linesize; + + for (y = 0; y < ysize; y++) { + // restrict safe: im is read-only, imOut is a fresh allocation. + UINT8 *restrict in = (UINT8 *)im->image[y]; + UINT8 *restrict out = (UINT8 *)imOut->image[y]; + for (x = 0; x < linesize; x++) { + out[x] = ~in[x]; } } diff --git a/src/libImaging/Quant.c b/src/libImaging/Quant.c index 46cff5b8b3c..99c2dbac3af 100644 --- a/src/libImaging/Quant.c +++ b/src/libImaging/Quant.c @@ -1696,11 +1696,14 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { return ImagingError_ModeError(); } - if (im->xsize > INT_MAX / im->ysize) { + // Hoisted here as these are invariant over the loops below. + int xsize = im->xsize, ysize = im->ysize; + + if (xsize > INT_MAX / ysize) { return ImagingError_MemoryError(); } /* malloc check ok, using calloc for final overflow, x*y above */ - p = calloc(im->xsize * im->ysize, sizeof(Pixel)); + p = calloc(xsize * ysize, sizeof(Pixel)); if (!p) { return ImagingError_MemoryError(); } @@ -1716,9 +1719,10 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { /* FIXME: converting a "L" image to "P" with 256 colors should be done by a simple copy... */ - for (i = y = 0; y < im->ysize; y++) { - for (x = 0; x < im->xsize; x++, i++) { - p[i].c.r = p[i].c.g = p[i].c.b = im->image8[y][x]; + for (i = y = 0; y < ysize; y++) { + UINT8 *in = im->image8[y]; + for (x = 0; x < xsize; x++, i++) { + p[i].c.r = p[i].c.g = p[i].c.b = in[x]; p[i].c.a = 255; } } @@ -1728,9 +1732,10 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { pp = im->palette->palette; - for (i = y = 0; y < im->ysize; y++) { - for (x = 0; x < im->xsize; x++, i++) { - v = im->image8[y][x]; + for (i = y = 0; y < ysize; y++) { + UINT8 *in = im->image8[y]; + for (x = 0; x < xsize; x++, i++) { + v = in[x]; p[i].c.r = pp[v * 4 + 0]; p[i].c.g = pp[v * 4 + 1]; p[i].c.b = pp[v * 4 + 2]; @@ -1744,9 +1749,10 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { withAlpha = im->mode == IMAGING_MODE_RGBA; int transparency = 0; unsigned char r = 0, g = 0, b = 0; - for (i = y = 0; y < im->ysize; y++) { - for (x = 0; x < im->xsize; x++, i++) { - p[i].v = im->image32[y][x]; + for (i = y = 0; y < ysize; y++) { + INT32 *in = im->image32[y]; + for (x = 0; x < xsize; x++, i++) { + p[i].v = in[x]; if (withAlpha) { if (p[i].c.a == 0) { if (transparency == 0) { @@ -1779,49 +1785,24 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { case 0: /* median cut */ result = quantize( - p, - im->xsize * im->ysize, - colors, - &palette, - &paletteLength, - &newData, - kmeans + p, xsize * ysize, colors, &palette, &paletteLength, &newData, kmeans ); break; case 1: /* maximum coverage */ result = quantize2( - p, - im->xsize * im->ysize, - colors, - &palette, - &paletteLength, - &newData, - kmeans + p, xsize * ysize, colors, &palette, &paletteLength, &newData, kmeans ); break; case 2: result = quantize_octree( - p, - im->xsize * im->ysize, - colors, - &palette, - &paletteLength, - &newData, - withAlpha + p, xsize * ysize, colors, &palette, &paletteLength, &newData, withAlpha ); break; case 3: #ifdef HAVE_LIBIMAGEQUANT result = quantize_pngquant( - p, - im->xsize, - im->ysize, - colors, - &palette, - &paletteLength, - &newData, - withAlpha + p, xsize, ysize, colors, &palette, &paletteLength, &newData, withAlpha ); #else result = -1; @@ -1836,7 +1817,7 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { ImagingSectionLeave(&cookie); if (result > 0) { - imOut = ImagingNewDirty(IMAGING_MODE_P, im->xsize, im->ysize); + imOut = ImagingNewDirty(IMAGING_MODE_P, xsize, ysize); if (!imOut) { free(newData); free(palette); @@ -1844,8 +1825,8 @@ ImagingQuantize(Imaging im, int colors, int mode, int kmeans) { } ImagingSectionEnter(&cookie); - for (i = y = 0; y < im->ysize; y++) { - for (x = 0; x < im->xsize; x++) { + for (i = y = 0; y < ysize; y++) { + for (x = 0; x < xsize; x++) { imOut->image8[y][x] = (unsigned char)newData[i++]; } }