Skip to content

Commit

Permalink
added mouse move event modifier to color wheel for constant saturation
Browse files Browse the repository at this point in the history
  • Loading branch information
bvirxx committed Dec 11, 2023
1 parent 4b266b3 commit d164ee2
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 36 deletions.
79 changes: 48 additions & 31 deletions bLUeGui/colorPatterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,14 @@
"""
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
from .colorCube import hsv2rgbVec, hsp2rgb, rgb2hsp, rgb2hspVec, hsv2rgb, rgb2hsB, rgb2hsBVec, hsp2rgbVec
from PySide6.QtGui import QImage, QPixmap, QPainter, QPainterPath, QColor

from bLUeGui.bLUeImage import bImage, QImageBuffer
from .graphicsForm import baseForm


class cmConverter(object):
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
"""
Expand All @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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):
"""
Expand All @@ -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):
Expand Down Expand Up @@ -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):
Expand All @@ -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):
Expand All @@ -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.
Expand Down
6 changes: 2 additions & 4 deletions bLUeTop/graphicsGrading.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -217,6 +214,7 @@ def g():
self.setWhatsThis(
"""<b>Color Grading</b><br>
Use shadows, midtones and highlights color wheels to pick the 3 corresponding colors.
For constant saturation, hold down the <i>Ctrl key</i> .
Use sliders to choose the shadows and highlights thresholds.<br>
Your choices are reflected by the gradient image shown at the bottom<br>. Overlap between colors
is controlled by the <i>overlap</i> slider.<br>
Expand Down
2 changes: 1 addition & 1 deletion version.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,4 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""

BLUE_VERSION = "V6-5.7.1"
BLUE_VERSION = "V6-5.7.2"

0 comments on commit d164ee2

Please sign in to comment.