]> Repositories - hackapet/Adafruit_Blinka_Displayio.git/blob - vectorio/_polygon.py
Run pre-commit
[hackapet/Adafruit_Blinka_Displayio.git] / vectorio / _polygon.py
1 # SPDX-FileCopyrightText: 2020 Melissa LeBlanc-Williams for Adafruit Industries
2 #
3 # SPDX-License-Identifier: MIT
4
5 """
6 `vectorio.polygon`
7 ================================================================================
8
9 vectorio Polygon for Blinka
10
11 **Software and Dependencies:**
12
13 * Adafruit Blinka:
14   https://github.com/adafruit/Adafruit_Blinka/releases
15
16 * Author(s): Melissa LeBlanc-Williams
17
18 """
19
20 from typing import Union, Tuple
21 from displayio._colorconverter import ColorConverter
22 from displayio._palette import Palette
23 from displayio._area import Area
24 from ._vectorshape import _VectorShape
25
26 __version__ = "0.0.0+auto.0"
27 __repo__ = "https://github.com/adafruit/Adafruit_Blinka_displayio.git"
28
29
30 class Polygon(_VectorShape):
31     """Vectorio Polygon"""
32
33     def __init__(
34         self,
35         *,
36         pixel_shader: Union[ColorConverter, Palette],
37         points: list | Tuple[int, int],
38         x: int,
39         y: int,
40     ):
41         """Represents a closed shape by ordered vertices. The path will be treated as
42         'closed', the last point will connect to the first point.
43
44         :param Union[~displayio.ColorConverter,~displayio.Palette] pixel_shader: The pixel
45             shader that produces colors from values
46         :param List[Tuple[int,int]] points: Vertices for the polygon
47         :param int x: Initial screen x position of the 0,0 origin in the points list.
48         :param int y: Initial screen y position of the 0,0 origin in the points list.
49         :param int color_index: Initial color_index to use when selecting color from the palette.
50         """
51         self._color_index = 1
52         self._points = []
53         super().__init__(pixel_shader, x, y)
54         self.points = points
55
56     @property
57     def points(self) -> list | Tuple[int, int]:
58         """The points of the polygon in pixels"""
59         return self._points
60
61     @points.setter
62     def points(self, value: list | Tuple[int, int]) -> None:
63         if len(value) < 3:
64             raise ValueError("Polygon needs at least 3 points")
65         self._points = value
66         self._shape_set_dirty()
67
68     @property
69     def color_index(self) -> int:
70         """The color_index of the polygon as 0 based index of the palette."""
71         return self._color_index - 1
72
73     @color_index.setter
74     def color_index(self, value: int) -> None:
75         self._color_index = abs(value + 1)
76         self._shape_set_dirty()
77
78     @staticmethod
79     def _line_side(
80         line_x1: int,
81         line_y1: int,
82         line_x2: int,
83         line_y2: int,
84         point_x: int,
85         point_y: int,
86     ):
87         # pylint: disable=too-many-arguments
88         return (point_x - line_x1) * (line_y2 - line_y1) - (point_y - line_y1) * (
89             line_x2 - line_x1
90         )
91
92     def _get_pixel(self, x: int, y: int) -> int:
93         # pylint: disable=invalid-name
94         if len(self._points) == 0:
95             return 0
96         winding_number = 0
97         x1 = self._points[0][0]
98         y1 = self._points[0][1]
99         for i in range(1, len(self._points)):
100             x2 = self._points[i][0]
101             y2 = self._points[i][1]
102             if y1 <= y:
103                 if y2 > y and self._line_side(x1, y1, x2, y2, x, y) < 0:
104                     # Wind up, point is to the left of the edge vector
105                     winding_number += 1
106             elif y2 <= y and self._line_side(x1, y1, x2, y2, x, y) > 0:
107                 # Wind down, point is to the right of the edge vector
108                 winding_number -= 1
109             x1 = x2
110             y1 = y2
111
112         return 0 if winding_number == 0 else self._color_index
113
114     def _get_area(self, out_area: Area) -> None:
115         # Figure out the shape dimensions by using min and max
116         out_area.x1 = 32768
117         out_area.y1 = 32768
118         out_area.x2 = 0
119         out_area.y2 = 0
120
121         for x, y in self._points:
122             if x < out_area.x1:
123                 out_area.x1 = x
124             if y < out_area.y1:
125                 out_area.y1 = y
126             if x > out_area.x2:
127                 out_area.x2 = x
128             if y > out_area.y2:
129                 out_area.y2 = y