""" Contains utility functions for image loading, preparation, and manipulation. Includes HEIC image format support via the optional 'pillow-heif' library. """ from PIL import Image, ImageOps, ImageDraw import os try: from pillow_heif import register_heif_opener register_heif_opener() print("HEIC opener registered successfully using pillow-heif.") _heic_support = True except ImportError: print("Warning: pillow-heif not installed. HEIC/HEIF support will be disabled.") _heic_support = False print("Loading Image Utils...") def prepare_image(image_filepath, target_size=512): """ Prepares an input image file for the diffusion pipeline. Loads an image from the given filepath (supports standard formats like JPG, PNG, WEBP, and HEIC/HEIF), ensures it's in RGB format, handles EXIF orientation, and performs a forced resize to a square target_size, ignoring the original aspect ratio. Args: image_filepath (str): The path to the image file. target_size (int): The target dimension for both width and height. Returns: PIL.Image.Image | None: The prepared image as a PIL Image object in RGB format, or None if loading or processing fails. """ if image_filepath is None: print("Warning: prepare_image received None filepath.") return None if not isinstance(image_filepath, str) or not os.path.exists(image_filepath): print(f"Error: Invalid filepath provided to prepare_image: {image_filepath}") if isinstance(image_filepath, Image.Image): print("Warning: Received PIL Image instead of filepath, proceeding...") image = image_filepath else: return None else: # --- Load Image from Filepath --- print(f"Loading image from path: {image_filepath}") try: image = Image.open(image_filepath) except ImportError as e: print(f"ImportError during Image.open: {e}. Is pillow-heif installed?") print("Cannot process image format.") return None except Exception as e: print(f"Error opening image file {image_filepath} with PIL: {e}") return None # --- Process PIL Image --- try: image = ImageOps.exif_transpose(image) image = image.convert("RGB") original_width, original_height = image.size final_width = target_size final_height = target_size resized_image = image.resize((final_width, final_height), Image.LANCZOS) print(f"Original size: ({original_width}, {original_height}), FORCED Resized to: ({final_width}, {final_height})") return resized_image except Exception as e: print(f"Error during PIL image processing steps: {e}") return None def create_blend_mask(tile_size=1024, overlap=256): """ Creates a feathered blending mask (alpha mask) for smooth tile stitching. Generates a square mask where the edges have a linear gradient ramp within the specified overlap zone, and the central area is fully opaque. Assumes overlap occurs equally on all four sides. Args: tile_size (int): The dimension (width and height) of the tiles being processed. overlap (int): The number of pixels that overlap between adjacent tiles. Returns: PIL.Image.Image: The blending mask as a PIL Image object in 'L' (grayscale) mode. White (255) areas are fully opaque, black (0) are transparent, gray values provide blending. """ if overlap >= tile_size // 2: print("Warning: Overlap is large relative to tile size, mask generation might be suboptimal.") overlap = tile_size // 2 - 1 mask = Image.new("L", (tile_size, tile_size), 0) draw = ImageDraw.Draw(mask) if overlap > 0: for i in range(overlap): alpha = int(255 * (i / float(overlap))) # Left edge ramp draw.line([(i, 0), (i, tile_size)], fill=alpha) # Right edge ramp draw.line([(tile_size - 1 - i, 0), (tile_size - 1 - i, tile_size)], fill=alpha) # Top edge ramp draw.line([(0, i), (tile_size, i)], fill=alpha) # Bottom edge ramp draw.line([(0, tile_size - 1 - i), (tile_size, tile_size - 1 - i)], fill=alpha) center_start = overlap center_end_x = tile_size - overlap center_end_y = tile_size - overlap if center_end_x > center_start and center_end_y > center_start: draw.rectangle( (center_start, center_start, center_end_x - 1, center_end_y - 1), fill=255 ) else: center_x, center_y = tile_size // 2, tile_size // 2 draw.point((center_x, center_y), fill=255) if tile_size % 2 == 0: draw.point((center_x-1, center_y), fill=255) draw.point((center_x, center_y-1), fill=255) draw.point((center_x-1, center_y-1), fill=255) print(f"Blend mask created (Size: {tile_size}x{tile_size}, Overlap: {overlap})") return mask