diff --git a/Tests/images/imagedraw_arc_width.png b/Tests/images/imagedraw_arc_width.png new file mode 100644 index 000000000..ff3f1f0b2 Binary files /dev/null and b/Tests/images/imagedraw_arc_width.png differ diff --git a/Tests/images/imagedraw_arc_width_fill.png b/Tests/images/imagedraw_arc_width_fill.png new file mode 100644 index 000000000..9572a6059 Binary files /dev/null and b/Tests/images/imagedraw_arc_width_fill.png differ diff --git a/Tests/images/imagedraw_chord_width.png b/Tests/images/imagedraw_chord_width.png new file mode 100644 index 000000000..33a59b487 Binary files /dev/null and b/Tests/images/imagedraw_chord_width.png differ diff --git a/Tests/images/imagedraw_chord_width_fill.png b/Tests/images/imagedraw_chord_width_fill.png new file mode 100644 index 000000000..809c3ea1c Binary files /dev/null and b/Tests/images/imagedraw_chord_width_fill.png differ diff --git a/Tests/images/imagedraw_ellipse_width.png b/Tests/images/imagedraw_ellipse_width.png new file mode 100644 index 000000000..ec0ca6731 Binary files /dev/null and b/Tests/images/imagedraw_ellipse_width.png differ diff --git a/Tests/images/imagedraw_ellipse_width_fill.png b/Tests/images/imagedraw_ellipse_width_fill.png new file mode 100644 index 000000000..9b7be6029 Binary files /dev/null and b/Tests/images/imagedraw_ellipse_width_fill.png differ diff --git a/Tests/images/imagedraw_pieslice_width.png b/Tests/images/imagedraw_pieslice_width.png new file mode 100644 index 000000000..a83c4ab7b Binary files /dev/null and b/Tests/images/imagedraw_pieslice_width.png differ diff --git a/Tests/images/imagedraw_pieslice_width_fill.png b/Tests/images/imagedraw_pieslice_width_fill.png new file mode 100644 index 000000000..166decb01 Binary files /dev/null and b/Tests/images/imagedraw_pieslice_width_fill.png differ diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index cd4eb2780..184aadafe 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -102,6 +102,30 @@ class TestImageDraw(PillowTestCase): self.assert_image_similar( im, Image.open("Tests/images/imagedraw_arc_no_loops.png"), 1) + def test_arc_width(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_arc_width.png" + + # Act + draw.arc(BBOX1, 10, 260, width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + + def test_arc_width_fill(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_arc_width_fill.png" + + # Act + draw.arc(BBOX1, 10, 260, fill="yellow", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + def test_bitmap(self): # Arrange small = Image.open("Tests/images/pil123rgba.png").resize((50, 50)) @@ -137,6 +161,30 @@ class TestImageDraw(PillowTestCase): self.helper_chord(mode, BBOX2, 0, 180) self.helper_chord(mode, BBOX2, 0.5, 180.4) + def test_chord_width(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_chord_width.png" + + # Act + draw.chord(BBOX1, 10, 260, outline="yellow", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + + def test_chord_width_fill(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_chord_width_fill.png" + + # Act + draw.chord(BBOX1, 10, 260, fill="red", outline="yellow", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + def helper_ellipse(self, mode, bbox): # Arrange im = Image.new(mode, (W, H)) @@ -179,6 +227,30 @@ class TestImageDraw(PillowTestCase): draw.ellipse(bbox, fill="green", outline="blue") self.assert_image_equal(im, im.transpose(Image.FLIP_LEFT_RIGHT)) + def test_ellipse_width(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_ellipse_width.png" + + # Act + draw.ellipse(BBOX1, outline="blue", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + + def test_ellipse_width_fill(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_ellipse_width_fill.png" + + # Act + draw.ellipse(BBOX1, fill="green", outline="blue", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + def helper_line(self, points): # Arrange im = Image.new("RGB", (W, H)) @@ -259,6 +331,30 @@ class TestImageDraw(PillowTestCase): self.helper_pieslice(BBOX2, -90, 45) self.helper_pieslice(BBOX2, -90.5, 45.4) + def test_pieslice_width(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_pieslice_width.png" + + # Act + draw.pieslice(BBOX1, 10, 260, outline="blue", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + + def test_pieslice_width_fill(self): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + expected = "Tests/images/imagedraw_pieslice_width_fill.png" + + # Act + draw.pieslice(BBOX1, 10, 260, fill="white", outline="blue", width=5) + + # Assert + self.assert_image_similar(im, Image.open(expected), 1) + def helper_point(self, points): # Arrange im = Image.new("RGB", (W, H)) diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 7b28aafb0..014075a0b 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -126,7 +126,7 @@ Methods :returns: An image font. -.. py:method:: PIL.ImageDraw.ImageDraw.arc(xy, start, end, fill=None) +.. py:method:: PIL.ImageDraw.ImageDraw.arc(xy, start, end, fill=None, width=0) Draws an arc (a portion of a circle outline) between the start and end angles, inside the given bounding box. @@ -138,6 +138,9 @@ Methods 3 o'clock, increasing clockwise. :param end: Ending angle, in degrees. :param fill: Color to use for the arc. + :param width: The line width, in pixels. + + .. versionadded:: 5.2.0 .. py:method:: PIL.ImageDraw.ImageDraw.bitmap(xy, bitmap, fill=None) @@ -150,7 +153,7 @@ Methods To paste pixel data into an image, use the :py:meth:`~PIL.Image.Image.paste` method on the image itself. -.. py:method:: PIL.ImageDraw.ImageDraw.chord(xy, start, end, fill=None, outline=None) +.. py:method:: PIL.ImageDraw.ImageDraw.chord(xy, start, end, fill=None, outline=None, width=0) Same as :py:meth:`~PIL.ImageDraw.ImageDraw.arc`, but connects the end points with a straight line. @@ -160,8 +163,11 @@ Methods where ``x1 >= x0`` and ``y1 >= y0``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. + :param width: The line width, in pixels. -.. py:method:: PIL.ImageDraw.ImageDraw.ellipse(xy, fill=None, outline=None) + .. versionadded:: 5.2.0 + +.. py:method:: PIL.ImageDraw.ImageDraw.ellipse(xy, fill=None, outline=None, width=0) Draws an ellipse inside the given bounding box. @@ -170,6 +176,9 @@ Methods where ``x1 >= x0`` and ``y1 >= y0``. :param outline: Color to use for the outline. :param fill: Color to use for the fill. + :param width: The line width, in pixels. + + .. versionadded:: 5.2.0 .. py:method:: PIL.ImageDraw.ImageDraw.line(xy, fill=None, width=0) @@ -185,7 +194,7 @@ Methods .. note:: This option was broken until version 1.1.6. -.. py:method:: PIL.ImageDraw.ImageDraw.pieslice(xy, start, end, fill=None, outline=None) +.. py:method:: PIL.ImageDraw.ImageDraw.pieslice(xy, start, end, fill=None, outline=None, width=0) Same as arc, but also draws straight lines between the end points and the center of the bounding box. @@ -198,6 +207,9 @@ Methods :param end: Ending angle, in degrees. :param fill: Color to use for the fill. :param outline: Color to use for the outline. + :param width: The outer line width, in pixels. + + .. versionadded:: 5.2.0 .. py:method:: PIL.ImageDraw.ImageDraw.point(xy, fill=None) diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 1bbb6e6a6..23a666e76 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -118,11 +118,11 @@ class ImageDraw(object): fill = self.draw.draw_ink(fill, self.mode) return ink, fill - def arc(self, xy, start, end, fill=None): + def arc(self, xy, start, end, fill=None, width=0): """Draw an arc.""" ink, fill = self._getink(fill) if ink is not None: - self.draw.draw_arc(xy, start, end, ink) + self.draw.draw_arc(xy, start, end, ink, width) def bitmap(self, xy, bitmap, fill=None): """Draw a bitmap.""" @@ -133,21 +133,21 @@ class ImageDraw(object): if ink is not None: self.draw.draw_bitmap(xy, bitmap.im, ink) - def chord(self, xy, start, end, fill=None, outline=None): + def chord(self, xy, start, end, fill=None, outline=None, width=0): """Draw a chord.""" ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_chord(xy, start, end, fill, 1) if ink is not None: - self.draw.draw_chord(xy, start, end, ink, 0) + self.draw.draw_chord(xy, start, end, ink, 0, width) - def ellipse(self, xy, fill=None, outline=None): + def ellipse(self, xy, fill=None, outline=None, width=0): """Draw an ellipse.""" ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_ellipse(xy, fill, 1) if ink is not None: - self.draw.draw_ellipse(xy, ink, 0) + self.draw.draw_ellipse(xy, ink, 0, width) def line(self, xy, fill=None, width=0): """Draw a line, or a connected sequence of line segments.""" @@ -164,13 +164,13 @@ class ImageDraw(object): if ink is not None: self.draw.draw_outline(shape, ink, 0) - def pieslice(self, xy, start, end, fill=None, outline=None): + def pieslice(self, xy, start, end, fill=None, outline=None, width=0): """Draw a pieslice.""" ink, fill = self._getink(outline, fill) if fill is not None: self.draw.draw_pieslice(xy, start, end, fill, 1) if ink is not None: - self.draw.draw_pieslice(xy, start, end, ink, 0) + self.draw.draw_pieslice(xy, start, end, ink, 0, width) def point(self, xy, fill=None): """Draw one or more individual pixels.""" diff --git a/src/_imaging.c b/src/_imaging.c index 3cced9a0f..f1116a36c 100644 --- a/src/_imaging.c +++ b/src/_imaging.c @@ -2561,9 +2561,10 @@ _draw_arc(ImagingDrawObject* self, PyObject* args) PyObject* data; int ink; + int width = 0; float start, end; int op = 0; - if (!PyArg_ParseTuple(args, "Offi|i", &data, &start, &end, &ink)) + if (!PyArg_ParseTuple(args, "Offi|ii", &data, &start, &end, &ink, &width)) return NULL; n = PyPath_Flatten(data, &xy); @@ -2577,7 +2578,7 @@ _draw_arc(ImagingDrawObject* self, PyObject* args) n = ImagingDrawArc(self->image->image, (int) xy[0], (int) xy[1], (int) xy[2], (int) xy[3], - start, end, &ink, op + start, end, &ink, width, op ); free(xy); @@ -2633,9 +2634,10 @@ _draw_chord(ImagingDrawObject* self, PyObject* args) PyObject* data; int ink, fill; + int width = 0; float start, end; - if (!PyArg_ParseTuple(args, "Offii", - &data, &start, &end, &ink, &fill)) + if (!PyArg_ParseTuple(args, "Offii|i", + &data, &start, &end, &ink, &fill, &width)) return NULL; n = PyPath_Flatten(data, &xy); @@ -2649,7 +2651,7 @@ _draw_chord(ImagingDrawObject* self, PyObject* args) n = ImagingDrawChord(self->image->image, (int) xy[0], (int) xy[1], (int) xy[2], (int) xy[3], - start, end, &ink, fill, self->blend + start, end, &ink, fill, width, self->blend ); free(xy); @@ -2670,7 +2672,8 @@ _draw_ellipse(ImagingDrawObject* self, PyObject* args) PyObject* data; int ink; int fill = 0; - if (!PyArg_ParseTuple(args, "Oi|i", &data, &ink, &fill)) + int width = 0; + if (!PyArg_ParseTuple(args, "Oi|ii", &data, &ink, &fill, &width)) return NULL; n = PyPath_Flatten(data, &xy); @@ -2684,7 +2687,7 @@ _draw_ellipse(ImagingDrawObject* self, PyObject* args) n = ImagingDrawEllipse(self->image->image, (int) xy[0], (int) xy[1], (int) xy[2], (int) xy[3], - &ink, fill, self->blend + &ink, fill, width, self->blend ); free(xy); @@ -2850,8 +2853,9 @@ _draw_pieslice(ImagingDrawObject* self, PyObject* args) PyObject* data; int ink, fill; + int width = 0; float start, end; - if (!PyArg_ParseTuple(args, "Offii", &data, &start, &end, &ink, &fill)) + if (!PyArg_ParseTuple(args, "Offii|i", &data, &start, &end, &ink, &fill, &width)) return NULL; n = PyPath_Flatten(data, &xy); @@ -2865,7 +2869,7 @@ _draw_pieslice(ImagingDrawObject* self, PyObject* args) n = ImagingDrawPieslice(self->image->image, (int) xy[0], (int) xy[1], (int) xy[2], (int) xy[3], - start, end, &ink, fill, self->blend + start, end, &ink, fill, width, self->blend ); free(xy); diff --git a/src/libImaging/Draw.c b/src/libImaging/Draw.c index fee1cac63..37969af70 100644 --- a/src/libImaging/Draw.c +++ b/src/libImaging/Draw.c @@ -762,9 +762,10 @@ ellipsePoint(int cx, int cy, int w, int h, static int ellipse(Imaging im, int x0, int y0, int x1, int y1, float start, float end, const void* ink_, int fill, - int mode, int op) + int width, int mode, int op) { float i; + int j; int n; int cx, cy; int w, h; @@ -774,120 +775,131 @@ ellipse(Imaging im, int x0, int y0, int x1, int y1, DRAW* draw; INT32 ink; - w = x1 - x0; - h = y1 - y0; - if (w < 0 || h < 0) - return 0; - DRAWINIT(); - cx = (x0 + x1) / 2; - cy = (y0 + y1) / 2; - - while (end < start) - end += 360; - - if (end - start > 360) { - /* no need to go in loops */ - end = start + 361; + if (width == 0) { + width = 1; } - if (mode != ARC && fill) { + for (j = 0; j < width; j++) { - /* Build edge list */ - /* malloc check UNDONE, FLOAT? */ - Edge* e = calloc((end - start + 3), sizeof(Edge)); - if (!e) { - ImagingError_MemoryError(); - return -1; + w = x1 - x0; + h = y1 - y0; + if (w < 0 || h < 0) + return 0; + + cx = (x0 + x1) / 2; + cy = (y0 + y1) / 2; + + while (end < start) + end += 360; + + if (end - start > 360) { + /* no need to go in loops */ + end = start + 361; } - n = 0; + if (mode != ARC && fill) { - for (i = start; i < end+1; i++) { - if (i > end) { - i = end; + /* Build edge list */ + /* malloc check UNDONE, FLOAT? */ + Edge* e = calloc((end - start + 3), sizeof(Edge)); + if (!e) { + ImagingError_MemoryError(); + return -1; } - ellipsePoint(cx, cy, w, h, i, &x, &y); - if (i != start) - add_edge(&e[n++], lx, ly, x, y); - else - sx = x, sy = y; - lx = x, ly = y; - } + n = 0; - if (n > 0) { - /* close and draw polygon */ - if (mode == PIESLICE) { - if (x != cx || y != cy) { - add_edge(&e[n++], x, y, cx, cy); - add_edge(&e[n++], cx, cy, sx, sy); + for (i = start; i < end+1; i++) { + if (i > end) { + i = end; } - } else { - if (x != sx || y != sy) - add_edge(&e[n++], x, y, sx, sy); + ellipsePoint(cx, cy, w, h, i, &x, &y); + if (i != start) + add_edge(&e[n++], lx, ly, x, y); + else + sx = x, sy = y; + lx = x, ly = y; } - draw->polygon(im, n, e, ink, 0); - } - free(e); - - } else { - - for (i = start; i < end+1; i++) { - if (i > end) { - i = end; - } - ellipsePoint(cx, cy, w, h, i, &x, &y); - if (i != start) - draw->line(im, lx, ly, x, y, ink); - else - sx = x, sy = y; - lx = x, ly = y; - } - - if (i != start) { - if (mode == PIESLICE) { - if (x != cx || y != cy) { - draw->line(im, x, y, cx, cy, ink); - draw->line(im, cx, cy, sx, sy, ink); + if (n > 0) { + /* close and draw polygon */ + if (mode == PIESLICE) { + if (x != cx || y != cy) { + add_edge(&e[n++], x, y, cx, cy); + add_edge(&e[n++], cx, cy, sx, sy); + } + } else { + if (x != sx || y != sy) + add_edge(&e[n++], x, y, sx, sy); + } + draw->polygon(im, n, e, ink, 0); + } + + free(e); + + } else { + + for (i = start; i < end+1; i++) { + if (i > end) { + i = end; + } + ellipsePoint(cx, cy, w, h, i, &x, &y); + if (i != start) + draw->line(im, lx, ly, x, y, ink); + else + sx = x, sy = y; + lx = x, ly = y; + } + + if (i != start) { + if (mode == PIESLICE) { + if (x != cx || y != cy) { + draw->line(im, x, y, cx, cy, ink); + draw->line(im, cx, cy, sx, sy, ink); + } + } else if (mode == CHORD) { + if (x != sx || y != sy) + draw->line(im, x, y, sx, sy, ink); } - } else if (mode == CHORD) { - if (x != sx || y != sy) - draw->line(im, x, y, sx, sy, ink); } } + x0++; + y0++; + x1--; + y1--; } - return 0; } int ImagingDrawArc(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink, int op) + float start, float end, const void* ink, int width, int op) { - return ellipse(im, x0, y0, x1, y1, start, end, ink, 0, ARC, op); + return ellipse(im, x0, y0, x1, y1, start, end, ink, 0, width, ARC, op); } int ImagingDrawChord(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink, int fill, int op) + float start, float end, const void* ink, int fill, + int width, int op) { - return ellipse(im, x0, y0, x1, y1, start, end, ink, fill, CHORD, op); + return ellipse(im, x0, y0, x1, y1, start, end, ink, fill, width, CHORD, op); } int ImagingDrawEllipse(Imaging im, int x0, int y0, int x1, int y1, - const void* ink, int fill, int op) + const void* ink, int fill, int width, int op) { - return ellipse(im, x0, y0, x1, y1, 0, 360, ink, fill, CHORD, op); + return ellipse(im, x0, y0, x1, y1, 0, 360, ink, fill, width, CHORD, op); } int ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink, int fill, int op) + float start, float end, const void* ink, int fill, + int width, int op) { - return ellipse(im, x0, y0, x1, y1, start, end, ink, fill, PIESLICE, op); + return ellipse(im, x0, y0, x1, y1, start, end, ink, fill, width, PIESLICE, op); } /* -------------------------------------------------------------------- */ diff --git a/src/libImaging/Imaging.h b/src/libImaging/Imaging.h index 4588602b1..e705e0a60 100644 --- a/src/libImaging/Imaging.h +++ b/src/libImaging/Imaging.h @@ -349,21 +349,22 @@ extern void ImagingCrack(Imaging im, int x0, int y0); /* Graphics */ extern int ImagingDrawArc(Imaging im, int x0, int y0, int x1, int y1, - float start, float end, const void* ink, int op); + float start, float end, const void* ink, int width, + int op); extern int ImagingDrawBitmap(Imaging im, int x0, int y0, Imaging bitmap, const void* ink, int op); extern int ImagingDrawChord(Imaging im, int x0, int y0, int x1, int y1, float start, float end, const void* ink, int fill, - int op); + int width, int op); extern int ImagingDrawEllipse(Imaging im, int x0, int y0, int x1, int y1, - const void* ink, int fill, int op); + const void* ink, int fill, int width, int op); extern int ImagingDrawLine(Imaging im, int x0, int y0, int x1, int y1, const void* ink, int op); extern int ImagingDrawWideLine(Imaging im, int x0, int y0, int x1, int y1, const void* ink, int width, int op); extern int ImagingDrawPieslice(Imaging im, int x0, int y0, int x1, int y1, float start, float end, const void* ink, int fill, - int op); + int width, int op); extern int ImagingDrawPoint(Imaging im, int x, int y, const void* ink, int op); extern int ImagingDrawPolygon(Imaging im, int points, int *xy, const void* ink, int fill, int op);