首页 > 解决方案 > FastBlur 可以模糊它背后的一切吗?

问题描述

我有这个代码,qml

import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.12
import QtGraphicalEffects 1.0
ApplicationWindow {
    property var theme: String("#ffffff")
    property var focusColor: String('transparent')
    id: applicationWindow
    visible: false                                      
    width: 600
    height:600
    Image {
        id: image_bug
        anchors.fill: parent
        source: "im.png"                                
    }   
    Rectangle {
    width: 100; height: 600
    color: "green"
   Text {
    id: helloText
    text: "Hello world!"
    anchors.verticalCenter: parent.verticalCenter
    anchors.horizontalCenter: parent.horizontalCenter
    font.pointSize: 10; font.bold: true
}
    MouseArea {
        anchors.fill: parent
        onClicked: { effectSource.width = 1200; effectSource.height = 1200;}
    }
}
    ShaderEffectSource {
        id: effectSource
        sourceItem: image_bug
        anchors.centerIn: image_bug
        width: 300 
        height: 300 
        sourceRect: Qt.rect(x,y, width, height)
    }
    FastBlur{
        id: blur
        anchors.fill: effectSource
        source: effectSource
        radius: 100
    }
}

PyQT5 或 Pyside2

import sys
import os                                                            # +++
from PyQt5.QtCore import Qt, QUrl
from PyQt5.QtWidgets import QApplication, QWidget, QMainWindow, QVBoxLayout, QLabel
from PyQt5.QtQml import QQmlApplicationEngine
from PyQt5.QtCore import (QCoreApplication, QMetaObject, QObject, QPoint,
    QRect, QSize, QUrl, Qt)
    '''
    from PySide2.QtCore import Qt, QUrl
    from PySide2.QtWidgets import QApplication, QWidget, QMainWindow, QVBoxLayout, QLabel
    from PySide2.QtQml import QQmlApplicationEngine
    from PySide2.QtCore import (QCoreApplication, QMetaObject, QObject, QPoint,
        QRect, QSize, QUrl, Qt)
    '''
DIR_PATH = os.path.dirname(os.path.realpath(__file__))
class GUI_MainWindow(QMainWindow):
    def __init__(self, widget, parent=None):
        QMainWindow.__init__(self, parent)
        self.setWindowTitle('GUI_MainWindow')
        self.resize(600, 600)
        self.widget = widget
        centralWidget = QWidget()
        self.setCentralWidget(centralWidget)    
        self.widget_test_2 = QLabel("<h1>Hello World !</h1>", alignment=Qt.AlignCenter)
        self.widget_test_2.setObjectName(u"widget_test_2")
        self.widget_test_2.setGeometry(QRect(180, 40, 151, 181))
        self.widget_test_2.raise_()
        layout = QVBoxLayout(centralWidget)
        layout.addWidget(self.widget_test_2)
        layout.addWidget(self.widget, stretch=1)#blur box
if __name__ == "__main__":
    myApp = QApplication(sys.argv)
    file = os.path.join(DIR_PATH, "qml_window.qml")                     
    url = QUrl.fromLocalFile(file)                                      
    engine = QQmlApplicationEngine()
    context = engine.rootContext()
    context.setContextProperty("main", engine)
    engine.load(url)
    if not engine.rootObjects():
        sys.exit(-1)
    widget = QWidget.createWindowContainer(engine.rootObjects()[0])
    window = GUI_MainWindow(widget)
    window.show()

    sys.exit(myApp.exec_())     

我希望 ShaderEffectSource 模糊它背后的一切,

甚至由 PyQt5 或 PySide2 创建的小部件。在移动或留在原地时

小部件后面的所有内容都应该是模糊的。

我已经尝试为此使用 QGraphicsBlurEffect 效果,但这并没有给我想要的结果。我希望 FastBlur 可以做到。如果还有其他选择,请告诉我

我可以做吗?

图片

标签: pythonqtpyqt5pyside2qt-quick

解决方案


我正在更新此答案,因为问题已在评论中部分清除,但我将在最后保留原始答案,因为它可能仍然有用。

除此之外,基本概念保持不变:图形效果应用于对象,它修改对象的外观,而不是底层对象的外观。如果您想将该效果应用于多个对象,它们必须是共同父对象的子对象,并且必须为该父对象设置效果,但该父对象下方的所有内容(并且不是其子对象)将只是部分影响效果的结果
将模糊效果想象为应用于部分透明的真实照片的滤镜:虽然照片中的图像是模糊的,但您在它后面看到的东西不会模糊。

子类化图形效果

QGraphicsEffects 不提供限制其处理范围的能力,因为它们通常会修改为其设置的对象的整个“边界矩形”。
为了实现这一点,子类化是必要的,并且draw()必须重写该方法,因为它是负责实际绘图的方法。

我将假设整个界面会以某种方式受到效果的影响:即使某些对象在效果的矩形“外部”,它们仍然是同一个父对象的一部分,所以这就是我们'要做:

  • 创建一个主小部件,作为完整界面的容器
  • 为主界面添加主布局(通常显示的那个)
  • 创建一个包含界面的子小部件,为其设置布局并将您需要的任何内容添加到该布局
  • 将子类图形效果设置为小部件
  • 为菜单创建一个小部件,将主小部件作为父小部件,因此它不会成为主布局的一部分;它将有自己的布局,包括按钮、标签等。
  • 添加一个根据菜单的几何形状更改图形效果的系统,并且每当更改时,效果将仅应用于该几何形状

一个非常酷的菜单!

class BlurEffect(QtWidgets.QGraphicsBlurEffect):
    effectRect = None

    def setEffectRect(self, rect):
        self.effectRect = rect
        self.update()

    def draw(self, qp):
        if self.effectRect is None or self.effectRect.isNull():
            # no valid effect rect to be used, use the default implementation
            super().draw(qp)
        else:
            qp.save()
            # clip the drawing so that it's restricted to the effectRect
            qp.setClipRect(self.effectRect)
            # call the default implementation, which will draw the effect
            super().draw(qp)
            # get the full region that should be painted
            fullRegion = QtGui.QRegion(qp.viewport())
            # and subtract the effect rectangle
            fullRegion -= QtGui.QRegion(self.effectRect)
            qp.setClipRegion(fullRegion)
            # draw the *source*, which has no effect applied
            self.drawSource(qp)
            qp.restore()


class Window(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()

        background = QtGui.QPixmap('background.png')

        # apply a background to this widget, note that this only serves for the
        # graphics effect to know what's outside the boundaries
        p = self.palette()
        p.setBrush(p.Window, QtGui.QBrush(background))
        self.setPalette(p)

        self.resize(background.size())

        # this layout is only for the child "sub" widget
        mainLayout = QtWidgets.QVBoxLayout(self)
        mainLayout.setContentsMargins(0, 0, 0, 0)

        # the "sub" widget, that contains the main interface
        self.subWidget = QtWidgets.QWidget()
        mainLayout.addWidget(self.subWidget)
        # set the background for the subwidget; note that we can't use setPalette()
        # because palette and fonts are inherited by children; using ".QWidget"
        # we ensure that the background is only applied to the subwidget
        self.subWidget.setStyleSheet('''
            .QWidget {
                background-image: url(background.png);
            }
        ''')

        # some random widgets
        subLayout = QtWidgets.QGridLayout(self.subWidget)
        for row in range(3):
            for col in range(3):
                btn = QtWidgets.QPushButton()
                subLayout.addWidget(btn, row, col)

        btn.setText('Open menu')
        btn.setFocus()
        btn.clicked.connect(self.openMenu)

        # create an instance of our effect subclass, and apply it to the subwidget
        self.effect = BlurEffect()
        self.subWidget.setGraphicsEffect(self.effect)
        self.effect.setEnabled(False)
        self.effect.setBlurRadius(10)

        # create the menu container, that *HAS* to have this main widget as parent
        self.topMenu = QtWidgets.QWidget(self)
        self.topMenu.setVisible(False)
        self.topMenu.setFixedWidth(200)
        # move the menu outside the window left margin
        self.topMenu.move(-self.topMenu.width(), 0)

        menuLayout = QtWidgets.QVBoxLayout(self.topMenu)
        menuLayout.addSpacing(20)
        for b in range(4):
            btn = QtWidgets.QPushButton('Button {}'.format(b + 1))
            menuLayout.addWidget(btn)

        menuLayout.addSpacing(10)

        closeButton = QtWidgets.QPushButton('Close menu')
        menuLayout.addWidget(closeButton)
        closeButton.clicked.connect(self.closeMenu)
        # a stretch to ensure that the items are always aligned on top
        menuLayout.addStretch(1)

        # an animation that will move the menu laterally
        self.menuAnimation = QtCore.QVariantAnimation()
        self.menuAnimation.setDuration(500)
        self.menuAnimation.setEasingCurve(QtCore.QEasingCurve.OutQuart)
        self.menuAnimation.setStartValue(-self.topMenu.width())
        self.menuAnimation.setEndValue(0)
        self.menuAnimation.valueChanged.connect(self.resizeMenu)
        self.menuAnimation.finished.connect(self.animationFinished)

        # a simple transparent widget that is used to hide the menu when
        # clicking outside it; the event filter is to capture click events
        # it may receive
        self.clickGrabber = QtWidgets.QWidget(self)
        self.clickGrabber.installEventFilter(self)
        self.clickGrabber.setVisible(False)

    def resizeMenu(self, value):
        # move the menu and set its geometry to the effect
        self.topMenu.move(value, 0)
        self.effect.setEffectRect(self.topMenu.geometry())

    def openMenu(self):
        if self.topMenu.x() >= 0:
            # the menu is already visible
            return
        # ensure that the menu starts hidden (that is, with its right border
        # aligned to the left of the main widget)
        self.topMenu.move(-self.topMenu.width(), 0)
        self.topMenu.setVisible(True)
        self.topMenu.setFocus()

        # enable the effect, set the forward direction for the animation, and
        # start it; it's important to set the effect rectangle here too, otherwise
        # some flickering might show at the beginning
        self.effect.setEffectRect(self.topMenu.geometry())
        self.effect.setEnabled(True)
        self.menuAnimation.setDirection(QtCore.QVariantAnimation.Forward)
        self.menuAnimation.start()

        # "show" the grabber (it's invisible, but it's there) and resize it
        # to cover the whole window area
        self.clickGrabber.setGeometry(self.rect())
        self.clickGrabber.setVisible(True)
        # ensure that it is stacked under the menu and above everything else
        self.clickGrabber.stackUnder(self.topMenu)

    def closeMenu(self):
        # in case that the menu has changed its size, set again the "start" value
        # to its negative width, then set the animation direction to backwards
        # and start it
        self.menuAnimation.setStartValue(-self.topMenu.width())
        self.menuAnimation.setDirection(QtCore.QVariantAnimation.Backward)
        self.menuAnimation.start()
        # hide the click grabber
        self.clickGrabber.setVisible(False)

    def animationFinished(self):
        # if the animation has ended and the direction was backwards it means that
        # the menu has been closed, hide it and disable the effect
        if self.menuAnimation.direction() == QtCore.QVariantAnimation.Backward:
            self.topMenu.hide()
            self.effect.setEnabled(False)

    def focusNextPrevChild(self, next):
        if self.topMenu.isVisible():
            # a small hack to prevent tab giving focus to widgets when the
            # menu is visible
            return False
        return super().focusNextPrevChild(next)

    def eventFilter(self, source, event):
        if source == self.clickGrabber and event.type() == QtCore.QEvent.MouseButtonPress:
            # the grabber has been clicked, close the menu
            self.closeMenu()
        return super().eventFilter(source, event)

    def resizeEvent(self, event):
        super().resizeEvent(event)
        # always set the menu height to that of the window
        self.topMenu.setFixedHeight(self.height())
        # resize the grabber to the window rectangle, even if it's invisible
        self.clickGrabber.setGeometry(self.rect())
        if self.topMenu.isVisible():
            # resize the effect rectangle
            self.effect.setEffectRect(self.topMenu.geometry())

上一个答案

由于您想将效果应用于底层对象,我相信解决方案是使用“容器”嵌入它们,然后对其应用模糊效果。同样的概念也适用于 QGraphicsBlurWidget。

ApplicationWindow {
    property var theme: String("#ffffff")
    property var focusColor: String('transparent')
    id: applicationWindow
    visible: false                                      
    width: 600
    height:600

    Rectangle {
        id: container
        anchors.fill: parent

        Image {
            id: image_bug
            anchors.fill: parent
            source: "im.png"                                
        }

        Rectangle {
            width: 100; height: 600
            color: "green"
            Text {
                id: helloText
                text: "Hello world!"
                anchors.verticalCenter: parent.verticalCenter
                anchors.horizontalCenter: parent.horizontalCenter
                font.pointSize: 10; font.bold: true
            }
            MouseArea {
                anchors.fill: parent
                onClicked: { effectSource.width = 1200; effectSource.height = 1200;}
            }
        }
    }

    ShaderEffectSource {
        id: effectSource
        sourceItem: container
        anchors.centerIn: image_bug
        width: 300 
        height: 300 
        sourceRect: Qt.rect(x,y, width, height)
    }

    FastBlur{
        id: blur
        anchors.fill: effectSource
        source: effectSource
        radius: 100
    }
}

产生完全模糊效果


推荐阅读