Skip to content

Use mask in C when drawing wide polygon lines #8984

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

radarhere
Copy link
Member

@radarhere radarhere commented May 29, 2025

Resolves #8976

If we just drew thicker lines when creating a polygon, then since the centre of the line is the edge, half of each line would be drawn outside the filled polygon. To avoid this, #5694 introduced masking using the Python API.

Pillow/src/PIL/ImageDraw.py

Lines 361 to 383 in 5a04b95

if width == 1:
self.draw.draw_polygon(xy, ink, 0, width)
elif self.im is not None:
# To avoid expanding the polygon outwards,
# use the fill as a mask
mask = Image.new("1", self.im.size)
mask_ink = self._getink(1)[0]
fill_im = mask.copy()
draw = Draw(fill_im)
draw.draw.draw_polygon(xy, mask_ink, 1)
ink_im = mask.copy()
draw = Draw(ink_im)
width = width * 2 - 1
draw.draw.draw_polygon(xy, mask_ink, 0, width)
mask.paste(ink_im, mask=fill_im)
im = Image.new(self.mode, self.im.size)
draw = Draw(im)
draw.draw.draw_polygon(xy, ink, 0, width)
self.im.paste(im.im, (0, 0) + im.size, mask.im)

However, #8976 finds this to be slow. So instead of

  • drawing a filled polygon
  • drawing the outline
  • combining those two to create a mask of just the outline inside the polygon
  • drawing the outline again, and pasting it onto the final image with that mask

this PR

  • draws a filled polygon
  • draws the outline onto the final image, passing the filled polygon as a mask into the C layer. Every time a pixel is to be 'drawn', the mask image is checked to see whether or not to do so.

The first two commits simplify the existing code by removing polygon from the DRAW struct.

typedef struct {
void (*point)(Imaging im, int x, int y, int ink);
void (*hline)(Imaging im, int x0, int y0, int x1, int ink);
void (*line)(Imaging im, int x0, int y0, int x1, int y1, int ink);
int (*polygon)(Imaging im, int n, Edge *e, int ink, int eofill);
} DRAW;

This just means that polygon_generic can be used more directly, and the mask doesn't need to be passed through each different type of polygon function.

@larsga
Copy link

larsga commented May 29, 2025

Nice! Thank you! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ImageDraw.polygon is very slow when width > 1
2 participants