diff --git a/Tests/images/imagedraw_dash_line.png b/Tests/images/imagedraw_dash_line.png new file mode 100644 index 00000000000..a3bb51434d6 Binary files /dev/null and b/Tests/images/imagedraw_dash_line.png differ diff --git a/Tests/images/imagedraw_dash_polygon.png b/Tests/images/imagedraw_dash_polygon.png new file mode 100644 index 00000000000..1da913f1e41 Binary files /dev/null and b/Tests/images/imagedraw_dash_polygon.png differ diff --git a/Tests/images/imagedraw_dash_rectangle.png b/Tests/images/imagedraw_dash_rectangle.png new file mode 100644 index 00000000000..fce36a021ea Binary files /dev/null and b/Tests/images/imagedraw_dash_rectangle.png differ diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 680a68c302b..5527f81651e 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -1774,3 +1774,65 @@ def test_incorrectly_ordered_coordinates(xy: tuple[int, int, int, int]) -> None: draw.rectangle(xy) with pytest.raises(ValueError): draw.rounded_rectangle(xy) + + +def test_dash_line() -> None: + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.line([(10, 90), (90, 90)], "green", dash=(10, 5)) + draw.line([(10, 10), (50, 50), (90, 10)], "green", dash=(8, 4)) + + # Assert + assert_image_equal_tofile(im, "Tests/images/imagedraw_dash_line.png") + + +def test_dash_polygon() -> None: + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.polygon( + [(10, 10), (90, 10), (10, 90)], + outline="green", + dash=(10, 5), + ) + draw.polygon( + [(20, 20), (60, 20), (20, 60)], + fill="red", + outline="green", + dash=(10, 5), + ) + + # Assert + assert_image_equal_tofile(im, "Tests/images/imagedraw_dash_polygon.png") + + +def test_dash_rectangle() -> None: + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.rectangle([10, 10, 90, 90], outline="green", dash=(10, 5)) + draw.rectangle([30, 30, 70, 70], fill="red", outline="green", dash=(10, 5)) + + # Assert + assert_image_equal_tofile(im, "Tests/images/imagedraw_dash_rectangle.png") + + +def test_dash_empty() -> None: + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + with pytest.raises(ValueError, match="dash must be a non-empty tuple of ints"): + draw.line([(10, 50), (90, 50)], dash=()) + + with pytest.raises(ValueError, match="dash must be a non-empty tuple of ints"): + draw.polygon([(10, 10), (90, 10), (90, 90)], dash=()) + + with pytest.raises(ValueError, match="dash must be a non-empty tuple of ints"): + draw.rectangle([10, 10, 90, 90], dash=()) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index faa3a236f45..70d3db2a2f2 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -287,7 +287,7 @@ Methods .. versionadded:: 5.3.0 -.. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None) +.. py:method:: ImageDraw.line(xy, fill=None, width=0, joint=None, dash=None) Draws a line between the coordinates in the ``xy`` list. The coordinate pixels are included in the drawn line. @@ -303,6 +303,14 @@ Methods :param joint: Joint type between a sequence of lines. It can be ``"curve"``, for rounded edges, or :data:`None`. .. versionadded:: 5.3.0 + :param dash: An optional dash pattern, given as a tuple of integers. + The dash pattern specifies the lengths of alternating drawn and blank segments + (e.g. ``(10, 5)`` draws 10 pixels, skips 5, and repeats). If an odd number of + values is given, it continues to alternate (e.g. ``(1, 2, 3)`` draws 1 pixel, + skips 2, draws 3, skips 1, draws 2, and so on). When ``dash`` is set, ``width`` + and ``joint`` are ignored. + + .. versionadded:: 13.0.0 .. py:method:: ImageDraw.pieslice(xy, start, end, fill=None, outline=None, width=1) @@ -329,7 +337,7 @@ Methods numeric values like ``[x, y, x, y, ...]``. :param fill: Color to use for the point. -.. py:method:: ImageDraw.polygon(xy, fill=None, outline=None, width=1) +.. py:method:: ImageDraw.polygon(xy, fill=None, outline=None, width=1, dash=None) Draws a polygon. @@ -342,6 +350,14 @@ Methods :param fill: Color to use for the fill. :param outline: Color to use for the outline. :param width: The line width, in pixels. + :param dash: An optional dash pattern, given as a tuple of integers. + The dash pattern specifies the lengths of alternating drawn and blank segments + (e.g. ``(10, 5)`` draws 10 pixels, skips 5, and repeats). If an odd number of + values is given, it continues to alternate (e.g. ``(1, 2, 3)`` draws 1 pixel, + skips 2, draws 3, skips 1, draws 2, and so on). When ``dash`` is set, ``width`` + is ignored. + + .. versionadded:: 13.0.0 .. py:method:: ImageDraw.regular_polygon(bounding_circle, n_sides, rotation=0, fill=None, outline=None, width=1) @@ -362,7 +378,7 @@ Methods :param width: The line width, in pixels. -.. py:method:: ImageDraw.rectangle(xy, fill=None, outline=None, width=1) +.. py:method:: ImageDraw.rectangle(xy, fill=None, outline=None, width=1, dash=None) Draws a rectangle. @@ -374,6 +390,14 @@ Methods :param width: The line width, in pixels. .. versionadded:: 5.3.0 + :param dash: An optional dash pattern, given as a tuple of integers. + The dash pattern specifies the lengths of alternating drawn and blank segments + (e.g. ``(10, 5)`` draws 10 pixels, skips 5, and repeats). If an odd number of + values is given, it continues to alternate (e.g. ``(1, 2, 3)`` draws 1 pixel, + skips 2, draws 3, skips 1, draws 2, and so on). When ``dash`` is set, ``width`` + is ignored. + + .. versionadded:: 13.0.0 .. py:method:: ImageDraw.rounded_rectangle(xy, radius=0, fill=None, outline=None, width=1, corners=None) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index e6e3be01bd6..ec994b62a5f 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -222,26 +222,38 @@ def circle( ellipse_xy = (xy[0] - radius, xy[1] - radius, xy[0] + radius, xy[1] + radius) self.ellipse(ellipse_xy, fill, outline, width) + def _normalize_coords(self, xy: Coords) -> Sequence[Sequence[float]]: + """Normalize 1 or 2 dimensional coord sequence into 2d sequence.""" + if isinstance(xy[0], (list, tuple)): + return cast("Sequence[Sequence[float]]", xy) + else: + return [ + cast("Sequence[float]", tuple(xy[i : i + 2])) + for i in range(0, len(xy), 2) + ] + def line( self, xy: Coords, fill: _Ink | None = None, width: int = 1, joint: str | None = None, + dash: tuple[int, ...] | None = None, ) -> None: """Draw a line, or a connected sequence of line segments.""" ink = self._getink(fill)[0] - if ink is not None and width != 0: + if ink is None or width == 0: + return + + if dash is not None: + if len(dash) == 0 or any(not isinstance(v, int) for v in dash): + msg = "dash must be a non-empty tuple of ints" + raise ValueError(msg) + self.draw.draw_lines(xy, ink, 1, dash) + else: self.draw.draw_lines(xy, ink, width) if joint == "curve" and width > 4: - points: Sequence[Sequence[float]] - if isinstance(xy[0], (list, tuple)): - points = cast("Sequence[Sequence[float]]", xy) - else: - points = [ - cast("Sequence[float]", tuple(xy[i : i + 2])) - for i in range(0, len(xy), 2) - ] + points = self._normalize_coords(xy) for i in range(1, len(points) - 1): point = points[i] angles = [ @@ -341,23 +353,31 @@ def polygon( fill: _Ink | None = None, outline: _Ink | None = None, width: int = 1, + dash: tuple[int, ...] | None = None, ) -> None: """Draw a polygon.""" ink, fill_ink = self._getink(outline, fill) if fill_ink is not None: self.draw.draw_polygon(xy, fill_ink, 1) - if ink is not None and ink != fill_ink and width != 0: - 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] - draw = Draw(mask) - draw.draw.draw_polygon(xy, mask_ink, 1) - - self.draw.draw_polygon(xy, ink, 0, width * 2 - 1, mask.im) + if ink is None or ink == fill_ink or width == 0: + return + + if dash is not None: + if len(dash) == 0 or any(not isinstance(v, int) for v in dash): + msg = "dash must be a non-empty tuple of ints" + raise ValueError(msg) + self.draw.draw_polygon(xy, ink, 0, 1, dash) + elif 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] + draw = Draw(mask) + draw.draw.draw_polygon(xy, mask_ink, 1) + + self.draw.draw_polygon(xy, ink, 0, width * 2 - 1, None, mask.im) def regular_polygon( self, @@ -378,12 +398,32 @@ def rectangle( fill: _Ink | None = None, outline: _Ink | None = None, width: int = 1, + dash: tuple[int, ...] | None = None, ) -> None: """Draw a rectangle.""" ink, fill_ink = self._getink(outline, fill) if fill_ink is not None: self.draw.draw_rectangle(xy, fill_ink, 1) - if ink is not None and ink != fill_ink and width != 0: + if ink is None or ink == fill_ink or width == 0: + return + + if dash is not None: + if len(dash) == 0 or any(not isinstance(v, int) for v in dash): + msg = "dash must be a non-empty tuple of ints" + raise ValueError(msg) + self.draw.draw_lines( + [ + (xy[0], xy[1]), + (xy[2], xy[1]), + (xy[2], xy[3]), + (xy[0], xy[3]), + (xy[0], xy[1]), + ], + ink, + 1, + dash, + ) + else: self.draw.draw_rectangle(xy, ink, 0, width) def rounded_rectangle( @@ -397,10 +437,7 @@ def rounded_rectangle( corners: tuple[bool, bool, bool, bool] | None = None, ) -> None: """Draw a rounded rectangle.""" - if isinstance(xy[0], (list, tuple)): - (x0, y0), (x1, y1) = cast("Sequence[Sequence[float]]", xy) - else: - x0, y0, x1, y1 = cast("Sequence[float]", xy) + (x0, y0), (x1, y1) = self._normalize_coords(xy) if x1 < x0: msg = "x1 must be greater than or equal to x0" raise ValueError(msg) diff --git a/src/_imaging.c b/src/_imaging.c index 9bdb6328782..1ff7638a240 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -3217,7 +3217,8 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) { PyObject *data; int ink; int width; - if (!PyArg_ParseTuple(args, "Oii", &data, &ink, &width)) { + PyObject *dash = NULL; + if (!PyArg_ParseTuple(args, "Oii|O", &data, &ink, &width, &dash)) { return NULL; } @@ -3228,6 +3229,7 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) { if (width == 1) { double *p = NULL; + int dash_offset = 0; for (i = 0; i < n - 1; i++) { p = &xy[i + i]; if (ImagingDrawLine( @@ -3237,7 +3239,9 @@ _draw_lines(ImagingDrawObject *self, PyObject *args) { (int)p[2], (int)p[3], &ink, - self->blend + self->blend, + dash, + &dash_offset ) < 0) { free(xy); return NULL; @@ -3399,8 +3403,9 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) { int fill = 0; int width = 0; ImagingObject *maskp = NULL; + PyObject *dash = NULL; if (!PyArg_ParseTuple( - args, "Oi|iiO!", &data, &ink, &fill, &width, &Imaging_Type, &maskp + args, "Oi|iiOO!", &data, &ink, &fill, &width, &dash, &Imaging_Type, &maskp )) { return NULL; } @@ -3439,7 +3444,8 @@ _draw_polygon(ImagingDrawObject *self, PyObject *args) { fill, width, self->blend, - maskp ? maskp->image : NULL + maskp ? maskp->image : NULL, + dash != Py_None ? dash : NULL ) < 0) { free(ixy); return NULL; @@ -3459,7 +3465,8 @@ _draw_rectangle(ImagingDrawObject *self, PyObject *args) { int ink; int fill = 0; int width = 0; - if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) { + PyObject *dash = NULL; + if (!PyArg_ParseTuple(args, "Oi|iiO", &data, &ink, &fill, &width, &dash)) { return NULL; } diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index 3217953a3e8..f5f1d93581f 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -205,8 +205,41 @@ hline32rgba(Imaging im, int x0, int y0, int x1, int ink, Imaging mask) { } } +static inline int +should_draw_dash(int i, int *dash_offset, PyObject *dash) { + i += *dash_offset; + int total = 0; + int tuple_index = -1; + int tuple_size = PyTuple_GET_SIZE(dash); + while (total <= i) { + tuple_index += 1; + if (tuple_index == tuple_size) { + tuple_index = 0; + } + PyObject *value = PyTuple_GetItem(dash, tuple_index); + if (!PyLong_Check(value)) { + return 0; + } + int v = PyLong_AsLongLong(value); + if (v == -1 && PyErr_Occurred()) { + return 0; + } + total += v; + } + return tuple_index % 2 == 0; +} + static inline void -line8(Imaging im, int x0, int y0, int x1, int y1, int ink) { +line8( + Imaging im, + int x0, + int y0, + int x1, + int y1, + int ink, + PyObject *dash, + int *dash_offset +) { int i, n, e; int dx, dy; int xs, ys; @@ -229,16 +262,36 @@ line8(Imaging im, int x0, int y0, int x1, int y1, int ink) { if (dx == 0) { /* vertical */ - for (i = 0; i < dy; i++) { - point8(im, x0, y0, ink); - y0 += ys; + if (dash != NULL) { + for (i = 0; i < dy; i++) { + if (should_draw_dash(i, dash_offset, dash)) { + point8(im, x0, y0, ink); + } + y0 += ys; + } + *dash_offset += dy; + } else { + for (i = 0; i < dy; i++) { + point8(im, x0, y0, ink); + y0 += ys; + } } } else if (dy == 0) { /* horizontal */ - for (i = 0; i < dx; i++) { - point8(im, x0, y0, ink); - x0 += xs; + if (dash != NULL) { + for (i = 0; i < dx; i++) { + if (should_draw_dash(i, dash_offset, dash)) { + point8(im, x0, y0, ink); + } + x0 += xs; + } + *dash_offset += dx; + } else { + for (i = 0; i < dx; i++) { + point8(im, x0, y0, ink); + x0 += xs; + } } } else if (dx > dy) { @@ -248,14 +301,29 @@ line8(Imaging im, int x0, int y0, int x1, int y1, int ink) { e = dy - dx; dx += dx; - for (i = 0; i < n; i++) { - point8(im, x0, y0, ink); - if (e >= 0) { - y0 += ys; - e -= dx; + if (dash != NULL) { + for (i = 0; i < n; i++) { + if (should_draw_dash(i, dash_offset, dash)) { + point8(im, x0, y0, ink); + } + if (e >= 0) { + y0 += ys; + e -= dx; + } + e += dy; + x0 += xs; + } + *dash_offset += n; + } else { + for (i = 0; i < n; i++) { + point8(im, x0, y0, ink); + if (e >= 0) { + y0 += ys; + e -= dx; + } + e += dy; + x0 += xs; } - e += dy; - x0 += xs; } } else { @@ -265,20 +333,44 @@ line8(Imaging im, int x0, int y0, int x1, int y1, int ink) { e = dx - dy; dy += dy; - for (i = 0; i < n; i++) { - point8(im, x0, y0, ink); - if (e >= 0) { - x0 += xs; - e -= dy; + if (dash != NULL) { + for (i = 0; i < n; i++) { + if (should_draw_dash(i, dash_offset, dash)) { + point8(im, x0, y0, ink); + } + if (e >= 0) { + x0 += xs; + e -= dy; + } + e += dx; + y0 += ys; + } + *dash_offset += n; + } else { + for (i = 0; i < n; i++) { + point8(im, x0, y0, ink); + if (e >= 0) { + x0 += xs; + e -= dy; + } + e += dx; + y0 += ys; } - e += dx; - y0 += ys; } } } static inline void -line32(Imaging im, int x0, int y0, int x1, int y1, int ink) { +line32( + Imaging im, + int x0, + int y0, + int x1, + int y1, + int ink, + PyObject *dash, + int *dash_offset +) { int i, n, e; int dx, dy; int xs, ys; @@ -301,16 +393,36 @@ line32(Imaging im, int x0, int y0, int x1, int y1, int ink) { if (dx == 0) { /* vertical */ - for (i = 0; i < dy; i++) { - point32(im, x0, y0, ink); - y0 += ys; + if (dash != NULL) { + for (i = 0; i < dy; i++) { + if (should_draw_dash(i, dash_offset, dash)) { + point32(im, x0, y0, ink); + } + y0 += ys; + } + *dash_offset += dy; + } else { + for (i = 0; i < dy; i++) { + point32(im, x0, y0, ink); + y0 += ys; + } } } else if (dy == 0) { /* horizontal */ - for (i = 0; i < dx; i++) { - point32(im, x0, y0, ink); - x0 += xs; + if (dash != NULL) { + for (i = 0; i < dx; i++) { + if (should_draw_dash(i, dash_offset, dash)) { + point32(im, x0, y0, ink); + } + x0 += xs; + } + *dash_offset += dx; + } else { + for (i = 0; i < dx; i++) { + point32(im, x0, y0, ink); + x0 += xs; + } } } else if (dx > dy) { @@ -320,14 +432,29 @@ line32(Imaging im, int x0, int y0, int x1, int y1, int ink) { e = dy - dx; dx += dx; - for (i = 0; i < n; i++) { - point32(im, x0, y0, ink); - if (e >= 0) { - y0 += ys; - e -= dx; + if (dash != NULL) { + for (i = 0; i < n; i++) { + if (should_draw_dash(i, dash_offset, dash)) { + point32(im, x0, y0, ink); + } + if (e >= 0) { + y0 += ys; + e -= dx; + } + e += dy; + x0 += xs; + } + *dash_offset += n; + } else { + for (i = 0; i < n; i++) { + point32(im, x0, y0, ink); + if (e >= 0) { + y0 += ys; + e -= dx; + } + e += dy; + x0 += xs; } - e += dy; - x0 += xs; } } else { @@ -337,20 +464,44 @@ line32(Imaging im, int x0, int y0, int x1, int y1, int ink) { e = dx - dy; dy += dy; - for (i = 0; i < n; i++) { - point32(im, x0, y0, ink); - if (e >= 0) { - x0 += xs; - e -= dy; + if (dash != NULL) { + for (i = 0; i < n; i++) { + if (should_draw_dash(i, dash_offset, dash)) { + point32(im, x0, y0, ink); + } + if (e >= 0) { + x0 += xs; + e -= dy; + } + e += dx; + y0 += ys; + } + *dash_offset += n; + } else { + for (i = 0; i < n; i++) { + point32(im, x0, y0, ink); + if (e >= 0) { + x0 += xs; + e -= dy; + } + e += dx; + y0 += ys; } - e += dx; - y0 += ys; } } } static inline void -line32rgba(Imaging im, int x0, int y0, int x1, int y1, int ink) { +line32rgba( + Imaging im, + int x0, + int y0, + int x1, + int y1, + int ink, + PyObject *dash, + int *dash_offset +) { int i, n, e; int dx, dy; int xs, ys; @@ -373,16 +524,36 @@ line32rgba(Imaging im, int x0, int y0, int x1, int y1, int ink) { if (dx == 0) { /* vertical */ - for (i = 0; i < dy; i++) { - point32rgba(im, x0, y0, ink); - y0 += ys; + if (dash != NULL) { + for (i = 0; i < dy; i++) { + if (should_draw_dash(i, dash_offset, dash)) { + point32rgba(im, x0, y0, ink); + } + y0 += ys; + } + *dash_offset += dy; + } else { + for (i = 0; i < dy; i++) { + point32rgba(im, x0, y0, ink); + y0 += ys; + } } } else if (dy == 0) { /* horizontal */ - for (i = 0; i < dx; i++) { - point32rgba(im, x0, y0, ink); - x0 += xs; + if (dash != NULL) { + for (i = 0; i < dx; i++) { + if (should_draw_dash(i, dash_offset, dash)) { + point32rgba(im, x0, y0, ink); + } + x0 += xs; + } + *dash_offset += dx; + } else { + for (i = 0; i < dx; i++) { + point32rgba(im, x0, y0, ink); + x0 += xs; + } } } else if (dx > dy) { @@ -392,14 +563,29 @@ line32rgba(Imaging im, int x0, int y0, int x1, int y1, int ink) { e = dy - dx; dx += dx; - for (i = 0; i < n; i++) { - point32rgba(im, x0, y0, ink); - if (e >= 0) { - y0 += ys; - e -= dx; + if (dash != NULL) { + for (i = 0; i < n; i++) { + if (should_draw_dash(i, dash_offset, dash)) { + point32rgba(im, x0, y0, ink); + } + if (e >= 0) { + y0 += ys; + e -= dx; + } + e += dy; + x0 += xs; + } + *dash_offset += n; + } else { + for (i = 0; i < n; i++) { + point32rgba(im, x0, y0, ink); + if (e >= 0) { + y0 += ys; + e -= dx; + } + e += dy; + x0 += xs; } - e += dy; - x0 += xs; } } else { @@ -409,14 +595,29 @@ line32rgba(Imaging im, int x0, int y0, int x1, int y1, int ink) { e = dx - dy; dy += dy; - for (i = 0; i < n; i++) { - point32rgba(im, x0, y0, ink); - if (e >= 0) { - x0 += xs; - e -= dy; + if (dash != NULL) { + for (i = 0; i < n; i++) { + if (should_draw_dash(i, dash_offset, dash)) { + point32rgba(im, x0, y0, ink); + } + if (e >= 0) { + x0 += xs; + e -= dy; + } + e += dx; + y0 += ys; + } + *dash_offset += n; + } else { + for (i = 0; i < n; i++) { + point32rgba(im, x0, y0, ink); + if (e >= 0) { + x0 += xs; + e -= dy; + } + e += dx; + y0 += ys; } - e += dx; - y0 += ys; } } } @@ -664,7 +865,16 @@ add_edge(Edge *e, int x0, int y0, int x1, int y1) { typedef struct { void (*point)(Imaging im, int x, int y, int ink); void (*hline)(Imaging im, int x0, int y0, int x1, int ink, Imaging mask); - void (*line)(Imaging im, int x0, int y0, int x1, int y1, int ink); + void (*line)( + Imaging im, + int x0, + int y0, + int x1, + int y1, + int ink, + PyObject *dash, + int *dash_offset + ); } DRAW; DRAW draw8 = {point8, hline8, line8}; @@ -701,13 +911,23 @@ ImagingDrawPoint(Imaging im, int x0, int y0, const void *ink_, int op) { } int -ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink_, int op) { +ImagingDrawLine( + Imaging im, + int x0, + int y0, + int x1, + int y1, + const void *ink_, + int op, + PyObject *dash, + int *dash_offset +) { DRAW *draw; INT32 ink; DRAWINIT(); - draw->line(im, x0, y0, x1, y1, ink); + draw->line(im, x0, y0, x1, y1, ink, dash, dash_offset); return 0; } @@ -816,8 +1036,8 @@ ImagingDrawRectangle( for (i = 0; i < width; i++) { draw->hline(im, x0, y0 + i, x1, ink, NULL); draw->hline(im, x0, y1 - i, x1, ink, NULL); - draw->line(im, x1 - i, y0 + width, x1 - i, y1 - width + 1, ink); - draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink); + draw->line(im, x1 - i, y0 + width, x1 - i, y1 - width + 1, ink, NULL, NULL); + draw->line(im, x0 + i, y0 + width, x0 + i, y1 - width + 1, ink, NULL, NULL); } } @@ -833,7 +1053,8 @@ ImagingDrawPolygon( int fill, int width, int op, - Imaging mask + Imaging mask, + PyObject *dash ) { int i, n, x0, y0, x1, y1; DRAW *draw; @@ -883,12 +1104,22 @@ ImagingDrawPolygon( } else { /* Outline */ if (width == 1) { + int dash_offset = 0; for (i = 0; i < count - 1; i++) { draw->line( - im, xy[i * 2], xy[i * 2 + 1], xy[i * 2 + 2], xy[i * 2 + 3], ink + im, + xy[i * 2], + xy[i * 2 + 1], + xy[i * 2 + 2], + xy[i * 2 + 3], + ink, + dash, + &dash_offset ); } - draw->line(im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink); + draw->line( + im, xy[i * 2], xy[i * 2 + 1], xy[0], xy[1], ink, dash, &dash_offset + ); } else { for (i = 0; i < count - 1; i++) { ImagingDrawWideLine( diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 472bda5d0fd..a784c1d3a58 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -498,7 +498,17 @@ ImagingDrawEllipse( int op ); extern int -ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void *ink, int op); +ImagingDrawLine( + Imaging im, + int x0, + int y0, + int x1, + int y1, + const void *ink, + int op, + PyObject *dash, + int *dash_offset +); extern int ImagingDrawWideLine( Imaging im, @@ -536,7 +546,8 @@ ImagingDrawPolygon( int fill, int width, int op, - Imaging mask + Imaging mask, + PyObject *dash ); extern int ImagingDrawRectangle(