Important: Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

rendering a gradient using a mask



  • What I am attempting to do is to draw onto a QBitmap some text that follows an arc. I am then creating a QConicalGradient that is transparent at either end of the gradient and in the middle of it is a color. when creating the gradient I set the rotation based on an input. This input is what is going to "point" the colored section of the gradient at a specific piece of text.

    rendering the text onto the screen by setting the the pen to the gradient is difficult to do because the text is rotated to follow an arc. trying to map where the center of the conical gradient should be after using transform and rotate to render the text I have not been able to do.

    So I opted to render the text to a QBitmap and then use that as a mask where I draw in the gradient. This does work but there is an anomaly, the text that should be completely transparent ends up being white. But not always.

    Here is an animated GIF showing the problem.

    speed_rpm_gauge.gif

    The code is rather large and I am not able to upload zip files or .py files. Here is the code for inside of the paintEvent

        def paintEvent(self, evt):
            if None in (
                self._speed_polygon,
                self._rpm_polygon,
                self._bezel_polygon,
            ):
                return
    
            bitmap = QBitmap (self.width, self.height)
            bitmap.clear()
            painter = QPainter(bitmap)
            # painter.setRenderHint(QPainter.Antialiasing, False)
            # painter.setPen(Qt.NoPen)
            # painter.setBrush(QBrush(Qt.color0))
            # painter.drawRect(0, 0, self.width, self.height)
    
            font = painter.font()
            font_size = font.pointSize()
            font_size = int(round(font_size * 2.5))
            font.setPointSize(font_size)
            painter.setFont(font)
    
            rpm = self._rpm / 7000
    
            outer_radius = (min(self.width, self.height) / 2) * 0.775
    
            painter.setBrush(Qt.NoBrush)
            painter.setPen(QPen(Qt.color1, 6))
    
            for i in range(8):
                value1 = (i * 1000) / 7000
                value2 = ((i + 1) * 1000) / 7000
                value3 = ((i - 1) * 1000) / 7000
    
                if value1 < rpm < value2:
                    diff = value2 - value1
                    value1_stop = value1 + (diff / 2)
                    if rpm <= value1_stop:
                        font_factor = _remap(rpm, value1_stop, value1, 0.5, 0.75)
                    else:
                        font_factor = 0.5
                elif value1 > rpm > value3:
                    diff = value1 - value3
                    value1_start = value1 - (diff / 2)
                    if rpm >= value1_start:
                        font_factor = _remap(rpm, value1_start, value1, 0.5, 0.75)
                    else:
                        font_factor = 0.5
                elif value1 == rpm:
                    font_factor = 0.75
                else:
                    font_factor = 0.5
    
                f = QFont(font)
                f.setPointSize(int(round(font_size * font_factor)))
                painter.setFont(f)
    
                angle = _remap(i * 1000, 0.0, self._max_rpm, 0.0, 270.0)
                text_angle = -(angle - 45) + 85
    
                t = angle + 135.0
                x = (outer_radius * math.cos(math.radians(t))) + (self.width / 2)
                y = outer_radius * math.sin(math.radians(t)) + (self.height / 2)
                point = QPointF(x, y)
    
                painter.save()
                painter.translate(point)
                painter.rotate(-text_angle)
                painter.drawText(0, 0, str(i * 1000))
                painter.restore()
    
            painter.end()
            del painter
    
            self._pixmap = bitmap
    
            painter = QPainter(self)
            painter.setRenderHint(QPainter.Antialiasing)
    
            font = painter.font()
            font.setPointSize(font.pointSize() * 2)
            painter.setFont(font)
            painter.setPen(QPen(QColor(255, 255, 255, int(round(190 * self._brightness)))))
    
            speed = str(int(round(self._speed)))
            if self._speed < 10:
                painter.drawText(188, 310, 60, 40, 0, speed)
            elif self._speed < 100:
                painter.drawText(183, 310, 60, 40, 0, speed)
            else:
                painter.drawText(178, 310, 60, 40, 0, speed)
    
            rpm = str(int(round(self._rpm)))
            font.setPointSize(int(round(font.pointSize() * 1.5)))
            painter.setFont(font)
    
            if self._rpm < 10:
                painter.drawText(170, 340, 70, 40, 0, rpm)
            elif self._rpm < 100:
                painter.drawText(165, 340, 70, 40, 0, rpm)
            else:
                painter.drawText(160, 340, 70, 40, 0, rpm)
    
            painter.setPen(Qt.NoPen)
    
            painter.setBrush(self._speed_gradient)
            painter.drawPolygon(self._speed_polygon)
    
            painter.setBrush(self._rpm_gradient)
            painter.drawPolygon(self._rpm_polygon)
            painter.setBrush(self._bezel_gradient)
            painter.drawPolygon(self._bezel_polygon)
    
            painter.setBrush(QColor(0, 0, 0, 0))
            painter.setPen(Qt.NoPen)
            painter.drawRect(0, 0, self.width, self.height)
            painter.setBrush(self._rpm_number_gradient)
            painter.setClipRegion(QRegion(self._pixmap))
            painter.setCompositionMode(QPainter.CompositionMode_Clear)
            painter.drawRect(0, 0, self.width, self.height)
            painter.setCompositionMode(QPainter.CompositionMode_Source)
            painter.drawRect(0, 0, self.width, self.height)
    
    
            # painter.drawImage(QPointF(0, 0), self._pixmap)
            event.set()
    

    and here is the code for the creation of the gradient use in the text

        def rpm(self, value):
            angle = _remap(value, 0.0, self._max_rpm, 0.0, 270.0)
            self._rpm_polygon = self._create_rpm_polygon(int(round(angle)))
            self._rpm = value
    
            x = self.width / 2.0
            y = self.height / 2.0
            self._rpm_number_gradient = QConicalGradient(QPointF(x, y), -(angle - 45))
            self._rpm_number_gradient.setColorAt(0.45, QColor(0, 0, 0, 0))
            self._rpm_number_gradient.setColorAt(0.5, QColor(0, 0, 255, int(round(255 * self._brightness))))
            self._rpm_number_gradient.setColorAt(0.55, QColor(0, 0, 0, 0))
    
            #
            # self._rpm_number_gradient.setColorAt(rpm_end, QColor(0, 0, 0, 0))
            # self._rpm_number_gradient.setColorAt(rpm_value, QColor(0, 0, 255, int(round(255 * self._brightness))))
            # self._rpm_number_gradient.setColorAt(rpm_start, QColor(0, 0, 0, 0))
            self.update()
    

    any help is greatly appreciated.



  • I figured out what was wrong. I am used to other GUI frameworks where a call to update() is going to block until the update is done. This is not the case with Qt. By having the gradient created outside of the paint event the gradient can be in a state that is not fully populated with the needed stops when the gradient gets used to paint.

    I need to figure out a way to get the code base to render faster. or figure out what is causing a variable delay between update() is called and when the paint event occurs. This can be a swing as large as 250 milliseconds which is not acceptable when trying to have real time animations based on an input.


Log in to reply