簡體   English   中英

如何使用 pyqt4 創建圓形圖像?

[英]How to create circular image using 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_())

一種可能的解決方案是覆蓋 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_())

在此處輸入圖像描述

更新:

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

第二種可能的解決方案:

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

在此處輸入圖像描述

另一種方法,與eyllanesc 提供的方法略有不同。 雖然這看起來可能比這復雜得多,但我相信它提供了更好的實現和接口,並增加了更好的性能。

在這種情況下,我使用shape() function 和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 相差很大,您總會遇到一些定位問題。 例如,對於我的圖像,它將始終顯示為距實際項目 position 60 像素。 如果你想避免這種情況, 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()

這將使boundingRect(和產生的內部形狀)position成為項目position左上角的整個項目。


下圖顯示了兩種方法之間的差異; 我使用了帶有透明區域的 PNG 來更好地解釋整個概念。
頂部是源圖像,中間是paint()覆蓋方法,最后是底部的shape()實現。

剪輯示例

雖然這兩種方法之間似乎沒有區別,如左側示例所示,但在右側,我通過顯示它們的boundingRect (藍色)、 shape (紅色)突出顯示了每個項目的實際邊界,這將用於鼠標事件、碰撞檢測和繪畫剪輯; 綠色圓圈顯示了用於形狀和繪畫的整體圓圈。
中間的示例顯示了基於原始圖像大小的定位,而右側則可以看到基於有效圓大小的絕對定位,如上所述。

在圖像周圍畫一個圓圈

不幸的是, ItemClipsToShape標志不支持剪切的抗鋸齒:如果我們在繪制圖像后只畫一個圓圈,結果會很丑。 在左側,您可以看到圓圈非常像素化,並且在圖像上沒有完美重疊。 右邊是正確的畫。

剪貼畫方法差異

為了支持這一點,不能設置標志,並且油漆 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