in ebcli/bundled/asciimatics/screen.py [0:0]
def fill_polygon(self, polygons, colour=7, bg=0):
"""
Draw a filled polygon.
This function uses the scan line algorithm to create the polygon. See
https://www.cs.uic.edu/~jbell/CourseNotes/ComputerGraphics/PolygonFilling.html for details.
:param polygons: A list of polygons (which are each a list of (x,y) coordinates for the
points of the polygon) - i.e. nested list of 2-tuples.
:param colour: The foreground colour to use for the polygon
:param bg: The background colour to use for the polygon
"""
def _add_edge(a, b):
# Ignore horizontal lines - they are redundant
if a[1] == b[1]:
return
# Ignore any edges that do not intersect the visible raster lines at all.
if (a[1] < 0 and b[1] < 0) or (a[1] >= self.height and b[1] >= self.height):
return
# Save off the edge, always starting at the lowest value of y.
new_edge = _DotDict()
if a[1] < b[1]:
new_edge.min_y = a[1]
new_edge.max_y = b[1]
new_edge.x = a[0]
new_edge.dx = (b[0] - a[0]) / (b[1] - a[1]) / 2
else:
new_edge.min_y = b[1]
new_edge.max_y = a[1]
new_edge.x = b[0]
new_edge.dx = (a[0] - b[0]) / (a[1] - b[1]) / 2
edges.append(new_edge)
# Create a table of all the edges in the polygon, sorted on smallest x.
logger.debug("Processing polygon: %s", polygons)
min_y = self.height
max_y = -1
edges = []
last = None
for polygon in polygons:
# Ignore lines and polygons.
if len(polygon) <= 2:
continue
# Ignore any polygons completely off the screen
x, y = zip(*polygon)
p_min_x = min(x)
p_max_x = max(x)
p_min_y = min(y)
p_max_y = max(y)
if p_max_x < 0 or p_min_x >= self.width or p_max_y < 0 or p_min_y > self.height:
continue
# Build up the edge list, maintaining bounding coordinates on the Y axis.
min_y = min(p_min_y, min_y)
max_y = max(p_max_y, max_y)
for i, point in enumerate(polygon):
if i != 0:
_add_edge(last, point)
last = point
_add_edge(polygon[0], polygon[-1])
edges = sorted(edges, key=lambda e: e.x)
# Check we still have something to do:
if len(edges) == 0:
return
# Re-base all edges to visible Y coordinates of the screen.
for edge in edges:
if edge.min_y < 0:
edge.x -= int(edge.min_y * 2) * edge.dx
edge.min_y = 0
min_y = max(0, min_y)
max_y = min(max_y - min_y, self.height)
logger.debug("Resulting edges: %s", edges)
# Render each line in the bounding rectangle.
for y in [min_y + (i / 2) for i in range(0, int(max_y) * 2)]:
# Create a list of live edges (for drawing this raster line) and edges for next
# iteration of the raster.
live_edges = []
new_edges = []
for edge in edges:
if edge.min_y <= y <= edge.max_y:
live_edges.append(edge)
if y < edge.max_y:
new_edges.append(edge)
# Draw the portions of the line that are inside the polygon.
count = 0
last_x = 0
for edge in live_edges:
# Draw the next segment
if 0 <= y < self.height:
if edge.max_y != y:
count += 1
if count % 2 == 1:
last_x = edge.x
else:
# Don't bother drawing lines entirely off the screen.
if not ((last_x < 0 and edge.x < 0) or
(last_x >= self.width and edge.x >= self.width)):
# Clip raster to screen width.
self.move(max(0, last_x), y)
self.draw(
min(edge.x, self.width), y, colour=colour, bg=bg, thin=True)
# Update the x location for this active edge.
edge.x += edge.dx
# Rely on the fact that we have the same dicts in both live_edges and new_edges, so
# we just need to resort new_edges for the next iteration.
edges = sorted(new_edges, key=lambda e: e.x)