Skip to content

Commit 53e02c4

Browse files
authored
Speed up Image.fill(), Image.linear_gradient() and Image.radial_gradient() (#9737)
1 parent af03747 commit 53e02c4

2 files changed

Lines changed: 67 additions & 37 deletions

File tree

Tests/benchmarks.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,22 @@ def test_fill(bench: BenchmarkFixture, mode: str, size: tuple[int, int]) -> None
365365
bench(Image.new, mode, size, color)
366366

367367

368+
@pytest.mark.benchmark(group="allocate")
369+
@pytest.mark.parametrize("mode", ["1", "F", "I", "L", "P"])
370+
def test_linear_gradient(bench: BenchmarkFixture, mode: str) -> None:
371+
result = bench(Image.linear_gradient, mode)
372+
assert result.size == (256, 256)
373+
assert result.mode == mode
374+
375+
376+
@pytest.mark.benchmark(group="allocate")
377+
@pytest.mark.parametrize("mode", ["1", "F", "I", "L", "P"])
378+
def test_radial_gradient(bench: BenchmarkFixture, mode: str) -> None:
379+
result = bench(Image.radial_gradient, mode)
380+
assert result.size == (256, 256)
381+
assert result.mode == mode
382+
383+
368384
CHOPS_OPS = [
369385
ImageChops.add,
370386
ImageChops.subtract,

src/libImaging/Fill.c

Lines changed: 51 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,30 @@
2121

2222
Imaging
2323
ImagingFill(Imaging im, const void *colour) {
24-
int x, y;
2524
ImagingSectionCookie cookie;
2625

2726
/* 0-width or 0-height image. No need to do anything */
2827
if (!im->linesize || !im->ysize) {
2928
return im;
3029
}
3130

31+
// xsize and ysize are invariant during the loops below.
32+
int xsize = im->xsize;
33+
int ysize = im->ysize;
34+
3235
if (im->type == IMAGING_TYPE_SPECIAL) {
3336
/* use generic API */
3437
ImagingAccess access = ImagingAccessNew(im);
3538
if (access) {
36-
for (y = 0; y < im->ysize; y++) {
37-
for (x = 0; x < im->xsize; x++) {
39+
for (int y = 0; y < ysize; y++) {
40+
for (int x = 0; x < xsize; x++) {
3841
access->put_pixel(im, x, y, colour);
3942
}
4043
}
4144
ImagingAccessDelete(im, access);
4245
} else {
4346
/* wipe the image */
44-
for (y = 0; y < im->ysize; y++) {
47+
for (int y = 0; y < ysize; y++) {
4548
memset(im->image[y], 0, im->linesize);
4649
}
4750
}
@@ -50,14 +53,16 @@ ImagingFill(Imaging im, const void *colour) {
5053
ImagingSectionEnter(&cookie);
5154
memcpy(&c, colour, im->pixelsize);
5255
if (im->image32 && c != 0L) {
53-
for (y = 0; y < im->ysize; y++) {
54-
for (x = 0; x < im->xsize; x++) {
55-
im->image32[y][x] = c;
56+
for (int y = 0; y < ysize; y++) {
57+
// Restrict safe: sole owner of image data here.
58+
INT32 *restrict row = im->image32[y];
59+
for (int x = 0; x < xsize; x++) {
60+
row[x] = c;
5661
}
5762
}
5863
} else {
5964
unsigned char cc = (unsigned char)*(UINT8 *)colour;
60-
for (y = 0; y < im->ysize; y++) {
65+
for (int y = 0; y < ysize; y++) {
6166
memset(im->image[y], cc, im->linesize);
6267
}
6368
}
@@ -70,7 +75,6 @@ ImagingFill(Imaging im, const void *colour) {
7075
Imaging
7176
ImagingFillLinearGradient(const ModeID mode) {
7277
Imaging im;
73-
int y;
7478

7579
if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && mode != IMAGING_MODE_I &&
7680
mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) {
@@ -82,19 +86,24 @@ ImagingFillLinearGradient(const ModeID mode) {
8286
return NULL;
8387
}
8488

89+
// Branch on pixel type outside the loops so the compiler can tighten them.
90+
// Restrict safe: sole owner of the freshly-allocated image data here.
8591
if (im->image8) {
86-
for (y = 0; y < 256; y++) {
92+
for (int y = 0; y < 256; y++) {
8793
memset(im->image8[y], (unsigned char)y, 256);
8894
}
95+
} else if (im->type == IMAGING_TYPE_FLOAT32) {
96+
for (int y = 0; y < 256; y++) {
97+
FLOAT32 *restrict row = (FLOAT32 *)im->image32[y];
98+
for (int x = 0; x < 256; x++) {
99+
row[x] = y;
100+
}
101+
}
89102
} else {
90-
int x;
91-
for (y = 0; y < 256; y++) {
92-
for (x = 0; x < 256; x++) {
93-
if (im->type == IMAGING_TYPE_FLOAT32) {
94-
IMAGING_PIXEL_FLOAT32(im, x, y) = y;
95-
} else {
96-
IMAGING_PIXEL_INT32(im, x, y) = y;
97-
}
103+
for (int y = 0; y < 256; y++) {
104+
INT32 *restrict row = im->image32[y];
105+
for (int x = 0; x < 256; x++) {
106+
row[x] = y;
98107
}
99108
}
100109
}
@@ -105,8 +114,6 @@ ImagingFillLinearGradient(const ModeID mode) {
105114
Imaging
106115
ImagingFillRadialGradient(const ModeID mode) {
107116
Imaging im;
108-
int x, y;
109-
int d;
110117

111118
if (mode != IMAGING_MODE_1 && mode != IMAGING_MODE_F && mode != IMAGING_MODE_I &&
112119
mode != IMAGING_MODE_L && mode != IMAGING_MODE_P) {
@@ -118,25 +125,32 @@ ImagingFillRadialGradient(const ModeID mode) {
118125
return NULL;
119126
}
120127

121-
for (y = 0; y < 256; y++) {
122-
for (x = 0; x < 256; x++) {
123-
d = (int)sqrt(
124-
(double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0
125-
);
126-
if (d >= 255) {
127-
d = 255;
128-
}
129-
if (im->image8) {
130-
im->image8[y][x] = d;
131-
} else {
132-
if (im->type == IMAGING_TYPE_FLOAT32) {
133-
IMAGING_PIXEL_FLOAT32(im, x, y) = d;
134-
} else {
135-
IMAGING_PIXEL_INT32(im, x, y) = d;
136-
}
137-
}
128+
#define ASSIGN_ROW(row, y) \
129+
for (int x = 0; x < 256; x++) { \
130+
int d = \
131+
(int)sqrt((double)((x - 128) * (x - 128) + (y - 128) * (y - 128)) * 2.0); \
132+
row[x] = d >= 255 ? 255 : d; \
133+
}
134+
135+
// Branch on pixel type outside the loops so the compiler can tighten them.
136+
// Restrict safe: sole owner of the freshly-allocated image data here.
137+
if (im->image8) {
138+
for (int y = 0; y < 256; y++) {
139+
UINT8 *restrict row = im->image8[y];
140+
ASSIGN_ROW(row, y);
141+
}
142+
} else if (im->type == IMAGING_TYPE_FLOAT32) {
143+
for (int y = 0; y < 256; y++) {
144+
FLOAT32 *restrict row = (FLOAT32 *)im->image32[y];
145+
ASSIGN_ROW(row, y);
146+
}
147+
} else {
148+
for (int y = 0; y < 256; y++) {
149+
INT32 *restrict row = im->image32[y];
150+
ASSIGN_ROW(row, y);
138151
}
139152
}
153+
#undef ASSIGN_ROW
140154

141155
return im;
142156
}

0 commit comments

Comments
 (0)