diff --git a/invokeai/app/api/routers/app_info.py b/invokeai/app/api/routers/app_info.py index 4cbdc81b28..21286ac2b0 100644 --- a/invokeai/app/api/routers/app_info.py +++ b/invokeai/app/api/routers/app_info.py @@ -12,7 +12,7 @@ from pydantic import BaseModel, Field from invokeai.app.invocations.upscale import ESRGAN_MODELS from invokeai.app.services.invocation_cache.invocation_cache_common import InvocationCacheStatus -from invokeai.backend.image_util.patchmatch import PatchMatch +from invokeai.backend.image_util.infill_methods.patchmatch import PatchMatch from invokeai.backend.image_util.safety_checker import SafetyChecker from invokeai.backend.util.logging import logging from invokeai.version import __version__ @@ -100,7 +100,7 @@ async def get_app_deps() -> AppDependencyVersions: @app_router.get("/config", operation_id="get_config", status_code=200, response_model=AppConfig) async def get_config() -> AppConfig: - infill_methods = ["tile", "lama", "cv2"] + infill_methods = ["tile", "lama", "cv2", "color"] # TODO: add mosaic back if PatchMatch.patchmatch_available(): infill_methods.append("patchmatch") diff --git a/invokeai/app/invocations/infill.py b/invokeai/app/invocations/infill.py index 8d14c0a8fe..418bc62fdc 100644 --- a/invokeai/app/invocations/infill.py +++ b/invokeai/app/invocations/infill.py @@ -1,154 +1,91 @@ -# Copyright (c) 2022 Kyle Schouviller (https://github.com/kyle0654) and the InvokeAI Team +from abc import abstractmethod +from typing import Literal, get_args -import math -from typing import Literal, Optional, get_args - -import numpy as np -from PIL import Image, ImageOps +from PIL import Image from invokeai.app.invocations.fields import ColorField, ImageField from invokeai.app.invocations.primitives import ImageOutput from invokeai.app.services.shared.invocation_context import InvocationContext -from invokeai.app.util.download_with_progress import download_with_progress_bar from invokeai.app.util.misc import SEED_MAX -from invokeai.backend.image_util.cv2_inpaint import cv2_inpaint -from invokeai.backend.image_util.lama import LaMA -from invokeai.backend.image_util.patchmatch import PatchMatch +from invokeai.backend.image_util.infill_methods.cv2_inpaint import cv2_inpaint +from invokeai.backend.image_util.infill_methods.lama import LaMA +from invokeai.backend.image_util.infill_methods.mosaic import infill_mosaic +from invokeai.backend.image_util.infill_methods.patchmatch import PatchMatch, infill_patchmatch +from invokeai.backend.image_util.infill_methods.tile import infill_tile +from invokeai.backend.util.logging import InvokeAILogger from .baseinvocation import BaseInvocation, invocation from .fields import InputField, WithBoard, WithMetadata from .image import PIL_RESAMPLING_MAP, PIL_RESAMPLING_MODES +logger = InvokeAILogger.get_logger() -def infill_methods() -> list[str]: - methods = ["tile", "solid", "lama", "cv2"] + +def get_infill_methods(): + methods = Literal["tile", "color", "lama", "cv2"] # TODO: add mosaic back if PatchMatch.patchmatch_available(): - methods.insert(0, "patchmatch") + methods = Literal["patchmatch", "tile", "color", "lama", "cv2"] # TODO: add mosaic back return methods -INFILL_METHODS = Literal[tuple(infill_methods())] +INFILL_METHODS = get_infill_methods() DEFAULT_INFILL_METHOD = "patchmatch" if "patchmatch" in get_args(INFILL_METHODS) else "tile" -def infill_lama(im: Image.Image) -> Image.Image: - lama = LaMA() - return lama(im) +class InfillImageProcessorInvocation(BaseInvocation, WithMetadata, WithBoard): + """Base class for invocations that preprocess images for Infilling""" + image: ImageField = InputField(description="The image to process") -def infill_patchmatch(im: Image.Image) -> Image.Image: - if im.mode != "RGBA": - return im + @abstractmethod + def infill(self, image: Image.Image) -> Image.Image: + """Infill the image with the specified method""" + pass - # Skip patchmatch if patchmatch isn't available - if not PatchMatch.patchmatch_available(): - return im + def load_image(self, context: InvocationContext) -> tuple[Image.Image, bool]: + """Process the image to have an alpha channel before being infilled""" + image = context.images.get_pil(self.image.image_name) + has_alpha = True if image.mode == "RGBA" else False + return image, has_alpha - # Patchmatch (note, we may want to expose patch_size? Increasing it significantly impacts performance though) - im_patched_np = PatchMatch.inpaint(im.convert("RGB"), ImageOps.invert(im.split()[-1]), patch_size=3) - im_patched = Image.fromarray(im_patched_np, mode="RGB") - return im_patched + def invoke(self, context: InvocationContext) -> ImageOutput: + # Retrieve and process image to be infilled + input_image, has_alpha = self.load_image(context) + # If the input image has no alpha channel, return it + if has_alpha is False: + return ImageOutput.build(context.images.get_dto(self.image.image_name)) -def infill_cv2(im: Image.Image) -> Image.Image: - return cv2_inpaint(im) + # Perform Infill action + infilled_image = self.infill(input_image) + # Create ImageDTO for Infilled Image + infilled_image_dto = context.images.save(image=infilled_image) -def get_tile_images(image: np.ndarray, width=8, height=8): - _nrows, _ncols, depth = image.shape - _strides = image.strides - - nrows, _m = divmod(_nrows, height) - ncols, _n = divmod(_ncols, width) - if _m != 0 or _n != 0: - return None - - return np.lib.stride_tricks.as_strided( - np.ravel(image), - shape=(nrows, ncols, height, width, depth), - strides=(height * _strides[0], width * _strides[1], *_strides), - writeable=False, - ) - - -def tile_fill_missing(im: Image.Image, tile_size: int = 16, seed: Optional[int] = None) -> Image.Image: - # Only fill if there's an alpha layer - if im.mode != "RGBA": - return im - - a = np.asarray(im, dtype=np.uint8) - - tile_size_tuple = (tile_size, tile_size) - - # Get the image as tiles of a specified size - tiles = get_tile_images(a, *tile_size_tuple).copy() - - # Get the mask as tiles - tiles_mask = tiles[:, :, :, :, 3] - - # Find any mask tiles with any fully transparent pixels (we will be replacing these later) - tmask_shape = tiles_mask.shape - tiles_mask = tiles_mask.reshape(math.prod(tiles_mask.shape)) - n, ny = (math.prod(tmask_shape[0:2])), math.prod(tmask_shape[2:]) - tiles_mask = tiles_mask > 0 - tiles_mask = tiles_mask.reshape((n, ny)).all(axis=1) - - # Get RGB tiles in single array and filter by the mask - tshape = tiles.shape - tiles_all = tiles.reshape((math.prod(tiles.shape[0:2]), *tiles.shape[2:])) - filtered_tiles = tiles_all[tiles_mask] - - if len(filtered_tiles) == 0: - return im - - # Find all invalid tiles and replace with a random valid tile - replace_count = (tiles_mask == False).sum() # noqa: E712 - rng = np.random.default_rng(seed=seed) - tiles_all[np.logical_not(tiles_mask)] = filtered_tiles[rng.choice(filtered_tiles.shape[0], replace_count), :, :, :] - - # Convert back to an image - tiles_all = tiles_all.reshape(tshape) - tiles_all = tiles_all.swapaxes(1, 2) - st = tiles_all.reshape( - ( - math.prod(tiles_all.shape[0:2]), - math.prod(tiles_all.shape[2:4]), - tiles_all.shape[4], - ) - ) - si = Image.fromarray(st, mode="RGBA") - - return si + # Return Infilled Image + return ImageOutput.build(infilled_image_dto) @invocation("infill_rgba", title="Solid Color Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.2") -class InfillColorInvocation(BaseInvocation, WithMetadata, WithBoard): +class InfillColorInvocation(InfillImageProcessorInvocation): """Infills transparent areas of an image with a solid color""" - image: ImageField = InputField(description="The image to infill") color: ColorField = InputField( default=ColorField(r=127, g=127, b=127, a=255), description="The color to use to infill", ) - def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.images.get_pil(self.image.image_name) - + def infill(self, image: Image.Image): solid_bg = Image.new("RGBA", image.size, self.color.tuple()) infilled = Image.alpha_composite(solid_bg, image.convert("RGBA")) - infilled.paste(image, (0, 0), image.split()[-1]) - - image_dto = context.images.save(image=infilled) - - return ImageOutput.build(image_dto) + return infilled @invocation("infill_tile", title="Tile Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.3") -class InfillTileInvocation(BaseInvocation, WithMetadata, WithBoard): +class InfillTileInvocation(InfillImageProcessorInvocation): """Infills transparent areas of an image with tiles of the image""" - image: ImageField = InputField(description="The image to infill") tile_size: int = InputField(default=32, ge=1, description="The tile size (px)") seed: int = InputField( default=0, @@ -157,92 +94,74 @@ class InfillTileInvocation(BaseInvocation, WithMetadata, WithBoard): description="The seed to use for tile generation (omit for random)", ) - def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.images.get_pil(self.image.image_name) - - infilled = tile_fill_missing(image.copy(), seed=self.seed, tile_size=self.tile_size) - infilled.paste(image, (0, 0), image.split()[-1]) - - image_dto = context.images.save(image=infilled) - - return ImageOutput.build(image_dto) + def infill(self, image: Image.Image): + output = infill_tile(image, seed=self.seed, tile_size=self.tile_size) + return output.infilled @invocation( "infill_patchmatch", title="PatchMatch Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.2" ) -class InfillPatchMatchInvocation(BaseInvocation, WithMetadata, WithBoard): +class InfillPatchMatchInvocation(InfillImageProcessorInvocation): """Infills transparent areas of an image using the PatchMatch algorithm""" - image: ImageField = InputField(description="The image to infill") downscale: float = InputField(default=2.0, gt=0, description="Run patchmatch on downscaled image to speedup infill") resample_mode: PIL_RESAMPLING_MODES = InputField(default="bicubic", description="The resampling mode") - def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.images.get_pil(self.image.image_name).convert("RGBA") - + def infill(self, image: Image.Image): resample_mode = PIL_RESAMPLING_MAP[self.resample_mode] - infill_image = image.copy() width = int(image.width / self.downscale) height = int(image.height / self.downscale) - infill_image = infill_image.resize( + + infilled = image.resize( (width, height), resample=resample_mode, ) - - if PatchMatch.patchmatch_available(): - infilled = infill_patchmatch(infill_image) - else: - raise ValueError("PatchMatch is not available on this system") - + infilled = infill_patchmatch(image) infilled = infilled.resize( (image.width, image.height), resample=resample_mode, ) - infilled.paste(image, (0, 0), mask=image.split()[-1]) - # image.paste(infilled, (0, 0), mask=image.split()[-1]) - image_dto = context.images.save(image=infilled) - - return ImageOutput.build(image_dto) + return infilled @invocation("infill_lama", title="LaMa Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.2") -class LaMaInfillInvocation(BaseInvocation, WithMetadata, WithBoard): +class LaMaInfillInvocation(InfillImageProcessorInvocation): """Infills transparent areas of an image using the LaMa model""" - image: ImageField = InputField(description="The image to infill") - - def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.images.get_pil(self.image.image_name) - - # Downloads the LaMa model if it doesn't already exist - download_with_progress_bar( - name="LaMa Inpainting Model", - url="https://github.com/Sanster/models/releases/download/add_big_lama/big-lama.pt", - dest_path=context.config.get().models_path / "core/misc/lama/lama.pt", - ) - - infilled = infill_lama(image.copy()) - - image_dto = context.images.save(image=infilled) - - return ImageOutput.build(image_dto) + def infill(self, image: Image.Image): + lama = LaMA() + return lama(image) @invocation("infill_cv2", title="CV2 Infill", tags=["image", "inpaint"], category="inpaint", version="1.2.2") -class CV2InfillInvocation(BaseInvocation, WithMetadata, WithBoard): +class CV2InfillInvocation(InfillImageProcessorInvocation): """Infills transparent areas of an image using OpenCV Inpainting""" + def infill(self, image: Image.Image): + return cv2_inpaint(image) + + +# @invocation( +# "infill_mosaic", title="Mosaic Infill", tags=["image", "inpaint", "outpaint"], category="inpaint", version="1.0.0" +# ) +class MosaicInfillInvocation(InfillImageProcessorInvocation): + """Infills transparent areas of an image with a mosaic pattern drawing colors from the rest of the image""" + image: ImageField = InputField(description="The image to infill") + tile_width: int = InputField(default=64, description="Width of the tile") + tile_height: int = InputField(default=64, description="Height of the tile") + min_color: ColorField = InputField( + default=ColorField(r=0, g=0, b=0, a=255), + description="The min threshold for color", + ) + max_color: ColorField = InputField( + default=ColorField(r=255, g=255, b=255, a=255), + description="The max threshold for color", + ) - def invoke(self, context: InvocationContext) -> ImageOutput: - image = context.images.get_pil(self.image.image_name) - - infilled = infill_cv2(image.copy()) - - image_dto = context.images.save(image=infilled) - - return ImageOutput.build(image_dto) + def infill(self, image: Image.Image): + return infill_mosaic(image, (self.tile_width, self.tile_height), self.min_color.tuple(), self.max_color.tuple()) diff --git a/invokeai/app/invocations/ip_adapter.py b/invokeai/app/invocations/ip_adapter.py index 40a667c9d0..0ac40e97fb 100644 --- a/invokeai/app/invocations/ip_adapter.py +++ b/invokeai/app/invocations/ip_adapter.py @@ -65,7 +65,7 @@ class IPAdapterInvocation(BaseInvocation): ui_order=-1, ui_type=UIType.IPAdapterModel, ) - clip_vision_model: Literal["auto", "ViT-H", "ViT-G"] = InputField( + clip_vision_model: Literal["ViT-H", "ViT-G"] = InputField( description="CLIP Vision model to use. Overrides model settings. Mandatory for checkpoint models.", default="auto", ui_order=2, @@ -96,14 +96,9 @@ class IPAdapterInvocation(BaseInvocation): ip_adapter_info = context.models.get_config(self.ip_adapter_model.key) assert isinstance(ip_adapter_info, (IPAdapterInvokeAIConfig, IPAdapterCheckpointConfig)) - if self.clip_vision_model == "auto": - if isinstance(ip_adapter_info, IPAdapterInvokeAIConfig): - image_encoder_model_id = ip_adapter_info.image_encoder_model_id - image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() - else: - raise RuntimeError( - "You need to set the appropriate CLIP Vision model for checkpoint IP Adapter models." - ) + if isinstance(ip_adapter_info, IPAdapterInvokeAIConfig): + image_encoder_model_id = ip_adapter_info.image_encoder_model_id + image_encoder_model_name = image_encoder_model_id.split("/")[-1].strip() else: image_encoder_model_name = CLIP_VISION_MODEL_MAP[self.clip_vision_model] diff --git a/invokeai/app/invocations/latent.py b/invokeai/app/invocations/latent.py index 3c66b7014f..449d013504 100644 --- a/invokeai/app/invocations/latent.py +++ b/invokeai/app/invocations/latent.py @@ -1254,7 +1254,7 @@ class IdealSizeInvocation(BaseInvocation): return tuple((x - x % multiple_of) for x in args) def invoke(self, context: InvocationContext) -> IdealSizeOutput: - unet_config = context.models.get_config(**self.unet.unet.model_dump()) + unet_config = context.models.get_config(self.unet.unet.key) aspect = self.width / self.height dimension: float = 512 if unet_config.base == BaseModelType.StableDiffusion2: diff --git a/invokeai/backend/image_util/__init__.py b/invokeai/backend/image_util/__init__.py index 473ecc4c87..dec2a92150 100644 --- a/invokeai/backend/image_util/__init__.py +++ b/invokeai/backend/image_util/__init__.py @@ -2,7 +2,7 @@ Initialization file for invokeai.backend.image_util methods. """ -from .patchmatch import PatchMatch # noqa: F401 +from .infill_methods.patchmatch import PatchMatch # noqa: F401 from .pngwriter import PngWriter, PromptFormatter, retrieve_metadata, write_metadata # noqa: F401 from .seamless import configure_model_padding # noqa: F401 from .util import InitImageResizer, make_grid # noqa: F401 diff --git a/invokeai/backend/image_util/cv2_inpaint.py b/invokeai/backend/image_util/infill_methods/cv2_inpaint.py similarity index 100% rename from invokeai/backend/image_util/cv2_inpaint.py rename to invokeai/backend/image_util/infill_methods/cv2_inpaint.py diff --git a/invokeai/backend/image_util/lama.py b/invokeai/backend/image_util/infill_methods/lama.py similarity index 82% rename from invokeai/backend/image_util/lama.py rename to invokeai/backend/image_util/infill_methods/lama.py index 5b3fc3a9c7..fa354aeed1 100644 --- a/invokeai/backend/image_util/lama.py +++ b/invokeai/backend/image_util/infill_methods/lama.py @@ -7,6 +7,7 @@ from PIL import Image import invokeai.backend.util.logging as logger from invokeai.app.services.config.config_default import get_config +from invokeai.app.util.download_with_progress import download_with_progress_bar from invokeai.backend.util.devices import choose_torch_device @@ -30,6 +31,14 @@ class LaMA: def __call__(self, input_image: Image.Image, *args: Any, **kwds: Any) -> Any: device = choose_torch_device() model_location = get_config().models_path / "core/misc/lama/lama.pt" + + if not model_location.exists(): + download_with_progress_bar( + name="LaMa Inpainting Model", + url="https://github.com/Sanster/models/releases/download/add_big_lama/big-lama.pt", + dest_path=model_location, + ) + model = load_jit_model(model_location, device) image = np.asarray(input_image.convert("RGB")) diff --git a/invokeai/backend/image_util/infill_methods/mosaic.py b/invokeai/backend/image_util/infill_methods/mosaic.py new file mode 100644 index 0000000000..2715a100d2 --- /dev/null +++ b/invokeai/backend/image_util/infill_methods/mosaic.py @@ -0,0 +1,60 @@ +from typing import Tuple + +import numpy as np +from PIL import Image + + +def infill_mosaic( + image: Image.Image, + tile_shape: Tuple[int, int] = (64, 64), + min_color: Tuple[int, int, int, int] = (0, 0, 0, 0), + max_color: Tuple[int, int, int, int] = (255, 255, 255, 0), +) -> Image.Image: + """ + image:PIL - A PIL Image + tile_shape: Tuple[int,int] - Tile width & Tile Height + min_color: Tuple[int,int,int] - RGB values for the lowest color to clip to (0-255) + max_color: Tuple[int,int,int] - RGB values for the highest color to clip to (0-255) + """ + + np_image = np.array(image) # Convert image to np array + alpha = np_image[:, :, 3] # Get the mask from the alpha channel of the image + non_transparent_pixels = np_image[alpha != 0, :3] # List of non-transparent pixels + + # Create color tiles to paste in the empty areas of the image + tile_width, tile_height = tile_shape + + # Clip the range of colors in the image to a particular spectrum only + r_min, g_min, b_min, _ = min_color + r_max, g_max, b_max, _ = max_color + non_transparent_pixels[:, 0] = np.clip(non_transparent_pixels[:, 0], r_min, r_max) + non_transparent_pixels[:, 1] = np.clip(non_transparent_pixels[:, 1], g_min, g_max) + non_transparent_pixels[:, 2] = np.clip(non_transparent_pixels[:, 2], b_min, b_max) + + tiles = [] + for _ in range(256): + color = non_transparent_pixels[np.random.randint(len(non_transparent_pixels))] + tile = np.zeros((tile_height, tile_width, 3), dtype=np.uint8) + tile[:, :] = color + tiles.append(tile) + + # Fill the transparent area with tiles + filled_image = np.zeros((image.height, image.width, 3), dtype=np.uint8) + + for x in range(image.width): + for y in range(image.height): + tile = tiles[np.random.randint(len(tiles))] + try: + filled_image[ + y - (y % tile_height) : y - (y % tile_height) + tile_height, + x - (x % tile_width) : x - (x % tile_width) + tile_width, + ] = tile + except ValueError: + # Need to handle edge cases - literally + pass + + filled_image = Image.fromarray(filled_image) # Convert the filled tiles image to PIL + image = Image.composite( + image, filled_image, image.split()[-1] + ) # Composite the original image on top of the filled tiles + return image diff --git a/invokeai/backend/image_util/infill_methods/patchmatch.py b/invokeai/backend/image_util/infill_methods/patchmatch.py new file mode 100644 index 0000000000..7e9cdf8fa4 --- /dev/null +++ b/invokeai/backend/image_util/infill_methods/patchmatch.py @@ -0,0 +1,67 @@ +""" +This module defines a singleton object, "patchmatch" that +wraps the actual patchmatch object. It respects the global +"try_patchmatch" attribute, so that patchmatch loading can +be suppressed or deferred +""" + +import numpy as np +from PIL import Image + +import invokeai.backend.util.logging as logger +from invokeai.app.services.config.config_default import get_config + + +class PatchMatch: + """ + Thin class wrapper around the patchmatch function. + """ + + patch_match = None + tried_load: bool = False + + def __init__(self): + super().__init__() + + @classmethod + def _load_patch_match(cls): + if cls.tried_load: + return + if get_config().patchmatch: + from patchmatch import patch_match as pm + + if pm.patchmatch_available: + logger.info("Patchmatch initialized") + cls.patch_match = pm + else: + logger.info("Patchmatch not loaded (nonfatal)") + else: + logger.info("Patchmatch loading disabled") + cls.tried_load = True + + @classmethod + def patchmatch_available(cls) -> bool: + cls._load_patch_match() + if not cls.patch_match: + return False + return cls.patch_match.patchmatch_available + + @classmethod + def inpaint(cls, image: Image.Image) -> Image.Image: + if cls.patch_match is None or not cls.patchmatch_available(): + return image + + np_image = np.array(image) + mask = 255 - np_image[:, :, 3] + infilled = cls.patch_match.inpaint(np_image[:, :, :3], mask, patch_size=3) + return Image.fromarray(infilled, mode="RGB") + + +def infill_patchmatch(image: Image.Image) -> Image.Image: + IS_PATCHMATCH_AVAILABLE = PatchMatch.patchmatch_available() + + if not IS_PATCHMATCH_AVAILABLE: + logger.warning("PatchMatch is not available on this system") + return image + + return PatchMatch.inpaint(image) diff --git a/invokeai/backend/image_util/infill_methods/test_images/source1.webp b/invokeai/backend/image_util/infill_methods/test_images/source1.webp new file mode 100644 index 0000000000..7057eefa85 Binary files /dev/null and b/invokeai/backend/image_util/infill_methods/test_images/source1.webp differ diff --git a/invokeai/backend/image_util/infill_methods/test_images/source10.webp b/invokeai/backend/image_util/infill_methods/test_images/source10.webp new file mode 100644 index 0000000000..f185d52a57 Binary files /dev/null and b/invokeai/backend/image_util/infill_methods/test_images/source10.webp differ diff --git a/invokeai/backend/image_util/infill_methods/test_images/source2.webp b/invokeai/backend/image_util/infill_methods/test_images/source2.webp new file mode 100644 index 0000000000..b25060024a Binary files /dev/null and b/invokeai/backend/image_util/infill_methods/test_images/source2.webp differ diff --git a/invokeai/backend/image_util/infill_methods/test_images/source3.webp b/invokeai/backend/image_util/infill_methods/test_images/source3.webp new file mode 100644 index 0000000000..64227084c7 Binary files /dev/null and b/invokeai/backend/image_util/infill_methods/test_images/source3.webp differ diff --git a/invokeai/backend/image_util/infill_methods/test_images/source4.webp b/invokeai/backend/image_util/infill_methods/test_images/source4.webp new file mode 100644 index 0000000000..66a4260063 Binary files /dev/null and b/invokeai/backend/image_util/infill_methods/test_images/source4.webp differ diff --git a/invokeai/backend/image_util/infill_methods/test_images/source5.webp b/invokeai/backend/image_util/infill_methods/test_images/source5.webp new file mode 100644 index 0000000000..49b87b268f Binary files /dev/null and b/invokeai/backend/image_util/infill_methods/test_images/source5.webp differ diff --git a/invokeai/backend/image_util/infill_methods/test_images/source6.webp b/invokeai/backend/image_util/infill_methods/test_images/source6.webp new file mode 100644 index 0000000000..e16e132004 Binary files /dev/null and b/invokeai/backend/image_util/infill_methods/test_images/source6.webp differ diff --git a/invokeai/backend/image_util/infill_methods/test_images/source7.webp b/invokeai/backend/image_util/infill_methods/test_images/source7.webp new file mode 100644 index 0000000000..723a5fddbd Binary files /dev/null and b/invokeai/backend/image_util/infill_methods/test_images/source7.webp differ diff --git a/invokeai/backend/image_util/infill_methods/test_images/source8.webp b/invokeai/backend/image_util/infill_methods/test_images/source8.webp new file mode 100644 index 0000000000..32a0fea109 Binary files /dev/null and b/invokeai/backend/image_util/infill_methods/test_images/source8.webp differ diff --git a/invokeai/backend/image_util/infill_methods/test_images/source9.webp b/invokeai/backend/image_util/infill_methods/test_images/source9.webp new file mode 100644 index 0000000000..062f25ed8d Binary files /dev/null and b/invokeai/backend/image_util/infill_methods/test_images/source9.webp differ diff --git a/invokeai/backend/image_util/infill_methods/tile.ipynb b/invokeai/backend/image_util/infill_methods/tile.ipynb new file mode 100644 index 0000000000..eac7a43657 --- /dev/null +++ b/invokeai/backend/image_util/infill_methods/tile.ipynb @@ -0,0 +1,95 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "\"\"\"Smoke test for the tile infill\"\"\"\n", + "\n", + "from pathlib import Path\n", + "from typing import Optional\n", + "from PIL import Image\n", + "from invokeai.backend.image_util.infill_methods.tile import infill_tile\n", + "\n", + "images: list[tuple[str, Image.Image]] = []\n", + "\n", + "for i in sorted(Path(\"./test_images/\").glob(\"*.webp\")):\n", + " images.append((i.name, Image.open(i)))\n", + " images.append((i.name, Image.open(i).transpose(Image.FLIP_LEFT_RIGHT)))\n", + " images.append((i.name, Image.open(i).transpose(Image.FLIP_TOP_BOTTOM)))\n", + " images.append((i.name, Image.open(i).resize((512, 512))))\n", + " images.append((i.name, Image.open(i).resize((1234, 461))))\n", + "\n", + "outputs: list[tuple[str, Image.Image, Image.Image, Optional[Image.Image]]] = []\n", + "\n", + "for name, image in images:\n", + " try:\n", + " output = infill_tile(image, seed=0, tile_size=32)\n", + " outputs.append((name, image, output.infilled, output.tile_image))\n", + " except ValueError as e:\n", + " print(f\"Skipping image {name}: {e}\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Display the images in jupyter notebook\n", + "import matplotlib.pyplot as plt\n", + "from PIL import ImageOps\n", + "\n", + "fig, axes = plt.subplots(len(outputs), 3, figsize=(10, 3 * len(outputs)))\n", + "plt.subplots_adjust(hspace=0)\n", + "\n", + "for i, (name, original, infilled, tile_image) in enumerate(outputs):\n", + " # Add a border to each image, helps to see the edges\n", + " size = original.size\n", + " original = ImageOps.expand(original, border=5, fill=\"red\")\n", + " filled = ImageOps.expand(infilled, border=5, fill=\"red\")\n", + " if tile_image:\n", + " tile_image = ImageOps.expand(tile_image, border=5, fill=\"red\")\n", + "\n", + " axes[i, 0].imshow(original)\n", + " axes[i, 0].axis(\"off\")\n", + " axes[i, 0].set_title(f\"Original ({name} - {size})\")\n", + "\n", + " if tile_image:\n", + " axes[i, 1].imshow(tile_image)\n", + " axes[i, 1].axis(\"off\")\n", + " axes[i, 1].set_title(\"Tile Image\")\n", + " else:\n", + " axes[i, 1].axis(\"off\")\n", + " axes[i, 1].set_title(\"NO TILES GENERATED (NO TRANSPARENCY)\")\n", + "\n", + " axes[i, 2].imshow(filled)\n", + " axes[i, 2].axis(\"off\")\n", + " axes[i, 2].set_title(\"Filled\")" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": ".invokeai", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/invokeai/backend/image_util/infill_methods/tile.py b/invokeai/backend/image_util/infill_methods/tile.py new file mode 100644 index 0000000000..03cb6c1a8c --- /dev/null +++ b/invokeai/backend/image_util/infill_methods/tile.py @@ -0,0 +1,122 @@ +from dataclasses import dataclass +from typing import Optional + +import numpy as np +from PIL import Image + + +def create_tile_pool(img_array: np.ndarray, tile_size: tuple[int, int]) -> list[np.ndarray]: + """ + Create a pool of tiles from non-transparent areas of the image by systematically walking through the image. + + Args: + img_array: numpy array of the image. + tile_size: tuple (tile_width, tile_height) specifying the size of each tile. + + Returns: + A list of numpy arrays, each representing a tile. + """ + tiles: list[np.ndarray] = [] + rows, cols = img_array.shape[:2] + tile_width, tile_height = tile_size + + for y in range(0, rows - tile_height + 1, tile_height): + for x in range(0, cols - tile_width + 1, tile_width): + tile = img_array[y : y + tile_height, x : x + tile_width] + # Check if the image has an alpha channel and the tile is completely opaque + if img_array.shape[2] == 4 and np.all(tile[:, :, 3] == 255): + tiles.append(tile) + elif img_array.shape[2] == 3: # If no alpha channel, append the tile + tiles.append(tile) + + if not tiles: + raise ValueError( + "Not enough opaque pixels to generate any tiles. Use a smaller tile size or a different image." + ) + + return tiles + + +def create_filled_image( + img_array: np.ndarray, tile_pool: list[np.ndarray], tile_size: tuple[int, int], seed: int +) -> np.ndarray: + """ + Create an image of the same dimensions as the original, filled entirely with tiles from the pool. + + Args: + img_array: numpy array of the original image. + tile_pool: A list of numpy arrays, each representing a tile. + tile_size: tuple (tile_width, tile_height) specifying the size of each tile. + + Returns: + A numpy array representing the filled image. + """ + + rows, cols, _ = img_array.shape + tile_width, tile_height = tile_size + + # Prep an empty RGB image + filled_img_array = np.zeros((rows, cols, 3), dtype=img_array.dtype) + + # Make the random tile selection reproducible + rng = np.random.default_rng(seed) + + for y in range(0, rows, tile_height): + for x in range(0, cols, tile_width): + # Pick a random tile from the pool + tile = tile_pool[rng.integers(len(tile_pool))] + + # Calculate the space available (may be less than tile size near the edges) + space_y = min(tile_height, rows - y) + space_x = min(tile_width, cols - x) + + # Crop the tile if necessary to fit into the available space + cropped_tile = tile[:space_y, :space_x, :3] + + # Fill the available space with the (possibly cropped) tile + filled_img_array[y : y + space_y, x : x + space_x, :3] = cropped_tile + + return filled_img_array + + +@dataclass +class InfillTileOutput: + infilled: Image.Image + tile_image: Optional[Image.Image] = None + + +def infill_tile(image_to_infill: Image.Image, seed: int, tile_size: int) -> InfillTileOutput: + """Infills an image with random tiles from the image itself. + + If the image is not an RGBA image, it is returned untouched. + + Args: + image: The image to infill. + tile_size: The size of the tiles to use for infilling. + + Raises: + ValueError: If there are not enough opaque pixels to generate any tiles. + """ + + if image_to_infill.mode != "RGBA": + return InfillTileOutput(infilled=image_to_infill) + + # Internally, we want a tuple of (tile_width, tile_height). In the future, the tile size can be any rectangle. + _tile_size = (tile_size, tile_size) + np_image = np.array(image_to_infill, dtype=np.uint8) + + # Create the pool of tiles that we will use to infill + tile_pool = create_tile_pool(np_image, _tile_size) + + # Create an image from the tiles, same size as the original + tile_np_image = create_filled_image(np_image, tile_pool, _tile_size, seed) + + # Paste the OG image over the tile image, effectively infilling the area + tile_image = Image.fromarray(tile_np_image, "RGB") + infilled = tile_image.copy() + infilled.paste(image_to_infill, (0, 0), image_to_infill.split()[-1]) + + # I think we want this to be "RGBA"? + infilled.convert("RGBA") + + return InfillTileOutput(infilled=infilled, tile_image=tile_image) diff --git a/invokeai/backend/image_util/patchmatch.py b/invokeai/backend/image_util/patchmatch.py deleted file mode 100644 index 8b7b468397..0000000000 --- a/invokeai/backend/image_util/patchmatch.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -This module defines a singleton object, "patchmatch" that -wraps the actual patchmatch object. It respects the global -"try_patchmatch" attribute, so that patchmatch loading can -be suppressed or deferred -""" - -import numpy as np - -import invokeai.backend.util.logging as logger -from invokeai.app.services.config.config_default import get_config - - -class PatchMatch: - """ - Thin class wrapper around the patchmatch function. - """ - - patch_match = None - tried_load: bool = False - - def __init__(self): - super().__init__() - - @classmethod - def _load_patch_match(self): - if self.tried_load: - return - if get_config().patchmatch: - from patchmatch import patch_match as pm - - if pm.patchmatch_available: - logger.info("Patchmatch initialized") - else: - logger.info("Patchmatch not loaded (nonfatal)") - self.patch_match = pm - else: - logger.info("Patchmatch loading disabled") - self.tried_load = True - - @classmethod - def patchmatch_available(self) -> bool: - self._load_patch_match() - return self.patch_match and self.patch_match.patchmatch_available - - @classmethod - def inpaint(self, *args, **kwargs) -> np.ndarray: - if self.patchmatch_available(): - return self.patch_match.inpaint(*args, **kwargs) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 5872d22dfe..0cf98289e4 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -684,6 +684,7 @@ "noModelsInstalled": "No Models Installed", "noModelsInstalledDesc1": "Install models with the", "noModelSelected": "No Model Selected", + "noMatchingModels": "No matching Models", "none": "none", "path": "Path", "pathToConfig": "Path To Config", @@ -887,6 +888,11 @@ "imageFit": "Fit Initial Image To Output Size", "images": "Images", "infillMethod": "Infill Method", + "infillMosaicTileWidth": "Tile Width", + "infillMosaicTileHeight": "Tile Height", + "infillMosaicMinColor": "Min Color", + "infillMosaicMaxColor": "Max Color", + "infillColorValue": "Fill Color", "info": "Info", "invoke": { "addingImagesTo": "Adding images to", diff --git a/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts b/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts index 6bdd829bb1..c637d30fd8 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts +++ b/invokeai/frontend/web/src/features/modelManagerV2/store/modelManagerV2Slice.ts @@ -3,7 +3,7 @@ import { createSlice } from '@reduxjs/toolkit'; import type { PersistConfig } from 'app/store/store'; import type { ModelType } from 'services/api/types'; -export type FilterableModelType = Exclude; +export type FilterableModelType = Exclude | 'refiner'; type ModelManagerState = { _version: 1; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx index 033841ec79..67e65dbfb6 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelList.tsx @@ -1,6 +1,7 @@ -import { Flex } from '@invoke-ai/ui-library'; +import { Flex, Text } from '@invoke-ai/ui-library'; import { useAppSelector } from 'app/store/storeHooks'; import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent'; +import type { FilterableModelType } from 'features/modelManagerV2/store/modelManagerV2Slice'; import { memo, useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { @@ -9,10 +10,11 @@ import { useIPAdapterModels, useLoRAModels, useMainModels, + useRefinerModels, useT2IAdapterModels, useVAEModels, } from 'services/api/hooks/modelsByType'; -import type { AnyModelConfig, ModelType } from 'services/api/types'; +import type { AnyModelConfig } from 'services/api/types'; import { FetchingModelsLoader } from './FetchingModelsLoader'; import { ModelListWrapper } from './ModelListWrapper'; @@ -27,6 +29,12 @@ const ModelList = () => { [mainModels, searchTerm, filteredModelType] ); + const [refinerModels, { isLoading: isLoadingRefinerModels }] = useRefinerModels(); + const filteredRefinerModels = useMemo( + () => modelsFilter(refinerModels, searchTerm, filteredModelType), + [refinerModels, searchTerm, filteredModelType] + ); + const [loraModels, { isLoading: isLoadingLoRAModels }] = useLoRAModels(); const filteredLoRAModels = useMemo( () => modelsFilter(loraModels, searchTerm, filteredModelType), @@ -63,6 +71,28 @@ const ModelList = () => { [vaeModels, searchTerm, filteredModelType] ); + const totalFilteredModels = useMemo(() => { + return ( + filteredMainModels.length + + filteredRefinerModels.length + + filteredLoRAModels.length + + filteredEmbeddingModels.length + + filteredControlNetModels.length + + filteredT2IAdapterModels.length + + filteredIPAdapterModels.length + + filteredVAEModels.length + ); + }, [ + filteredControlNetModels.length, + filteredEmbeddingModels.length, + filteredIPAdapterModels.length, + filteredLoRAModels.length, + filteredMainModels.length, + filteredRefinerModels.length, + filteredT2IAdapterModels.length, + filteredVAEModels.length, + ]); + return ( @@ -71,6 +101,11 @@ const ModelList = () => { {!isLoadingMainModels && filteredMainModels.length > 0 && ( )} + {/* Refiner Model List */} + {isLoadingRefinerModels && } + {!isLoadingRefinerModels && filteredRefinerModels.length > 0 && ( + + )} {/* LoRAs List */} {isLoadingLoRAModels && } {!isLoadingLoRAModels && filteredLoRAModels.length > 0 && ( @@ -108,6 +143,11 @@ const ModelList = () => { {!isLoadingT2IAdapterModels && filteredT2IAdapterModels.length > 0 && ( )} + {totalFilteredModels === 0 && ( + + {t('modelManager.noMatchingModels')} + + )} ); @@ -118,12 +158,24 @@ export default memo(ModelList); const modelsFilter = ( data: T[], nameFilter: string, - filteredModelType: ModelType | null + filteredModelType: FilterableModelType | null ): T[] => { return data.filter((model) => { const matchesFilter = model.name.toLowerCase().includes(nameFilter.toLowerCase()); - const matchesType = filteredModelType ? model.type === filteredModelType : true; + const matchesType = getMatchesType(model, filteredModelType); return matchesFilter && matchesType; }); }; + +const getMatchesType = (modelConfig: AnyModelConfig, filteredModelType: FilterableModelType | null): boolean => { + if (filteredModelType === 'refiner') { + return modelConfig.base === 'sdxl-refiner'; + } + + if (filteredModelType === 'main' && modelConfig.base === 'sdxl-refiner') { + return false; + } + + return filteredModelType ? modelConfig.type === filteredModelType : true; +}; diff --git a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx index 0b8ad3f600..76802b36e7 100644 --- a/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx +++ b/invokeai/frontend/web/src/features/modelManagerV2/subpanels/ModelManagerPanel/ModelTypeFilter.tsx @@ -13,6 +13,7 @@ export const ModelTypeFilter = () => { const MODEL_TYPE_LABELS: Record = useMemo( () => ({ main: t('modelManager.main'), + refiner: t('sdxl.refiner'), lora: 'LoRA', embedding: t('modelManager.textualInversions'), controlnet: 'ControlNet', diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts index d847ccbfb5..6a59c51872 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasOutpaintGraph.ts @@ -65,6 +65,11 @@ export const buildCanvasOutpaintGraph = async ( infillTileSize, infillPatchmatchDownscaleSize, infillMethod, + // infillMosaicTileWidth, + // infillMosaicTileHeight, + // infillMosaicMinColor, + // infillMosaicMaxColor, + infillColorValue, clipSkip, seamlessXAxis, seamlessYAxis, @@ -356,6 +361,28 @@ export const buildCanvasOutpaintGraph = async ( }; } + // TODO: add mosaic back + // if (infillMethod === 'mosaic') { + // graph.nodes[INPAINT_INFILL] = { + // type: 'infill_mosaic', + // id: INPAINT_INFILL, + // is_intermediate, + // tile_width: infillMosaicTileWidth, + // tile_height: infillMosaicTileHeight, + // min_color: infillMosaicMinColor, + // max_color: infillMosaicMaxColor, + // }; + // } + + if (infillMethod === 'color') { + graph.nodes[INPAINT_INFILL] = { + type: 'infill_rgba', + id: INPAINT_INFILL, + color: infillColorValue, + is_intermediate, + }; + } + // Handle Scale Before Processing if (isUsingScaledDimensions) { const scaledWidth: number = scaledBoundingBoxDimensions.width; diff --git a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts index 39a54fd9d1..b932b26660 100644 --- a/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/graph/buildCanvasSDXLOutpaintGraph.ts @@ -66,6 +66,11 @@ export const buildCanvasSDXLOutpaintGraph = async ( infillTileSize, infillPatchmatchDownscaleSize, infillMethod, + // infillMosaicTileWidth, + // infillMosaicTileHeight, + // infillMosaicMinColor, + // infillMosaicMaxColor, + infillColorValue, seamlessXAxis, seamlessYAxis, canvasCoherenceMode, @@ -365,6 +370,28 @@ export const buildCanvasSDXLOutpaintGraph = async ( }; } + // TODO: add mosaic back + // if (infillMethod === 'mosaic') { + // graph.nodes[INPAINT_INFILL] = { + // type: 'infill_mosaic', + // id: INPAINT_INFILL, + // is_intermediate, + // tile_width: infillMosaicTileWidth, + // tile_height: infillMosaicTileHeight, + // min_color: infillMosaicMinColor, + // max_color: infillMosaicMaxColor, + // }; + // } + + if (infillMethod === 'color') { + graph.nodes[INPAINT_INFILL] = { + type: 'infill_rgba', + id: INPAINT_INFILL, + is_intermediate, + color: infillColorValue, + }; + } + // Handle Scale Before Processing if (isUsingScaledDimensions) { const scaledWidth: number = scaledBoundingBoxDimensions.width; diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillColorOptions.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillColorOptions.tsx new file mode 100644 index 0000000000..1cafe4310e --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillColorOptions.tsx @@ -0,0 +1,46 @@ +import { Box, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIColorPicker from 'common/components/IAIColorPicker'; +import { selectGenerationSlice, setInfillColorValue } from 'features/parameters/store/generationSlice'; +import { memo, useCallback, useMemo } from 'react'; +import type { RgbaColor } from 'react-colorful'; +import { useTranslation } from 'react-i18next'; + +const ParamInfillColorOptions = () => { + const dispatch = useAppDispatch(); + + const selector = useMemo( + () => + createSelector(selectGenerationSlice, (generation) => ({ + infillColor: generation.infillColorValue, + })), + [] + ); + + const { infillColor } = useAppSelector(selector); + + const infillMethod = useAppSelector((s) => s.generation.infillMethod); + + const { t } = useTranslation(); + + const handleInfillColor = useCallback( + (v: RgbaColor) => { + dispatch(setInfillColorValue(v)); + }, + [dispatch] + ); + + return ( + + + {t('parameters.infillColorValue')} + + + + + + ); +}; + +export default memo(ParamInfillColorOptions); diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillMosaicOptions.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillMosaicOptions.tsx new file mode 100644 index 0000000000..f164bb903e --- /dev/null +++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillMosaicOptions.tsx @@ -0,0 +1,127 @@ +import { Box, CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import IAIColorPicker from 'common/components/IAIColorPicker'; +import { + selectGenerationSlice, + setInfillMosaicMaxColor, + setInfillMosaicMinColor, + setInfillMosaicTileHeight, + setInfillMosaicTileWidth, +} from 'features/parameters/store/generationSlice'; +import { memo, useCallback, useMemo } from 'react'; +import type { RgbaColor } from 'react-colorful'; +import { useTranslation } from 'react-i18next'; + +const ParamInfillMosaicTileSize = () => { + const dispatch = useAppDispatch(); + + const selector = useMemo( + () => + createSelector(selectGenerationSlice, (generation) => ({ + infillMosaicTileWidth: generation.infillMosaicTileWidth, + infillMosaicTileHeight: generation.infillMosaicTileHeight, + infillMosaicMinColor: generation.infillMosaicMinColor, + infillMosaicMaxColor: generation.infillMosaicMaxColor, + })), + [] + ); + + const { infillMosaicTileWidth, infillMosaicTileHeight, infillMosaicMinColor, infillMosaicMaxColor } = + useAppSelector(selector); + + const infillMethod = useAppSelector((s) => s.generation.infillMethod); + + const { t } = useTranslation(); + + const handleInfillMosaicTileWidthChange = useCallback( + (v: number) => { + dispatch(setInfillMosaicTileWidth(v)); + }, + [dispatch] + ); + + const handleInfillMosaicTileHeightChange = useCallback( + (v: number) => { + dispatch(setInfillMosaicTileHeight(v)); + }, + [dispatch] + ); + + const handleInfillMosaicMinColor = useCallback( + (v: RgbaColor) => { + dispatch(setInfillMosaicMinColor(v)); + }, + [dispatch] + ); + + const handleInfillMosaicMaxColor = useCallback( + (v: RgbaColor) => { + dispatch(setInfillMosaicMaxColor(v)); + }, + [dispatch] + ); + + return ( + + + {t('parameters.infillMosaicTileWidth')} + + + + + {t('parameters.infillMosaicTileHeight')} + + + + + {t('parameters.infillMosaicMinColor')} + + + + + + {t('parameters.infillMosaicMaxColor')} + + + + + + ); +}; + +export default memo(ParamInfillMosaicTileSize); diff --git a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillOptions.tsx b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillOptions.tsx index 16cbffe56a..04e4727885 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillOptions.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Canvas/InfillAndScaling/ParamInfillOptions.tsx @@ -1,6 +1,8 @@ import { useAppSelector } from 'app/store/storeHooks'; import { memo } from 'react'; +import ParamInfillColorOptions from './ParamInfillColorOptions'; +import ParamInfillMosaicOptions from './ParamInfillMosaicOptions'; import ParamInfillPatchmatchDownscaleSize from './ParamInfillPatchmatchDownscaleSize'; import ParamInfillTilesize from './ParamInfillTilesize'; @@ -14,6 +16,14 @@ const ParamInfillOptions = () => { return ; } + if (infillMethod === 'mosaic') { + return ; + } + + if (infillMethod === 'color') { + return ; + } + return null; }; diff --git a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts index e272cd278e..0da6e21d9f 100644 --- a/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts +++ b/invokeai/frontend/web/src/features/parameters/store/generationSlice.ts @@ -19,6 +19,7 @@ import type { import { getIsSizeOptimal, getOptimalDimension } from 'features/parameters/util/optimalDimension'; import { configChanged } from 'features/system/store/configSlice'; import { clamp } from 'lodash-es'; +import type { RgbaColor } from 'react-colorful'; import type { ImageDTO } from 'services/api/types'; import type { GenerationState } from './types'; @@ -43,8 +44,6 @@ const initialGenerationState: GenerationState = { shouldFitToWidthHeight: true, shouldRandomizeSeed: true, steps: 50, - infillTileSize: 32, - infillPatchmatchDownscaleSize: 1, width: 512, model: null, vae: null, @@ -55,6 +54,13 @@ const initialGenerationState: GenerationState = { shouldUseCpuNoise: true, shouldShowAdvancedOptions: false, aspectRatio: { ...initialAspectRatioState }, + infillTileSize: 32, + infillPatchmatchDownscaleSize: 1, + infillMosaicTileWidth: 64, + infillMosaicTileHeight: 64, + infillMosaicMinColor: { r: 0, g: 0, b: 0, a: 1 }, + infillMosaicMaxColor: { r: 255, g: 255, b: 255, a: 1 }, + infillColorValue: { r: 0, g: 0, b: 0, a: 1 }, }; export const generationSlice = createSlice({ @@ -116,15 +122,6 @@ export const generationSlice = createSlice({ setCanvasCoherenceMinDenoise: (state, action: PayloadAction) => { state.canvasCoherenceMinDenoise = action.payload; }, - setInfillMethod: (state, action: PayloadAction) => { - state.infillMethod = action.payload; - }, - setInfillTileSize: (state, action: PayloadAction) => { - state.infillTileSize = action.payload; - }, - setInfillPatchmatchDownscaleSize: (state, action: PayloadAction) => { - state.infillPatchmatchDownscaleSize = action.payload; - }, initialImageChanged: (state, action: PayloadAction) => { const { image_name, width, height } = action.payload; state.initialImage = { imageName: image_name, width, height }; @@ -206,6 +203,30 @@ export const generationSlice = createSlice({ aspectRatioChanged: (state, action: PayloadAction) => { state.aspectRatio = action.payload; }, + setInfillMethod: (state, action: PayloadAction) => { + state.infillMethod = action.payload; + }, + setInfillTileSize: (state, action: PayloadAction) => { + state.infillTileSize = action.payload; + }, + setInfillPatchmatchDownscaleSize: (state, action: PayloadAction) => { + state.infillPatchmatchDownscaleSize = action.payload; + }, + setInfillMosaicTileWidth: (state, action: PayloadAction) => { + state.infillMosaicTileWidth = action.payload; + }, + setInfillMosaicTileHeight: (state, action: PayloadAction) => { + state.infillMosaicTileHeight = action.payload; + }, + setInfillMosaicMinColor: (state, action: PayloadAction) => { + state.infillMosaicMinColor = action.payload; + }, + setInfillMosaicMaxColor: (state, action: PayloadAction) => { + state.infillMosaicMaxColor = action.payload; + }, + setInfillColorValue: (state, action: PayloadAction) => { + state.infillColorValue = action.payload; + }, }, extraReducers: (builder) => { builder.addCase(configChanged, (state, action) => { @@ -249,8 +270,6 @@ export const { setShouldFitToWidthHeight, setShouldRandomizeSeed, setSteps, - setInfillTileSize, - setInfillPatchmatchDownscaleSize, initialImageChanged, modelChanged, vaeSelected, @@ -264,6 +283,13 @@ export const { heightChanged, widthRecalled, heightRecalled, + setInfillTileSize, + setInfillPatchmatchDownscaleSize, + setInfillMosaicTileWidth, + setInfillMosaicTileHeight, + setInfillMosaicMinColor, + setInfillMosaicMaxColor, + setInfillColorValue, } = generationSlice.actions; export const { selectOptimalDimension } = generationSlice.selectors; diff --git a/invokeai/frontend/web/src/features/parameters/store/types.ts b/invokeai/frontend/web/src/features/parameters/store/types.ts index 73185754ee..773cfbf925 100644 --- a/invokeai/frontend/web/src/features/parameters/store/types.ts +++ b/invokeai/frontend/web/src/features/parameters/store/types.ts @@ -17,6 +17,7 @@ import type { ParameterVAEModel, ParameterWidth, } from 'features/parameters/types/parameterSchemas'; +import type { RgbaColor } from 'react-colorful'; export interface GenerationState { _version: 2; @@ -39,8 +40,6 @@ export interface GenerationState { shouldFitToWidthHeight: boolean; shouldRandomizeSeed: boolean; steps: ParameterSteps; - infillTileSize: number; - infillPatchmatchDownscaleSize: number; width: ParameterWidth; model: ParameterModel | null; vae: ParameterVAEModel | null; @@ -51,6 +50,13 @@ export interface GenerationState { shouldUseCpuNoise: boolean; shouldShowAdvancedOptions: boolean; aspectRatio: AspectRatioState; + infillTileSize: number; + infillPatchmatchDownscaleSize: number; + infillMosaicTileWidth: number; + infillMosaicTileHeight: number; + infillMosaicMinColor: RgbaColor; + infillMosaicMaxColor: RgbaColor; + infillColorValue: RgbaColor; } export type PayloadActionWithOptimalDimension = PayloadAction; diff --git a/invokeai/frontend/web/src/services/api/schema.ts b/invokeai/frontend/web/src/services/api/schema.ts index 6e3ddf678b..72da0f1f8c 100644 --- a/invokeai/frontend/web/src/services/api/schema.ts +++ b/invokeai/frontend/web/src/services/api/schema.ts @@ -1419,7 +1419,7 @@ export type components = { * @default true */ use_cache?: boolean; - /** @description The image to infill */ + /** @description The image to process */ image?: components["schemas"]["ImageField"]; /** * type @@ -4112,7 +4112,7 @@ export type components = { * @description The nodes in this graph */ nodes: { - [key: string]: components["schemas"]["StringInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["ImagePasteInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["ColorInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["LoRALoaderInvocation"]; + [key: string]: components["schemas"]["ColorInvocation"] | components["schemas"]["SubtractInvocation"] | components["schemas"]["PidiImageProcessorInvocation"] | components["schemas"]["StringSplitInvocation"] | components["schemas"]["BlankImageInvocation"] | components["schemas"]["ShowImageInvocation"] | components["schemas"]["ImageChannelMultiplyInvocation"] | components["schemas"]["LoRALoaderInvocation"] | components["schemas"]["CreateDenoiseMaskInvocation"] | components["schemas"]["SDXLCompelPromptInvocation"] | components["schemas"]["ColorMapImageProcessorInvocation"] | components["schemas"]["RandomIntInvocation"] | components["schemas"]["ConditioningInvocation"] | components["schemas"]["SeamlessModeInvocation"] | components["schemas"]["ImageCollectionInvocation"] | components["schemas"]["MidasDepthImageProcessorInvocation"] | components["schemas"]["MaskCombineInvocation"] | components["schemas"]["ImageResizeInvocation"] | components["schemas"]["ResizeLatentsInvocation"] | components["schemas"]["StringJoinInvocation"] | components["schemas"]["TileToPropertiesInvocation"] | components["schemas"]["MaskFromIDInvocation"] | components["schemas"]["UnsharpMaskInvocation"] | components["schemas"]["FaceIdentifierInvocation"] | components["schemas"]["SDXLLoRALoaderInvocation"] | components["schemas"]["FloatToIntegerInvocation"] | components["schemas"]["FreeUInvocation"] | components["schemas"]["ImageWatermarkInvocation"] | components["schemas"]["MetadataItemInvocation"] | components["schemas"]["SaveImageInvocation"] | components["schemas"]["DivideInvocation"] | components["schemas"]["DepthAnythingImageProcessorInvocation"] | components["schemas"]["IntegerCollectionInvocation"] | components["schemas"]["InfillColorInvocation"] | components["schemas"]["SchedulerInvocation"] | components["schemas"]["CanvasPasteBackInvocation"] | components["schemas"]["IdealSizeInvocation"] | components["schemas"]["TileResamplerProcessorInvocation"] | components["schemas"]["NoiseInvocation"] | components["schemas"]["LatentsInvocation"] | components["schemas"]["LatentsCollectionInvocation"] | components["schemas"]["ControlNetInvocation"] | components["schemas"]["MlsdImageProcessorInvocation"] | components["schemas"]["StringJoinThreeInvocation"] | components["schemas"]["FloatMathInvocation"] | components["schemas"]["AddInvocation"] | components["schemas"]["CalculateImageTilesMinimumOverlapInvocation"] | components["schemas"]["BooleanCollectionInvocation"] | components["schemas"]["InfillTileInvocation"] | components["schemas"]["IterateInvocation"] | components["schemas"]["ConditioningCollectionInvocation"] | components["schemas"]["CalculateImageTilesInvocation"] | components["schemas"]["HedImageProcessorInvocation"] | components["schemas"]["LaMaInfillInvocation"] | components["schemas"]["ScaleLatentsInvocation"] | components["schemas"]["ImageConvertInvocation"] | components["schemas"]["StringInvocation"] | components["schemas"]["DWOpenposeImageProcessorInvocation"] | components["schemas"]["T2IAdapterInvocation"] | components["schemas"]["MediapipeFaceProcessorInvocation"] | components["schemas"]["CvInpaintInvocation"] | components["schemas"]["NormalbaeImageProcessorInvocation"] | components["schemas"]["ImageChannelInvocation"] | components["schemas"]["MaskEdgeInvocation"] | components["schemas"]["MultiplyInvocation"] | components["schemas"]["CollectInvocation"] | components["schemas"]["MergeTilesToImageInvocation"] | components["schemas"]["ColorCorrectInvocation"] | components["schemas"]["ImageMultiplyInvocation"] | components["schemas"]["MetadataInvocation"] | components["schemas"]["SegmentAnythingProcessorInvocation"] | components["schemas"]["ZoeDepthImageProcessorInvocation"] | components["schemas"]["FloatCollectionInvocation"] | components["schemas"]["CLIPSkipInvocation"] | components["schemas"]["MergeMetadataInvocation"] | components["schemas"]["ImageInverseLerpInvocation"] | components["schemas"]["DenoiseLatentsInvocation"] | components["schemas"]["RoundInvocation"] | components["schemas"]["PairTileImageInvocation"] | components["schemas"]["PromptsFromFileInvocation"] | components["schemas"]["CropLatentsCoreInvocation"] | components["schemas"]["SDXLRefinerCompelPromptInvocation"] | components["schemas"]["StringReplaceInvocation"] | components["schemas"]["ImageHueAdjustmentInvocation"] | components["schemas"]["RangeInvocation"] | components["schemas"]["InfillPatchMatchInvocation"] | components["schemas"]["LatentsToImageInvocation"] | components["schemas"]["ImageLerpInvocation"] | components["schemas"]["VAELoaderInvocation"] | components["schemas"]["RangeOfSizeInvocation"] | components["schemas"]["MaskFromAlphaInvocation"] | components["schemas"]["IntegerInvocation"] | components["schemas"]["CV2InfillInvocation"] | components["schemas"]["SDXLRefinerModelLoaderInvocation"] | components["schemas"]["ImageInvocation"] | components["schemas"]["DynamicPromptInvocation"] | components["schemas"]["ContentShuffleImageProcessorInvocation"] | components["schemas"]["LineartAnimeImageProcessorInvocation"] | components["schemas"]["IPAdapterInvocation"] | components["schemas"]["CalculateImageTilesEvenSplitInvocation"] | components["schemas"]["ImageBlurInvocation"] | components["schemas"]["LineartImageProcessorInvocation"] | components["schemas"]["CompelInvocation"] | components["schemas"]["FaceMaskInvocation"] | components["schemas"]["CreateGradientMaskInvocation"] | components["schemas"]["StringCollectionInvocation"] | components["schemas"]["FloatLinearRangeInvocation"] | components["schemas"]["CoreMetadataInvocation"] | components["schemas"]["StepParamEasingInvocation"] | components["schemas"]["FaceOffInvocation"] | components["schemas"]["RandomRangeInvocation"] | components["schemas"]["ESRGANInvocation"] | components["schemas"]["StringSplitNegInvocation"] | components["schemas"]["BooleanInvocation"] | components["schemas"]["LeresImageProcessorInvocation"] | components["schemas"]["SDXLModelLoaderInvocation"] | components["schemas"]["ImageScaleInvocation"] | components["schemas"]["RandomFloatInvocation"] | components["schemas"]["IntegerMathInvocation"] | components["schemas"]["ImageToLatentsInvocation"] | components["schemas"]["BlendLatentsInvocation"] | components["schemas"]["MainModelLoaderInvocation"] | components["schemas"]["ImageChannelOffsetInvocation"] | components["schemas"]["ImageNSFWBlurInvocation"] | components["schemas"]["FloatInvocation"] | components["schemas"]["ImageCropInvocation"] | components["schemas"]["CenterPadCropInvocation"] | components["schemas"]["CannyImageProcessorInvocation"] | components["schemas"]["ImagePasteInvocation"]; }; /** * Edges @@ -4149,7 +4149,7 @@ export type components = { * @description The results of node executions */ results: { - [key: string]: components["schemas"]["CLIPOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["String2Output"] | components["schemas"]["IntegerOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["StringOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["CalculateImageTilesOutput"]; + [key: string]: components["schemas"]["LatentsCollectionOutput"] | components["schemas"]["GradientMaskOutput"] | components["schemas"]["SDXLModelLoaderOutput"] | components["schemas"]["ConditioningCollectionOutput"] | components["schemas"]["CLIPOutput"] | components["schemas"]["SchedulerOutput"] | components["schemas"]["VAEOutput"] | components["schemas"]["FaceMaskOutput"] | components["schemas"]["BooleanOutput"] | components["schemas"]["IntegerCollectionOutput"] | components["schemas"]["SeamlessModeOutput"] | components["schemas"]["PairTileImageOutput"] | components["schemas"]["LoRALoaderOutput"] | components["schemas"]["MetadataOutput"] | components["schemas"]["IPAdapterOutput"] | components["schemas"]["NoiseOutput"] | components["schemas"]["IdealSizeOutput"] | components["schemas"]["FloatOutput"] | components["schemas"]["StringPosNegOutput"] | components["schemas"]["LatentsOutput"] | components["schemas"]["BooleanCollectionOutput"] | components["schemas"]["TileToPropertiesOutput"] | components["schemas"]["CalculateImageTilesOutput"] | components["schemas"]["FloatCollectionOutput"] | components["schemas"]["IntegerOutput"] | components["schemas"]["CLIPSkipInvocationOutput"] | components["schemas"]["FaceOffOutput"] | components["schemas"]["ImageCollectionOutput"] | components["schemas"]["UNetOutput"] | components["schemas"]["T2IAdapterOutput"] | components["schemas"]["ImageOutput"] | components["schemas"]["ColorOutput"] | components["schemas"]["ColorCollectionOutput"] | components["schemas"]["SDXLRefinerModelLoaderOutput"] | components["schemas"]["StringCollectionOutput"] | components["schemas"]["SDXLLoRALoaderOutput"] | components["schemas"]["ConditioningOutput"] | components["schemas"]["ModelLoaderOutput"] | components["schemas"]["DenoiseMaskOutput"] | components["schemas"]["MetadataItemOutput"] | components["schemas"]["IterateInvocationOutput"] | components["schemas"]["CollectInvocationOutput"] | components["schemas"]["ControlOutput"] | components["schemas"]["String2Output"] | components["schemas"]["StringOutput"]; }; /** * Errors @@ -4438,7 +4438,7 @@ export type components = { * @default auto * @enum {string} */ - clip_vision_model?: "auto" | "ViT-H" | "ViT-G"; + clip_vision_model?: "ViT-H" | "ViT-G"; /** * Weight * @description The weight given to the IP-Adapter @@ -5693,7 +5693,7 @@ export type components = { * @default true */ use_cache?: boolean; - /** @description The image to infill */ + /** @description The image to process */ image?: components["schemas"]["ImageField"]; /** * @description The color to use to infill @@ -5738,7 +5738,7 @@ export type components = { * @default true */ use_cache?: boolean; - /** @description The image to infill */ + /** @description The image to process */ image?: components["schemas"]["ImageField"]; /** * Downscale @@ -5786,7 +5786,7 @@ export type components = { * @default true */ use_cache?: boolean; - /** @description The image to infill */ + /** @description The image to process */ image?: components["schemas"]["ImageField"]; /** * Tile Size @@ -6088,7 +6088,7 @@ export type components = { * @default true */ use_cache?: boolean; - /** @description The image to infill */ + /** @description The image to process */ image?: components["schemas"]["ImageField"]; /** * type