7
\$\begingroup\$

I have written some Python code for creating an image made of lots of circles. It works, but I wonder if it could be made faster, shorter or more pythonic somehow.

Example image (took 28 seconds):

example image

from PIL import Image
from random import randint
from datetime import datetime


def circle(data, center, radius, color, size):
    for x in range(size):
        for y in range(size):
            if (x - center[0]) ** 2 + (y - center[1]) ** 2 <= radius ** 2:
                data[x * size + y] = color
    return data


def main():
    start_time = datetime.now()
    size = 500
    circle_amount = 200
    data = [(0, 0, 0)] * size ** 2
    img = Image.new("RGB", (size, size), "black")
    for _ in range(circle_amount):
        data = circle(data, (randint(0, size), randint(0, size)), randint(0, max(size, size)) / randint(5, 10), (randint(0, 255), randint(0, 255), randint(0, 255)), size)
    img.putdata(data)
    img.save("image.png")
    print(f"Time needed: {datetime.now() - start_time}")


if __name__ == "__main__":
    main()
\$\endgroup\$

1 Answer 1

10
\$\begingroup\$

Don't set the data. Don't iterate over pixels. Iterate over circles only and use PIL's own drawing facilities:

from PIL import Image, ImageDraw
from random import randint, randrange


def circle(draw: ImageDraw, size: int) -> None:
    xc, yc = randrange(size), randrange(size)
    radius = randrange(size) / randint(5, 10)
    color = randrange(256), randrange(256), randrange(256)

    draw.ellipse(
        xy=(
            xc - radius, yc - radius,
            xc + radius, yc + radius,
        ),
        fill=color,
    )


def main():
    size = 500
    circle_amount = 100_000
    img: Image = Image.new("RGB", (size, size), "black")
    draw: ImageDraw = ImageDraw.Draw(img)

    for _ in range(circle_amount):
        circle(draw, size)

    img.save("image.png")


if __name__ == "__main__":
    main()

This took about one second for a hundred thousand circles. We expect this approach to be much faster because internally it's not implemented in Python, but rather in C:

static int
ellipseNew(
    Imaging im,
    int x0,
    int y0,
    int x1,
    int y1,
    const void *ink_,
    int fill,
    int width,
    int op) {
    DRAW *draw;
    INT32 ink;
    DRAWINIT();

    int a = x1 - x0;
    int b = y1 - y0;
    if (a < 0 || b < 0) {
        return 0;
    }
    if (fill) {
        width = a + b;
    }

    ellipse_state st;
    ellipse_init(&st, a, b, width);
    int32_t X0, Y, X1;
    while (ellipse_next(&st, &X0, &Y, &X1) != -1) {
        draw->hline(im, x0 + (X0 + a) / 2, y0 + (Y + b) / 2, x0 + (X1 + a) / 2, ink);
    }
    return 0;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.