Search code examples
pythonpyqt5qgraphicsviewqgraphicssceneqpolygon

PyQt5: Filling in the holes in QPolygon // Increasing the size (height) of a qpolygon


I have the following QPolygon: enter image description here

and I need to increase its size in height to get a following QPolygon:

enter image description here

whereas the lower boundary of the qpolygon stays in the same location, that is x1,y1 in the first image, is still x1,y1 in the second image and the black area that is added above should be half of the height of the qpolygon itself (ideally with a straight line, but not too important)

To do so, I took the bounding box of the QPolygon

poly1_bounding_rect=QPolygonF(poly_1_coords).boundingRect()

and the X0,Y0 coordinates, as well as width and height, and then made the polygon addition to be united with the original polygon

max_y, min_x, height_, width_ = poly1_bounding_rect.y(), poly1_bounding_rect.x(), poly1_bounding_rect.height(), poly1_bounding_rect.width()
poly1_addition = QPolygonF(
    [QPointF(min_x, max_y), QPointF(min_x, max_y - height_ * .5),
     QPointF(min_x + width_, max_y - height_ * .5),
     QPointF(min_x + width_, max_y)])

However this solution leaves the QPolygon to be with holes and I am unsure how should I fill them in, or alternatively create a QPolygon without these wholes in the first place.

Any guidance is highly appreciated, thanks!

enter image description here

Here's a minimum reproducible example:

import PyQt5
from PyQt5 import QtCore

import sys
import PyQt5
from PyQt5.QtCore import *#QPointF, QRectF
from PyQt5.QtGui import *#QPainterPath, QPolygonF, QBrush,QPen,QFont,QColor, QTransform
from PyQt5.QtWidgets import *#QApplication, QGraphicsScene, QGraphicsView, QGraphicsSimpleTextItem


poly_1_coords= [PyQt5.QtCore.QPointF(412.01, 257.98), PyQt5.QtCore.QPointF(372.24, 197.66), PyQt5.QtCore.QPointF(358.88, 230.95), PyQt5.QtCore.QPointF(371.46, 278.4), PyQt5.QtCore.QPointF(381.16, 291.77), PyQt5.QtCore.QPointF(428.34, 308.81), PyQt5.QtCore.QPointF(483.66, 341.84), PyQt5.QtCore.QPointF(648.02, 373.29), PyQt5.QtCore.QPointF(945.05, 386.61), PyQt5.QtCore.QPointF(1087.66, 374.02), PyQt5.QtCore.QPointF(1285.26, 331.05), PyQt5.QtCore.QPointF(1317.34, 304.71), PyQt5.QtCore.QPointF(1345.85, 265.03), PyQt5.QtCore.QPointF(1340.6, 214.96), PyQt5.QtCore.QPointF(1326.19, 197.39), PyQt5.QtCore.QPointF(1303.38, 243.79), PyQt5.QtCore.QPointF(1134.3, 290.19), PyQt5.QtCore.QPointF(1091.57, 306.71), PyQt5.QtCore.QPointF(1067.45, 295.44), PyQt5.QtCore.QPointF(1017.38, 250.09), PyQt5.QtCore.QPointF(993.53, 218.63), PyQt5.QtCore.QPointF(925.89, 219.41), PyQt5.QtCore.QPointF(852.23, 233.31), PyQt5.QtCore.QPointF(723.52, 219.41), PyQt5.QtCore.QPointF(671.51, 309.58), PyQt5.QtCore.QPointF(638.88, 313.37), PyQt5.QtCore.QPointF(583.5, 299.71), PyQt5.QtCore.QPointF(485.61, 240.53), PyQt5.QtCore.QPointF(451.47, 270.88)]

def main():
    app = QApplication(sys.argv)



    scene = QGraphicsScene()
    view = QGraphicsView(scene)
    pen_ = QPen(QColor(0, 20, 255))
    brush_ = QBrush(QColor(0,0,0))

    poly_1 = QPolygonF(poly_1_coords)
    #scene.addPolygon(poly_1,pen_,brush_)

    poly1_bounding_rect = poly_1.boundingRect()
    max_y, min_x, height_, width_ = poly1_bounding_rect.y(), poly1_bounding_rect.x(), poly1_bounding_rect.height(), poly1_bounding_rect.width()

    poly1_addition = QPolygonF(
        [QPointF(min_x, max_y), QPointF(min_x, max_y - height_ * .5),
         QPointF(min_x + width_, max_y - height_ * .5),
         QPointF(min_x + width_, max_y)])

    complete_poly1 = poly_1.united(poly1_addition)




    scene.addPolygon(complete_poly1,pen_,brush_)



    view.show()

    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

Solution

  • Assuming that the polygon is always a simple one, without any intersection, you can do the following:

    • find the points that are at the minimum and maximum x (left and right) and the maximum y (bottom);
    • build two separate paths that will be the top and bottom sides starting from the two points above;
    • find which one includes the bottom point, which will mean that it's the "bottom side";
    • translate the bounding rect by half its height, and use its corners as additional points for the polygon;
        poly_1 = QPolygonF(poly_1_coords)
        poly_1_bounding_rect = poly_1.boundingRect()
        # translate the bounding rect by half its height to get the top corners
        poly_1_bounding_rect.translate(
            QPointF(0, -poly_1_bounding_rect.height() * .5))
    
        # find the left/right/bottom-most points
        left = right = bottom = None
        for point in poly_1_coords:
            if not left or left.x() > point.x():
                left = point
            if not right or right.x() < point.x():
                right = point
            if not bottom or bottom.y() < point.y():
                bottom = point
    
        # assuming points are in correct order and there is no intersection between
        # lines, get the indexes of the left and right points to build two separate
        # paths, one from the top side, one for the bottom
        leftIndex = poly_1_coords.index(left)
        rightIndex = poly_1_coords.index(right)
        if leftIndex < rightIndex:
            side_a = poly_1_coords[leftIndex:rightIndex + 1]
            side_b = poly_1_coords[rightIndex:] + poly_1_coords[:leftIndex + 1]
        else:
            side_a = poly_1_coords[rightIndex:leftIndex + 1]
            side_b = poly_1_coords[leftIndex:] + poly_1_coords[:rightIndex + 1]
    
        # look for the bottom point to get the bottom side
        if bottom in side_a:
            final = side_a
        else:
            final = side_b
    
        # if the first point is also the left, the coordinates are counter clockwise
        # so we add the top corners accordingly, and vice versa
        if final[0] == left:
            final += [poly_1_bounding_rect.topRight(), poly_1_bounding_rect.topLeft()]
        else:
            final += [poly_1_bounding_rect.topLeft(), poly_1_bounding_rect.topRight()]
    
        item = scene.addPolygon(QPolygonF(final))
        item.setPen(pen_)
        item.setBrush(brush_)
    

    Alternatively, you can add the top side by translating it as required:

        # look for the bottom point to get the bottom side and add the other
        # by translating by half the bounding rect height
        delta = QPointF(0, -poly_1_bounding_rect.height() * .5)
        if bottom in side_a:
            final = side_a + [p + delta for p in side_b]
        else:
            final = side_b + [p + delta for p in side_a]