Skip to content

Commit c5b9c8f

Browse files
Merge branch 'main' into lama-infill
2 parents fb5ac78 + 10eec54 commit c5b9c8f

File tree

3 files changed

+228
-176
lines changed

3 files changed

+228
-176
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
@@ -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

Comments
 (0)