diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_nnnn.png b/Tests/images/imagedraw_rounded_rectangle_corners_nnnn.png new file mode 100644 index 000000000..3e79e21ae Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_nnnn.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_nnny.png b/Tests/images/imagedraw_rounded_rectangle_corners_nnny.png new file mode 100644 index 000000000..7fa09a3c0 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_nnny.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_nnyn.png b/Tests/images/imagedraw_rounded_rectangle_corners_nnyn.png new file mode 100644 index 000000000..d825ad263 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_nnyn.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_nnyy.png b/Tests/images/imagedraw_rounded_rectangle_corners_nnyy.png new file mode 100644 index 000000000..c19da698e Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_nnyy.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_nynn.png b/Tests/images/imagedraw_rounded_rectangle_corners_nynn.png new file mode 100644 index 000000000..f3e95d487 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_nynn.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_nyny.png b/Tests/images/imagedraw_rounded_rectangle_corners_nyny.png new file mode 100644 index 000000000..274d27984 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_nyny.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_nyyn.png b/Tests/images/imagedraw_rounded_rectangle_corners_nyyn.png new file mode 100644 index 000000000..c5f40bfdb Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_nyyn.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_nyyy.png b/Tests/images/imagedraw_rounded_rectangle_corners_nyyy.png new file mode 100644 index 000000000..01bfd1750 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_nyyy.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_ynnn.png b/Tests/images/imagedraw_rounded_rectangle_corners_ynnn.png new file mode 100644 index 000000000..efd27be4f Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_ynnn.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_ynny.png b/Tests/images/imagedraw_rounded_rectangle_corners_ynny.png new file mode 100644 index 000000000..d3acd01ab Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_ynny.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_ynyn.png b/Tests/images/imagedraw_rounded_rectangle_corners_ynyn.png new file mode 100644 index 000000000..55ddbc033 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_ynyn.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_ynyy.png b/Tests/images/imagedraw_rounded_rectangle_corners_ynyy.png new file mode 100644 index 000000000..c000b26e9 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_ynyy.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_yynn.png b/Tests/images/imagedraw_rounded_rectangle_corners_yynn.png new file mode 100644 index 000000000..7056b4fd9 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_yynn.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_yyny.png b/Tests/images/imagedraw_rounded_rectangle_corners_yyny.png new file mode 100644 index 000000000..5eca030b9 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_yyny.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_yyyn.png b/Tests/images/imagedraw_rounded_rectangle_corners_yyyn.png new file mode 100644 index 000000000..7f1f00344 Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_yyyn.png differ diff --git a/Tests/images/imagedraw_rounded_rectangle_corners_yyyy.png b/Tests/images/imagedraw_rounded_rectangle_corners_yyyy.png new file mode 100644 index 000000000..2e815f4ad Binary files /dev/null and b/Tests/images/imagedraw_rounded_rectangle_corners_yyyy.png differ diff --git a/Tests/test_imagedraw.py b/Tests/test_imagedraw.py index d4723c924..ea0711ad5 100644 --- a/Tests/test_imagedraw.py +++ b/Tests/test_imagedraw.py @@ -735,6 +735,36 @@ def test_rounded_rectangle(xy): assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png") +@pytest.mark.parametrize("top_left", (True, False)) +@pytest.mark.parametrize("top_right", (True, False)) +@pytest.mark.parametrize("bottom_right", (True, False)) +@pytest.mark.parametrize("bottom_left", (True, False)) +def test_rounded_rectangle_corners(top_left, top_right, bottom_right, bottom_left): + corners = (top_left, top_right, bottom_right, bottom_left) + + # Arrange + im = Image.new("RGB", (200, 200)) + draw = ImageDraw.Draw(im) + + # Act + draw.rounded_rectangle( + (10, 20, 190, 180), 30, fill="red", outline="green", width=5, corners=corners + ) + + # Assert + suffix = "".join( + ( + ("y" if top_left else "n"), + ("y" if top_right else "n"), + ("y" if bottom_right else "n"), + ("y" if bottom_left else "n"), + ) + ) + assert_image_equal_tofile( + im, "Tests/images/imagedraw_rounded_rectangle_corners_" + suffix + ".png" + ) + + @pytest.mark.parametrize( "xy, radius, type", [ diff --git a/docs/reference/ImageDraw.rst b/docs/reference/ImageDraw.rst index 9aa26916a..14729e2f5 100644 --- a/docs/reference/ImageDraw.rst +++ b/docs/reference/ImageDraw.rst @@ -337,6 +337,8 @@ Methods :param outline: Color to use for the outline. :param fill: Color to use for the fill. :param width: The line width, in pixels. + :param corners: A tuple of whether to round each corner, + `(top_left, top_right, bottom_right, bottom_left)`. .. versionadded:: 8.2.0 diff --git a/src/PIL/ImageDraw.py b/src/PIL/ImageDraw.py index 163828d31..dc77c06ea 100644 --- a/src/PIL/ImageDraw.py +++ b/src/PIL/ImageDraw.py @@ -295,29 +295,37 @@ class ImageDraw: 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): + def rounded_rectangle( + self, xy, radius=0, fill=None, outline=None, width=1, corners=None + ): """Draw a rounded rectangle.""" if isinstance(xy[0], (list, tuple)): (x0, y0), (x1, y1) = xy else: x0, y0, x1, y1 = xy + if corners is None: + corners = (True, True, True, True) d = radius * 2 - full_x = d >= x1 - x0 - if full_x: - # The two left and two right corners are joined - d = x1 - x0 - full_y = d >= y1 - y0 - if full_y: - # The two top and two bottom corners are joined - d = y1 - y0 - if full_x and full_y: - # If all corners are joined, that is a circle - return self.ellipse(xy, fill, outline, width) + full_x, full_y = False, False + if all(corners): + full_x = d >= x1 - x0 + if full_x: + # The two left and two right corners are joined + d = x1 - x0 + full_y = d >= y1 - y0 + if full_y: + # The two top and two bottom corners are joined + d = y1 - y0 + if full_x and full_y: + # If all corners are joined, that is a circle + return self.ellipse(xy, fill, outline, width) - if d == 0: - # If the corners have no curve, that is a rectangle + if d == 0 or not any(corners): + # If the corners have no curve, + # or there are no corners, + # that is a rectangle return self.rectangle(xy, fill, outline, width) r = d // 2 @@ -338,12 +346,17 @@ class ImageDraw: ) else: # Draw four separate corners - parts = ( - ((x1 - d, y0, x1, y0 + d), 270, 360), - ((x1 - d, y1 - d, x1, y1), 0, 90), - ((x0, y1 - d, x0 + d, y1), 90, 180), - ((x0, y0, x0 + d, y0 + d), 180, 270), - ) + parts = [] + for i, part in enumerate( + ( + ((x0, y0, x0 + d, y0 + d), 180, 270), + ((x1 - d, y0, x1, y0 + d), 270, 360), + ((x1 - d, y1 - d, x1, y1), 0, 90), + ((x0, y1 - d, x0 + d, y1), 90, 180), + ) + ): + if corners[i]: + parts.append(part) for part in parts: if pieslice: self.draw.draw_pieslice(*(part + (fill, 1))) @@ -358,25 +371,50 @@ class ImageDraw: else: self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1) if not full_x and not full_y: - self.draw.draw_rectangle((x0, y0 + r + 1, x0 + r, y1 - r - 1), fill, 1) - self.draw.draw_rectangle((x1 - r, y0 + r + 1, x1, y1 - r - 1), fill, 1) + left = [x0, y0, x0 + r, y1] + if corners[0]: + left[1] += r + 1 + if corners[3]: + left[3] -= r + 1 + self.draw.draw_rectangle(left, fill, 1) + + right = [x1 - r, y0, x1, y1] + if corners[1]: + right[1] += r + 1 + if corners[2]: + right[3] -= r + 1 + self.draw.draw_rectangle(right, fill, 1) if ink is not None and ink != fill and width != 0: draw_corners(False) if not full_x: - self.draw.draw_rectangle( - (x0 + r + 1, y0, x1 - r - 1, y0 + width - 1), ink, 1 - ) - self.draw.draw_rectangle( - (x0 + r + 1, y1 - width + 1, x1 - r - 1, y1), ink, 1 - ) + top = [x0, y0, x1, y0 + width - 1] + if corners[0]: + top[0] += r + 1 + if corners[1]: + top[2] -= r + 1 + self.draw.draw_rectangle(top, ink, 1) + + bottom = [x0, y1 - width + 1, x1, y1] + if corners[3]: + bottom[0] += r + 1 + if corners[2]: + bottom[2] -= r + 1 + self.draw.draw_rectangle(bottom, ink, 1) if not full_y: - self.draw.draw_rectangle( - (x0, y0 + r + 1, x0 + width - 1, y1 - r - 1), ink, 1 - ) - self.draw.draw_rectangle( - (x1 - width + 1, y0 + r + 1, x1, y1 - r - 1), ink, 1 - ) + left = [x0, y0, x0 + width - 1, y1] + if corners[0]: + left[1] += r + 1 + if corners[3]: + left[3] -= r + 1 + self.draw.draw_rectangle(left, ink, 1) + + right = [x1 - width + 1, y0, x1, y1] + if corners[1]: + right[1] += r + 1 + if corners[2]: + right[3] -= r + 1 + self.draw.draw_rectangle(right, ink, 1) def _multiline_check(self, text): """Draw text."""