Instructions to use yijunwang2/krea2-outpaint with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use yijunwang2/krea2-outpaint with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline from diffusers.utils import load_image # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("krea/Krea-2-Turbo", torch_dtype=torch.bfloat16, device_map="cuda") pipe.load_lora_weights("yijunwang2/krea2-outpaint") prompt = "Turn this cat into a dog" input_image = load_image("https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/diffusers/cat.png") image = pipe(image=input_image, prompt=prompt).images[0] - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
| from __future__ import annotations | |
| from dataclasses import dataclass | |
| import math | |
| import numpy as np | |
| from PIL import Image | |
| SOURCE_MAX_EDGE = 384 | |
| SEAM_PX = 32 | |
| class RegisteredSource: | |
| condition: Image.Image | |
| placed_source: Image.Image | |
| canvas_size: tuple[int, int] | |
| bbox: tuple[int, int, int, int] | |
| seam_px: int = SEAM_PX | |
| def bbox_normalized(self) -> list[float]: | |
| width, height = self.canvas_size | |
| x0, y0, x1, y1 = self.bbox | |
| return [x0 / width, y0 / height, x1 / width, y1 / height] | |
| class PassPlan: | |
| axis: str | |
| intermediate_size: tuple[int, int] | None | |
| first_bbox: tuple[int, int, int, int] | |
| second_bbox: tuple[int, int, int, int] | None | |
| def pass_count(self) -> int: | |
| return 1 if self.intermediate_size is None else 2 | |
| def _resize_max_edge(image: Image.Image, max_edge: int) -> Image.Image: | |
| if max(image.size) <= max_edge: | |
| return image.copy() | |
| scale = max_edge / max(image.size) | |
| size = (max(1, round(image.width * scale)), max(1, round(image.height * scale))) | |
| return image.resize(size, Image.Resampling.LANCZOS) | |
| def prepare_source( | |
| source: Image.Image, | |
| canvas_size: tuple[int, int], | |
| bbox: tuple[int, int, int, int], | |
| *, | |
| source_max_edge: int = SOURCE_MAX_EDGE, | |
| seam_px: int = SEAM_PX, | |
| ) -> RegisteredSource: | |
| width, height = canvas_size | |
| x0, y0, x1, y1 = bbox | |
| if width < 16 or height < 16 or width % 16 or height % 16: | |
| raise ValueError("Canvas dimensions must be positive multiples of 16") | |
| if not (0 <= x0 < x1 <= width and 0 <= y0 < y1 <= height): | |
| raise ValueError(f"Source bbox is outside the canvas: {bbox}") | |
| source = source.convert("RGB") | |
| box_width, box_height = x1 - x0, y1 - y0 | |
| source_ratio = source.width / source.height | |
| box_ratio = box_width / box_height | |
| tolerance = max(0.025, 2.0 / min(box_width, box_height)) | |
| if abs(box_ratio / source_ratio - 1.0) > tolerance: | |
| raise ValueError("Source bbox must preserve the source image aspect ratio") | |
| placed = source.resize((box_width, box_height), Image.Resampling.LANCZOS) | |
| return RegisteredSource( | |
| condition=_resize_max_edge(placed, source_max_edge), | |
| placed_source=placed, | |
| canvas_size=canvas_size, | |
| bbox=bbox, | |
| seam_px=seam_px, | |
| ) | |
| def _align_up(value: int, alignment: int = 16) -> int: | |
| return int(math.ceil(value / alignment) * alignment) | |
| def plan_passes(prepared: RegisteredSource) -> PassPlan: | |
| width, height = prepared.canvas_size | |
| x0, y0, x1, y1 = prepared.bbox | |
| source_width, source_height = x1 - x0, y1 - y0 | |
| if source_width == width or source_height == height: | |
| return PassPlan("direct", None, prepared.bbox, None) | |
| candidates: list[tuple[int, PassPlan]] = [] | |
| intermediate_height = _align_up(source_height) | |
| if intermediate_height < height: | |
| intermediate_y = max( | |
| 0, | |
| min(height - intermediate_height, y0 - (intermediate_height - source_height) // 2), | |
| ) | |
| local_y = y0 - intermediate_y | |
| candidates.append( | |
| ( | |
| width * intermediate_height, | |
| PassPlan( | |
| "horizontal_first", | |
| (width, intermediate_height), | |
| (x0, local_y, x1, local_y + source_height), | |
| (0, intermediate_y, width, intermediate_y + intermediate_height), | |
| ), | |
| ) | |
| ) | |
| intermediate_width = _align_up(source_width) | |
| if intermediate_width < width: | |
| intermediate_x = max( | |
| 0, | |
| min(width - intermediate_width, x0 - (intermediate_width - source_width) // 2), | |
| ) | |
| local_x = x0 - intermediate_x | |
| candidates.append( | |
| ( | |
| intermediate_width * height, | |
| PassPlan( | |
| "vertical_first", | |
| (intermediate_width, height), | |
| (local_x, y0, local_x + source_width, y1), | |
| (intermediate_x, 0, intermediate_x + intermediate_width, height), | |
| ), | |
| ) | |
| ) | |
| if not candidates: | |
| return PassPlan("direct", None, prepared.bbox, None) | |
| return min(candidates, key=lambda item: (item[0], item[1].axis))[1] | |
| def composite(generated: Image.Image, prepared: RegisteredSource) -> Image.Image: | |
| generated = generated.convert("RGB") | |
| if generated.size != prepared.canvas_size: | |
| raise ValueError("Generated image size does not match the canvas") | |
| width, height = prepared.placed_source.size | |
| yy, xx = np.mgrid[:height, :width] | |
| edge_distance = np.minimum.reduce((xx, yy, width - 1 - xx, height - 1 - yy)) | |
| alpha = np.clip(edge_distance / max(1, prepared.seam_px), 0.0, 1.0) | |
| alpha_image = Image.fromarray((alpha * 255).astype(np.uint8), mode="L") | |
| result = generated.copy() | |
| result.paste(prepared.placed_source, prepared.bbox[:2], alpha_image) | |
| return result | |