Pillow icon indicating copy to clipboard operation
Pillow copied to clipboard

Support for dashed lines?

Open larsga opened this issue 4 months ago • 8 comments

I need dashed lines, and it's not really on the cards for me to implement it myself outside of Pillow, because it would be much too slow.

I'm not the only one who's wanted this.

Is there any chance this will be added to Pillow?

larsga avatar Aug 02 '25 10:08 larsga

Are you just talking about straight lines?

From that StackOverflow post, you're aware of how to do this in Python, but you're requesting it be implemented by us in C so that it will be faster?

radarhere avatar Aug 02 '25 10:08 radarhere

Are you just talking about straight lines?

Yes. In my case I need it specifically in the ImageDraw.polygon function, but I imagine if you do this it will be done for all straight lines?

From that StackOverflow post, you're aware of how to do this in Python, but you're requesting it be implemented by us in C so that it will be faster?

Yes, exactly.

larsga avatar Aug 02 '25 10:08 larsga

Not hard to find more examples of people wanting this: 1, 2, 3.

larsga avatar Aug 02 '25 10:08 larsga

I'm not convinced a need for this to be fast is so widespread - it looks like most of the requests that you've linked to result in answers for how to achieve this in Python, and not comments from people asking for more speed. If you wanted this implemented in Python, that would seem achievable. From #8976, I think you have an above average need for speed.

However, in C, we use a scanline algorithm - for polygons, we're not drawing each line one at a time like a person would, we're determining which pixels should be visible in each horizontal row. To start using dashed lines, I think we would have to take a new approach.

I'm not saying this is impossible, just that it would not be simple. If you are able to post some example code, and precisely specify how fast you need it to run, that would be helpful.

radarhere avatar Aug 05 '25 03:08 radarhere

From https://github.com/python-pillow/Pillow/issues/8976, I think you have an https://github.com/python-pillow/Pillow/issues/8976#issuecomment-2910546870 need for speed.

I think that's fair to say. In fact, I have implemented this in Python now, and was surprised to find that performance was completely acceptable. So if you do implement this feature request you don't need to do it for me.

Still, I find it strange that people have to implement this themselves. To me it seems like it should be in the library, but whether you want to do something about that is of course up to you.

I did look at implementing this myself in C, and as far as I can tell the scanlines you're talking about is for filled polygons. For unfilled polygons the draws one line at a time. It was with a view to adding a C implementation of dashing here that I first prototyped it in Python.

In case it will be useful to someone, here's my Python implementation of the SVG algorithm:

class Dasher:

    def __init__(self, dash_pattern: tuple[int]):
        self._dash_pattern = dash_pattern
        self._pixels_used = 0 # how many pixels of current step used?
        self._steps = 0       # how many steps through pattern?

    def make_next_step(self, max_length: float) -> float:
        current = self._steps % len(self._dash_pattern)
        step = min(
            self._dash_pattern[current] - self._pixels_used,
            max_length
        )

        self._pixels_used += step
        if self._pixels_used >= self._dash_pattern[current]:
            self._pixels_used = 0
            self._steps += 1

        return step

    def is_current_dash(self):
        return self._steps % 2 == 0

def dist(p1, p2):
    return math.sqrt((p1[0] - p2[0]) ** 2 + (p1[1] - p2[1]) ** 2)

def draw_dashed_polygon(draw, coords, lc, lw, fc, dashing):
    if fc:
        draw.polygon(coords, outline = None, width = 0, fill = fc)

    dasher = Dasher(dashing)

    for ix in range(len(coords) - 1):
        (x, y) = coords[ix]
        (target_x, target_y) = coords[ix + 1]
        rem_length = dist((x, y), (target_x, target_y))
        vx = (target_x - x) / rem_length
        vy = (target_y - y) / rem_length

        while rem_length > 0:
            step = dasher.make_next_step(rem_length)
            nx = x + vx * step
            ny = y + vy * step

            if dasher.is_current_dash():
                draw.line((x, y, nx, ny), fill = lc, width = lw)

            x = nx
            y = ny
            rem_length -= step

larsga avatar Aug 05 '25 07:08 larsga

It strikes me now that it would be possible to add support for dashed lines in the Python layer of Pillow. Is that a contribution that would be interesting?

larsga avatar Aug 05 '25 14:08 larsga

You're offering to create a PR? Sure, feel free.

radarhere avatar Aug 05 '25 22:08 radarhere