简体   繁体   English

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

[英]How to create circular image using pyqt4?

Here I wrote this code but did not work:在这里我写了这段代码但没有工作:

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_())

One possible solution is to overwrite the paint() method of the QGraphicsPixmapItem and use setClipPath to restrict the painting region:一种可能的解决方案是覆盖 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_())

在此处输入图像描述

Update:更新:

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

Second possible solution:第二种可能的解决方案:

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_())

在此处输入图像描述

Another approach, slightly different from the one provided by eyllanesc .另一种方法,与eyllanesc 提供的方法略有不同。 While this might seem much more complicated than that, I believe that it offers a better implementation and interface, with the addition of better performance.虽然这看起来可能比这复杂得多,但我相信它提供了更好的实现和接口,并增加了更好的性能。

In this case, instead of overriding the paint method (that is run everytime the item is painted, which happens very often), I'm using the shape() function along with the QGraphicsItem.ItemClipsToShape flag, that allows to limit the painting only within the boundaries of the path shape.在这种情况下,我使用shape() function 和QGraphicsItem.ItemClipsToShape标志,而不是覆盖绘制方法(即在每次绘制项目时运行,这种情况经常发生),它只允许限制绘制路径形状的边界内。

What shape() does is to return a QPainterPath that includes only the "opaque" portions of an item that will react to mouse events and collision detection (with the scene boundaries and its other items). shape()的作用是返回一个 QPainterPath,其中仅包含一个项目的“不透明”部分,这些部分将对鼠标事件和碰撞检测(与场景边界及其其他项目)做出反应。 In the case of a QGraphicsPixmapItem this also considers the possible mask (for example, a PNG based pixmap with transparent areas, or an SVG image).在 QGraphicsPixmapItem 的情况下,这也考虑了可能的掩码(例如,具有透明区域的基于 PNG 的像素图,或 SVG 图像)。 By setting the ItemClipsToShape we can ensure that the painting will only cover the parts of the image that are within that shape.通过设置 ItemClipsToShape,我们可以确保绘画只会覆盖该形状内的图像部分。

The main advantage of this approach is that mouse interaction and collision detection with other items honors the actual circle shape of the item.这种方法的主要优点是鼠标与其他项目的交互和碰撞检测尊重项目的实际圆形形状。

This means that if you click outside the circle (but still within the rectangle area of the full image), the item will not receive the event.这意味着,如果您在圆圈外单击(但仍在整个图像的矩形区域内),则该项目将不会收到该事件。 Also, if the image supports masking (a PNG with transparent areas) which by default would not be part of the shape, this method will take that into account.此外,如果图像支持默认情况下不属于形状的蒙版(具有透明区域的 PNG),则此方法将考虑到这一点。

Also, by "caching" the shape we are also speeding up the painting process a bit (since Qt will take care of it, without any processing done using python).此外,通过“缓存”形状,我们也稍微加快了绘制过程(因为 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

Keep in mind that there's a catch about both methods: if the aspect ratio of the image differs very much from 1:1, you'll always end up with some positioning issues.请记住,这两种方法都有一个问题:如果图像的纵横比与 1:1 相差很大,您总会遇到一些定位问题。 With my image, for example, it will always be shown 60 pixel right from the actual item position.例如,对于我的图像,它将始终显示为距实际项目 position 60 像素。 If you want to avoid that, the updateRect function will be slightly different and, unfortunately, you'll have to override the paint() function (while still keeping it a bit faster than other options):如果你想避免这种情况, updateRect function 会略有不同,不幸的是,你必须覆盖paint() function (同时仍然比其他选项快一点):

    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()

This will make the boundingRect (and resulting internal shape) position the whole item at the top-left of the item position.这将使boundingRect(和产生的内部形状)position成为项目position左上角的整个项目。


The following image shows the differences between the two approaches;下图显示了两种方法之间的差异; I've used a PNG with transparent areas to better explain the whole concept.我使用了带有透明区域的 PNG 来更好地解释整个概念。
On the top there is the source image, in the middle the paint() override approach, and finally the shape() implementation at the bottom.顶部是源图像,中间是paint()覆盖方法,最后是底部的shape()实现。

剪辑示例

While there seems to be no difference between the two methods, as shown on the examples on the left, on the right I've highlighted the actual boundaries of each item, by showing their boundingRect (in blue), shape (in red), which will be used for mouse events, collision detection and paint clipping;虽然这两种方法之间似乎没有区别,如左侧示例所示,但在右侧,我通过显示它们的boundingRect (蓝色)、 shape (红色)突出显示了每个项目的实际边界,这将用于鼠标事件、碰撞检测和绘画剪辑; the green circle shows the overall circle used for both shape and painting.绿色圆圈显示了用于形状和绘画的整体圆圈。
The examples in the middle show the positioning based on the original image size, while on the right you can see the absolute positioning based on the effective circle size as explained above.中间的示例显示了基于原始图像大小的定位,而右侧则可以看到基于有效圆大小的绝对定位,如上所述。

Drawing a circle around the image在图像周围画一个圆圈

Unfortunately, the ItemClipsToShape flag doesn't support antialiasing for clipping: if we just draw a circle after painting the image the result will be ugly.不幸的是, ItemClipsToShape标志不支持剪切的抗锯齿:如果我们在绘制图像后只画一个圆圈,结果会很丑。 On the left you can see that the circle is very pixellated and does not overlap perfectly on the image.在左侧,您可以看到圆圈非常像素化,并且在图像上没有完美重叠。 On the right the correct painting.右边是正确的画。

剪贴画方法差异

To support that, the flag must not be set, and the paint function will be a bit different.为了支持这一点,不能设置标志,并且油漆 function 会有点不同。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM