Skip to content

fix(nodes) paste_image paste mask transparency and img_scale add multiple of #8437

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions invokeai/app/invocations/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ def invoke(self, context: InvocationContext) -> ImageOutput:
title="Paste Image",
tags=["image", "paste"],
category="image",
version="1.2.2",
version="1.3.0",
)
class ImagePasteInvocation(BaseInvocation, WithMetadata, WithBoard):
"""Pastes an image into another image."""
Expand All @@ -161,15 +161,16 @@ class ImagePasteInvocation(BaseInvocation, WithMetadata, WithBoard):
x: int = InputField(default=0, description="The left x coordinate at which to paste the image")
y: int = InputField(default=0, description="The top y coordinate at which to paste the image")
crop: bool = InputField(default=False, description="Crop to base image dimensions")
invert_mask: bool = InputField(default=True, description="Invert mask color")

def invoke(self, context: InvocationContext) -> ImageOutput:
base_image = context.images.get_pil(self.base_image.image_name, mode="RGBA")
image = context.images.get_pil(self.image.image_name, mode="RGBA")
mask = None
if self.mask is not None:
mask = context.images.get_pil(self.mask.image_name, mode="L")
mask = ImageOps.invert(mask)
# TODO: probably shouldn't invert mask here... should user be required to do it?
if self.invert_mask:
mask = ImageOps.invert(mask)

min_x = min(0, self.x)
min_y = min(0, self.y)
Expand All @@ -180,7 +181,7 @@ def invoke(self, context: InvocationContext) -> ImageOutput:
new_image.paste(base_image, (abs(min_x), abs(min_y)))

# Create a temporary image to paste the image with transparency
temp_image = Image.new("RGBA", new_image.size)
temp_image = new_image
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems off. We are assigning new_image to temp_image. They now both refer to the same object. Then we paste image onto temp_image. Finally, we do an alpha composite of new_image and temp_image - so alpha compositing the same image onto itself?

I think I understand the root issue you want to address, but if you can provide some example input images to test with, I'd be better able to review. Ty

temp_image.paste(image, (max(0, self.x), max(0, self.y)), mask=mask)
new_image = Image.alpha_composite(new_image, temp_image)

Expand Down Expand Up @@ -452,7 +453,7 @@ def invoke(self, context: InvocationContext) -> ImageOutput:
title="Scale Image",
tags=["image", "scale"],
category="image",
version="1.2.2",
version="1.3.0",
)
class ImageScaleInvocation(BaseInvocation, WithMetadata, WithBoard):
"""Scales an image by a factor"""
Expand All @@ -464,16 +465,25 @@ class ImageScaleInvocation(BaseInvocation, WithMetadata, WithBoard):
description="The factor by which to scale the image",
)
resample_mode: PIL_RESAMPLING_MODES = InputField(default="bicubic", description="The resampling mode")
multiple_of: int = InputField(
default=1,
description="If > 1, the output image will be resized to the nearest multiple in both dimensions.",
)

def invoke(self, context: InvocationContext) -> ImageOutput:
image = context.images.get_pil(self.image.image_name)

resample_mode = PIL_RESAMPLING_MAP[self.resample_mode]
width = int(image.width * self.scale_factor)
height = int(image.height * self.scale_factor)
target_width = int(image.width * self.scale_factor)
target_height = int(image.height * self.scale_factor)

# We may need to resize the image to a multiple of 8. Use floor division to ensure we don't scale the image up in the final resize
if self.multiple_of > 1:
target_width = int(target_width // self.multiple_of * self.multiple_of)
target_height = int(target_height // self.multiple_of * self.multiple_of)

resize_image = image.resize(
(width, height),
(target_width, target_height),
resample=resample_mode,
)

Expand Down