From f8f094514075f2acb567f33e4b5cd718c54fc0d6 Mon Sep 17 00:00:00 2001 From: Eric Mehl Date: Tue, 16 Jul 2024 14:45:27 -0400 Subject: [PATCH 1/3] ColorChooser : Add TMI Slider infrastructure --- SConstruct | 2 +- python/GafferUI/ColorChooser.py | 103 +++++++++++++++++++----- python/GafferUITest/ColorChooserTest.py | 89 ++++++++++++++++++++ python/GafferUITest/__init__.py | 1 + python/GafferUITest/images/tmi.exr | Bin 0 -> 17338 bytes 5 files changed, 173 insertions(+), 22 deletions(-) create mode 100644 python/GafferUITest/ColorChooserTest.py create mode 100644 python/GafferUITest/images/tmi.exr diff --git a/SConstruct b/SConstruct index 89db06faf4..b763a95cd4 100644 --- a/SConstruct +++ b/SConstruct @@ -1075,7 +1075,7 @@ libraries = { "GafferUITest" : { - "additionalFiles" : glob.glob( "python/GafferUITest/scripts/*.gfr" ), + "additionalFiles" : glob.glob( "python/GafferUITest/scripts/*.gfr" ) + glob.glob( "python/GafferUITest/images/*" ), }, diff --git a/python/GafferUI/ColorChooser.py b/python/GafferUI/ColorChooser.py index 18ed5cfaae..03366f6b2c 100644 --- a/python/GafferUI/ColorChooser.py +++ b/python/GafferUI/ColorChooser.py @@ -44,15 +44,46 @@ from Qt import QtGui +__tmiToRGBMatrix = imath.M33f( + -1.0 / 2.0, 0.0, 1.0 / 2.0, + 1.0 / 3.0, -2.0 / 3.0, 1.0 / 3.0, + 1.0, 1.0, 1.0 +) +__rgb2tmiMatrix = __tmiToRGBMatrix.inverse() + +def _tmiToRGB( c ) : + rgb = imath.V3f( c.r, c.g, c.b ) * __tmiToRGBMatrix + + result = c.__class__( c ) + result.r = rgb.x + result.g = rgb.y + result.b = rgb.z + + return result + +def _rgbToTMI( c ) : + tmi = imath.V3f( c.r, c.g, c.b ) * __rgb2tmiMatrix + + result = c.__class__( c ) + result.r = tmi.x + result.g = tmi.y + result.b = tmi.z + + return result + # A custom slider for drawing the backgrounds. class _ComponentSlider( GafferUI.Slider ) : def __init__( self, color, component, **kw ) : - min = hardMin = 0 - max = hardMax = 1 + if component in "tm" : + min = hardMin = -1 + max = hardMax = 1 + else : + min = hardMin = 0 + max = hardMax = 1 - if component in ( "r", "g", "b", "v" ) : + if component in ( "r", "g", "b", "v", "i" ) : hardMax = sys.float_info.max GafferUI.Slider.__init__( self, 0.0, min, max, hardMin, hardMax, **kw ) @@ -60,8 +91,8 @@ def __init__( self, color, component, **kw ) : self.color = color self.component = component - # Sets the slider color in RGB space for RGBA channels and - # HSV space for HSV channels. + # Sets the slider color in RGB space for RGBA channels, + # HSV space for HSV channels and TMI space for TMI channels. def setColor( self, color ) : self.color = color @@ -84,8 +115,8 @@ def _drawBackground( self, painter ) : else : c1 = imath.Color3f( self.color[0], self.color[1], self.color[2] ) c2 = imath.Color3f( self.color[0], self.color[1], self.color[2] ) - a = { "r" : 0, "g" : 1, "b" : 2, "h" : 0, "s" : 1, "v": 2 }[self.component] - c1[a] = 0 + a = { "r" : 0, "g" : 1, "b" : 2, "h" : 0, "s" : 1, "v": 2, "t" : 0, "m" : 1, "i" : 2 }[self.component] + c1[a] = -1 if self.component in "tm" else 0 c2[a] = 1 numStops = max( 2, size.x // 2 ) @@ -95,6 +126,8 @@ def _drawBackground( self, painter ) : c = c1 + (c2-c1) * t if self.component in "hsv" : c = c.hsv2rgb() + elif self.component in "tmi" : + c = _tmiToRGB( c ) grad.setColorAt( t, self._qtColor( displayTransform( c ) ) ) @@ -110,6 +143,8 @@ class ColorChooser( GafferUI.Widget ) : ColorChangedReason = enum.Enum( "ColorChangedReason", [ "Invalid", "SetColor", "Reset" ] ) + __ColorSpace = enum.Enum( "__ColorSpace", [ "RGB", "HSV", "TMI" ] ) + def __init__( self, color=imath.Color3f( 1 ), **kw ) : self.__column = GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Vertical, spacing = 4 ) @@ -118,6 +153,7 @@ def __init__( self, color=imath.Color3f( 1 ), **kw ) : self.__color = color self.__colorHSV = self.__color.rgb2hsv() + self.__colorTMI = _rgbToTMI( self.__color ) self.__defaultColor = color self.__sliders = {} @@ -229,8 +265,11 @@ def __componentValueChanged( self, componentWidget, reason ) : # doesn't provide the capability itself. Add the functionality # into the NumericWidget and remove this code. componentValue = componentWidget.getValue() - componentValue = max( componentValue, 0 ) - if componentWidget.component in ( "a", "h", "s" ) : + if componentWidget.component in "tm" : + componentValue = max( componentValue, -1 ) + else : + componentValue = max( componentValue, 0 ) + if componentWidget.component in ( "a", "h", "s", "t", "m" ) : componentValue = min( componentValue, 1 ) if componentWidget.component in ( "r", "g", "b", "a" ) : @@ -240,15 +279,22 @@ def __componentValueChanged( self, componentWidget, reason ) : newColor[a] = componentValue self.__setColorInternal( newColor, reason ) - else : + elif componentWidget.component in ( "h", "s", "v" ) : newColor = self.__colorHSV.__class__( self.__colorHSV ) a = { "h" : 0, "s" : 1, "v" : 2 }[componentWidget.component] newColor[a] = componentValue - self.__setColorInternal( newColor, reason, True ) + self.__setColorInternal( newColor, reason, self.__ColorSpace.HSV ) + elif componentWidget.component in ( "t", "m", "i" ) : + newColor = self.__colorTMI.__class__( self.__colorTMI ) + + a = { "t" : 0, "m" : 1, "i" : 2 }[componentWidget.component] + newColor[a] = componentValue - def __setColorInternal( self, color, reason, hsv = False ) : + self.__setColorInternal( newColor, reason, self.__ColorSpace.TMI ) + + def __setColorInternal( self, color, reason, colorSpace = __ColorSpace.RGB ) : dragBeginOrEnd = reason in ( GafferUI.Slider.ValueChangedReason.DragBegin, @@ -257,19 +303,34 @@ def __setColorInternal( self, color, reason, hsv = False ) : GafferUI.NumericWidget.ValueChangedReason.DragEnd, ) - colorChanged = color != ( self.__colorHSV if hsv else self.__color ) + colorChanged = color != { + self.__ColorSpace.RGB : self.__color, + self.__ColorSpace.HSV : self.__colorHSV, + self.__ColorSpace.TMI : self.__colorTMI + }[colorSpace] if colorChanged : - colorRGB = color.hsv2rgb() if hsv else color - self.__color = colorRGB - self.__colorSwatch.setColor( colorRGB ) - - hsv = color if hsv else color.rgb2hsv() + if colorSpace == self.__ColorSpace.RGB : + colorRGB = color + colorHSV = color.rgb2hsv() + colorTMI = _rgbToTMI( colorRGB ) + elif colorSpace == self.__ColorSpace.HSV : + colorRGB = color.hsv2rgb() + colorHSV = color + colorTMI = _rgbToTMI( colorRGB ) + elif colorSpace == self.__ColorSpace.TMI : + colorRGB = _tmiToRGB( color ) + colorHSV = colorRGB.rgb2hsv() + colorTMI = color + + colorHSV[0] = colorHSV[0] if colorHSV[1] > 1e-7 and colorHSV[2] > 1e-7 else self.__colorHSV[0] + colorHSV[1] = colorHSV[1] if colorHSV[2] > 1e-7 else self.__colorHSV[1] - hsv[0] = hsv[0] if hsv[1] > 1e-7 and hsv[2] > 1e-7 else self.__colorHSV[0] - hsv[1] = hsv[1] if hsv[2] > 1e-7 else self.__colorHSV[1] + self.__color = colorRGB + self.__colorHSV = colorHSV + self.__colorTMI = colorTMI - self.__colorHSV = hsv + self.__colorSwatch.setColor( colorRGB ) ## \todo This is outside the conditional because the clamping we do # in __componentValueChanged means the color value may not correspond diff --git a/python/GafferUITest/ColorChooserTest.py b/python/GafferUITest/ColorChooserTest.py new file mode 100644 index 0000000000..dc333fd2e3 --- /dev/null +++ b/python/GafferUITest/ColorChooserTest.py @@ -0,0 +1,89 @@ +########################################################################## +# +# Copyright (c) 2024, Cinesite VFX Ltd. All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above +# copyright notice, this list of conditions and the following +# disclaimer. +# +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided with +# the distribution. +# +# * Neither the name of John Haddon nor the names of +# any other contributors to this software may be used to endorse or +# promote products derived from this software without specific prior +# written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +########################################################################## + +import pathlib +import unittest + +import imath +import OpenImageIO + +from GafferUI.ColorChooser import _tmiToRGB +from GafferUI.ColorChooser import _rgbToTMI +import GafferUITest + +class ColorChooserTest( GafferUITest.TestCase ) : + + def testTMI( self ) : + # Load a precomputed image generated by Houdini to test our TMI <-> RGB calculation. + # The image is 200px x 20px. Each 20px square varies the temperature from -1.0 to 1.0 + # on the X axis and magenta from -1.0 to 1.0 on the Y-axis. Each of the 10 squares, from + # left to right, varies the intensity from 0.0 to 0.9. + tileWidth = 20 + tileCount = 10 + tmiTargetImage = OpenImageIO.ImageBuf( ( pathlib.Path( __file__ ).parent / "images" / "tmi.exr" ).as_posix() ) + + for tile in range ( 0, tileCount ) : + for y in range( 0, tileWidth ) : + for x in range( 0, tileWidth ) : + tmiOriginal = imath.Color3f( + ( float( x ) / tileWidth ) * 2.0 - 1.0, + ( float( y ) / tileWidth ) * 2.0 - 1.0, + float( tile ) / tileCount + ) + + rgbConverted = _tmiToRGB( tmiOriginal ) + tmiTarget = tmiTargetImage.getpixel( x + ( tile * tileWidth ), y ) + self.assertAlmostEqual( tmiTarget[0], rgbConverted.r, places = 6 ) + self.assertAlmostEqual( tmiTarget[1], rgbConverted.g, places = 6 ) + self.assertAlmostEqual( tmiTarget[2], rgbConverted.b, places = 6 ) + + tmiConverted = _rgbToTMI( rgbConverted ) + self.assertAlmostEqual( tmiConverted.r, tmiOriginal.r, places = 6 ) + self.assertAlmostEqual( tmiConverted.g, tmiOriginal.g, places = 6 ) + self.assertAlmostEqual( tmiConverted.b, tmiOriginal.b, places = 6 ) + + def testTMIAlpha( self ) : + + rgb = imath.Color4f( 0.5 ) + tmi = _rgbToTMI( rgb ) + self.assertEqual( tmi.a, rgb.a ) + + rgbConverted = _tmiToRGB( tmi ) + self.assertEqual( rgbConverted.a, rgb.a ) + + +if __name__ == "__main__" : + unittest.main() \ No newline at end of file diff --git a/python/GafferUITest/__init__.py b/python/GafferUITest/__init__.py index ad99a025b9..6e2635e70c 100644 --- a/python/GafferUITest/__init__.py +++ b/python/GafferUITest/__init__.py @@ -133,6 +133,7 @@ from .BoxIOUITest import BoxIOUITest from .AnnotationsGadgetTest import AnnotationsGadgetTest from .PopupWindowTest import PopupWindowTest +from .ColorChooserTest import ColorChooserTest if __name__ == "__main__": unittest.main() diff --git a/python/GafferUITest/images/tmi.exr b/python/GafferUITest/images/tmi.exr new file mode 100644 index 0000000000000000000000000000000000000000..ec7eeb5aef281d16e8fb617c598475c539a9eb8c GIT binary patch literal 17338 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 Date: Mon, 15 Jul 2024 11:37:11 -0400 Subject: [PATCH 2/3] ColorChooser : Improve plug alignment This was broken slightly in #5902 when we introduced channel name labels. The text width varies somewhat, causing the numeric widget and sliders to be imperfectly aligned. --- python/GafferUI/ColorChooser.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/python/GafferUI/ColorChooser.py b/python/GafferUI/ColorChooser.py index 03366f6b2c..607a8615ad 100644 --- a/python/GafferUI/ColorChooser.py +++ b/python/GafferUI/ColorChooser.py @@ -158,23 +158,23 @@ def __init__( self, color=imath.Color3f( 1 ), **kw ) : self.__sliders = {} self.__numericWidgets = {} + self.__channelLabels = {} self.__componentValueChangedConnections = [] with self.__column : - # sliders and numeric widgets - for component in "rgbahsv" : - with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 4 ) : + with GafferUI.GridContainer( spacing = 4 ) : - with GafferUI.ListContainer( GafferUI.ListContainer.Orientation.Horizontal, spacing = 8 ) : - GafferUI.Label( component.capitalize() ) - numericWidget = GafferUI.NumericWidget( 0.0 ) + # sliders and numeric widgets + for row, component in enumerate( "rgbahsv" ) : + self.__channelLabels[component] = GafferUI.Label( component.capitalize(), parenting = { "index" : ( 0, row ), "alignment" : ( GafferUI.HorizontalAlignment.Center, GafferUI.VerticalAlignment.Center ) } ) + numericWidget = GafferUI.NumericWidget( 0.0, parenting = { "index" : ( 1, row ) } ) numericWidget.setFixedCharacterWidth( 6 ) numericWidget.component = component self.__numericWidgets[component] = numericWidget - slider = _ComponentSlider( color, component ) + slider = _ComponentSlider( color, component, parenting = { "index" : ( 2, row ) } ) self.__sliders[component] = slider self.__componentValueChangedConnections.append( @@ -360,9 +360,14 @@ def __updateUIFromColor( self ) : if c.dimensions() == 4 : self.__sliders["a"].setValue( c[3] ) self.__numericWidgets["a"].setValue( c[3] ) - self.__sliders["a"].parent().setVisible( True ) + + self.__sliders["a"].setVisible( True ) + self.__numericWidgets["a"].setVisible( True ) + self.__channelLabels["a"].setVisible( True ) else : - self.__sliders["a"].parent().setVisible( False ) + self.__sliders["a"].setVisible( False ) + self.__numericWidgets["a"].setVisible( False ) + self.__channelLabels["a"].setVisible( False ) for slider in [ v for k, v in self.__sliders.items() if k in "hsv" ] : slider.setColor( self.__colorHSV ) From cbb550d1e455147a37c119a57e97466d3c73940b Mon Sep 17 00:00:00 2001 From: John Haddon Date: Thu, 18 Jul 2024 11:04:47 +0100 Subject: [PATCH 3/3] ColorChooser : Allow negative values for RGBVTMI We don't really want to allow these, but they can occur naturally in one component while editing values in another. By accommodating that within the slider `hardMin`, we get a nice "out of range" triangle to alert us when we've gone negative, whereas before the slider clamped to zero and gave us a misleading value. For consistency, we now also allow negative values to be entered directly in the numeric widgets - if you can get into that state using a slider or numeric widget for another compoment, why preclude it in the field itself? --- python/GafferUI/ColorChooser.py | 48 +++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/python/GafferUI/ColorChooser.py b/python/GafferUI/ColorChooser.py index 607a8615ad..66b92f56f7 100644 --- a/python/GafferUI/ColorChooser.py +++ b/python/GafferUI/ColorChooser.py @@ -35,6 +35,7 @@ # ########################################################################## +import collections import enum import sys import imath @@ -71,22 +72,39 @@ def _rgbToTMI( c ) : return result +__Range = collections.namedtuple( "__Range", [ "min", "max", "hardMin", "hardMax" ] ) + +_ranges = { + # We don't _really_ want to allow negative values for RGB, but + # they can arise from TMI values in the allowed TMI range. It's + # better to allow these to be displayed (as an "out of range" + # triangle in the sliders) than to show inconsistent values between + # components. + "r" : __Range( 0, 1, -sys.float_info.max, sys.float_info.max ), + "g" : __Range( 0, 1, -sys.float_info.max, sys.float_info.max ), + "b" : __Range( 0, 1, -sys.float_info.max, sys.float_info.max ), + "a" : __Range( 0, 1, 0, 1 ), + "h" : __Range( 0, 1, 0, 1 ), + "s" : __Range( 0, 1, 0, 1 ), + # As above, we're allowing out-of-regular-range values here too, + # because they can arise when authoring in-range values via RGB. + "v" : __Range( 0, 1, -sys.float_info.max, sys.float_info.max ), + "t" : __Range( -1, 1, -sys.float_info.max, sys.float_info.max ), + "m" : __Range( -1, 1, -sys.float_info.max, sys.float_info.max ), + "i" : __Range( 0, 1, -sys.float_info.max, sys.float_info.max ), +} + # A custom slider for drawing the backgrounds. class _ComponentSlider( GafferUI.Slider ) : def __init__( self, color, component, **kw ) : - if component in "tm" : - min = hardMin = -1 - max = hardMax = 1 - else : - min = hardMin = 0 - max = hardMax = 1 - - if component in ( "r", "g", "b", "v", "i" ) : - hardMax = sys.float_info.max - - GafferUI.Slider.__init__( self, 0.0, min, max, hardMin, hardMax, **kw ) + GafferUI.Slider.__init__( + self, 0.0, + min = _ranges[component].min, max = _ranges[component].max, + hardMin = _ranges[component].hardMin, hardMax = _ranges[component].hardMax, + **kw + ) self.color = color self.component = component @@ -265,12 +283,8 @@ def __componentValueChanged( self, componentWidget, reason ) : # doesn't provide the capability itself. Add the functionality # into the NumericWidget and remove this code. componentValue = componentWidget.getValue() - if componentWidget.component in "tm" : - componentValue = max( componentValue, -1 ) - else : - componentValue = max( componentValue, 0 ) - if componentWidget.component in ( "a", "h", "s", "t", "m" ) : - componentValue = min( componentValue, 1 ) + componentValue = max( componentValue, _ranges[componentWidget.component].hardMin ) + componentValue = min( componentValue, _ranges[componentWidget.component].hardMax ) if componentWidget.component in ( "r", "g", "b", "a" ) : newColor = self.__color.__class__( self.__color )