Ну, в конце концов я решил, что это было забавно: -)
ВАЖНО : Считайте, что это своего рода хак, потому что он использует private и недокументированная функция Qt (то же самое, что и QGraphicsBlurEffect), и не гарантируется, что она будет работать везде.
Я смог добиться этого, позаимствовав некоторый код из средства просмотра Live TV, известного как Atropine , интересные части находятся в источнике effect.py .
Обратите внимание, что аналогичного эффекта, вероятно, можно добиться, используя «абстрактный» рисунок частного QGraphicsScene внутри QGraphicsEffect. само по себе, но это будет намного медленнее (поскольку вам придется создавать новые QGraphicsPixmapItems каждый раз, когда вызывается метод эффекта draw()
) и, вероятно, будет иметь некоторые побочные эффекты.
Хитрость заключалась в том, чтобы получить имя функции через ctypes, я только смог найти экспортированные имена функций в Linux и Windows (но я не смог проверить это там):
# Linux:
$ nm -D /usr/lib/libQt5Widgets.so |grep qt_blurImage
004adc30 T <b>_Z12qt_blurImageP8QPainterR6QImagedbbi</b>
004ae0e0 T _Z12qt_blurImageR6QImagedbi
# Windows (through Mingw):
> objdump -p /QtGui4.dll |grep blurImage
[8695] ?qt_blurImage@@YAXAAVQImage@@N_NH@Z
[8696] <b>?qt_blurImage@@YAXPAVQPainter@@AAVQImage@@N_N2H@Z</b>
Я не могу выполнить тестирование для MacO, но я думаю , что это должна быть та же самая командная строка, что и для linux.
Обратите внимание, что эти имена функций кажутся чтобы быть каким-то образом c в одной и той же операционной системе и версий Qt: я запускаю ту же самую команду nn
для старого libQtGui.so
файла для Qt4, и она дает тот же результат.
Итак, вот ваш прекрасный неоновый эффект ...
А вот код, я добавил пример программа для его проверки:
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), если кто-то захочет прокомментировать тестирование на других платформах, я буду рад обновить этот ответ соответствующим образом.