Rename b_circle and bounding_circle + accept ((x0, y0), r)

Summary of changes

- Rename `b_circle` and `bounding_circle`
-`bounding_circle` now accepts both formats below:
    - (x0, y0, r)
    - ((x0, y0), r)
This commit is contained in:
Tommy C 2020-08-21 10:12:00 +01:00
parent df9329f9f0
commit b142560488
3 changed files with 68 additions and 47 deletions

View File

@ -1107,7 +1107,8 @@ def test_draw_regular_polygon(n_sides, rotation, polygon_name):
else f"{filename_base}_rotate_{rotation}.png" else f"{filename_base}_rotate_{rotation}.png"
) )
draw = ImageDraw.Draw(im) draw = ImageDraw.Draw(im)
draw.regular_polygon([W // 2, H // 2, 25], n_sides, rotation=rotation, fill="red") bounding_circle = ((W // 2, H // 2), 25)
draw.regular_polygon(bounding_circle, n_sides, rotation=rotation, fill="red")
assert_image_equal(im, Image.open(filename)) assert_image_equal(im, Image.open(filename))
@ -1140,39 +1141,46 @@ def test_draw_regular_polygon(n_sides, rotation, polygon_name):
], ],
) )
def test_compute_regular_polygon_vertices(n_sides, expected_vertices): def test_compute_regular_polygon_vertices(n_sides, expected_vertices):
vertices = ImageDraw._compute_regular_polygon_vertices( bounding_circle = (W // 2, H // 2, 25)
[W // 2, H // 2, 25], n_sides, 0 vertices = ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, 0)
)
assert vertices == expected_vertices assert vertices == expected_vertices
@pytest.mark.parametrize( @pytest.mark.parametrize(
"n_sides, b_circle, rotation, expected_error, error_message", "n_sides, bounding_circle, rotation, expected_error, error_message",
[ [
(None, [50, 50, 25], 0, TypeError, "n_sides should be an int"), (None, (50, 50, 25), 0, TypeError, "n_sides should be an int"),
(1, [50, 50, 25], 0, ValueError, "n_sides should be an int > 2"), (1, (50, 50, 25), 0, ValueError, "n_sides should be an int > 2"),
(3, 50, 0, TypeError, "b_circle should be a list/tuple"), (3, 50, 0, TypeError, "bounding_circle should be a tuple"),
( (
3, 3,
[50, 50, 100, 100], (50, 50, 100, 100),
0, 0,
ValueError, ValueError,
"b_circle should contain 2D coordinates and a radius (e.g. [x0, y0, r])", "bounding_circle should contain 2D coordinates "
"and a radius (e.g. (x, y, r) or ((x, y), r) )",
), ),
( (
3, 3,
[50, 50, "25"], (50, 50, "25"),
0, 0,
ValueError, ValueError,
"b_circle should only contain numeric data", "bounding_circle should only contain numeric data",
), ),
(3, [50, 50, 0], 0, ValueError, "b_circle radius should be > 0",), (
(3, [50, 50, 25], "0", ValueError, "rotation should be an int or float",), 3,
((50, 50, 50), 25),
0,
ValueError,
"bounding_circle centre should contain 2D coordinates (e.g. (x, y))",
),
(3, (50, 50, 0), 0, ValueError, "bounding_circle radius should be > 0",),
(3, (50, 50, 25), "0", ValueError, "rotation should be an int or float",),
], ],
) )
def test_compute_regular_polygon_vertices_input_error_handling( def test_compute_regular_polygon_vertices_input_error_handling(
n_sides, b_circle, rotation, expected_error, error_message n_sides, bounding_circle, rotation, expected_error, error_message
): ):
with pytest.raises(expected_error) as e: with pytest.raises(expected_error) as e:
ImageDraw._compute_regular_polygon_vertices(b_circle, n_sides, rotation) ImageDraw._compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
assert str(e.value) == error_message assert str(e.value) == error_message

View File

@ -255,17 +255,19 @@ Methods
:param fill: Color to use for the fill. :param fill: Color to use for the fill.
.. py:method:: ImageDraw.regular_polygon(b_circle, n_sides, rotation=0, fill=None, outline=None) .. py:method:: ImageDraw.regular_polygon(bounding_circle, n_sides, rotation=0, fill=None, outline=None)
Draws a regular polygon inscribed in ``b_circle``, Draws a regular polygon inscribed in ``bounding_circle``,
with ``n_sides``, and rotation of ``rotation`` degrees. with ``n_sides``, and rotation of ``rotation`` degrees.
:param b_circle: A bounding circle which inscribes the polygon :param bounding_circle: The bounding circle is a tuple defined
(e.g. b_circle=[50, 50, 25]). by a point and radius.
(e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``).
The polygon is inscribed in this circle.
:param n_sides: Number of sides :param n_sides: Number of sides
(e.g. n_sides=3 for a triangle, 6 for a hexagon). (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon).
:param rotation: Apply an arbitrary rotation to the polygon :param rotation: Apply an arbitrary rotation to the polygon
(e.g. rotation=90, applies a 90 degree rotation). (e.g. ``rotation=90``, applies a 90 degree rotation).
:param fill: Color to use for the fill. :param fill: Color to use for the fill.
:param outline: Color to use for the outline. :param outline: Color to use for the outline.

View File

@ -242,9 +242,11 @@ class ImageDraw:
if ink is not None and ink != fill: if ink is not None and ink != fill:
self.draw.draw_polygon(xy, ink, 0) self.draw.draw_polygon(xy, ink, 0)
def regular_polygon(self, b_circle, n_sides, rotation=0, fill=None, outline=None): def regular_polygon(
self, bounding_circle, n_sides, rotation=0, fill=None, outline=None
):
"""Draw a regular polygon.""" """Draw a regular polygon."""
xy = _compute_regular_polygon_vertices(b_circle, n_sides, rotation) xy = _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation)
self.polygon(xy, fill, outline) self.polygon(xy, fill, outline)
def rectangle(self, xy, fill=None, outline=None, width=1): def rectangle(self, xy, fill=None, outline=None, width=1):
@ -560,25 +562,26 @@ def floodfill(image, xy, value, border=None, thresh=0):
edge = new_edge edge = new_edge
def _compute_regular_polygon_vertices(b_circle, n_sides, rotation): def _compute_regular_polygon_vertices(bounding_circle, n_sides, rotation):
""" """
Generate a list of vertices for a 2D regular polygon. Generate a list of vertices for a 2D regular polygon.
:param b_circle: A bounding circle which inscribes the polygon :param bounding_circle: The bounding circle is a tuple defined
(e.g. b_circle = [x0, y0, r]) by a point and radius. The polygon is inscribed in this circle.
(e.g. ``bounding_circle=(x, y, r)`` or ``((x, y), r)``)
:param n_sides: Number of sides :param n_sides: Number of sides
(e.g. n_sides = 3 for a triangle, 6 for a hexagon) (e.g. ``n_sides=3`` for a triangle, ``6`` for a hexagon)
:param rotation: Apply an arbitrary rotation to the polygon :param rotation: Apply an arbitrary rotation to the polygon
(e.g. rotation=90, applies a 90 degree rotation) (e.g. ``rotation=90``, applies a 90 degree rotation)
:return: List of regular polygon vertices :return: List of regular polygon vertices
(e.g. [(25, 50), (50, 50), (50, 25), (25, 25)]) (e.g. ``[(25, 50), (50, 50), (50, 25), (25, 25)]``)
How are the vertices computed? How are the vertices computed?
1. Compute the following variables 1. Compute the following variables
- theta: Angle between the apothem & the nearest polygon vertex - theta: Angle between the apothem & the nearest polygon vertex
- side_length: Length of each polygon edge - side_length: Length of each polygon edge
- centroid: Center of bounding circle (1st, 2nd elements of b_circle) - centroid: Center of bounding circle (1st, 2nd elements of bounding_circle)
- polygon_radius: Polygon radius (3rd element of b_circle) - polygon_radius: Polygon radius (last element of bounding_circle)
- angles: Location of each polygon vertex in polar grid - angles: Location of each polygon vertex in polar grid
(e.g. A square with 0 degree rotation => [225.0, 315.0, 45.0, 135.0]) (e.g. A square with 0 degree rotation => [225.0, 315.0, 45.0, 135.0])
@ -605,20 +608,30 @@ def _compute_regular_polygon_vertices(b_circle, n_sides, rotation):
if n_sides < 3: if n_sides < 3:
raise ValueError("n_sides should be an int > 2") raise ValueError("n_sides should be an int > 2")
# 1.2 Check `b_circle` has an appropriate value # 1.2 Check `bounding_circle` has an appropriate value
if not isinstance(b_circle, (list, tuple)): if not isinstance(bounding_circle, (list, tuple)):
raise TypeError("b_circle should be a list/tuple") raise TypeError("bounding_circle should be a tuple")
if not len(b_circle) == 3: if len(bounding_circle) == 3:
*centroid, polygon_radius = bounding_circle
elif len(bounding_circle) == 2:
centroid, polygon_radius = bounding_circle
else:
raise ValueError( raise ValueError(
"b_circle should contain 2D coordinates and a radius (e.g. [x0, y0, r])" "bounding_circle should contain 2D coordinates "
"and a radius (e.g. (x, y, r) or ((x, y), r) )"
) )
if not all(isinstance(i, (int, float)) for i in b_circle): if not all(isinstance(i, (int, float)) for i in (*centroid, polygon_radius)):
raise ValueError("b_circle should only contain numeric data") raise ValueError("bounding_circle should only contain numeric data")
if b_circle[-1] <= 0: if not len(centroid) == 2:
raise ValueError("b_circle radius should be > 0") raise ValueError(
"bounding_circle centre should contain 2D coordinates (e.g. (x, y))"
)
if polygon_radius <= 0:
raise ValueError("bounding_circle radius should be > 0")
# 1.3 Check `rotation` has an appropriate value # 1.3 Check `rotation` has an appropriate value
if not isinstance(rotation, (int, float)): if not isinstance(rotation, (int, float)):
@ -641,7 +654,7 @@ def _compute_regular_polygon_vertices(b_circle, n_sides, rotation):
), ),
) )
def _compute_polygon_vertex(centroid, angle, polygon_radius): def _compute_polygon_vertex(centroid, polygon_radius, angle):
start_point = [polygon_radius, 0] start_point = [polygon_radius, 0]
return _apply_rotation(start_point, angle, centroid) return _apply_rotation(start_point, angle, centroid)
@ -658,14 +671,12 @@ def _compute_regular_polygon_vertices(b_circle, n_sides, rotation):
return angles return angles
# 3. Variable Declarations # 3. Variable Declarations
vertices = []
*centroid, polygon_radius = b_circle
angles = _get_angles(n_sides, rotation) angles = _get_angles(n_sides, rotation)
# 4. Compute Vertices # 4. Compute Vertices
for angle in angles: return [
vertices.append(_compute_polygon_vertex(centroid, angle, polygon_radius)) _compute_polygon_vertex(centroid, polygon_radius, angle) for angle in angles
return vertices ]
def _color_diff(color1, color2): def _color_diff(color1, color2):