怎样才能做出漂亮的霓虹灯效果呢?

2022-02-23 00:00:00 python pyqt5 qt qtstylesheets

问题描述

我想做一个漂亮多汁的霓虹灯效果,能够控制光线的力量。为此,我构建了这样的代码

import sys
from PyQt5.QtWidgets import (QRadioButton, QHBoxLayout, QButtonGroup, 
    QApplication, QGraphicsScene,QGraphicsView, QGraphicsLinearLayout, QGraphicsWidget, QWidget, QLabel)
from PyQt5.QtGui import QIcon, QPixmap
from PyQt5.QtCore import QSize, QPoint,Qt
from PyQt5 import QtCore, QtGui, QtWidgets
from PyQt5.QtWidgets import *



from PyQt5.QtGui import QPainter


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

        self.resize(800, 800)

        self.setStyleSheet('background:black;')


        mainLayout = QtWidgets.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 = QtWidgets.QGraphicsBlurEffect(blurRadius=blut_L)
            label.setGraphicsEffect(self.effect)

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

但是代码太繁琐了。结果灯光太不自然了,

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

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


解决方案

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

重要信息:请考虑这是某种黑客攻击,因为它使用了私有的和未记录的Qt函数(与QGraphicsBlurEffect使用的函数相同),并且不能保证它在任何地方都可以工作。
我从一个叫做Atropine的直播电视观众那里借用了一些代码来实现它,有趣的部分在effects.py源代码中。

请注意,通过在QGraphicsEffect本身内使用私有QGraphicsScene的"抽象"绘制,可能会达到类似的效果,但速度会慢得多(因为每次调用效果的draw()方法时都必须创建新的QGraphicsPixmapItems),并且可能会有一些副作用。

诀窍是通过ctype获取函数名,我只能在Linux和Windows中找到导出的函数名(但无法在那里进行测试):

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

    # Windows (through Mingw):
    > objdump -p /QtGui4.dll |grep blurImage
        [8695] ?qt_blurImage@@YAXAAVQImage@@N_NH@Z
        [8696] ?qt_blurImage@@YAXPAVQPainter@@AAVQImage@@N_N2H@Z

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

请注意,这些函数名称似乎是同一操作系统和Qt版本中的静电:我对Qt4的旧libQtGui.so文件运行相同的nn命令,结果相同。

那么,这就是您的漂亮的霓虹灯效果.

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

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('QtGui5.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=250)
        self.glowAni.setStartValue(1)
        self.glowAni.setEndValue(5)
        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):
        d = QtWidgets.QColorDialog(self.effect.color(), self)
        if d.exec_():
            self.effect.setColor(d.currentColor())

请注意,根据我的测试,使用大于1且模糊半径小于4的发光因子可能会导致某些绘画瑕疵。

此外,还必须注意调色板。例如,如果您希望将效果应用于QLineEdit,您可能还需要将QPalette.Base设置为透明:

我已经在两台Linux计算机(Qt 5.7和5.12)上成功测试了它,如果有人愿意对在其他平台上的测试发表意见,我很乐意相应地更新此答案。

相关文章