Objective I want to get the visual color at an arbitrary position within the 0.0-1.0 range of any given QGradient. This may be necessary to properly compute new intermediate stops between existing ones, or to get a color at a specific point in order to use it as a display reference within the gradient. The QVariantAnimation approach At first, I thought about using QVariantAnimation, which seemed reasonable. Gradients can have many colors set at different points, which can be set by using QVariantAnimation.setKeyValueAt(). They also can have no 0.0 or 1.0 stops, meaning that if, for example, the first stop is at 0.2 and the last is at 0.8, the start and end values of the animation must be set as well with the relative first and last color. My original attempt was to create a temporary animation, then set its key values based on the gradient stops: using setCurrentTime() based on the computed position within the animation duration() allows to get the intermediate value. Here is my original implementation: Note that I'm not able to write C++ code, so I'll provide Python based code for the following examples. C++ based answers will be well received as well, though. def gradientColorAt(grad, pos): if pos <= 0: return grad.stops()[0][1] elif pos >= 1: return grad.stops()[-1][1] ani = QVariantAnimation() ani.setDuration(1000) startDone = False for stop, color in grad.stops(): if not startDone: startDone = True ani.setStartValue(color) if not stop: continue ani.setKeyValueAt(stop, color) if stop < 1: ani.setEndValue(color) ani.setCurrentTime(round(1000 * pos)) return ani.currentValue() The above apparently works fine, as long as all colors are fully opaque. I later realized that a similar post also suggested a similar approach in some of its answers. The issue with non opaque colors Unfortunately, something unexpected happens whenever a color in the gradient has an alpha() value less than 255 (or 1.0 for QColor.alphaF()). Qt seems to follow the convention also used in modern web browsers for which the transitions between colors do not strictly follow linear values in their components (red, green, blue and alpha), and also considering the "transparent" color based on the possible transition to another color, and even though the concept of transparent is actually a "transparent black". Be aware, though, that QColor(Qt.transparent) is exactly QColor(0, 0, 0, 0). Even though QColor(255, 0, 0, 0) would have the same result for the purposes of gradients in both Qt and common browsers, it is not the same color. Consider the following example: from PyQt5.QtCore import * from PyQt5.QtGui import * from PyQt5.QtWidgets import * app = QApplication([]) win = QWidget() lay = QVBoxLayout(win) grad = QLinearGradient(0, 0, 1, 0) grad.setCoordinateMode(grad.ObjectBoundingMode) grad.setColorAt(1, Qt.green) transBlack = QColor(Qt.transparent) transRed = QColor(255, 0, 0, 0) pal = app.palette() for color in (transBlack, transRed): grad.setColorAt(0, color) pal.setBrush(QPalette.Window, QBrush(grad)) widget = QWidget() widget.setAutoFillBackground(True) widget.setPalette(pal) lay.addWidget(widget) win.resize(400, 400) win.show() app.exec() And here is the result: Some research suggested a similar pattern in browsers (like this post), and I can understand the reasoning behind that. Nonetheless, the issue is that the above QVariantAnimation attempt does not follow the same pattern, as it simply uses an internal interpolator that linearly evaluates all color components. Let's add the gradientColorAt() function to the above code, along with the following class: class GradientViewer(QWidget): def __init__(self, gradient): super().__init__() self.gradient = QLinearGradient(gradient) def paintEvent(self, event): qp = QPainter(self) width = self.width() height = self.height() for x in range(width + 1): qp.setPen(gradientColorAt(self.gradient, x / width)) qp.drawLine(x, 0, x, height) Then add related instances to the layout using the same gradients: ... # replace the above for loop for color in (transBlack, transRed): grad.setColorAt(0, color) pal.setBrush(QPalette.Window, QBrush(grad)) widget = QWidget() widget.setAutoFillBackground(True) widget.setPalette(pal) lay.addWidget(widget) # custom widget using the same gradient lay.addWidget(GradientViewer(grad)) ... And here is the result: Some digging Further research resulted in studying the Qt sources, which looked promising: the raster painting engine has a generateGradientColorTable() function (see it in code browser), which Qt seems to use to create a bitmap cache based on an arbitrary size of color interpolations within a gradient. I thought about overriding QVariant::interpolated(), and then recreating a similar function with a QVariantAnimation subclass, based on that code: class QColorInterpolator(QVariantAnimation): def interpolated(self, c1, c2, pos): if pos <= 0: return c1 elif pos >= 1: return c2 first = c1.rgba64() first = QRgba64.fromRgba64( first.red(), first.green(), first.blue(), (first.alpha() * 256) >> 8 ).premultiplied() second = c2.rgba64() second = QRgba64.fromRgba64( second.red(), second.green(), second.blue(), (second.alpha() * 256) >> 8 ).premultiplied() r1 = first.red() << 16 g1 = first.green() << 16 b1 = first.blue() << 16 a1 = first.alpha() << 16 r2 = second.red() << 16 g2 = second.green() << 16 b2 = second.blue() << 16 a2 = second.alpha() << 16 rd = round((r2 - r1) * pos) gd = round((g2 - g1) * pos) bd = round((b2 - b1) * pos) ad = round((a2 - a1) * pos) return QColor.fromRgba64( (r1 + 32768 + rd) >> 16, (g1 + 32768 + gd) >> 16, (b1 + 32768 + bd) >> 16, (a1 + 32768 + ad) >> 16, ) Obviously, the above class isn't really needed, as the interpolation could be achieved by using relative steps between stops. The only benefit is the possibility of using interpolated(), which will always use a 0-1 range between key frames. It may not be completely efficient, but results in a cleaner code. Unfortunately, my poor understanding of color behavior and alpha premultiplication seem to show its problems: replacing QVariantAnimation with QColorInterpolator in the above gradientColorAt() function will give exactly the same result as in using Qt.transparent. Working solution, not really efficient Right now, the only working solution I came out with was to use an internal cache of QImages, each one corresponding to possible intermediate gradients (every range between existing stops), paint the gradient on it, and then use QImage's pixelColor() to get the actual color of the computed position: class QColorInterpolator(QVariantAnimation): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.cache = {} def setStartValue(self, value): if value != self.startValue(): self.cache.clear() super().setStartValue(value) def setEndValue(self, value): if value != self.endValue(): self.cache.clear() super().setEndValue(value) def setKeyValueAt(self, pos, value): if self.keyValueAt(pos) != value: self.cache.clear() super().setKeyValueAt(pos, value) def setKeyValues(self, values): self.cache.clear() super().setKeyValues(values) def _createCacheGradient(self, c1, c2): g = QLinearGradient(0, 0, 1024, 0) g.setStops([(0, c1), (1, c2)]) image = QImage(1024, 1, QImage.Format_ARGB32) image.fill(Qt.transparent) qp = QPainter(image) qp.fillRect(image.rect(), g) qp.end() return image def interpolated(self, c1, c2, pos): if pos <= 0: return c1 if pos >= 1: return c2 if c1 == c2: return c1 key = c1.rgba64(), c2.rgba64() if key in self.cache: gradImg = self.cache[key] else: self.cache[key] = gradImg = self._createCacheGradient(c1, c2) return gradImg.pixelColor(round(1023 * pos), 0) Replacing the QColorInterpolator with the new class works as expected: Final request While the last attempt works as expected, I don't really like it, for the following reasons: as far as I can understand, Qt already creates an internal "color table" cache, and while it obviously cannot be externally accessed, creating separated QImages doesn't seem very efficient; there is no direct computation of color values, only an assumed one based on the pixel data of QImage; due to all the above, any temporary computation of intermediate colors would require to create a QVariantAnimation, which I'd prefer to avoid; The primary question, therefore, is: how (and why) can my interpolated() function be fixed in order to provide correct color values, as used in a visually painted gradient by Qt? If you want to expand the above, there are some follow ups I'd like to know more about; it seems that QGradient/QBrush allows to set two different types of interpolations (ColorInterpolation and ComponentInterpolation): what is the difference? Does any of those result in what the linear computation used by QVariantAnimation actually does? Is it possible to get the opposite painting behavior, only following linear computation? Continue reading...