diff --git a/clothing_workflow.ipynb b/clothing_workflow.ipynb index 8dcff681ec..5b0e00b0d9 100644 --- a/clothing_workflow.ipynb +++ b/clothing_workflow.ipynb @@ -13,7 +13,10 @@ "import cv2\n", "\n", "from invokeai.backend.vto_workflow.overlay_pattern import generate_dress_mask\n", - "from invokeai.backend.vto_workflow.extract_channel import extract_channel, ImageChannel\n" + "from invokeai.backend.vto_workflow.extract_channel import extract_channel, ImageChannel\n", + "from invokeai.backend.vto_workflow.seamless_mapping import map_seamless_tiles\n", + "\n", + "\n" ] }, { @@ -83,6 +86,28 @@ "id": "dbb53794", "metadata": {}, "outputs": [], + "source": [ + "expanded_pattern = map_seamless_tiles(seamless_tile=pattern_image, target_hw=(model_image.height, model_image.width), num_repeats_h=5.0)\n", + "expanded_pattern" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a12c166", + "metadata": {}, + "outputs": [], + "source": [ + "expanded_pattern = map_seamless_tiles(seamless_tile=pattern_image, target_hw=(model_image.height, model_image.width), num_repeats_h=1.0)\n", + "expanded_pattern" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f4f22d02", + "metadata": {}, + "outputs": [], "source": [] } ], diff --git a/invokeai/backend/vto_workflow/seamless_mapping.py b/invokeai/backend/vto_workflow/seamless_mapping.py new file mode 100644 index 0000000000..4a6fb8578a --- /dev/null +++ b/invokeai/backend/vto_workflow/seamless_mapping.py @@ -0,0 +1,31 @@ +import math + +import numpy as np +from PIL import Image + + +def map_seamless_tiles(seamless_tile: Image.Image, target_hw: tuple[int, int], num_repeats_h: float) -> Image.Image: + """Map seamless tiles to a target size with a given number of repeats along the height dimension.""" + # TODO(ryand): Add option to flip odd rows and columns if the tile is not seamless. + # - May also want the option to decide on a per-axis basis. + + target_h, target_w = target_hw + + # Calculate the height of the tile that is necessary to achieve the desired number of repeats. + # Take the ceiling so that the last tile overflows the target height. + target_tile_h = math.ceil(target_h / num_repeats_h) + + # Resize the tile to the target height. + # Determine the target_tile_w that preserves the original aspect ratio. + target_tile_w = int(target_tile_h / seamless_tile.height * seamless_tile.width) + resized_tile = seamless_tile.resize((target_tile_w, target_tile_h)) + + # Repeat the tile along the height and width dimensions. + num_repeats_h_int = math.ceil(num_repeats_h) + num_repeats_w_int = math.ceil(target_w / target_tile_w) + seamless_tiles_np = np.array(resized_tile) + repeated_tiles_np = np.tile(seamless_tiles_np, (num_repeats_h_int, num_repeats_w_int, 1)) + + # Crop the repeated tiles to the target size. + output_pattern = Image.fromarray(repeated_tiles_np[:target_h, :target_w]) + return output_pattern