Spaces:
Sleeping
Sleeping
| import os | |
| from pathlib import Path | |
| from PIL import Image, ImageFilter, ImageOps, ImageDraw, ImageChops | |
| import tempfile | |
| def validate_path(p: str) -> Path: | |
| path = Path(os.path.expanduser(p)).resolve() | |
| if not path.exists(): | |
| raise FileNotFoundError(f"Path does not exist: {path}") | |
| return path | |
| def temp_output(suffix=".png"): | |
| return tempfile.NamedTemporaryFile(delete=False, suffix=suffix).name | |
| def ensure_path_from_img(img) -> Path: | |
| """ | |
| Ensures that the input is converted to a valid image file path. | |
| If `img` is a PIL.Image object, it saves it to a temporary file and returns the path. | |
| If `img` is already a path (string or Path), it wraps and returns it as a Path. | |
| Args: | |
| img: A PIL.Image or a path string | |
| Returns: | |
| Path to the image file | |
| """ | |
| if isinstance(img, Image.Image): | |
| temp_input = temp_output() | |
| img.save(temp_input) | |
| return Path(temp_input) | |
| return validate_path(img) | |
| def resize_image(input_path: str, width: int, height: int, output_path: str = None) -> str: | |
| """ | |
| Resize an image and save to a new file. | |
| Args: | |
| input_path (str): Path to source image. | |
| output_path (str): Path to save resized image. | |
| width (int): New width in pixels. | |
| height (int): New height in pixels. | |
| Returns: | |
| str: Path to resized image. | |
| """ | |
| if output_path is None: | |
| output_path = temp_output() | |
| input_path = validate_path(input_path) | |
| output_path = Path(output_path).expanduser().resolve() | |
| with Image.open(input_path) as img: | |
| img = img.resize((width, height)) | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| img.save(output_path) | |
| return str(output_path) | |
| def convert_grayscale(input_path: str, output_path: str = None) -> str: | |
| """ | |
| Convert an image to grayscale. | |
| Args: | |
| input_path (str): Source image path. | |
| output_path (str): Path to save grayscale version. | |
| Returns: | |
| str: Path to grayscale image. | |
| """ | |
| if output_path is None: | |
| output_path = temp_output() | |
| input_path = validate_path(input_path) | |
| output_path = Path(output_path).expanduser().resolve() | |
| with Image.open(input_path) as img: | |
| gray = img.convert("L") | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| gray.save(output_path) | |
| return str(output_path) | |
| def image_metadata(path: str) -> str: | |
| """ | |
| Get image metadata. | |
| Args: | |
| path (str): Image path. | |
| Returns: | |
| str: Format, size, and mode info. | |
| """ | |
| path = validate_path(path) | |
| with Image.open(path) as img: | |
| return "\n".join([ | |
| f"Format: {img.format}", | |
| f"Size: {img.size}", | |
| f"Mode: {img.mode}" | |
| ]) | |
| def convert_format(input_path: str, format: str, output_path: str = None) -> str: | |
| """ | |
| Convert an image to a new format. | |
| Args: | |
| input_path (str): Source image path. | |
| output_path (str): Destination path with extension. | |
| format (str): Format (e.g. 'png', 'jpeg'). | |
| Returns: | |
| str: Path to converted image. | |
| """ | |
| if output_path is None: | |
| output_path = temp_output() | |
| input_path = validate_path(input_path) | |
| output_path = Path(output_path).expanduser().resolve() | |
| with Image.open(input_path) as img: | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| img.save(output_path, format=format.upper()) | |
| return str(output_path) | |
| def blur_image(input_path: str, radius: float, output_path: str = None) -> str: | |
| """ | |
| Apply Gaussian blur to an image. | |
| Args: | |
| input_path (str): Image path. | |
| output_path (str): Path to save blurred image. | |
| radius (float): Blur radius. | |
| Returns: | |
| str: Path to blurred image. | |
| """ | |
| if output_path is None: | |
| output_path = temp_output() | |
| input_path = validate_path(input_path) | |
| output_path = Path(output_path).expanduser().resolve() | |
| with Image.open(input_path) as img: | |
| img = img.filter(ImageFilter.GaussianBlur(radius)) | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| img.save(output_path) | |
| return str(output_path) | |
| def rotate_image(input_path: str, angle: float, output_path: str = None) -> str: | |
| """ | |
| Rotate an image. | |
| Args: | |
| input_path (str): Source path. | |
| output_path (str): Destination path. | |
| angle (float): Rotation angle in degrees. | |
| Returns: | |
| str: Path to rotated image. | |
| """ | |
| if output_path is None: | |
| output_path = temp_output() | |
| input_path = validate_path(input_path) | |
| output_path = Path(output_path).expanduser().resolve() | |
| with Image.open(input_path) as img: | |
| img = img.rotate(angle, expand=True) | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| img.save(output_path) | |
| return str(output_path) | |
| def crop_image(input_path: str, left: int, top: int, right: int, bottom: int, output_path: str = None) -> str: | |
| """ | |
| Crop an image. | |
| Args: | |
| input_path (str): Source image. | |
| output_path (str): Destination. | |
| left, top, right, bottom (int): Crop box coordinates. | |
| Returns: | |
| str: Path to cropped image. | |
| """ | |
| if output_path is None: | |
| output_path = temp_output() | |
| input_path = validate_path(input_path) | |
| output_path = Path(output_path).expanduser().resolve() | |
| with Image.open(input_path) as img: | |
| img = img.crop((left, top, right, bottom)) | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| img.save(output_path) | |
| return str(output_path) | |
| def thumbnail_image(input_path: str, max_width: int, max_height: int, output_path: str = None) -> str: | |
| """ | |
| Resize image while maintaining aspect ratio. | |
| Args: | |
| input_path (str): Source. | |
| output_path (str): Target. | |
| max_width (int), max_height (int): Constraints. | |
| Returns: | |
| str: Path to thumbnail image. | |
| """ | |
| if output_path is None: | |
| output_path = temp_output() | |
| input_path = validate_path(input_path) | |
| output_path = Path(output_path).expanduser().resolve() | |
| with Image.open(input_path) as img: | |
| img.thumbnail((max_width, max_height)) | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| img.save(output_path) | |
| return str(output_path) | |
| def add_watermark(input_path: str, text: str, x: int, y: int, output_path: str = None) -> str: | |
| """ | |
| Add watermark text. | |
| Args: | |
| input_path (str): Image file. | |
| output_path (str): Save path. | |
| text (str): Watermark content. | |
| x (int), y (int): Position. | |
| Returns: | |
| str: Path to watermarked image. | |
| """ | |
| if output_path is None: | |
| output_path = temp_output() | |
| input_path = validate_path(input_path) | |
| output_path = Path(output_path).expanduser().resolve() | |
| with Image.open(input_path).convert("RGBA") as img: | |
| watermark = Image.new("RGBA", img.size) | |
| draw = ImageDraw.Draw(watermark) | |
| draw.text((x, y), text, fill=(255, 255, 255, 128)) | |
| img = Image.alpha_composite(img, watermark).convert("RGB") | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| img.save(output_path) | |
| return str(output_path) | |
| def flip_image(input_path: str, mode: str, output_path: str = None) -> str: | |
| """ | |
| Flip image horizontally or vertically. | |
| Args: | |
| input_path (str): Source. | |
| output_path (str): Destination. | |
| mode (str): 'horizontal' or 'vertical'. | |
| Returns: | |
| str: Path to flipped image. | |
| """ | |
| if output_path is None: | |
| output_path = temp_output() | |
| input_path = validate_path(input_path) | |
| output_path = Path(output_path).expanduser().resolve() | |
| with Image.open(input_path) as img: | |
| if mode == "horizontal": | |
| img = img.transpose(Image.FLIP_LEFT_RIGHT) | |
| elif mode == "vertical": | |
| img = img.transpose(Image.FLIP_TOP_BOTTOM) | |
| else: | |
| raise ValueError("Invalid mode") | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| img.save(output_path) | |
| return str(output_path) | |
| def invert_colors(input_path: str, output_path: str = None) -> str: | |
| """ | |
| Invert RGB color values. | |
| Args: | |
| input_path (str): Image file. | |
| output_path (str): Save location. | |
| Returns: | |
| str: Path to inverted image. | |
| """ | |
| if output_path is None: | |
| output_path = temp_output() | |
| input_path = validate_path(input_path) | |
| output_path = Path(output_path).expanduser().resolve() | |
| with Image.open(input_path) as img: | |
| img = ImageOps.invert(img.convert("RGB")) | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| img.save(output_path) | |
| return str(output_path) | |
| def list_images_in_directory(path: str) -> str: | |
| """ | |
| List image files in a directory. | |
| Args: | |
| path (str): Directory path. | |
| Returns: | |
| str: Newline-separated list of image paths. | |
| """ | |
| dir_path = Path(path).expanduser().resolve() | |
| if not dir_path.is_dir(): | |
| raise NotADirectoryError(f"{dir_path} is not a directory.") | |
| exts = {".png", ".jpg", ".jpeg", ".webp", ".bmp", ".gif"} | |
| files = [str(p) for p in dir_path.iterdir() if p.suffix.lower() in exts] | |
| return "\n".join(files) if files else "No image files found." | |
| def get_image_metadata(path: str) -> dict: | |
| """ | |
| Get metadata from an image. | |
| Args: | |
| path (str): Image path. | |
| Returns: | |
| dict: Metadata fields including format, size, mode. | |
| """ | |
| path = validate_path(path) | |
| with Image.open(path) as img: | |
| return { | |
| "path": str(path), | |
| "format": img.format, | |
| "mode": img.mode, | |
| "size": img.size, | |
| "width": img.width, | |
| "height": img.height, | |
| "info": img.info | |
| } | |
| import random | |
| def apply_random_color_variation(input_path: str, strength: float = 0.1,output_path: str = None) -> str: | |
| """ | |
| Apply random color variation to an image and save the result. | |
| Args: | |
| input_path (str): Source image path. | |
| output_path (str): Target image path. | |
| strength (float): Amount of variation per channel (0.0 to 1.0, default = 0.1). | |
| Returns: | |
| str: Path to the color-augmented image. | |
| """ | |
| if output_path is None: | |
| output_path = temp_output() | |
| input_path = validate_path(input_path) | |
| output_path = Path(output_path).expanduser().resolve() | |
| with Image.open(input_path).convert("RGB") as img: | |
| pixels = img.load() | |
| for y in range(img.height): | |
| for x in range(img.width): | |
| r, g, b = pixels[x, y] | |
| r = int(min(max(r + random.randint(-int(255 * strength), int(255 * strength)), 0), 255)) | |
| g = int(min(max(g + random.randint(-int(255 * strength), int(255 * strength)), 0), 255)) | |
| b = int(min(max(b + random.randint(-int(255 * strength), int(255 * strength)), 0), 255)) | |
| pixels[x, y] = (r, g, b) | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| img.save(output_path) | |
| return str(output_path) | |
| def add_images(image_path1: str, image_path2: str, output_path: str = None) -> str: | |
| """ | |
| Add two images pixel-wise and save the result. | |
| Args: | |
| image_path1 (str): Path to the first image. | |
| image_path2 (str): Path to the second image. | |
| output_path (str): Path to save the resulting image. | |
| Returns: | |
| str: Path to the added image. | |
| """ | |
| if output_path is None: | |
| output_path = temp_output() | |
| path1 = validate_path(image_path1) | |
| path2 = validate_path(image_path2) | |
| output_path = Path(output_path).expanduser().resolve() | |
| with Image.open(path1).convert("RGB") as img1, Image.open(path2).convert("RGB") as img2: | |
| if img1.size != img2.size: | |
| raise ValueError("Images must be the same size") | |
| result = ImageChops.add(img1, img2, scale=1.0, offset=0) | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| result.save(output_path) | |
| return str(output_path) | |
| from PIL import Image | |
| from pathlib import Path | |
| def concat_images(image_path1: str, image_path2: str, mode: str = "horizontal", output_path: str = None) -> str: | |
| """ | |
| Concatenate two images either horizontally or vertically. | |
| Args: | |
| image_path1 (str): Path to the first image. | |
| image_path2 (str): Path to the second image. | |
| output_path (str): Path to save the concatenated image. | |
| mode (str): 'horizontal' or 'vertical'. | |
| Returns: | |
| str: Path to the output image. | |
| """ | |
| if output_path is None: | |
| output_path = temp_output() | |
| img1 = Image.open(image_path1).convert("RGB") | |
| img2 = Image.open(image_path2).convert("RGB") | |
| if mode == "horizontal": | |
| new_width = img1.width + img2.width | |
| new_height = max(img1.height, img2.height) | |
| new_img = Image.new("RGB", (new_width, new_height)) | |
| new_img.paste(img1, (0, 0)) | |
| new_img.paste(img2, (img1.width, 0)) | |
| elif mode == "vertical": | |
| new_width = max(img1.width, img2.width) | |
| new_height = img1.height + img2.height | |
| new_img = Image.new("RGB", (new_width, new_height)) | |
| new_img.paste(img1, (0, 0)) | |
| new_img.paste(img2, (0, img1.height)) | |
| else: | |
| raise ValueError("Invalid mode. Use 'horizontal' or 'vertical'.") | |
| output_path = Path(output_path).expanduser().resolve() | |
| output_path.parent.mkdir(parents=True, exist_ok=True) | |
| new_img.save(output_path) | |
| return str(output_path) | |