@@ -778,39 +778,95 @@ def invoke(self, context: InvocationContext) -> ImageOutput:
778778 )
779779
780780
781+ COLOR_CHANNELS = Literal [
782+ "Red (RGBA)" ,
783+ "Green (RGBA)" ,
784+ "Blue (RGBA)" ,
785+ "Alpha (RGBA)" ,
786+ "Cyan (CMYK)" ,
787+ "Magenta (CMYK)" ,
788+ "Yellow (CMYK)" ,
789+ "Black (CMYK)" ,
790+ "Hue (HSV)" ,
791+ "Saturation (HSV)" ,
792+ "Value (HSV)" ,
793+ "Luminosity (LAB)" ,
794+ "A (LAB)" ,
795+ "B (LAB)" ,
796+ "Y (YCbCr)" ,
797+ "Cb (YCbCr)" ,
798+ "Cr (YCbCr)" ,
799+ ]
800+
801+ CHANNEL_FORMATS = {
802+ "Red (RGBA)" : ("RGBA" , 0 ),
803+ "Green (RGBA)" : ("RGBA" , 1 ),
804+ "Blue (RGBA)" : ("RGBA" , 2 ),
805+ "Alpha (RGBA)" : ("RGBA" , 3 ),
806+ "Cyan (CMYK)" : ("CMYK" , 0 ),
807+ "Magenta (CMYK)" : ("CMYK" , 1 ),
808+ "Yellow (CMYK)" : ("CMYK" , 2 ),
809+ "Black (CMYK)" : ("CMYK" , 3 ),
810+ "Hue (HSV)" : ("HSV" , 0 ),
811+ "Saturation (HSV)" : ("HSV" , 1 ),
812+ "Value (HSV)" : ("HSV" , 2 ),
813+ "Luminosity (LAB)" : ("LAB" , 0 ),
814+ "A (LAB)" : ("LAB" , 1 ),
815+ "B (LAB)" : ("LAB" , 2 ),
816+ "Y (YCbCr)" : ("YCbCr" , 0 ),
817+ "Cb (YCbCr)" : ("YCbCr" , 1 ),
818+ "Cr (YCbCr)" : ("YCbCr" , 2 ),
819+ }
820+
821+
781822@invocation (
782- "img_luminosity_adjust" ,
783- title = "Adjust Image Luminosity" ,
784- tags = ["image" , "luminosity" , "hsl" ],
823+ "img_channel_offset" ,
824+ title = "Offset Image Channel" ,
825+ tags = [
826+ "image" ,
827+ "offset" ,
828+ "red" ,
829+ "green" ,
830+ "blue" ,
831+ "alpha" ,
832+ "cyan" ,
833+ "magenta" ,
834+ "yellow" ,
835+ "black" ,
836+ "hue" ,
837+ "saturation" ,
838+ "luminosity" ,
839+ "value" ,
840+ ],
785841 category = "image" ,
786842 version = "1.0.0" ,
787843)
788- class ImageLuminosityAdjustmentInvocation (BaseInvocation ):
789- """Adjusts the Luminosity (Value) of an image."""
844+ class ImageChannelOffsetInvocation (BaseInvocation ):
845+ """Add or subtract a value from a specific color channel of an image."""
790846
791847 image : ImageField = InputField (description = "The image to adjust" )
792- luminosity : float = InputField (
793- default = 1.0 , ge = 0 , le = 1 , description = "The factor by which to adjust the luminosity (value)"
794- )
848+ channel : COLOR_CHANNELS = InputField (description = "Which channel to adjust" )
849+ offset : int = InputField (default = 0 , ge = - 255 , le = 255 , description = "The amount to adjust the channel by" )
795850
796851 def invoke (self , context : InvocationContext ) -> ImageOutput :
797852 pil_image = context .services .images .get_pil_image (self .image .image_name )
798853
799- # Convert PIL image to OpenCV format (numpy array), note color channel
800- # ordering is changed from RGB to BGR
801- image = numpy . array ( pil_image . convert ( "RGB" ))[:, :, :: - 1 ]
854+ # extract the channel and mode from the input and reference tuple
855+ mode = CHANNEL_FORMATS [ self . channel ][ 0 ]
856+ channel_number = CHANNEL_FORMATS [ self . channel ][ 1 ]
802857
803- # Convert image to HSV color space
804- hsv_image = cv2 .cvtColor (image , cv2 .COLOR_BGR2HSV )
858+ # Convert PIL image to new format
859+ converted_image = numpy .array (pil_image .convert (mode )).astype (int )
860+ image_channel = converted_image [:, :, channel_number ]
805861
806- # Adjust the luminosity ( value)
807- hsv_image [:, :, 2 ] = numpy .clip (hsv_image [:, :, 2 ] * self .luminosity , 0 , 255 )
862+ # Adjust the value, clipping to 0..255
863+ image_channel = numpy .clip (image_channel + self .offset , 0 , 255 )
808864
809- # Convert image back to BGR color space
810- image = cv2 . cvtColor ( hsv_image , cv2 . COLOR_HSV2BGR )
865+ # Put the channel back into the image
866+ converted_image [:, :, channel_number ] = image_channel
811867
812- # Convert back to PIL format and to original color mode
813- pil_image = Image .fromarray (image [:, :, :: - 1 ], "RGB" ).convert ("RGBA" )
868+ # Convert back to RGBA format and output
869+ pil_image = Image .fromarray (converted_image . astype ( numpy . uint8 ), mode = mode ).convert ("RGBA" )
814870
815871 image_dto = context .services .images .create (
816872 image = pil_image ,
@@ -832,36 +888,60 @@ def invoke(self, context: InvocationContext) -> ImageOutput:
832888
833889
834890@invocation (
835- "img_saturation_adjust" ,
836- title = "Adjust Image Saturation" ,
837- tags = ["image" , "saturation" , "hsl" ],
891+ "img_channel_multiply" ,
892+ title = "Multiply Image Channel" ,
893+ tags = [
894+ "image" ,
895+ "invert" ,
896+ "scale" ,
897+ "multiply" ,
898+ "red" ,
899+ "green" ,
900+ "blue" ,
901+ "alpha" ,
902+ "cyan" ,
903+ "magenta" ,
904+ "yellow" ,
905+ "black" ,
906+ "hue" ,
907+ "saturation" ,
908+ "luminosity" ,
909+ "value" ,
910+ ],
838911 category = "image" ,
839912 version = "1.0.0" ,
840913)
841- class ImageSaturationAdjustmentInvocation (BaseInvocation ):
842- """Adjusts the Saturation of an image."""
914+ class ImageChannelMultiplyInvocation (BaseInvocation ):
915+ """Scale a specific color channel of an image."""
843916
844917 image : ImageField = InputField (description = "The image to adjust" )
845- saturation : float = InputField (default = 1.0 , ge = 0 , le = 1 , description = "The factor by which to adjust the saturation" )
918+ channel : COLOR_CHANNELS = InputField (description = "Which channel to adjust" )
919+ scale : float = InputField (default = 1.0 , ge = 0.0 , description = "The amount to scale the channel by." )
920+ invert_channel : bool = InputField (default = False , description = "Invert the channel after scaling" )
846921
847922 def invoke (self , context : InvocationContext ) -> ImageOutput :
848923 pil_image = context .services .images .get_pil_image (self .image .image_name )
849924
850- # Convert PIL image to OpenCV format (numpy array), note color channel
851- # ordering is changed from RGB to BGR
852- image = numpy . array ( pil_image . convert ( "RGB" ))[:, :, :: - 1 ]
925+ # extract the channel and mode from the input and reference tuple
926+ mode = CHANNEL_FORMATS [ self . channel ][ 0 ]
927+ channel_number = CHANNEL_FORMATS [ self . channel ][ 1 ]
853928
854- # Convert image to HSV color space
855- hsv_image = cv2 .cvtColor (image , cv2 .COLOR_BGR2HSV )
929+ # Convert PIL image to new format
930+ converted_image = numpy .array (pil_image .convert (mode )).astype (float )
931+ image_channel = converted_image [:, :, channel_number ]
856932
857- # Adjust the saturation
858- hsv_image [:, :, 1 ] = numpy .clip (hsv_image [:, :, 1 ] * self .saturation , 0 , 255 )
933+ # Adjust the value, clipping to 0..255
934+ image_channel = numpy .clip (image_channel * self .scale , 0 , 255 )
859935
860- # Convert image back to BGR color space
861- image = cv2 .cvtColor (hsv_image , cv2 .COLOR_HSV2BGR )
936+ # Invert the channel if requested
937+ if self .invert_channel :
938+ image_channel = 255 - image_channel
862939
863- # Convert back to PIL format and to original color mode
864- pil_image = Image .fromarray (image [:, :, ::- 1 ], "RGB" ).convert ("RGBA" )
940+ # Put the channel back into the image
941+ converted_image [:, :, channel_number ] = image_channel
942+
943+ # Convert back to RGBA format and output
944+ pil_image = Image .fromarray (converted_image .astype (numpy .uint8 ), mode = mode ).convert ("RGBA" )
865945
866946 image_dto = context .services .images .create (
867947 image = pil_image ,
0 commit comments