Как сделать красивый неоновый эффект? - PullRequest
0 голосов
/ 27 марта 2020

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

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

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

enter image description here

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

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

1 Ответ

0 голосов
/ 28 марта 2020

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

ВАЖНО : Считайте, что это своего рода хак, потому что он использует 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, и она дает тот же результат.

Итак, вот ваш прекрасный неоновый эффект ...

a wonderful neon effect!

А вот код, я добавил пример программа для его проверки:

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 в прозрачный:

a glowing line edit!

Я смог успешно протестировать его на двух Linux машинах (с Qt 5.7 и 5.12), если кто-то захочет прокомментировать тестирование на других платформах, я буду рад обновить этот ответ соответствующим образом.

...