diff --git a/bLUeGui/colorPatterns.py b/bLUeGui/colorPatterns.py index 48c94fc..5b0b7ce 100644 --- a/bLUeGui/colorPatterns.py +++ b/bLUeGui/colorPatterns.py @@ -17,7 +17,7 @@ """ import numpy as np from PySide6 import QtCore -from PySide6.QtCore import QPoint, QLine, Qt, QPointF +from PySide6.QtCore import Qt, QPointF, QLineF from PySide6.QtWidgets import QLabel, QVBoxLayout, QWidget, QHBoxLayout from bLUeTop.utils import QbLUeSlider @@ -25,7 +25,6 @@ from PySide6.QtGui import QImage, QPixmap, QPainter, QPainterPath, QColor from bLUeGui.bLUeImage import bImage, QImageBuffer -from .graphicsForm import baseForm class cmConverter(object): @@ -51,9 +50,8 @@ def __init__(self): class hueSatPattern(bImage): """ (hue, sat) color wheel image. - For fast display, the correspondence with RGB values is tabulated for each brightness. """ - # hue rotation + # wheel rotation rotation = 315 # default brightness defaultBr = 0.45 @@ -62,7 +60,7 @@ def __init__(self, w, h, converter, bright=defaultBr, border=0): """ Builds a (hue, sat) color wheel image of size (w, h) For fast display, the correspondence with RGB values is tabulated - for each value of the brightness. + for each brightness. :param w: image width :type w: int @@ -71,7 +69,7 @@ def __init__(self, w, h, converter, bright=defaultBr, border=0): :param converter: color space converter :type converter: cmConverter :param bright: image brightness - :type bright: int + :type bright: float :param border: image border :type border: int """ @@ -92,10 +90,10 @@ def __init__(self, w, h, converter, bright=defaultBr, border=0): # init array of grid (cartesian) coordinates coord = np.dstack(np.meshgrid(np.arange(w), - np.arange(h))) - # center : i1 = i - cx, j1 = -j + cy - cx = w / 2 - cy = h / 2 - coord = coord + [-cx, cy] # np.array([-cx, cy]) + # wheel center : i1 = i - cx, j1 = -j + cy + self.center = QPointF(w, h) / 2 + cx, cy = self.center.toTuple() + coord = coord + [-cx, cy] # init hue and sat arrays as polar coordinates. # arctan2 values are in range -pi, pi @@ -104,6 +102,7 @@ def __init__(self, w, h, converter, bright=defaultBr, border=0): hue = hue - np.floor(hue / 360.0) * 360.0 sat = np.linalg.norm(coord, axis=2, ord=2) / (cx - border) np.minimum(sat, 1.0, out=sat) + # init a stack of image buffers, one for each brightness in integer range 0..100 hsBuf = np.dstack((hue, sat))[np.newaxis, :] # shape (1, h, w, 2) hsBuf = np.tile(hsBuf, (101, 1, 1, 1)) # (101, h, w, 2) @@ -142,12 +141,10 @@ def GetPoint(self, h, s): :return:cartesian coordinates :rtype: 2-uple of float """ - cx = self.width() / 2 - cy = self.height() / 2 + cx, cy = self.center.toTuple() x, y = (cx - self.border) * s * np.cos((h - self.rotation) * np.pi / 180.0), \ (cy - self.border) * s * np.sin((h - self.rotation) * np.pi / 180.0) - x, y = x + cx, -y + cy - return x, y + return x + cx, -y + cy def GetPointVec(self, hsarray): """ @@ -161,12 +158,10 @@ def GetPointVec(self, hsarray): :rtype: ndarray, shape=(w,h,2), dtype=float """ h, s = hsarray[:, :, 0], hsarray[:, :, 1] - cx = self.width() / 2 - cy = self.height() / 2 + cx, cy = self.center.toTuple() x, y = (cx - self.border) * s * np.cos((h - self.rotation) * np.pi / 180.0), \ (cy - self.border) * s * np.sin((h - self.rotation) * np.pi / 180.0) - x, y = x + cx, - y + cy - return np.dstack((x, y)) + return np.dstack((x + cx, -y + cy)) class brightnessPattern(bImage): @@ -227,27 +222,36 @@ def __init__(self, w, h): self.currentColor = QColor(255, 255, 255) - cx, cy = int(w/2), int(h / 2) - self.p = QPointF(cx, cy) + self.center = QPointF(w, h) / 2 + cx, cy = self.center.toTuple() + self.p = QPointF(cx, cy) # copy + + self.l1 = QLineF(cx - 5, cy, cx + 5, cy) + self.l2 = QLineF(cx, cy - 5, cx, cy + 5) - self.l1 = QLine(cx - 5, cy, cx + 5, cy) - self.l2 = QLine(cx, cy - 5, cx, cy + 5) + self.radius, self.theta = 0.0, 0.0 # used by mousePressEvent and mouseMoveEvent + self.w, self.h = w, h # used by paintEvent self.qp = QPainter() - self.path = QPainterPath() - self.path.addEllipse(0.0, 0.0, w, h) + self.clPath = QPainterPath() + self.clPath.addEllipse(0.0, 0.0, w, h) self.setPixmap(self.bareWheel) def paintEvent(self, e): self.qp.begin(self) - self.qp.setClipPath(self.path) + self.qp.setClipPath(self.clPath) self.qp.drawPixmap(0, 0, self.bareWheel) + self.qp.setPen(Qt.black) + # central crosshair self.qp.drawLine(self.l1) self.qp.drawLine(self.l2) - self.qp.setPen(Qt.black) - self.qp.drawEllipse(self.p, 5, 5) + # current radius + u = self.p - self.center + u *= max(self.w, self.h) / max(u.manhattanLength(), 0.001) + self.qp.drawLine(QLineF(self.center, self.p + u)) + self.qp.drawEllipse(self.p, 6.0, 6.0) self.qp.end() def setCurrentColor(self, h, v): @@ -266,14 +270,26 @@ def setCurrentColor(self, h, v): def mousePressEvent(self, e): p = e.position() - self.p.setX(p.x()) - self.p.setY(p.y()) + x, y = p.toTuple() + self.radius = np.sqrt((x - self.center.x()) ** 2 + (y - self.center.y()) ** 2) + self.theta = np.arctan2(-y + self.center.y(), x - self.center.x()) + self.p.setX(x) + self.p.setY(y) self.update() def mouseMoveEvent(self, e): p = e.position() - self.p.setX(p.x()) - self.p.setY(p.y()) + x, y = p.toTuple() + modifiers = e.modifiers() + if modifiers == Qt.ControlModifier: + # constant radius = self.radius + self.theta = np.arctan2(y - self.center.y(), x - self.center.x()) + x, y = self.radius * np.cos(self.theta) + self.center.x(), self.radius * np.sin(self.theta) + self.center.y() + else: + self.theta = np.arctan2(y - self.center.y(), x - self.center.x()) + self.radius = np.sqrt((x - self.center.x()) ** 2 + (y - self.center.y()) ** 2) + self.p.setX(x) + self.p.setY(y) self.update() def mouseReleaseEvent(self, e): @@ -284,6 +300,7 @@ def update(self): self.colorChanged.emit() self.repaint() + class colorWheelChooser(QWidget): """ (Hue, Sat) color wheel picker, displaying a a slider and a sample of the current color. diff --git a/bLUeTop/graphicsGrading.py b/bLUeTop/graphicsGrading.py index 799ffe9..3c5b982 100644 --- a/bLUeTop/graphicsGrading.py +++ b/bLUeTop/graphicsGrading.py @@ -40,8 +40,6 @@ def getNewWindow(cls, targetImage=None, axeSize=500, LUTSize=LUTSIZE, layer=None build a graphicsFormGrading object. The parameter axeSize represents the size of the color wheel, border not included (the size of the window is adjusted). - :param cModel: color Model converter - :type cModel: cmConverter :param targetImage :type targetImage: :param axeSize: size of the color wheel (default 500) @@ -70,8 +68,7 @@ def getNewWindow(cls, targetImage=None, axeSize=500, LUTSize=LUTSIZE, layer=None def __init__(self, targetImage=None, axeSize=500, LUTSize=LUTSIZE, layer=None, parent=None, mainForm=None): """ - :param cModel: color space used by colorPicker, slider2D and colorPicker - :type cModel: cmConverter object + :param axeSize: size of the color wheel :type axeSize: int :param targetImage: @@ -217,6 +214,7 @@ def g(): self.setWhatsThis( """Color Grading
Use shadows, midtones and highlights color wheels to pick the 3 corresponding colors. + For constant saturation, hold down the Ctrl key . Use sliders to choose the shadows and highlights thresholds.
Your choices are reflected by the gradient image shown at the bottom
. Overlap between colors is controlled by the overlap slider.
diff --git a/version.py b/version.py index c4079f9..61d2bb6 100644 --- a/version.py +++ b/version.py @@ -16,4 +16,4 @@ along with this program. If not, see . """ -BLUE_VERSION = "V6-5.7.1" +BLUE_VERSION = "V6-5.7.2"