我想制作一个美丽多汁的霓虹灯效果,并且能够控制光的强度。为此,我构建了这样的代码
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_())
但是代码太麻烦了。而且光线太不自然了
如果您指示光线强度较弱,看起来会特别糟糕。
有没有更好的选择来制作霓虹灯效果?或者为什么他看起来那么糟糕?
嗯,我最终觉得这样做很有趣:-)
重要:考虑到这是某种黑客行为,因为它使用了 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)上成功测试它,如果有人愿意对其他平台上的测试发表评论,我将很高兴相应地更新此答案。
我无法评论和编辑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')
这是截图:
在 PyQt6 中属性名称 “?qt_blurImage@@YAXPEAVQPainter@@AEAVQImage@@N_N2H@Z'”已略有更改。
已提供答案这里