Skip to content

Commit 384ad2d

Browse files
authored
Merge branch 'main' into patch-2
2 parents ac3bf81 + 10eec54 commit 384ad2d

File tree

3 files changed

+223
-125
lines changed

3 files changed

+223
-125
lines changed

docs/nodes/defaultNodes.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ The table below contains a list of the default nodes shipped with InvokeAI and t
3535
|Inverse Lerp Image | Inverse linear interpolation of all pixels of an image|
3636
|Image Primitive | An image primitive value|
3737
|Lerp Image | Linear interpolation of all pixels of an image|
38-
|Image Luminosity Adjustment | Adjusts the Luminosity (Value) of an image.|
38+
|Offset Image Channel | Add to or subtract from an image color channel by a uniform value.|
39+
|Multiply Image Channel | Multiply or Invert an image color channel by a scalar value.|
3940
|Multiply Images | Multiplies two images together using `PIL.ImageChops.multiply()`.|
4041
|Blur NSFW Image | Add blur to NSFW-flagged images|
4142
|Paste Image | Pastes an image into another image.|
4243
|ImageProcessor | Base class for invocations that preprocess images for ControlNet|
4344
|Resize Image | Resizes an image to specific dimensions|
44-
|Image Saturation Adjustment | Adjusts the Saturation of an image.|
4545
|Scale Image | Scales an image by a factor|
4646
|Image to Latents | Encodes an image into latents.|
4747
|Add Invisible Watermark | Add an invisible watermark to an image|

invokeai/app/invocations/image.py

Lines changed: 116 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)