小部件假定它的形状始终是正方形;该代码为此提供了一个自定义AspectLayout,但这不是必需的。
问题在于,当形状不是正方形时,颜色的计算是错误的,因为当一个维度比另一个维度大得多时,坐标没有正确映射。例如,如果小部件比高度宽得多,x 坐标会“移动”,因为圆(现在是实际的椭圆)显示为居中,但颜色函数使用 最小 大小。
解决方案是创建一个始终显示在中心的内部QRect,并将其用于绘制和计算:
class ColorCircle(QWidget):
# ...
def resizeEvent(self, ev: QResizeEvent) -> None:
size = min(self.width(), self.height())
self.radius = size / 2
self.square = QRect(0, 0, size, size)
self.square.moveCenter(self.rect().center())
def paintEvent(self, ev: QPaintEvent) -> None:
# ...
p.setPen(Qt.transparent)
p.setBrush(hsv_grad)
p.drawEllipse(self.square)
p.setBrush(val_grad)
p.drawEllipse(self.square)
# ...
def map_color(self, x: int, y: int) -> QColor:
x -= self.square.x()
y -= self.square.y()
# ...
请注意,该代码使用 numpy,但它用于那些显然不需要 numpy 性能的应用程序真正需要这样一个 huge 库的函数。
例如,line_circle_inter 使用一种复杂的方法来计算“光标”(小圆圈)的位置,但这绝对没有必要,因为色相和饱和度值已经提供了“可用”坐标:色相表示圆中的角度(从 12 小时位置开始,逆时针方向),而饱和度是到中心的距离。
QLineF 提供了一个方便的函数fromPolar(),它返回一条具有给定长度和角度的线:长度将是半径乘以饱和度,角度是色相乘以 360(加上 90°,因为角度总是从3点);然后我们可以在圆的中心平移那条线,光标将定位在线段的第二个点:
def paintEvent(self, event):
# ...
p.setPen(Qt.black)
p.setBrush(self.selected_color)
line = QLineF.fromPolar(self.radius * self.s, 360 * self.h + 90)
line.translate(self.square.center())
p.drawEllipse(line.p2(), 10, 10)
地图颜色函数可以使用相同的逻辑,但是颠倒:我们从中心开始构建一条线到鼠标光标位置,然后饱和度是长度除以半径(将值清理为1.0,如这是最大可能值),而色调是线角度(如上减 90°)除以 360 并针对正值0.0-1.0 范围进行了清理。
def map_color(self, x: int, y: int) -> QColor:
line = QLineF(QPointF(self.rect().center()), QPointF(x, y))
s = min(1.0, line.length() / self.radius)
h = (line.angle() - 90) / 360 % 1.
return h, s, self.v