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

Posted

技术标签:

【中文标题】如何制作漂亮的霓虹灯效果?【英文标题】:How to make a beautiful neon effect? 【发布时间】:2021-12-31 12:06:50 【问题描述】:

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

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

但是代码太麻烦了。结果发现光线太不自然了,

如果你指示弱光强度,它看起来特别糟糕。

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

【问题讨论】:

代码可以稍微改进(但它只是关于可读性和可用性,而不是真正的性能提升):不需要做所有那些if 语句,只需创建一个返回所有值的函数正确的顺序(这样你就不需要每次都做lower())。除此之外,它看起来很“糟糕”,因为这不是创建 QGraphicsBlurEffect 的目的,所以您只是在创建一个类似于您想要实现的“组合”。恐怕唯一的选择是创建自己的 QGraphicsEffect 子类,可能通过效果的大小和/或更改模糊颜色。 【参考方案1】:

好吧,我最终决定这样做很有趣:-)

重要提示:考虑到这是某种 hack,因为它使用了一个 private 和未记录的 Qt 函数(与 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 blurImage [8695] ?qt_blurImage@@YAXAAVQImage@@N_NH@Z [8696] ?qt_blurImage@@YAXPAVQPainter@@AAVQImage@@N_N2H@Z

我无法对 MacO 进行测试,但我认为它应该是与 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)上成功测试它,如果有人愿意评论在其他平台上的测试,我会很高兴相应地更新这个答案。

【讨论】:

抱歉这个愚蠢的问题,但什么是“import sip”我已经设置了“pip install sip”和“pip install PyQt5-sip”但是 paython 没有找到 SIP 模块 如果你有 PyQt,你应该已经有 sip。也许检查 PyQt5 的安装并在 *** 上搜索类似的错误。 我发现的唯一问题是PYQT4 上的一个问题,但据我所知,他们建议单独安装 sip。这个问题对我没有帮助。 PyQt5文件夹里有一个sip.cp37-win_amd64.pyd file.这个文件是负责sip的吗? 现在代码抛出错误Traceback (most recent call last): File "C:\Users\user\Desktop\neon.py", line 35, in &lt;module&gt; _qt_blurImage = getattr(ctypes.CDLL('QtGui5.dll'), File "C:\Python37\lib\ctypes\__init__.py", line 364, in __init__ self._handle = _dlopen(self._name, mode) OSError: [WinError 126] The specified module was not found 可能是 Qt5Gui.dll 或 Qt5Widgets.dll。只是寻找它。正如我已经说过的,我无法在 Windows 上对其进行测试【参考方案2】:

我无法评论和编辑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')

截图如下:

【讨论】:

这个选项不会报错,但是现在代码又问sip我还是不明白为什么没有,我安装了好几次PyQT5 @askqest 试试from PyQt5 import sip 它有效!你是如何设法混合白色和蓝色的? @askqest 代码和musicamante的一样,但是我猜windows上的字体渲染是不一样的,这导致了不同的效果。基本上它只是一个带有蓝色模糊图像的白色标签,诀窍是使模糊更适合文本。 我尝试更改setStyleSheet('color:red') 的颜色,但没有任何影响。我想知道是否可以向内添加NeonEffect。换一种说法。以某种方式反转setBlurRadius() 以使白色更薄。喜欢here

以上是关于如何制作漂亮的霓虹灯效果?的主要内容,如果未能解决你的问题,请参考以下文章

SVG霓虹灯效果

Flutter 小技巧之霓虹灯文本的「故障」效果的实现

在线仿真Arduino WS2812b环形24颗霓虹灯动态效果显示

在线仿真Arduino WS2812b环形24颗霓虹灯动态效果显示

Android 帧布局FrameLayout之霓虹灯效果

七彩霓虹灯能够实现两种效果(更新版本号2)