Как создать круговое изображение, используя pyqt4? - PullRequest
2 голосов
/ 05 ноября 2019

Здесь я написал этот код, но не работал:

import sys
from PyQt4 import QtGui, QtCore

class CricleImage(QtCore.QObject):
    def __init__(self):
        super(CricleImage, self).__init__()

        self.pix = QtGui.QGraphicsPixmapItem(QtGui.QPixmap("bird(01).jpg"))

        #drawRoundCircle
        rect = self.pix.boundingRect()
        self.gri = QtGui.QGraphicsRectItem(rect)
        self.gri.setPen(QtGui.QColor('red'))


if __name__ == '__main__':

    myQApplication = QtGui.QApplication(sys.argv)

    IMG = CricleImage()
    #scene
    scene = QtGui.QGraphicsScene(0, 0, 400, 300)
    scene.addItem(IMG.pix)
    #view
    view = QtGui.QGraphicsView(scene)
    view.show()
    sys.exit(myQApplication.exec_())

Ответы [ 3 ]

1 голос
/ 06 ноября 2019

Второе возможное решение:

import sys
#from PyQt4 import QtCore, QtGui

from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *


class Label(QLabel):
    def __init__(self, *args, antialiasing=True, **kwargs):
        super(Label, self).__init__(*args, **kwargs)
        self.Antialiasing = antialiasing
        self.setMaximumSize(200, 200)
        self.setMinimumSize(200, 200)
        self.radius = 100 

        self.target = QPixmap(self.size())  
        self.target.fill(Qt.transparent)    # Fill the background with transparent

        # Upload image and zoom to control level
        p = QPixmap("head2.jpg").scaled(  
            200, 200, Qt.KeepAspectRatioByExpanding, Qt.SmoothTransformation)

        painter = QPainter(self.target)
        if self.Antialiasing:
            # antialiasing
            painter.setRenderHint(QPainter.Antialiasing, True)
            painter.setRenderHint(QPainter.HighQualityAntialiasing, True)
            painter.setRenderHint(QPainter.SmoothPixmapTransform, True)

        path = QPainterPath()
        path.addRoundedRect(
            0, 0, self.width(), self.height(), self.radius, self.radius)

        # pruning
        painter.setClipPath(path)
        painter.drawPixmap(0, 0, p)
        self.setPixmap(self.target)


class Window(QWidget):
    def __init__(self, *args, **kwargs):
        super(Window, self).__init__(*args, **kwargs)
        layout = QHBoxLayout(self)
        layout.addWidget(Label(self))
        self.setStyleSheet("background: green;")           


if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = Window()
    w.show()
    sys.exit(app.exec_())

enter image description here

1 голос
/ 08 ноября 2019

Другой подход, немного отличающийся от подхода , предоставляемого eyllanesc . Хотя это может показаться гораздо более сложным, чем это, я считаю, что он предлагает лучшую реализацию и интерфейс, с добавлением лучшей производительности.

В этом случае вместо переопределения метода рисования (который запускается каждый раз, когдаэлемент окрашен, что случается очень часто), я использую функцию shape() вместе с флагом QGraphicsItem.ItemClipsToShape, которая позволяет ограничить рисование только в пределах границы формы пути.

Что делает shape(), так это возвращает QPainterPath, который включает только «непрозрачные» части элемента, которые будут реагировать на события мыши и обнаружение столкновений (со сценойграницы и другие ее элементы). В случае QGraphicsPixmapItem это также учитывает возможную маску (например, растровое изображение на основе PNG с прозрачными областями или изображение SVG). Установив ItemClipsToShape, мы можем гарантировать, что рисование будет охватывать только те части изображения, которые находятся внутри этой фигуры.

Основным преимуществом этого подхода является то, что взаимодействие мыши и обнаружение столкновений с другими элементами учитывают фактический кругФорма изделия

Это означает, что если вы щелкнете за пределами круга (но все еще в пределах прямоугольника область полного изображения), элемент не получит событие. Кроме того, если изображение поддерживает маскирование (PNG с прозрачными областями), которое по умолчанию не будет частью фигуры, этот метод будет учитывать это.

Кроме того, «кэшируя» фигуру, мы такженемного ускорить процесс рисования (поскольку Qt позаботится об этом, без какой-либо обработки, выполняемой с использованием python).

class CircleClipPixmapItem(QtGui.QGraphicsPixmapItem):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.setFlag(self.ItemClipsToShape)
        self.updateRect()

    def updateRect(self):
        baseRect = super().boundingRect()
        minSize = min(baseRect.width(), baseRect.height())
        self._boundingRect = QtCore.QRectF(0, 0, minSize, minSize)
        self._boundingRect.moveCenter(baseRect.center())
        self._shape = QtGui.QPainterPath()
        self._shape.addEllipse(self._boundingRect)
        # the shape might include transparent areas, using the & operator
        # I'm ensuring that _shape only includes the areas that intersect
        # the shape provided by the base implementation
        self._shape &= super().shape()

    def setPixmap(self, pm):
        super().setPixmap(pm)
        # update the shape to reflect the new image size
        self.updateRect()

    def setShapeMode(self, mode):
        super().setShapeMode(mode)
        # update the shape with the new mode
        self.updateRect()

    def boundingRect(self):
        return self._boundingRect

    def shape(self):
        return self._shape

Имейте в виду, что есть ловушка для обоих методов:если соотношение сторон изображения сильно отличается от 1: 1, у вас всегда будут проблемы с позиционированием. Например, для моего изображения оно всегда будет отображаться в 60 пикселях справа от фактической позиции элемента. Если вы хотите избежать этого, функция updateRect будет немного отличаться, и, к сожалению, вам придется переопределить функцию paint() (при этом она останется немного быстрее, чем другие параметры):

    def updateRect(self):
        baseRect = super().boundingRect()
        minSize = min(baseRect.width(), baseRect.height())
        self._boundingRect = QtCore.QRectF(0, 0, minSize, minSize)
        # the _boundingRect is *not* centered anymore, but a new rect is created
        # as a reference for both shape intersection and painting
        refRect= QtCore.QRectF(self._boundingRect)
        refRect.moveCenter(baseRect.center())
        # note the minus sign!
        self._reference = -refRect.topLeft()
        self._shape = QtGui.QPainterPath()
        self._shape.addEllipse(self._boundingRect)
        self._shape &= super().shape().translated(self._reference)

    # ...

    def paint(self, painter, option, widget):
        # we are going to translate the painter to the "reference" position,
        # let's save its state before that
        painter.save()
        painter.translate(self._reference)
        super().paint(painter, option, widget)
        painter.restore()

Это заставит boundingRect (и полученную внутреннюю форму) позиционировать весь элемент в верхнем левом углу позиции элемента.


На следующем рисунке показаны различия между двумя подходами;Я использовал PNG с прозрачными областями, чтобы лучше объяснить всю концепцию.
В верхней части находится исходное изображение, в середине - подход переопределения paint() и, наконец, реализация shape() внизу.

clipping examples

Хотя, кажется, нет никакой разницы между двумя методами, как показано в примерах слева, справа я выделил фактическиеграницы каждого элемента, показывая их boundingRect (синим цветом), shape (красным цветом), которые будут использоваться для событий мыши, обнаружения столкновений и отсечения краски;зеленый кружок показывает общий круг, используемый как для формы, так и для рисования.
Примеры в центре показывают позиционирование на основе исходного размера изображения, в то время как справа вы можете видеть абсолютное позиционирование на основе эффективного размера круга, как объяснено. выше.

Рисование круга вокруг изображения

К сожалению, флаг ItemClipsToShape не поддерживает сглаживание для отсечения: если мы просто нарисуем круг после рисования изображения, результат будет ужасным,Слева вы можете видеть, что круг очень пикселизирован и не полностью перекрывает изображение. Справа - правильное рисование.

clip paint method differencies

Для поддержки этого флаг не должен быть установлен, и функция рисования будет немного отличаться.

class CircleClipPixmapItem(QtGui.QGraphicsPixmapItem):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # we don't need this anymore:
        # self.setFlag(self.ItemClipsToShape)

        # always set the shapeMode to the bounding rect without any masking:
        # if the image has transparent areas they will be clickable anyway
        self.setShapeMode(self.BoundingRectShape)
        self.updateRect()
        self.pen = QtGui.QPen(QtCore.Qt.red, 2)

    # ...

    def setPen(self, pen):
        self.pen = pen
        self.update()

    def paint(self, painter, option, widget):
        # we are going to translate the painter to the "reference" position,
        # and we are also changing the pen, let's save the state before that
        painter.save()
        painter.translate(.5, .5)
        painter.setRenderHints(painter.Antialiasing)
        # another painter save "level"
        painter.save()
        # apply the clipping to the painter
        painter.setClipPath(self._shape)
        painter.translate(self._reference)
        super().paint(painter, option, widget)
        painter.restore()

        painter.setPen(self.pen)
        # adjust the rectangle to precisely match the circle to the image
        painter.drawEllipse(self._boundingRect.adjusted(.5, .5, -.5, -.5))
        painter.restore()
        # restore the state of the painter
1 голос
/ 06 ноября 2019

Одним из возможных решений является перезапись метода paint () в QGraphicsPixmapItem и использование setClipPath для ограничения области рисования:

from PyQt4 import QtCore, QtGui


class CirclePixmapItem(QtGui.QGraphicsPixmapItem):
    @property
    def radius(self):
        if not hasattr(self, "_radius"):
            self._radius = 0
        return self._radius

    @radius.setter
    def radius(self, value):
        if value >= 0:
            self._radius = value
            self.update()

    def paint(self, painter, option, widget=None):
        painter.save()
        rect = QtCore.QRectF(QtCore.QPointF(), 2 * self.radius * QtCore.QSizeF(1, 1))
        rect.moveCenter(self.boundingRect().center())
        path = QtGui.QPainterPath()
        path.addEllipse(rect)
        painter.setClipPath(path)
        super().paint(painter, option, widget)
        painter.restore()


if __name__ == "__main__":
    import sys

    app = QtGui.QApplication(sys.argv)

    pixmap = QtGui.QPixmap("logo.jpg")

    scene = QtGui.QGraphicsScene()
    view = QtGui.QGraphicsView(scene)
    view.setRenderHints(
        QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform
    )

    it = CirclePixmapItem(pixmap)
    scene.addItem(it)

    it.radius = pixmap.width() / 2

    view.show()
    sys.exit(app.exec_())

enter image description here

Обновление:

# ...
view = QtGui.QGraphicsView(
    scene, <b>alignment=QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft</b>
)
# ...
view.show()
<b>it.setPos(80, 80)</b>
sys.exit(app.exec_())
...