Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

adding filled polygon. Add support for stroke on Polygon #76

Merged
merged 2 commits into from
Nov 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 142 additions & 0 deletions adafruit_display_shapes/filled_polygon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
#
# SPDX-License-Identifier: MIT

"""
`filled_polygon`
================================================================================

Various common shapes for use with displayio - Polygon that supports
both fill and outline


* Author(s): Bernhard Bablok, Tim Cocks

Implementation Notes
--------------------

**Software and Dependencies:**

* Adafruit CircuitPython firmware for the supported boards:
https://github.com/adafruit/circuitpython/releases

"""

try:
from typing import Optional, List, Tuple
except ImportError:
pass

import displayio
from adafruit_display_shapes.polygon import Polygon

try:
import vectorio

HAVE_VECTORIO = True
except ImportError:
HAVE_VECTORIO = False

__version__ = "0.0.0+auto.0"
__repo__ = "https://github.com/adafruit/Adafruit_CircuitPython_Display_Shapes.git"


class FilledPolygon(displayio.Group):
# pylint: disable=too-few-public-methods, invalid-name
"""A filled polygon. Technically, an FilledPolygon is a Group with one or two polygons.

:param list points: A list of (x, y) tuples of the points
:param int|None outline: The outline of the arc. Can be a hex value for a color or
``None`` for no outline.
:param int|None fill: The fill-color of the arc. Can be a hex value for a color or
``None`` for no filling. Ignored if port does not support vectorio.
:param bool close: (Optional) Wether to connect first and last point. (True)
:param int stroke: Thickness of the outline.

"""

def __init__(
self,
points: List[Tuple[int, int]],
*,
outline: Optional[int] = None,
fill: Optional[int] = None,
close: Optional[bool] = True,
stroke: int = 1,
) -> None:
super().__init__()
self._points = points
self._outline = outline
self._fill = fill
self.close = close
self.stroke = stroke

self.palette = None
self.vector_polygon = None
self.outline_polygon = None

self._init_polygon()

def _init_polygon(self):
# create polygon(s) and add to ourselves
if HAVE_VECTORIO and self._fill is not None:
if self.palette is None:
self.palette = displayio.Palette(1)
self.palette[0] = self._fill
if self.vector_polygon is None:
self.vector_polygon = vectorio.Polygon(
pixel_shader=self.palette, points=self.points, x=0, y=0
)
self.append(self.vector_polygon)
else:
self.vector_polygon.points = self.points

if self._outline is not None:
if self.outline_polygon is None:
self.outline_polygon = Polygon(
self.points,
outline=self._outline,
colors=1,
close=self.close,
stroke=self.stroke,
)
else:
self.remove(self.outline_polygon)
self.outline_polygon = Polygon(
self.points,
outline=self._outline,
colors=1,
close=self.close,
stroke=self.stroke,
)
self.append(self.outline_polygon)

@property
def points(self):
"""The points that make up the polygon"""
return self._points

@points.setter
def points(self, points):
self._points = points
self._init_polygon()

@property
def outline(self):
"""The outline color. None for no outline"""
return self._outline

@outline.setter
def outline(self, value):
self._outline = value
self._init_polygon()

@property
def fill(self):
"""The fill color. None for no fill"""
return self._fill

@fill.setter
def fill(self, value):
self._fill = value
self._init_polygon()
37 changes: 28 additions & 9 deletions adafruit_display_shapes/polygon.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
except ImportError:
pass

import bitmaptools
import displayio

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

_OUTLINE = 1
Expand All @@ -54,6 +56,8 @@ def __init__(
outline: Optional[int] = None,
close: Optional[bool] = True,
colors: Optional[int] = 2,
stroke: int = 1,
# pylint: disable=too-many-arguments
) -> None:
(x_s, y_s) = zip(*points)

Expand All @@ -66,13 +70,14 @@ def __init__(

self._palette = displayio.Palette(colors + 1)
self._palette.make_transparent(0)
self._bitmap = displayio.Bitmap(width, height, colors + 1)
self._bitmap = displayio.Bitmap(width + stroke, height + stroke, colors + 1)
self._stroke = stroke

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

if outline is not None:
self.outline = outline
self.draw(self._bitmap, shifted, self._OUTLINE, close)
self.draw(self._bitmap, shifted, self._OUTLINE, close, stroke)

super().__init__(
self._bitmap, pixel_shader=self._palette, x=x_offset, y=y_offset
Expand All @@ -84,6 +89,7 @@ def draw(
points: List[Tuple[int, int]],
color_id: int,
close: Optional[bool] = True,
stroke=1,
) -> None:
"""Draw a polygon conecting points on provided bitmap with provided color_id

Expand All @@ -97,7 +103,7 @@ def draw(
points.append(points[0])

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

# pylint: disable=too-many-arguments
def _line(
Expand Down Expand Up @@ -129,23 +135,36 @@ def _line_on(
p_0: Tuple[int, int],
p_1: Tuple[int, int],
color: int,
stroke: int = 1,
) -> None:
(x_0, y_0) = p_0
(x_1, y_1) = p_1

def pt_on(x, y):
Polygon._safe_draw(bitmap, (x, y), color)
def pt_on(x, y, pt_size=1):
if pt_size > 1:
x = x + pt_size // 2
y = y + pt_size // 2
bitmaptools.fill_region(
bitmap,
x - (pt_size // 2),
y - (pt_size // 2),
x + (pt_size // 2),
y + (pt_size // 2),
color,
)
else:
Polygon._safe_draw(bitmap, (x, y), color)

if x_0 == x_1:
if y_0 > y_1:
y_0, y_1 = y_1, y_0
for _h in range(y_0, y_1 + 1):
pt_on(x_0, _h)
pt_on(x_0, _h, stroke)
elif y_0 == y_1:
if x_0 > x_1:
x_0, x_1 = x_1, x_0
for _w in range(x_0, x_1 + 1):
pt_on(_w, y_0)
pt_on(_w, y_0, stroke)
else:
steep = abs(y_1 - y_0) > abs(x_1 - x_0)
if steep:
Expand All @@ -168,9 +187,9 @@ def pt_on(x, y):

for x in range(x_0, x_1 + 1):
if steep:
pt_on(y_0, x)
pt_on(y_0, x, stroke)
else:
pt_on(x, y_0)
pt_on(x, y_0, stroke)
err -= d_y
if err < 0:
y_0 += ystep
Expand Down
3 changes: 3 additions & 0 deletions docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@
.. automodule:: adafruit_display_shapes.polygon
:members:

.. automodule:: adafruit_display_shapes.filled_polygon
:members:

.. automodule:: adafruit_display_shapes.sparkline
:members:

Expand Down
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
# Uncomment the below if you use native CircuitPython modules such as
# digitalio, micropython and busio. List the modules you use. Without it, the
# autodoc module docs will fail to generate with a warning.
autodoc_mock_imports = ["displayio"]
autodoc_mock_imports = ["displayio", "bitmaptools"]


intersphinx_mapping = {
Expand Down
9 changes: 9 additions & 0 deletions docs/examples.rst
Original file line number Diff line number Diff line change
Expand Up @@ -60,3 +60,12 @@ Example demonstrating various arcs.
.. literalinclude:: ../examples/display_shapes_arc.py
:caption: examples/display_shapes_arc.py
:linenos:

Filled Polygon Simple Test
--------------------------

Example demonstrating a filled polygon

.. literalinclude:: ../examples/display_shapes_filled_polygon_simpletest.py
:caption: examples/display_shapes_filled_polygon_simpletest.py
:linenos:
41 changes: 41 additions & 0 deletions examples/display_shapes_filled_polygon_simpletest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# SPDX-FileCopyrightText: 2024 Tim Cocks for Adafruit Industries
# SPDX-License-Identifier: MIT

import board
import displayio
from adafruit_display_shapes.filled_polygon import FilledPolygon

# Make the display context
splash = displayio.Group()
board.DISPLAY.root_group = splash

# Make a background color fill
color_bitmap = displayio.Bitmap(320, 240, 1)
color_palette = displayio.Palette(1)
color_palette[0] = 0xFFFFFF
bg_sprite = displayio.TileGrid(color_bitmap, x=0, y=0, pixel_shader=color_palette)
splash.append(bg_sprite)
##########################################################################

# Draw a star with blue outline and pink fill
polygon = FilledPolygon(
[
(55, 40),
(62, 62),
(85, 62),
(65, 76),
(75, 100),
(55, 84),
(35, 100),
(45, 76),
(25, 62),
(48, 62),
],
outline=0x0000FF,
stroke=4,
fill=0xFF00FF,
)
splash.append(polygon)

while True:
pass