Skip to content
Open
Show file tree
Hide file tree
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
8 changes: 5 additions & 3 deletions invokeai/app/invocations/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ def invoke(self, context: InvocationContext) -> ImageOutput:
title="Mask from Alpha",
tags=["image", "mask"],
category="mask",
version="1.2.2",
version="1.3.0",
)
class MaskFromAlphaInvocation(BaseInvocation, WithMetadata, WithBoard):
"""Extracts the alpha channel of an image as a mask."""
Expand All @@ -239,9 +239,11 @@ class MaskFromAlphaInvocation(BaseInvocation, WithMetadata, WithBoard):
invert: bool = InputField(default=False, description="Whether or not to invert the mask")

def invoke(self, context: InvocationContext) -> ImageOutput:
image = context.images.get_pil(self.image.image_name)
# Converting gives an image without an alpha channel a fully opaque one. Its last band is a color or
# palette channel, not alpha.
image = context.images.get_pil(self.image.image_name, mode="RGBA")

image_mask = image.split()[-1]
image_mask = image.getchannel("A")
if self.invert:
image_mask = ImageOps.invert(image_mask)

Expand Down
2 changes: 1 addition & 1 deletion invokeai/frontend/api/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -85692,7 +85692,7 @@
"tags": ["image", "mask"],
"title": "Mask from Alpha",
"type": "object",
"version": "1.2.2",
"version": "1.3.0",
"output": {
"$ref": "#/components/schemas/ImageOutput"
}
Expand Down
52 changes: 51 additions & 1 deletion tests/app/invocations/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@
import torch
from PIL import Image, ImageFilter

from invokeai.app.invocations.image import ImageField, OklabUnsharpMaskInvocation, OklchImageHueAdjustmentInvocation
from invokeai.app.invocations.image import (
ImageField,
MaskFromAlphaInvocation,
OklabUnsharpMaskInvocation,
OklchImageHueAdjustmentInvocation,
)
from invokeai.app.invocations.primitives import ImageCollectionInvocation
from invokeai.backend.image_util.color_conversion import (
linear_srgb_from_oklab,
Expand Down Expand Up @@ -49,6 +54,51 @@ def _max_abs_diff_uint8(left: Image.Image, right: Image.Image) -> int:
return int(numpy.abs(left_arr - right_arr).max())


def _invoke_mask_from_alpha(input_image: Image.Image, invert: bool = False) -> list[int]:
context = MagicMock()
# Like the real context, convert to the requested mode.
context.images.get_pil.side_effect = lambda image_name, mode=None: (
input_image.convert(mode) if mode else input_image.copy()
)
context.images.save.side_effect = lambda image, image_category: SimpleNamespace(
image_name="out", width=image.width, height=image.height
)

MaskFromAlphaInvocation(image=ImageField(image_name="in"), invert=invert).invoke(context)
saved_image = context.images.save.call_args.kwargs["image"]

assert saved_image.mode == "L"
return numpy.asarray(saved_image).reshape(-1).tolist()


def test_mask_from_alpha_uses_the_alpha_channel() -> None:
input_image = Image.new("RGBA", (3, 1))
input_image.putdata([(10, 20, 30, 0), (10, 20, 30, 128), (10, 20, 30, 255)])

assert _invoke_mask_from_alpha(input_image) == [0, 128, 255]
assert _invoke_mask_from_alpha(input_image, invert=True) == [255, 127, 0]


def test_mask_from_alpha_treats_an_image_without_alpha_as_opaque() -> None:
rgb_image = Image.new("RGB", (3, 1))
rgb_image.putdata([(255, 0, 0), (0, 255, 0), (0, 0, 64)])
grayscale_image = Image.new("L", (3, 1))
grayscale_image.putdata([0, 64, 200])

assert _invoke_mask_from_alpha(rgb_image) == [255, 255, 255]
assert _invoke_mask_from_alpha(rgb_image, invert=True) == [0, 0, 0]
assert _invoke_mask_from_alpha(grayscale_image) == [255, 255, 255]


def test_mask_from_alpha_uses_palette_transparency() -> None:
palette_image = Image.new("P", (3, 1))
palette_image.putpalette([0, 0, 0, 255, 255, 255])
palette_image.putdata([0, 1, 0])
palette_image.info["transparency"] = 0

assert _invoke_mask_from_alpha(palette_image) == [0, 255, 0]


def test_image_collection_invocation_preserves_existing_collection_values() -> None:
images = [ImageField(image_name="first"), ImageField(image_name="second")]

Expand Down
Loading