如何使用pyqt4创建圆形图像?

时间:2019-11-05 14:37:24

标签: python pyqt pyqt4

我在这里编写了这段代码,但是没有用:

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 个答案:

答案 0 :(得分:2)

一种可能的解决方案是覆盖QGraphicsPixmapItem的paint()方法并使用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, alignment=QtCore.Qt.AlignTop | QtCore.Qt.AlignLeft
)
# ...
view.show()
it.setPos(80, 80)
sys.exit(app.exec_())

答案 1 :(得分:2)

第二种可能的解决方案:

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

答案 2 :(得分:2)

另一种方法,与provided by eyllanesc略有不同。尽管这看起来可能比这复杂得多,但我相信它提供了更好的实现和接口,并具有更好的性能。

在这种情况下,我将使用shape()函数以及以下命令,而不是覆盖paint方法(每次绘制该项目时都会运行,这种情况经常发生 )。 QGraphicsItem.ItemClipsToShape标志,用于仅在路径形状的边界内 限制绘画。

shape()的作用是返回一个QPainterPath,该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