gpt4 book ai didi

pyqt - 我可以将 Neumorphism 效果应用于 QWidget 吗?

转载 作者:行者123 更新时间:2023-12-04 11:20:45 31 4
gpt4 key购买 nike

虽然 Qt 提供了 QGraphicsDropShadowEffect,但没有可用的“Neumorphism”效果:

Comparison between drop shadow and neumorphism

在 css 中有 box-shadow属性(上图中就是这样做的),它可以有多种颜色,但 Qt 缺乏对该属性的支持,并且一次应用多个图形效果是不可能的。

这能做到吗?

最佳答案

解决方案是创建 QGraphicsEffect 的自定义子类并使用渐变。

起初我想遵循用于 CSS 的相同概念,继承 QGraphicsDropShadowEffect 并在内部使用另一个来绘制“另一个”阴影,但我不喜欢结果:在某些情况下(通常是半径和对比度太大时)它只是不起作用:

wrong result

如果仔细观察,您会发现结果与阴影太相似,就像对象在 float ,而应该是“挤出”。

我发现的唯一有效解决方案是手动绘制所有内容,使用边界的线性渐变和角的复合渐变。虽然第一个非常合乎逻辑,但第二个需要一些使用 QPainter 复合模式的独创性:Qt 只有径向和锥形渐变,但它们之间没有“混合”。

然后的技巧是为“浅”色创建径向渐变,中心为全色,边框为 0 alpha 的相同颜色,然后为“暗”色(带有“暗”色)叠加锥形渐变开始时的颜色和 90° 处的“光”),将使用第一个渐变的 alpha 分量进行绘制。

steps to create the composite gradient

然后只需创建函数来更新每个属性:距离(效果的范围)、颜色(用于渐变,默认为应用程序的 QPalette.Window 颜色角色)、原点(用作光源的“源”)和圆形边框的可选 clipRadius。

一些重要的注意事项:

  • 由于它是 QGraphicsEffect,它只能应用于“父”小部件:子部件不能对它们应用其他效果,这意味着如果你有一个像 QGroupBox 或 QTabWidget 这样的容器,你必须选择是否要将它应用到 parent 或每个 child ;
  • 由于其“简单”的性质,它只支持矩形形状:如果小部件有 mask ,效果形状仍将基于矩形;
  • 应考虑布局边距和间距,因为如果使用它们的小部件太窄,则多种效果可能会重叠;我建议使用 QProxyStyle 并为 PM_Layout[*]Margin 和 PM_Layout[*]Spacing 设置最小默认值,并根据 length 返回一个值。属性(property);
  • clipRadius属性允许圆角边框剪裁,但并不完美,因为QPainter的剪裁不支持抗锯齿;我会看看我将来是否可以解决这个问题;
  • 当应用于 QGraphicsScene 项目时,与 QGraphicsDropShadowEffect 类似,效果在设备坐标中,因此不会应用转换(旋转、缩放、剪切);每当我能够解决这个问题时,我都会更新这个答案;

  • final neumorphism effect result

    这是 Qt QGraphicsDropShadowEffect、css 仿真和我的 NeumorphismEffect 之间的比较(最后两个有圆角边框:css 版本使用 border-radius 属性,而我的设置为 clipRadius ):

    a cool comparison

    class NeumorphismEffect(QtWidgets.QGraphicsEffect):
    originChanged = QtCore.pyqtSignal(QtCore.Qt.Corner)
    distanceChanged = QtCore.pyqtSignal(float)
    colorChanged = QtCore.pyqtSignal(QtGui.QColor)
    clipRadiusChanged = QtCore.pyqtSignal(int)

    _cornerShift = (QtCore.Qt.TopLeftCorner, QtCore.Qt.TopRightCorner,
    QtCore.Qt.BottomRightCorner, QtCore.Qt.BottomLeftCorner)

    def __init__(self, distance=4, color=None, origin=QtCore.Qt.TopLeftCorner, clipRadius=0):
    super().__init__()

    self._leftGradient = QtGui.QLinearGradient(1, 0, 0, 0)
    self._leftGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
    self._topGradient = QtGui.QLinearGradient(0, 1, 0, 0)
    self._topGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)

    self._rightGradient = QtGui.QLinearGradient(0, 0, 1, 0)
    self._rightGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
    self._bottomGradient = QtGui.QLinearGradient(0, 0, 0, 1)
    self._bottomGradient.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)

    self._radial = QtGui.QRadialGradient(.5, .5, .5)
    self._radial.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)
    self._conical = QtGui.QConicalGradient(.5, .5, 0)
    self._conical.setCoordinateMode(QtGui.QGradient.ObjectBoundingMode)

    self._origin = origin
    distance = max(0, distance)
    self._clipRadius = min(distance, max(0, clipRadius))
    self._setColor(color or QtWidgets.QApplication.palette().color(QtGui.QPalette.Window))
    self._setDistance(distance)

    def color(self):
    return self._color

    @QtCore.pyqtSlot(QtGui.QColor)
    @QtCore.pyqtSlot(QtCore.Qt.GlobalColor)
    def setColor(self, color):
    if isinstance(color, QtCore.Qt.GlobalColor):
    color = QtGui.QColor(color)
    if color == self._color:
    return
    self._setColor(color)
    self._setDistance(self._distance)
    self.update()
    self.colorChanged.emit(self._color)

    def _setColor(self, color):
    self._color = color
    self._baseStart = color.lighter(125)
    self._baseStop = QtGui.QColor(self._baseStart)
    self._baseStop.setAlpha(0)
    self._shadowStart = self._baseStart.darker(125)
    self._shadowStop = QtGui.QColor(self._shadowStart)
    self._shadowStop.setAlpha(0)

    self.lightSideStops = [(0, self._baseStart), (1, self._baseStop)]
    self.shadowSideStops = [(0, self._shadowStart), (1, self._shadowStop)]
    self.cornerStops = [(0, self._shadowStart), (.25, self._shadowStop),
    (.75, self._shadowStop), (1, self._shadowStart)]

    self._setOrigin(self._origin)

    def distance(self):
    return self._distance

    def setDistance(self, distance):
    if distance == self._distance:
    return
    oldRadius = self._clipRadius
    self._setDistance(distance)
    self.updateBoundingRect()
    self.distanceChanged.emit(self._distance)
    if oldRadius != self._clipRadius:
    self.clipRadiusChanged.emit(self._clipRadius)

    def _getCornerPixmap(self, rect, grad1, grad2=None):
    pm = QtGui.QPixmap(self._distance + self._clipRadius, self._distance + self._clipRadius)
    pm.fill(QtCore.Qt.transparent)
    qp = QtGui.QPainter(pm)
    if self._clipRadius > 1:
    path = QtGui.QPainterPath()
    path.addRect(rect)
    size = self._clipRadius * 2 - 1
    mask = QtCore.QRectF(0, 0, size, size)
    mask.moveCenter(rect.center())
    path.addEllipse(mask)
    qp.setClipPath(path)
    qp.fillRect(rect, grad1)
    if grad2:
    qp.setCompositionMode(qp.CompositionMode_SourceAtop)
    qp.fillRect(rect, grad2)
    qp.end()
    return pm

    def _setDistance(self, distance):
    distance = max(1, distance)
    self._distance = distance
    if self._clipRadius > distance:
    self._clipRadius = distance
    distance += self._clipRadius
    r = QtCore.QRectF(0, 0, distance * 2, distance * 2)

    lightSideStops = self.lightSideStops[:]
    shadowSideStops = self.shadowSideStops[:]
    if self._clipRadius:
    gradStart = self._clipRadius / (self._distance + self._clipRadius)
    lightSideStops[0] = (gradStart, lightSideStops[0][1])
    shadowSideStops[0] = (gradStart, shadowSideStops[0][1])

    # create the 4 corners as if the light source was top-left
    self._radial.setStops(lightSideStops)
    topLeft = self._getCornerPixmap(r, self._radial)

    self._conical.setAngle(359.9)
    self._conical.setStops(self.cornerStops)
    topRight = self._getCornerPixmap(r.translated(-distance, 0), self._radial, self._conical)

    self._conical.setAngle(270)
    self._conical.setStops(self.cornerStops)
    bottomLeft = self._getCornerPixmap(r.translated(0, -distance), self._radial, self._conical)

    self._radial.setStops(shadowSideStops)
    bottomRight = self._getCornerPixmap(r.translated(-distance, -distance), self._radial)

    # rotate the images according to the actual light source
    images = topLeft, topRight, bottomRight, bottomLeft
    shift = self._cornerShift.index(self._origin)
    if shift:
    transform = QtGui.QTransform().rotate(shift * 90)
    for img in images:
    img.swap(img.transformed(transform, QtCore.Qt.SmoothTransformation))

    # and reorder them if required
    self.topLeft, self.topRight, self.bottomRight, self.bottomLeft = images[-shift:] + images[:-shift]

    def origin(self):
    return self._origin

    @QtCore.pyqtSlot(QtCore.Qt.Corner)
    def setOrigin(self, origin):
    origin = QtCore.Qt.Corner(origin)
    if origin == self._origin:
    return
    self._setOrigin(origin)
    self._setDistance(self._distance)
    self.update()
    self.originChanged.emit(self._origin)

    def _setOrigin(self, origin):
    self._origin = origin

    gradients = self._leftGradient, self._topGradient, self._rightGradient, self._bottomGradient
    stops = self.lightSideStops, self.lightSideStops, self.shadowSideStops, self.shadowSideStops

    # assign color stops to gradients based on the light source position
    shift = self._cornerShift.index(self._origin)
    for grad, stops in zip(gradients, stops[-shift:] + stops[:-shift]):
    grad.setStops(stops)

    def clipRadius(self):
    return self._clipRadius

    @QtCore.pyqtSlot(int)
    @QtCore.pyqtSlot(float)
    def setClipRadius(self, radius):
    if radius == self._clipRadius:
    return
    oldRadius = self._clipRadius
    self._setClipRadius(radius)
    self.update()
    if oldRadius != self._clipRadius:
    self.clipRadiusChanged.emit(self._clipRadius)

    def _setClipRadius(self, radius):
    radius = min(self._distance, max(0, int(radius)))
    self._clipRadius = radius
    self._setDistance(self._distance)

    def boundingRectFor(self, rect):
    d = self._distance + 1
    return rect.adjusted(-d, -d, d, d)

    def draw(self, qp):
    restoreTransform = qp.worldTransform()

    qp.setPen(QtCore.Qt.NoPen)
    x, y, width, height = self.sourceBoundingRect(QtCore.Qt.DeviceCoordinates).getRect()
    right = x + width
    bottom = y + height
    clip = self._clipRadius
    doubleClip = clip * 2

    qp.setWorldTransform(QtGui.QTransform())
    leftRect = QtCore.QRectF(x - self._distance, y + clip, self._distance, height - doubleClip)
    qp.setBrush(self._leftGradient)
    qp.drawRect(leftRect)

    topRect = QtCore.QRectF(x + clip, y - self._distance, width - doubleClip, self._distance)
    qp.setBrush(self._topGradient)
    qp.drawRect(topRect)

    rightRect = QtCore.QRectF(right, y + clip, self._distance, height - doubleClip)
    qp.setBrush(self._rightGradient)
    qp.drawRect(rightRect)

    bottomRect = QtCore.QRectF(x + clip, bottom, width - doubleClip, self._distance)
    qp.setBrush(self._bottomGradient)
    qp.drawRect(bottomRect)

    qp.drawPixmap(x - self._distance, y - self._distance, self.topLeft)
    qp.drawPixmap(right - clip, y - self._distance, self.topRight)
    qp.drawPixmap(right - clip, bottom - clip, self.bottomRight)
    qp.drawPixmap(x - self._distance, bottom - clip, self.bottomLeft)

    qp.setWorldTransform(restoreTransform)
    if self._clipRadius:
    path = QtGui.QPainterPath()
    source, offset = self.sourcePixmap(QtCore.Qt.DeviceCoordinates)

    sourceBoundingRect = self.sourceBoundingRect(QtCore.Qt.DeviceCoordinates)
    qp.save()
    qp.setTransform(QtGui.QTransform())
    path.addRoundedRect(sourceBoundingRect, self._clipRadius, self._clipRadius)
    qp.setClipPath(path)
    qp.drawPixmap(source.rect().translated(offset), source)
    qp.restore()
    else:
    self.drawSource(qp)

    关于pyqt - 我可以将 Neumorphism 效果应用于 QWidget 吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/60626717/

    31 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com