Added line joints

This commit is contained in:
Andrew Murray 2018-09-16 21:29:09 +10:00
parent 74f5007bf1
commit f3842460ba
4 changed files with 74 additions and 5 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -568,6 +568,20 @@ class TestImageDraw(PillowTestCase):
# Assert # Assert
self.assert_image_similar(im, Image.open(expected), 1) self.assert_image_similar(im, Image.open(expected), 1)
def test_line_joint(self):
im = Image.new("RGB", (500, 325))
draw = ImageDraw.Draw(im)
expected = "Tests/images/imagedraw_line_joint_curve.png"
# Act
xy = [(400, 280), (380, 280), (450, 280), (440, 120), (350, 200),
(310, 280), (300, 280), (250, 280), (250, 200), (150, 200),
(150, 260), (50, 200), (150, 50), (250, 100)]
draw.line(xy, GRAY, 50, "curve")
# Assert
self.assert_image_similar(im, Image.open(expected), 3)
def test_textsize_empty_string(self): def test_textsize_empty_string(self):
# https://github.com/python-pillow/Pillow/issues/2783 # https://github.com/python-pillow/Pillow/issues/2783
# Arrange # Arrange

View File

@ -171,19 +171,20 @@ 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.
.. py:method:: PIL.ImageDraw.ImageDraw.line(xy, fill=None, width=0) .. py:method:: PIL.ImageDraw.ImageDraw.line(xy, fill=None, width=0, joint=None)
Draws a line between the coordinates in the **xy** list. Draws a line between the coordinates in the **xy** list.
:param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or :param xy: Sequence of either 2-tuples like ``[(x, y), (x, y), ...]`` or
numeric values like ``[x, y, x, y, ...]``. numeric values like ``[x, y, x, y, ...]``.
:param fill: Color to use for the line. :param fill: Color to use for the line.
:param width: The line width, in pixels. Note that line :param width: The line width, in pixels.
joins are not handled well, so wide polylines will not look good.
.. versionadded:: 1.1.5 .. versionadded:: 1.1.5
.. note:: This option was broken until version 1.1.6. .. note:: This option was broken until version 1.1.6.
:param joint: Joint type between a sequence of lines. It can be "curve",
for rounded edges, or None.
.. 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)

View File

@ -30,6 +30,7 @@
# See the README file for information on usage and redistribution. # See the README file for information on usage and redistribution.
# #
import math
import numbers import numbers
from . import Image, ImageColor from . import Image, ImageColor
@ -149,11 +150,64 @@ class ImageDraw(object):
if ink is not None and ink != fill: if ink is not None and ink != fill:
self.draw.draw_ellipse(xy, ink, 0) self.draw.draw_ellipse(xy, ink, 0)
def line(self, xy, fill=None, width=0): def line(self, xy, fill=None, width=0, joint=None):
"""Draw a line, or a connected sequence of line segments.""" """Draw a line, or a connected sequence of line segments."""
ink, fill = self._getink(fill) ink = self._getink(fill)[0]
if ink is not None: if ink is not None:
self.draw.draw_lines(xy, ink, width) self.draw.draw_lines(xy, ink, width)
if joint == "curve" and width > 4:
for i in range(1, len(xy)-1):
point = xy[i]
angles = [
math.degrees(math.atan2(
end[0] - start[0], start[1] - end[1]
)) % 360
for start, end in ((xy[i-1], point), (point, xy[i+1]))
]
if angles[0] == angles[1]:
# This is a straight line, so no joint is required
continue
def coord_at_angle(coord, angle):
x, y = coord
angle -= 90
distance = width/2 - 1
return tuple([
p +
(math.floor(p_d) if p_d > 0 else math.ceil(p_d))
for p, p_d in
((x, distance * math.cos(math.radians(angle))),
(y, distance * math.sin(math.radians(angle))))
])
flipped = ((angles[1] > angles[0] and
angles[1] - 180 > angles[0]) or
(angles[1] < angles[0] and
angles[1] + 180 > angles[0]))
coords = [
(point[0] - width/2 + 1, point[1] - width/2 + 1),
(point[0] + width/2 - 1, point[1] + width/2 - 1)
]
if flipped:
start, end = (angles[1] + 90, angles[0] + 90)
else:
start, end = (angles[0] - 90, angles[1] - 90)
self.pieslice(coords, start - 90, end - 90, fill)
if width > 8:
# Cover potential gaps between the line and the joint
if flipped:
gapCoords = [
coord_at_angle(point, angles[0]+90),
point,
coord_at_angle(point, angles[1]+90)
]
else:
gapCoords = [
coord_at_angle(point, angles[0]-90),
point,
coord_at_angle(point, angles[1]-90)
]
self.line(gapCoords, fill, width=3)
def shape(self, shape, fill=None, outline=None): def shape(self, shape, fill=None, outline=None):
"""(Experimental) Draw a shape.""" """(Experimental) Draw a shape."""