Repository: python-pillow/Pillow
Pull request: Added "corners" argument to ImageDraw rounded_rectangle()
Resolves #6953, by adding a `corners` argument to `ImageDraw.rounded_rectangle` - a tuple of booleans for whether or not to round each corner, going clockwise, starting with top left and ending with bottom left.

Problem statement:
Add a corners parameter for `ImageDraw.rounded_rectangle`?
### What's your feature request?
To add an optional bool tuple parameter (`corners=(True, True, True, True)`), or 4 optional bool parameters (`top_left=True, top_right=True,  ...`) describing which corners of a rectangle should be rounded for `ImageDraw.rounded_rectangle` and possibly similar functions.

### What are your OS, Python and Pillow versions?

* OS: Windows 10
* Python: 3.10.10
* Pillow: latest


## Example of feature request
```python
draw.rounded_rectangle((x0, y0, x1, y1), radius=4, fill = white, corners=(True, False, True, False)) # TL TR BL BR
draw.rounded_rectangle((x0, y0, x1, y1), radius=4, fill=white, top_right=False, bottom_right=False)
```

Hints from issue discussion:

Commit message: Added "corners" argument to rounded_rectangle()

Patch to review:
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 00000000000..3e79e21aedb
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 00000000000..7fa09a3c03a
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 00000000000..d825ad26317
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 00000000000..c19da698e38
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 00000000000..f3e95d48749
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 00000000000..274d27984dc
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 00000000000..c5f40bfdbdd
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 00000000000..01bfd1750a4
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 00000000000..efd27be4f9d
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 00000000000..d3acd01abe0
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 00000000000..55ddbc033fb
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 00000000000..c000b26e967
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 00000000000..7056b4fd934
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 00000000000..5eca030b9a0
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 00000000000..7f1f0034400
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 00000000000..2e815f4ada2
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 d4723c92401..ea0711ad53c 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 9aa26916a90..14729e2f529 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 163828d31d2..dc77c06ea51 100644
--- a/src/PIL/ImageDraw.py
+++ b/src/PIL/ImageDraw.py
@@ -295,29 +295,37 @@ 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):
+    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)
-
-        if d == 0:
-            # If the corners have no curve, that is a rectangle
+        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 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 @@ def draw_corners(pieslice):
                 )
             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 @@ def draw_corners(pieslice):
             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."""


Code review comments:
diff_hunk: @@ -296,15 +296,14 @@ def rectangle(self, xy, fill=None, outline=None, width=1):
             self.draw.draw_rectangle(xy, ink, 0, width)
 
     def rounded_rectangle(
-        self, xy, radius=0, fill=None, outline=None, width=1, corners=None
+        self, xy, radius=0, fill=None, outline=None, width=1, **kwargs
path: src/PIL/ImageDraw.py
text: @user1:
Or something like this for more transparency? Better for `help(rounded_rectangle)` and so on.

```suggestion
        self, xy, radius=0, fill=None, outline=None, width=1, *, corners=None
```

Or even this?

```suggestion
        self, xy, radius=0, fill=None, outline=None, width=1, *, corners=(True, True, True, True)
```

(And update/delete the below to match.)


@user2:
I think `corners=(True, True, True, True)` is best. But what would the ordering be? Top left, top right, bottom right, bottom left (clock wise)?