Skip to content

Commit 50d81fd

Browse files
authored
Merge pull request #76 from FoamyGuy/polygon_fill
adding filled polygon. Add support for stroke on Polygon
2 parents a756764 + 431396f commit 50d81fd

File tree

6 files changed

+224
-10
lines changed

6 files changed

+224
-10
lines changed
+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
2+
#
3+
# SPDX-License-Identifier: MIT
4+
5+
"""
6+
`filled_polygon`
7+
================================================================================
8+
9+
Various common shapes for use with displayio - Polygon that supports
10+
both fill and outline
11+
12+
13+
* Author(s): Bernhard Bablok, Tim Cocks
14+
15+
Implementation Notes
16+
--------------------
17+
18+
**Software and Dependencies:**
19+
20+
* Adafruit CircuitPython firmware for the supported boards:
21+
https://github.com/adafruit/circuitpython/releases
22+
23+
"""
24+
25+
try:
26+
from typing import Optional, List, Tuple
27+
except ImportError:
28+
pass
29+
30+
import displayio
31+
from adafruit_display_shapes.polygon import Polygon
32+
33+
try:
34+
import vectorio
35+
36+
HAVE_VECTORIO = True
37+
except ImportError:
38+
HAVE_VECTORIO = False
39+
40+
__version__ = "0.0.0+auto.0"
41+
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Shapes.git"
42+
43+
44+
class FilledPolygon(displayio.Group):
45+
# pylint: disable=too-few-public-methods, invalid-name
46+
"""A filled polygon. Technically, an FilledPolygon is a Group with one or two polygons.
47+
48+
:param list points: A list of (x, y) tuples of the points
49+
:param int|None outline: The outline of the arc. Can be a hex value for a color or
50+
``None`` for no outline.
51+
:param int|None fill: The fill-color of the arc. Can be a hex value for a color or
52+
``None`` for no filling. Ignored if port does not support vectorio.
53+
:param bool close: (Optional) Wether to connect first and last point. (True)
54+
:param int stroke: Thickness of the outline.
55+
56+
"""
57+
58+
def __init__(
59+
self,
60+
points: List[Tuple[int, int]],
61+
*,
62+
outline: Optional[int] = None,
63+
fill: Optional[int] = None,
64+
close: Optional[bool] = True,
65+
stroke: int = 1,
66+
) -> None:
67+
super().__init__()
68+
self._points = points
69+
self._outline = outline
70+
self._fill = fill
71+
self.close = close
72+
self.stroke = stroke
73+
74+
self.palette = None
75+
self.vector_polygon = None
76+
self.outline_polygon = None
77+
78+
self._init_polygon()
79+
80+
def _init_polygon(self):
81+
# create polygon(s) and add to ourselves
82+
if HAVE_VECTORIO and self._fill is not None:
83+
if self.palette is None:
84+
self.palette = displayio.Palette(1)
85+
self.palette[0] = self._fill
86+
if self.vector_polygon is None:
87+
self.vector_polygon = vectorio.Polygon(
88+
pixel_shader=self.palette, points=self.points, x=0, y=0
89+
)
90+
self.append(self.vector_polygon)
91+
else:
92+
self.vector_polygon.points = self.points
93+
94+
if self._outline is not None:
95+
if self.outline_polygon is None:
96+
self.outline_polygon = Polygon(
97+
self.points,
98+
outline=self._outline,
99+
colors=1,
100+
close=self.close,
101+
stroke=self.stroke,
102+
)
103+
else:
104+
self.remove(self.outline_polygon)
105+
self.outline_polygon = Polygon(
106+
self.points,
107+
outline=self._outline,
108+
colors=1,
109+
close=self.close,
110+
stroke=self.stroke,
111+
)
112+
self.append(self.outline_polygon)
113+
114+
@property
115+
def points(self):
116+
"""The points that make up the polygon"""
117+
return self._points
118+
119+
@points.setter
120+
def points(self, points):
121+
self._points = points
122+
self._init_polygon()
123+
124+
@property
125+
def outline(self):
126+
"""The outline color. None for no outline"""
127+
return self._outline
128+
129+
@outline.setter
130+
def outline(self, value):
131+
self._outline = value
132+
self._init_polygon()
133+
134+
@property
135+
def fill(self):
136+
"""The fill color. None for no fill"""
137+
return self._fill
138+
139+
@fill.setter
140+
def fill(self, value):
141+
self._fill = value
142+
self._init_polygon()

adafruit_display_shapes/polygon.py

+28-9
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
except ImportError:
2727
pass
2828

29+
import bitmaptools
2930
import displayio
3031

3132
__version__ = "0.0.0+auto.0"
@@ -42,6 +43,7 @@ class Polygon(displayio.TileGrid):
4243
:param int colors: (Optional) Number of colors to use. Most polygons would use two, one for
4344
outline and one for fill. If you're not filling your polygon, set this to 1
4445
for smaller memory footprint. (2)
46+
:param int stroke: Thickness of the outline.
4547
"""
4648

4749
_OUTLINE = 1
@@ -54,6 +56,8 @@ def __init__(
5456
outline: Optional[int] = None,
5557
close: Optional[bool] = True,
5658
colors: Optional[int] = 2,
59+
stroke: int = 1,
60+
# pylint: disable=too-many-arguments
5761
) -> None:
5862
(x_s, y_s) = zip(*points)
5963

@@ -66,13 +70,14 @@ def __init__(
6670

6771
self._palette = displayio.Palette(colors + 1)
6872
self._palette.make_transparent(0)
69-
self._bitmap = displayio.Bitmap(width, height, colors + 1)
73+
self._bitmap = displayio.Bitmap(width + stroke, height + stroke, colors + 1)
74+
self._stroke = stroke
7075

7176
shifted = [(x - x_offset, y - y_offset) for (x, y) in points]
7277

7378
if outline is not None:
7479
self.outline = outline
75-
self.draw(self._bitmap, shifted, self._OUTLINE, close)
80+
self.draw(self._bitmap, shifted, self._OUTLINE, close, stroke)
7681

7782
super().__init__(
7883
self._bitmap, pixel_shader=self._palette, x=x_offset, y=y_offset
@@ -84,6 +89,7 @@ def draw(
8489
points: List[Tuple[int, int]],
8590
color_id: int,
8691
close: Optional[bool] = True,
92+
stroke=1,
8793
) -> None:
8894
"""Draw a polygon conecting points on provided bitmap with provided color_id
8995
@@ -97,7 +103,7 @@ def draw(
97103
points.append(points[0])
98104

99105
for index in range(len(points) - 1):
100-
Polygon._line_on(bitmap, points[index], points[index + 1], color_id)
106+
Polygon._line_on(bitmap, points[index], points[index + 1], color_id, stroke)
101107

102108
# pylint: disable=too-many-arguments
103109
def _line(
@@ -129,23 +135,36 @@ def _line_on(
129135
p_0: Tuple[int, int],
130136
p_1: Tuple[int, int],
131137
color: int,
138+
stroke: int = 1,
132139
) -> None:
133140
(x_0, y_0) = p_0
134141
(x_1, y_1) = p_1
135142

136-
def pt_on(x, y):
137-
Polygon._safe_draw(bitmap, (x, y), color)
143+
def pt_on(x, y, pt_size=1):
144+
if pt_size > 1:
145+
x = x + pt_size // 2
146+
y = y + pt_size // 2
147+
bitmaptools.fill_region(
148+
bitmap,
149+
x - (pt_size // 2),
150+
y - (pt_size // 2),
151+
x + (pt_size // 2),
152+
y + (pt_size // 2),
153+
color,
154+
)
155+
else:
156+
Polygon._safe_draw(bitmap, (x, y), color)
138157

139158
if x_0 == x_1:
140159
if y_0 > y_1:
141160
y_0, y_1 = y_1, y_0
142161
for _h in range(y_0, y_1 + 1):
143-
pt_on(x_0, _h)
162+
pt_on(x_0, _h, stroke)
144163
elif y_0 == y_1:
145164
if x_0 > x_1:
146165
x_0, x_1 = x_1, x_0
147166
for _w in range(x_0, x_1 + 1):
148-
pt_on(_w, y_0)
167+
pt_on(_w, y_0, stroke)
149168
else:
150169
steep = abs(y_1 - y_0) > abs(x_1 - x_0)
151170
if steep:
@@ -168,9 +187,9 @@ def pt_on(x, y):
168187

169188
for x in range(x_0, x_1 + 1):
170189
if steep:
171-
pt_on(y_0, x)
190+
pt_on(y_0, x, stroke)
172191
else:
173-
pt_on(x, y_0)
192+
pt_on(x, y_0, stroke)
174193
err -= d_y
175194
if err < 0:
176195
y_0 += ystep

docs/api.rst

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
.. automodule:: adafruit_display_shapes.polygon
2323
:members:
2424

25+
.. automodule:: adafruit_display_shapes.filled_polygon
26+
:members:
27+
2528
.. automodule:: adafruit_display_shapes.sparkline
2629
:members:
2730

docs/conf.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
# Uncomment the below if you use native CircuitPython modules such as
2828
# digitalio, micropython and busio. List the modules you use. Without it, the
2929
# autodoc module docs will fail to generate with a warning.
30-
autodoc_mock_imports = ["displayio"]
30+
autodoc_mock_imports = ["displayio", "bitmaptools"]
3131

3232

3333
intersphinx_mapping = {

docs/examples.rst

+9
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,12 @@ Example demonstrating various arcs.
6060
.. literalinclude:: ../examples/display_shapes_arc.py
6161
:caption: examples/display_shapes_arc.py
6262
:linenos:
63+
64+
Filled Polygon Simple Test
65+
--------------------------
66+
67+
Example demonstrating a filled polygon
68+
69+
.. literalinclude:: ../examples/display_shapes_filled_polygon_simpletest.py
70+
:caption: examples/display_shapes_filled_polygon_simpletest.py
71+
:linenos:
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
2+
# SPDX-License-Identifier: MIT
3+
4+
import board
5+
import displayio
6+
from adafruit_display_shapes.filled_polygon import FilledPolygon
7+
8+
# Make the display context
9+
splash = displayio.Group()
10+
board.DISPLAY.root_group = splash
11+
12+
# Make a background color fill
13+
color_bitmap = displayio.Bitmap(320, 240, 1)
14+
color_palette = displayio.Palette(1)
15+
color_palette[0] = 0xFFFFFF
16+
bg_sprite = displayio.TileGrid(color_bitmap, x=0, y=0, pixel_shader=color_palette)
17+
splash.append(bg_sprite)
18+
##########################################################################
19+
20+
# Draw a star with blue outline and pink fill
21+
polygon = FilledPolygon(
22+
[
23+
(55, 40),
24+
(62, 62),
25+
(85, 62),
26+
(65, 76),
27+
(75, 100),
28+
(55, 84),
29+
(35, 100),
30+
(45, 76),
31+
(25, 62),
32+
(48, 62),
33+
],
34+
outline=0x0000FF,
35+
stroke=4,
36+
fill=0xFF00FF,
37+
)
38+
splash.append(polygon)
39+
40+
while True:
41+
pass

0 commit comments

Comments
 (0)