Как сделать красивый неоновый эффект?

Хочу сделать красивый и сочный неоновый эффект с возможностью управления силой света. Для этого я построил такой код

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

но код слишком громоздкий. И свет получился слишком ненатуралистичным,

введите здесь описание изображения

особенно плохо это выглядит, если указать слабую интенсивность света.

Есть ли лучшие варианты для создания неонового эффекта? или почему он так плохо выглядит?


person askqest    schedule 27.03.2020    source источник
comment
Код можно немного улучшить (но речь идет только о читабельности и удобстве использования, а не о реальном приросте производительности): не нужно делать все эти операторы if, просто создайте функцию, которая возвращает все значения в правильном порядке (чтобы вы не нужно делать lower() каждый раз). Кроме того, это выглядит плохо, потому что QGraphicsBlurEffect был создан не для этого, поэтому вы просто создаете композицию, которая напоминает то, чего вы хотите достичь. Боюсь, что единственной альтернативой является создание собственного подкласса QGraphicsEffect, возможно, по размеру эффекта и/или изменению цвета размытия.   -  person musicamante    schedule 27.03.2020


Ответы (2)


Ну, в конце концов я решил, что это весело :-)

ВАЖНО. Учтите, что это какой-то взлом, потому что он использует частную и недокументированную функцию Qt (та же самая, что используется в QGraphicsBlurEffect), и это не гарантируется. будет работать везде.
Мне удалось добиться этого, позаимствовав некоторый код из средства просмотра прямых трансляций, известного как Атропин, самое интересное в эффектах. py источник.

Обратите внимание, что аналогичный эффект, вероятно, может быть достигнут с помощью «абстрактного» рисования частной QGraphicsScene внутри самого QGraphicsEffect, но это будет намного медленнее (поскольку вам придется создавать новые QGraphicsPixmapItems каждый раз, когда метод draw() эффекта называется) и, вероятно, будет иметь некоторые побочные эффекты.

Хитрость заключалась в том, чтобы получить имя функции через ctypes, я смог найти экспортированные имена функций только в 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

Я не могу проводить тестирование для MacO, но думаю, что это должна быть та же командная строка, что и в Linux.

Обратите внимание, что имена этих функций кажутся статичными в одной и той же версии операционной системы и 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), если кто-то захочет прокомментировать тестирование на других платформах, я буду рад соответствующим образом обновить этот ответ.

person musicamante    schedule 27.03.2020
comment
Извините за глупый вопрос, но что такое import sip? Я установил pip install sip и pip install PyQt5-sip, НО paython не находит модуль SIP - person askqest; 28.03.2020
comment
Если у вас есть PyQt, у вас уже должен быть sip. Возможно, проверьте установку PyQt5 и поищите здесь, в StackOverflow, похожие ошибки. - person musicamante; 28.03.2020
comment
Единственное, что я нашел, это вопрос по PYQT4. , но там, я так понимаю, рекомендуют ставить sip отдельно. И этот вопрос мне не поможет. В папке PyQt5 есть sip.cp37-win_amd64.pyd file. Этот файл отвечает за sip? - person askqest; 28.03.2020
comment
Теперь код выдает ошибку Traceback (most recent call last): File "C:\Users\user\Desktop\neon.py", line 35, in <module> _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 - person askqest; 29.03.2020
comment
Возможно, это Qt5Gui.dll или Qt5Widgets.dll. Просто поищите. Как я уже сказал, я не смог протестировать его на Windows. - person musicamante; 29.03.2020
comment
Я не понимаю, что не так с этими файлами (Qt5Gui.dll и Qt5Widgets.dll) Что мне искать в Интернете? - person askqest; 29.03.2020
comment
Что вы имеете в виду под тем, что не так с этими файлами? Они у вас есть? Вы искали их на своем компьютере? Всегда ли ошибка такая же, как в вашем предыдущем комментарии, с использованием других имен? Как я уже говорил вам, пожалуйста, не давайте информацию по частям, будьте более четкими. - person musicamante; 29.03.2020
comment
У меня есть эти файлы. Мне показалось бы странным, если бы этих файлов не существовало. Qt5Gui и Qt5Widgets — это основные файлы, на которых держится программа Qt. Извините, что я вас неправильно понял. - person askqest; 31.03.2020
comment
Я знаю, для чего нужны эти файлы, но, поскольку я не работаю в Windows, я не могу быть уверен в их именах. И я не просил вас это объяснять, так что еще раз, и надеюсь, в последний раз, в чем проблема? Он все еще говорит, что модуль не может быть найден? Или это другая ошибка? Вы должны ответить на все вопросы, которые мы вам задаем!!! - person musicamante; 31.03.2020
comment
Я нашел, в чем была проблема. скрипт говорит (QtGui5.dll) И мой файл называется (Qt5Gui.dll). Я этого не заметил, потому что искал QtGui. Но теперь код выявил новую проблему. Traceback (most recent call last):File"C:\neon.py",line36,in<module>'qt_blurImage@@YAXPAVQPainter@@AAVQImage@@N_N2H@Z')File"C:\Python37\lib\ctypes\__init__.py",line377,in__getattr__func=self.__getitem__(name)File"C:\Python37\lib\ctypes\__init__.py",line382,in__getitem__func=self._FuncPtr((name_or_ordinal,self))AttributeError:function'qt_blurImage@@YAXPAVQPainter@@AAVQImage@@N_N2H@Z' not found - person askqest; 02.04.2020
comment
?qt_blurImage@@YAXAAVQImage@@N_NH@Z и ?qt_blurImage@@YAXPAVQPainter@@AAVQImage@@N_N2H@Z не работают - person askqest; 02.04.2020
comment
@askqest У меня такая же проблема с окном 10. Я тогда сдамся. Предоставленный код очень странный. - person Luk Aron; 11.06.2020
comment
Я поставил это предупреждение по определенной причине: учтите, что это своего рода хак, потому что он использует закрытую и недокументированную функцию Qt (та же самая, что используется QGraphicsBlurEffect), и нет гарантии, что она будет работать везде. - person musicamante; 11.06.2020
comment
WoW, я искал светящиеся диаграммы в qtwidgets (в qml есть тип свечения: qt.io/blog/2017/02/07/glowing-qt-charts). Это работает и с графиками, молодец! - person Muhammed B. Aydemir; 02.09.2020

Я не могу комментировать, а редактирование поста 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')

Вот скриншот:

введите здесь описание изображения

person closer_ex    schedule 09.07.2020
comment
Этот вариант не вызывает ошибку, но теперь код снова спрашивает sip я так и не понял почему у меня его нет, устанавливал PyQT5 несколько раз - person askqest; 09.07.2020
comment
@askqest попробуй from PyQt5 import sip - person closer_ex; 09.07.2020
comment
оно работает!! Как вам удалось смешать белый и синий? - person askqest; 10.07.2020
comment
@askqest Код такой же, как у musicamante, но рендеринг шрифта в Windows, я думаю, отличается, что приводит к другому эффекту. По сути, это просто белая метка с синим размытым изображением, хитрость в том, чтобы размытие лучше соответствовало тексту. - person closer_ex; 11.07.2020
comment
Я пытался изменить цвет в setStyleSheet('color:red'), но это ни на что не повлияло. Интересно, можно ли добавить NeonEffect внутрь. другими словами. Каким-то образом инвертируйте setBlurRadius(), чтобы сделать белый цвет тоньше. нравится здесь - person askqest; 11.07.2020
comment
@askqest, вероятно, читал о CompositionMode, чтобы узнать, как эффект смешивается с меткой. В результате в режиме SourceIn цвет текста всегда совпадает с цветом размытия, переход в режим DestinationIn изменит цвет текста и цвет размытия. но если вы имеете в виду рисование белой линии внутри буквы, то я думаю, что эффект размытия - это не то, что вам следует искать. - person closer_ex; 11.07.2020