]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blobdiff - vectorio/_polygon.py
Add vectorio support
[hackapet/Adafruit_Blinka_Displayio.git] / vectorio / _polygon.py
diff --git a/vectorio/_polygon.py b/vectorio/_polygon.py
new file mode 100644 (file)
index 0000000..4e6e444
--- /dev/null
@@ -0,0 +1,129 @@
+# SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
+
+"""
+`vectorio.polygon`
+================================================================================
+
+vectorio Polygon for Blinka
+
+**Software and Dependencies:**
+
+* Adafruit Blinka:
+  https://github.com/adafruit/Adafruit_Blinka/releases
+
+* Author(s): Melissa LeBlanc-Williams
+
+"""
+
+from typing import Union, Tuple
+from displayio._colorconverter import ColorConverter
+from displayio._palette import Palette
+from displayio._area import Area
+from ._vectorshape import _VectorShape
+
+__version__ = "0.0.0+auto.0"
+__repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
+
+
+class Polygon(_VectorShape):
+    """Vectorio Polygon"""
+
+    def __init__(
+        self,
+        *,
+        pixel_shader: Union[ColorConverter, Palette],
+        points: list | Tuple[int, int],
+        x: int,
+        y: int,
+    ):
+        """Represents a closed shape by ordered vertices. The path will be treated as
+        'closed', the last point will connect to the first point.
+
+        :param Union[~displayio.ColorConverter,~displayio.Palette] pixel_shader: The pixel
+            shader that produces colors from values
+        :param List[Tuple[int,int]] points: Vertices for the polygon
+        :param int x: Initial screen x position of the 0,0 origin in the points list.
+        :param int y: Initial screen y position of the 0,0 origin in the points list.
+        :param int color_index: Initial color_index to use when selecting color from the palette.
+        """
+        self._color_index = 1
+        self._points = []
+        super().__init__(pixel_shader, x, y)
+        self.points = points
+
+    @property
+    def points(self) -> list | Tuple[int, int]:
+        """The points of the polygon in pixels"""
+        return self._points
+
+    @points.setter
+    def points(self, value: list | Tuple[int, int]) -> None:
+        if len(value) < 3:
+            raise ValueError("Polygon needs at least 3 points")
+        self._points = value
+        self._shape_set_dirty()
+
+    @property
+    def color_index(self) -> int:
+        """The color_index of the polygon as 0 based index of the palette."""
+        return self._color_index - 1
+
+    @color_index.setter
+    def color_index(self, value: int) -> None:
+        self._color_index = abs(value + 1)
+        self._shape_set_dirty()
+
+    @staticmethod
+    def _line_side(
+        line_x1: int,
+        line_y1: int,
+        line_x2: int,
+        line_y2: int,
+        point_x: int,
+        point_y: int,
+    ):
+        # pylint: disable=too-many-arguments
+        return (point_x - line_x1) * (line_y2 - line_y1) - (point_y - line_y1) * (
+            line_x2 - line_x1
+        )
+
+    def _get_pixel(self, x: int, y: int) -> int:
+        # pylint: disable=invalid-name
+        if len(self._points) == 0:
+            return 0
+        winding_number = 0
+        x1 = self._points[0][0]
+        y1 = self._points[0][1]
+        for i in range(1, len(self._points)):
+            x2 = self._points[i][0]
+            y2 = self._points[i][1]
+            if y1 <= y:
+                if y2 > y and self._line_side(x1, y1, x2, y2, x, y) < 0:
+                    # Wind up, point is to the left of the edge vector
+                    winding_number += 1
+            elif y2 <= y and self._line_side(x1, y1, x2, y2, x, y) > 0:
+                # Wind down, point is to the right of the edge vector
+                winding_number -= 1
+            x1 = x2
+            y1 = y2
+
+        return 0 if winding_number == 0 else self._color_index
+
+    def _get_area(self, out_area: Area) -> None:
+        # Figure out the shape dimensions by using min and max
+        out_area.x1 = 32768
+        out_area.y1 = 32768
+        out_area.x2 = 0
+        out_area.y2 = 0
+
+        for x, y in self._points:
+            if x < out_area.x1:
+                out_area.x1 = x
+            if y < out_area.y1:
+                out_area.y1 = y
+            if x > out_area.x2:
+                out_area.x2 = x
+            if y > out_area.y2:
+                out_area.y2 = y