@@ -773,39 +773,95 @@ def invoke(self, context: InvocationContext) -> ImageOutput:
773773 )
774774
775775
776+ COLOR_CHANNELS = Literal [
777+ "Red (RGBA)" ,
778+ "Green (RGBA)" ,
779+ "Blue (RGBA)" ,
780+ "Alpha (RGBA)" ,
781+ "Cyan (CMYK)" ,
782+ "Magenta (CMYK)" ,
783+ "Yellow (CMYK)" ,
784+ "Black (CMYK)" ,
785+ "Hue (HSV)" ,
786+ "Saturation (HSV)" ,
787+ "Value (HSV)" ,
788+ "Luminosity (LAB)" ,
789+ "A (LAB)" ,
790+ "B (LAB)" ,
791+ "Y (YCbCr)" ,
792+ "Cb (YCbCr)" ,
793+ "Cr (YCbCr)" ,
794+ ]
795+
796+ CHANNEL_FORMATS = {
797+ "Red (RGBA)" : ("RGBA" , 0 ),
798+ "Green (RGBA)" : ("RGBA" , 1 ),
799+ "Blue (RGBA)" : ("RGBA" , 2 ),
800+ "Alpha (RGBA)" : ("RGBA" , 3 ),
801+ "Cyan (CMYK)" : ("CMYK" , 0 ),
802+ "Magenta (CMYK)" : ("CMYK" , 1 ),
803+ "Yellow (CMYK)" : ("CMYK" , 2 ),
804+ "Black (CMYK)" : ("CMYK" , 3 ),
805+ "Hue (HSV)" : ("HSV" , 0 ),
806+ "Saturation (HSV)" : ("HSV" , 1 ),
807+ "Value (HSV)" : ("HSV" , 2 ),
808+ "Luminosity (LAB)" : ("LAB" , 0 ),
809+ "A (LAB)" : ("LAB" , 1 ),
810+ "B (LAB)" : ("LAB" , 2 ),
811+ "Y (YCbCr)" : ("YCbCr" , 0 ),
812+ "Cb (YCbCr)" : ("YCbCr" , 1 ),
813+ "Cr (YCbCr)" : ("YCbCr" , 2 ),
814+ }
815+
816+
776817@invocation (
777- "img_luminosity_adjust" ,
778- title = "Adjust Image Luminosity" ,
779- tags = ["image" , "luminosity" , "hsl" ],
818+ "img_channel_offset" ,
819+ title = "Offset Image Channel" ,
820+ tags = [
821+ "image" ,
822+ "offset" ,
823+ "red" ,
824+ "green" ,
825+ "blue" ,
826+ "alpha" ,
827+ "cyan" ,
828+ "magenta" ,
829+ "yellow" ,
830+ "black" ,
831+ "hue" ,
832+ "saturation" ,
833+ "luminosity" ,
834+ "value" ,
835+ ],
780836 category = "image" ,
781837 version = "1.0.0" ,
782838)
783- class ImageLuminosityAdjustmentInvocation (BaseInvocation ):
784- """Adjusts the Luminosity (Value) of an image."""
839+ class ImageChannelOffsetInvocation (BaseInvocation ):
840+ """Add or subtract a value from a specific color channel of an image."""
785841
786842 image : ImageField = InputField (description = "The image to adjust" )
787- luminosity : float = InputField (
788- default = 1.0 , ge = 0 , le = 1 , description = "The factor by which to adjust the luminosity (value)"
789- )
843+ channel : COLOR_CHANNELS = InputField (description = "Which channel to adjust" )
844+ offset : int = InputField (default = 0 , ge = - 255 , le = 255 , description = "The amount to adjust the channel by" )
790845
791846 def invoke (self , context : InvocationContext ) -> ImageOutput :
792847 pil_image = context .services .images .get_pil_image (self .image .image_name )
793848
794- # Convert PIL image to OpenCV format (numpy array), note color channel
795- # ordering is changed from RGB to BGR
796- image = numpy . array ( pil_image . convert ( "RGB" ))[:, :, :: - 1 ]
849+ # extract the channel and mode from the input and reference tuple
850+ mode = CHANNEL_FORMATS [ self . channel ][ 0 ]
851+ channel_number = CHANNEL_FORMATS [ self . channel ][ 1 ]
797852
798- # Convert image to HSV color space
799- hsv_image = cv2 .cvtColor (image , cv2 .COLOR_BGR2HSV )
853+ # Convert PIL image to new format
854+ converted_image = numpy .array (pil_image .convert (mode )).astype (int )
855+ image_channel = converted_image [:, :, channel_number ]
800856
801- # Adjust the luminosity ( value)
802- hsv_image [:, :, 2 ] = numpy .clip (hsv_image [:, :, 2 ] * self .luminosity , 0 , 255 )
857+ # Adjust the value, clipping to 0..255
858+ image_channel = numpy .clip (image_channel + self .offset , 0 , 255 )
803859
804- # Convert image back to BGR color space
805- image = cv2 . cvtColor ( hsv_image , cv2 . COLOR_HSV2BGR )
860+ # Put the channel back into the image
861+ converted_image [:, :, channel_number ] = image_channel
806862
807- # Convert back to PIL format and to original color mode
808- pil_image = Image .fromarray (image [:, :, :: - 1 ], "RGB" ).convert ("RGBA" )
863+ # Convert back to RGBA format and output
864+ pil_image = Image .fromarray (converted_image . astype ( numpy . uint8 ), mode = mode ).convert ("RGBA" )
809865
810866 image_dto = context .services .images .create (
811867 image = pil_image ,
@@ -827,36 +883,60 @@ def invoke(self, context: InvocationContext) -> ImageOutput:
827883
828884
829885@invocation (
830- "img_saturation_adjust" ,
831- title = "Adjust Image Saturation" ,
832- tags = ["image" , "saturation" , "hsl" ],
886+ "img_channel_multiply" ,
887+ title = "Multiply Image Channel" ,
888+ tags = [
889+ "image" ,
890+ "invert" ,
891+ "scale" ,
892+ "multiply" ,
893+ "red" ,
894+ "green" ,
895+ "blue" ,
896+ "alpha" ,
897+ "cyan" ,
898+ "magenta" ,
899+ "yellow" ,
900+ "black" ,
901+ "hue" ,
902+ "saturation" ,
903+ "luminosity" ,
904+ "value" ,
905+ ],
833906 category = "image" ,
834907 version = "1.0.0" ,
835908)
836- class ImageSaturationAdjustmentInvocation (BaseInvocation ):
837- """Adjusts the Saturation of an image."""
909+ class ImageChannelMultiplyInvocation (BaseInvocation ):
910+ """Scale a specific color channel of an image."""
838911
839912 image : ImageField = InputField (description = "The image to adjust" )
840- saturation : float = InputField (default = 1.0 , ge = 0 , le = 1 , description = "The factor by which to adjust the saturation" )
913+ channel : COLOR_CHANNELS = InputField (description = "Which channel to adjust" )
914+ scale : float = InputField (default = 1.0 , ge = 0.0 , description = "The amount to scale the channel by." )
915+ invert_channel : bool = InputField (default = False , description = "Invert the channel after scaling" )
841916
842917 def invoke (self , context : InvocationContext ) -> ImageOutput :
843918 pil_image = context .services .images .get_pil_image (self .image .image_name )
844919
845- # Convert PIL image to OpenCV format (numpy array), note color channel
846- # ordering is changed from RGB to BGR
847- image = numpy . array ( pil_image . convert ( "RGB" ))[:, :, :: - 1 ]
920+ # extract the channel and mode from the input and reference tuple
921+ mode = CHANNEL_FORMATS [ self . channel ][ 0 ]
922+ channel_number = CHANNEL_FORMATS [ self . channel ][ 1 ]
848923
849- # Convert image to HSV color space
850- hsv_image = cv2 .cvtColor (image , cv2 .COLOR_BGR2HSV )
924+ # Convert PIL image to new format
925+ converted_image = numpy .array (pil_image .convert (mode )).astype (float )
926+ image_channel = converted_image [:, :, channel_number ]
851927
852- # Adjust the saturation
853- hsv_image [:, :, 1 ] = numpy .clip (hsv_image [:, :, 1 ] * self .saturation , 0 , 255 )
928+ # Adjust the value, clipping to 0..255
929+ image_channel = numpy .clip (image_channel * self .scale , 0 , 255 )
854930
855- # Convert image back to BGR color space
856- image = cv2 .cvtColor (hsv_image , cv2 .COLOR_HSV2BGR )
931+ # Invert the channel if requested
932+ if self .invert_channel :
933+ image_channel = 255 - image_channel
857934
858- # Convert back to PIL format and to original color mode
859- pil_image = Image .fromarray (image [:, :, ::- 1 ], "RGB" ).convert ("RGBA" )
935+ # Put the channel back into the image
936+ converted_image [:, :, channel_number ] = image_channel
937+
938+ # Convert back to RGBA format and output
939+ pil_image = Image .fromarray (converted_image .astype (numpy .uint8 ), mode = mode ).convert ("RGBA" )
860940
861941 image_dto = context .services .images .create (
862942 image = pil_image ,
0 commit comments