如何在Qt中制作漂亮的霓虹灯效果?

问题描述 投票:0回答:3

我想制作一个美丽多汁的霓虹灯效果,并且能够控制光的强度。为此,我构建了这样的代码

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


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

        self.resize(800, 800)

        self.setStyleSheet('background:black;')


        mainLayout = QVBoxLayout(self)
        mainLayout.setContentsMargins(0, 0, 0, 0)

        color_1 = '162, 162, 162,'
        color_2 = '255, 255, 255,'
        color_3 = '0, 255, 255,'

        d_ = 1

        power = int(255/100*d_)

        for x in range(6):
            label = QLabel(self)


            color_L = color_1
            glass_L = 255
            size_L = 60
            blut_L = 0


            label.raise_()

            if x < 1 :
                color_L = color_1
            elif x < 2 :
                color_L = color_3
                glass_L = power
            elif x < 3 :
                color_L = color_2
                blut_L = 6
                glass_L = power
            elif x < 4:
                color_L = color_2
                blut_L = 40
                glass_L = power
            elif x < 5 :
                label.lower()
                color_L = color_3
                blut_L = 40
                size_L = 70
                glass_L = power
            elif x < 6 :
                label.lower()
                color_L = color_3
                blut_L = 150
                size_L = 70
                glass_L = power

            label.setText('test')
            label.setStyleSheet('background:rgba(0, 0, 0, 0);color:rgba({} {}); font-size:{}px;'.format(color_L, glass_L,size_L))
            label.resize(self.width(), self.height())
            label.setAlignment(Qt.AlignCenter)

            self.effect = QGraphicsBlurEffect(blurRadius=blut_L)
            label.setGraphicsEffect(self.effect)

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

但是代码太麻烦了。而且光线太不自然了

如果您指示光线强度较弱,看起来会特别糟糕。

有没有更好的选择来制作霓虹灯效果?或者为什么他看起来那么糟糕?

python qt pyqt5 qtstylesheets
3个回答
6
投票

嗯,我最终觉得这样做很有趣:-)

重要:考虑到这是某种黑客行为,因为它使用了 Qt 的private和未记录的函数(与 QGraphicsBlurEffect 使用的相同),并且不能保证它在任何地方都能工作。
我已经能够通过从称为Atropine的直播电视查看器借用一些代码来实现它,有趣的部分在effects.py源中。

请注意,通过在 QGraphicsEffect 本身内使用私有 QGraphicsScene 的“抽象”绘制可能可以实现类似的效果,但速度会慢得多(因为每次使用 的

draw()
方法时,您都必须创建新的 QGraphicsPixmapItems该效果被称为)并且可能会产生一些副作用。

技巧是通过ctypes获取函数名称,我只能在Linux和Windows中找到导出的函数名称(但我无法在那里测试它):

 # Linux:
    $ nm -D /usr/lib/libQt5Widgets.so |grep qt_blurImage
    004adc30 T _Z12qt_blurImageP8QPainterR6QImagedbbi
    004ae0e0 T _Z12qt_blurImageR6QImagedbi

    # Windows(通过 Mingw):
    > objdump -p /QtGui4.dll |grep 模糊图像
        [8695服]第8695服[双线] 新服
        [8696] ?qt_blurImage@@YAXPAVQPainter@@AAVQImage@@N_N2H@Z

我无法对 MacO 进行测试,但我认为它应该与 Linux 上的命令行相同。

如果您在 Windows 上没有 GNU binutils,请使用

dumpbin
实用程序。

请注意,这些名称在相同的操作系统 Qt 版本中似乎在某种程度上是一致的:我在 Qt4 的旧

nn
文件上运行相同的
libQtGui.so
命令,它给出了相同的结果。

然而,在 Windows 上,该字符串自 Qt5 的某些版本(我不确定是哪一个,但可能在 5.8 之后)以来略有变化,并且在 Qt6 中似乎仍然有效:

?qt_blurImage@@YAXPEAVQPainter@@AEAVQImage@@N_N2H@Z


所以,这是你的美丽的霓虹灯效果...

这是代码,我添加了一个示例程序来测试它:

import sys
import sip
import ctypes
from PyQt5 import QtCore, QtGui, QtWidgets

if sys.platform == 'win32':
    # the exported function name has illegal characters on Windows, let's use
    # getattr to access it
    _qt_blurImage  = getattr(ctypes.CDLL('Qt5Widgets.dll'), 
        '?qt_blurImage@@YAXPAVQPainter@@AAVQImage@@N_N2H@Z')
else:
    try:
        qtgui = ctypes.CDLL('libQt5Widgets.so')
    except:
        qtgui = ctypes.CDLL('libQt5Widgets.so.5')
    _qt_blurImage = qtgui._Z12qt_blurImageP8QPainterR6QImagedbbi


class NeonEffect(QtWidgets.QGraphicsColorizeEffect):
    _blurRadius = 5.
    _glow = 2

    def glow(self):
        return self._glow

    @QtCore.pyqtSlot(int)
    def setGlow(self, glow):
        if glow == self._glow:
            return
        self._glow = max(1, min(glow, 10))
        self.update()

    def blurRadius(self):
        return self._blurRadius

    @QtCore.pyqtSlot(int)
    @QtCore.pyqtSlot(float)
    def setBlurRadius(self, radius):
        if radius == self._blurRadius:
            return
        self._blurRadius = max(1., float(radius))
        self.update()

    def applyBlurEffect(self, blurImage, radius, quality, alphaOnly, transposed=0, qp=None):
        blurImage = ctypes.c_void_p(sip.unwrapinstance(blurImage))
        radius = ctypes.c_double(radius)
        quality = ctypes.c_bool(quality)
        alphaOnly = ctypes.c_bool(alphaOnly)
        transposed = ctypes.c_int(transposed)
        if qp:
            qp = ctypes.c_void_p(sip.unwrapinstance(qp))
        _qt_blurImage(qp, blurImage, radius, quality, alphaOnly, transposed)

    def draw(self, qp):
        pm, offset = self.sourcePixmap(QtCore.Qt.LogicalCoordinates, self.PadToEffectiveBoundingRect)
        if pm.isNull():
            return

        # use a double sized image to increase the blur factor
        scaledSize = QtCore.QSize(pm.width() * 2, pm.height() * 2)
        blurImage = QtGui.QImage(scaledSize, QtGui.QImage.Format_ARGB32_Premultiplied)
        blurImage.fill(0)
        blurPainter = QtGui.QPainter(blurImage)
        blurPainter.drawPixmap(0, 0, pm.scaled(scaledSize, 
            QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))
        blurPainter.end()
        
        # apply the blurred effect on the image
        self.applyBlurEffect(blurImage, 1 * self._blurRadius, True, False)

        # start the painter that will use the previous image as alpha
        tmpPainter = QtGui.QPainter(blurImage)
        # using SourceIn composition mode we use the existing alpha values
        # to paint over
        tmpPainter.setCompositionMode(tmpPainter.CompositionMode_SourceIn)
        color = QtGui.QColor(self.color())
        color.setAlpha(color.alpha() * self.strength())
        # fill using the color
        tmpPainter.fillRect(pm.rect(), color)
        tmpPainter.end()
        
        # repeat the effect which will make it more "glowing"
        for g in range(self._glow):
            qp.drawImage(0, 0, blurImage.scaled(pm.size(), 
                QtCore.Qt.KeepAspectRatio, QtCore.Qt.SmoothTransformation))

        super().draw(qp)


class NeonTest(QtWidgets.QWidget):
    def __init__(self):
        super().__init__()
        layout = QtWidgets.QGridLayout(self)

        palette = self.palette()
        palette.setColor(palette.Window, QtCore.Qt.black)
        palette.setColor(palette.WindowText, QtCore.Qt.white)
        self.setPalette(palette)

        
        self.label = QtWidgets.QLabel('NEON EFFECT')
        layout.addWidget(self.label, 0, 0, 1, 2)
        self.label.setPalette(QtWidgets.QApplication.palette())
        self.label.setContentsMargins(20, 20, 20, 20)
        f = self.font()
        f.setPointSizeF(48)
        f.setBold(True)
        self.label.setFont(f)
        self.effect = NeonEffect(color=QtGui.QColor(152, 255, 250))
        self.label.setGraphicsEffect(self.effect)
        self.effect.setBlurRadius(40)

        layout.addWidget(QtWidgets.QLabel('blur radius'))
        radiusSpin = QtWidgets.QDoubleSpinBox(minimum=1, maximum=100, singleStep=5)
        layout.addWidget(radiusSpin, 1, 1)
        radiusSpin.setValue(self.effect.blurRadius())
        radiusSpin.valueChanged.connect(self.effect.setBlurRadius)

        layout.addWidget(QtWidgets.QLabel('glow factor'))
        glowSpin = QtWidgets.QSpinBox(minimum=1, maximum=10)
        layout.addWidget(glowSpin, 2, 1)
        glowSpin.setValue(self.effect.glow())
        glowSpin.valueChanged.connect(self.effect.setGlow)

        layout.addWidget(QtWidgets.QLabel('color strength'))
        strengthSpin = QtWidgets.QDoubleSpinBox(minimum=0, maximum=1, singleStep=.05)
        strengthSpin.setValue(1)
        layout.addWidget(strengthSpin, 3, 1)
        strengthSpin.valueChanged.connect(self.effect.setStrength)

        colorBtn = QtWidgets.QPushButton('color')
        layout.addWidget(colorBtn, 4, 0)
        colorBtn.clicked.connect(self.setColor)

        self.aniBtn = QtWidgets.QPushButton('play animation')
        layout.addWidget(self.aniBtn, 4, 1)
        self.aniBtn.setCheckable(True)

        self.glowAni = QtCore.QVariantAnimation(duration=1000)
        self.glowAni.setStartValue(1)
        self.glowAni.setEndValue(10)
        self.glowAni.setEasingCurve(QtCore.QEasingCurve.InQuad)
        self.glowAni.valueChanged.connect(glowSpin.setValue)
        self.glowAni.finished.connect(self.animationFinished)

        self.aniBtn.toggled.connect(self.glowAni.start)

    def animationFinished(self):
        if self.aniBtn.isChecked():
            self.glowAni.setDirection(not self.glowAni.direction())
            self.glowAni.start()

    def setColor(self):
        color = QtWidgets.QColorDialog.getColor(self.effect.color(), self)
        if color.isValid():
            self.effect.setColor(color)


if __name__ == '__main__':
    import sys
    app = QtWidgets.QApplication(sys.argv)
    test = NeonTest()
    test.show()
    sys.exit(app.exec())

请记住,这个答案是为 Qt5 提供的:自 Qt6 起,PyQt(以及后来的 PySide)切换到实际的 Python 类型作为标志和枚举,因此您应对所有这些使用完整的命名空间(例如,

Qt.GlobalColor.black
,等等)。

请注意,根据我的测试,使用高于 1 的发光系数且模糊半径小于 4 可能会导致一些绘画伪影。

此外,调色板也必须小心。例如,如果您想将效果应用于 QLineEdit,您可能还想将

QPalette.Base
设置为透明:

我已经能够在两台 Linux 机器(使用 Qt 5.7 和 5.12)上成功测试它,如果有人愿意对其他平台上的测试发表评论,我将很高兴相应地更新此答案。


1
投票

我无法评论和编辑musicamante的帖子,一直给我代码格式错误,所以我将其发布在这里。

考虑到 QGraphicsEffect 是从 QtWidgets 而不是 QtGui 导入的,所以我 objdump Qt5Widgets.dll,结果如下:

.\objdump.exe -p /Qt5Widgets.dll | findstr "blurImage"
        [5154] ?qt_blurImage@@YAXAEAVQImage@@N_NH@Z
        [5155] ?qt_blurImage@@YAXPEAVQPainter@@AEAVQImage@@N_N2H@Z

所以在 Windows 上应该是:

_qt_blurImage = getattr(ctypes.CDLL('Qt5Widgets.dll'),
        '?qt_blurImage@@YAXPEAVQPainter@@AEAVQImage@@N_N2H@Z')

这是截图:


0
投票

PyQt6 中属性名称 “?qt_blurImage@@YAXPEAVQPainter@@AEAVQImage@@N_N2H@Z'”已略有更改。

已提供答案这里

© www.soinside.com 2019 - 2024. All rights reserved.