diff --git a/Tests/images/imagedraw_rounded_rectangle.png b/Tests/images/imagedraw_rounded_rectangle.png new file mode 100644 index 00000000000..2e815f4ada2 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle.png differ diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index 7b1faadb795..0a2c7937da2 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -706,6 +706,34 @@ def test_rectangle_translucent_outline(): ) +@pytest.mark.parametrize( + "xy", + [(10, 20, 190, 180), ([10, 20], [190, 180]), ((10, 20), (190, 180))], +) +def test_rounded_rectangle(xy): + # Arrange + im = Image.new("RGB", (200, 200)) + draw = ImageDraw.Draw(im) + + # Act + draw.rounded_rectangle(xy, 30, fill="red", outline="green", width=5) + + # Assert + assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png") + + +def test_rounded_rectangle_zero_radius(): + # Arrange + im = Image.new("RGB", (W, H)) + draw = ImageDraw.Draw(im) + + # Act + draw.rounded_rectangle(BBOX1, 0, fill="blue", outline="green", width=5) + + # Assert + assert_image_equal_tofile(im, "Tests/images/imagedraw_rectangle_width_fill.png") + + def test_floodfill(): red = ImageColor.getrgb("red") diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index c780d714e8c..37fb5f72611 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -285,6 +285,20 @@ Methods .. versionadded:: 5.3.0 +.. py:method:: ImageDraw.rounded_rectangle(xy, radius=0, fill=None, outline=None, width=1) + + Draws a rounded rectangle. + + :param xy: Two points to define the bounding box. Sequence of either + ``[(x0, y0), (x1, y1)]`` or ``[x0, y0, x1, y1]``. The second point + is just outside the drawn rectangle. + :param radius: Radius of the corners. + :param outline: Color to use for the outline. + :param fill: Color to use for the fill. + :param width: The line width, in pixels. + + .. versionadded:: 8.2.0 + .. py:method:: ImageDraw.shape(shape, fill=None, outline=None) .. warning:: This method is experimental. diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index b823be9a2aa..41ef1194e32 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -257,6 +257,51 @@ def rectangle(self, xy, fill=None, outline=None, width=1): if ink is not None and ink != fill and width != 0: self.draw.draw_rectangle(xy, ink, 0, width) + def rounded_rectangle(self, xy, radius=0, fill=None, outline=None, width=1): + """Draw a rounded rectangle.""" + if isinstance(xy[0], (list, tuple)): + (x0, y0), (x1, y1) = xy + else: + x0, y0, x1, y1 = xy + + # Do not allow the diameter to be greater than the width or height + d = min(radius * 2, x1 - x0, y1 - y0) + if d == 0: + return self.rectangle(xy, fill, outline, width) + + ink, fill = self._getink(outline, fill) + if fill is not None: + self.draw.draw_pieslice((x1 - d, y0, x1, y0 + d), 270, 360, fill, 1) + self.draw.draw_pieslice((x1 - d, y1 - d, x1, y1), 0, 90, fill, 1) + self.draw.draw_pieslice((x0, y1 - d, x0 + d, y1), 90, 180, fill, 1) + self.draw.draw_pieslice((x0, y0, x0 + d, y0 + d), 180, 270, fill, 1) + + self.draw.draw_rectangle((x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y1), fill, 1) + self.draw.draw_rectangle( + (x0, y0 + d / 2 + 1, x0 + d / 2, y1 - d / 2 - 1), fill, 1 + ) + self.draw.draw_rectangle( + (x1 - d / 2, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), fill, 1 + ) + if ink is not None and ink != fill and width != 0: + self.draw.draw_arc((x1 - d, y0, x1, y0 + d), 270, 360, ink, width) + self.draw.draw_arc((x1 - d, y1 - d, x1, y1), 0, 90, ink, width) + self.draw.draw_arc((x0, y1 - d, x0 + d, y1), 90, 180, ink, width) + self.draw.draw_arc((x0, y0, x0 + d, y0 + d), 180, 270, ink, width) + + self.draw.draw_rectangle( + (x1 - width + 1, y0 + d / 2 + 1, x1, y1 - d / 2 - 1), ink, 1 + ) + self.draw.draw_rectangle( + (x0 + d / 2 + 1, y1 - width + 1, x1 - d / 2 - 1, y1), ink, 1 + ) + self.draw.draw_rectangle( + (x0, y0 + d / 2 + 1, x0 + width - 1, y1 - d / 2 - 1), ink, 1 + ) + self.draw.draw_rectangle( + (x0 + d / 2 + 1, y0, x1 - d / 2 - 1, y0 + width - 1), ink, 1 + ) + def _multiline_check(self, text): """Draw text.""" split_character = "\n" if isinstance(text, str) else b"\n"