def fill_polygon()

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)