Merge pull request #6954 from radarhere/corners
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnnn.png
Normal file
After Width: | Height: | Size: 544 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnny.png
Normal file
After Width: | Height: | Size: 685 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnyn.png
Normal file
After Width: | Height: | Size: 649 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nnyy.png
Normal file
After Width: | Height: | Size: 755 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nynn.png
Normal file
After Width: | Height: | Size: 643 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nyny.png
Normal file
After Width: | Height: | Size: 775 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nyyn.png
Normal file
After Width: | Height: | Size: 741 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_nyyy.png
Normal file
After Width: | Height: | Size: 844 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynnn.png
Normal file
After Width: | Height: | Size: 656 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynny.png
Normal file
After Width: | Height: | Size: 785 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynyn.png
Normal file
After Width: | Height: | Size: 752 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_ynyy.png
Normal file
After Width: | Height: | Size: 856 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yynn.png
Normal file
After Width: | Height: | Size: 737 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yyny.png
Normal file
After Width: | Height: | Size: 870 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yyyn.png
Normal file
After Width: | Height: | Size: 835 B |
BIN
Tests/images/imagedraw_rounded_rectangle_corners_yyyy.png
Normal file
After Width: | Height: | Size: 934 B |
|
@ -735,6 +735,36 @@ def test_rounded_rectangle(xy):
|
||||||
assert_image_equal_tofile(im, "Tests/images/imagedraw_rounded_rectangle.png")
|
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(
|
@pytest.mark.parametrize(
|
||||||
"xy, radius, type",
|
"xy, radius, type",
|
||||||
[
|
[
|
||||||
|
|
|
@ -337,6 +337,8 @@ Methods
|
||||||
:param outline: Color to use for the outline.
|
:param outline: Color to use for the outline.
|
||||||
:param fill: Color to use for the fill.
|
:param fill: Color to use for the fill.
|
||||||
:param width: The line width, in pixels.
|
: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
|
.. versionadded:: 8.2.0
|
||||||
|
|
||||||
|
|
|
@ -295,29 +295,37 @@ class ImageDraw:
|
||||||
if ink is not None and ink != fill and width != 0:
|
if ink is not None and ink != fill and width != 0:
|
||||||
self.draw.draw_rectangle(xy, ink, 0, width)
|
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."""
|
"""Draw a rounded rectangle."""
|
||||||
if isinstance(xy[0], (list, tuple)):
|
if isinstance(xy[0], (list, tuple)):
|
||||||
(x0, y0), (x1, y1) = xy
|
(x0, y0), (x1, y1) = xy
|
||||||
else:
|
else:
|
||||||
x0, y0, x1, y1 = xy
|
x0, y0, x1, y1 = xy
|
||||||
|
if corners is None:
|
||||||
|
corners = (True, True, True, True)
|
||||||
|
|
||||||
d = radius * 2
|
d = radius * 2
|
||||||
|
|
||||||
full_x = d >= x1 - x0
|
full_x, full_y = False, False
|
||||||
if full_x:
|
if all(corners):
|
||||||
# The two left and two right corners are joined
|
full_x = d >= x1 - x0
|
||||||
d = x1 - x0
|
if full_x:
|
||||||
full_y = d >= y1 - y0
|
# The two left and two right corners are joined
|
||||||
if full_y:
|
d = x1 - x0
|
||||||
# The two top and two bottom corners are joined
|
full_y = d >= y1 - y0
|
||||||
d = y1 - y0
|
if full_y:
|
||||||
if full_x and full_y:
|
# The two top and two bottom corners are joined
|
||||||
# If all corners are joined, that is a circle
|
d = y1 - y0
|
||||||
return self.ellipse(xy, fill, outline, width)
|
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 d == 0 or not any(corners):
|
||||||
# If the corners have no curve, that is a rectangle
|
# If the corners have no curve,
|
||||||
|
# or there are no corners,
|
||||||
|
# that is a rectangle
|
||||||
return self.rectangle(xy, fill, outline, width)
|
return self.rectangle(xy, fill, outline, width)
|
||||||
|
|
||||||
r = d // 2
|
r = d // 2
|
||||||
|
@ -338,12 +346,17 @@ class ImageDraw:
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# Draw four separate corners
|
# Draw four separate corners
|
||||||
parts = (
|
parts = []
|
||||||
((x1 - d, y0, x1, y0 + d), 270, 360),
|
for i, part in enumerate(
|
||||||
((x1 - d, y1 - d, x1, y1), 0, 90),
|
(
|
||||||
((x0, y1 - d, x0 + d, y1), 90, 180),
|
((x0, y0, x0 + d, y0 + d), 180, 270),
|
||||||
((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:
|
for part in parts:
|
||||||
if pieslice:
|
if pieslice:
|
||||||
self.draw.draw_pieslice(*(part + (fill, 1)))
|
self.draw.draw_pieslice(*(part + (fill, 1)))
|
||||||
|
@ -358,25 +371,50 @@ class ImageDraw:
|
||||||
else:
|
else:
|
||||||
self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)
|
self.draw.draw_rectangle((x0 + r + 1, y0, x1 - r - 1, y1), fill, 1)
|
||||||
if not full_x and not full_y:
|
if not full_x and not full_y:
|
||||||
self.draw.draw_rectangle((x0, y0 + r + 1, x0 + r, y1 - r - 1), fill, 1)
|
left = [x0, y0, x0 + r, y1]
|
||||||
self.draw.draw_rectangle((x1 - r, y0 + r + 1, x1, y1 - r - 1), fill, 1)
|
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:
|
if ink is not None and ink != fill and width != 0:
|
||||||
draw_corners(False)
|
draw_corners(False)
|
||||||
|
|
||||||
if not full_x:
|
if not full_x:
|
||||||
self.draw.draw_rectangle(
|
top = [x0, y0, x1, y0 + width - 1]
|
||||||
(x0 + r + 1, y0, x1 - r - 1, y0 + width - 1), ink, 1
|
if corners[0]:
|
||||||
)
|
top[0] += r + 1
|
||||||
self.draw.draw_rectangle(
|
if corners[1]:
|
||||||
(x0 + r + 1, y1 - width + 1, x1 - r - 1, y1), ink, 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:
|
if not full_y:
|
||||||
self.draw.draw_rectangle(
|
left = [x0, y0, x0 + width - 1, y1]
|
||||||
(x0, y0 + r + 1, x0 + width - 1, y1 - r - 1), ink, 1
|
if corners[0]:
|
||||||
)
|
left[1] += r + 1
|
||||||
self.draw.draw_rectangle(
|
if corners[3]:
|
||||||
(x1 - width + 1, y0 + r + 1, x1, y1 - r - 1), ink, 1
|
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):
|
def _multiline_check(self, text):
|
||||||
"""Draw text."""
|
"""Draw text."""
|
||||||
|
|