CoolFace
Apppublic

chwellofficial/nt360Slides

sourceHugging Faceupdated 5mo agoView on Hugging Face
0likes
image_utils.py259 linesDownload Raw Back to utils
1from typing import List2 3from PIL import Image, ImageDraw4 5from models.pptx_models import PptxObjectFitEnum, PptxObjectFitModel6 7 8def clip_image(9    image: Image.Image,10    width: int,11    height: int,12    focus_x: float = 50.0,13    focus_y: float = 50.0,14) -> Image.Image:15    img_width, img_height = image.size16 17    img_aspect = img_width / img_height18    box_aspect = width / height19 20    if img_aspect > box_aspect:21        new_height = height22        new_width = int(new_height * img_aspect)23    else:24        new_width = width25        new_height = int(new_width / img_aspect)26 27    resized_image = image.resize((new_width, new_height), Image.LANCZOS)28 29    # Calculate clipping position based on focus30    # Convert focus percentages (0-100) to position in the resized image31    focus_x = max(0.0, min(100.0, focus_x))  # Clamp to 0-100 range32    focus_y = max(0.0, min(100.0, focus_y))  # Clamp to 0-100 range33 34    # Calculate the center point based on focus35    center_x = int((new_width - width) * (focus_x / 100.0))36    center_y = int((new_height - height) * (focus_y / 100.0))37 38    # Calculate clipping box39    left = center_x40    top = center_y41    right = left + width42    bottom = top + height43 44    clipped_image = resized_image.crop((left, top, right, bottom))45 46    return clipped_image47 48 49def round_image_corners(image: Image.Image, radii: List[int]) -> Image.Image:50    if len(radii) != 4:51        raise ValueError(52            "Image Border Radius - radii must contain exactly 4 values for each corner"53        )54 55    w, h = image.size56 57    # Clamp border radius to not exceed half the width or height58    max_radius = min(w // 2, h // 2)59    clamped_radii = [min(radius, max_radius) for radius in radii]60 61    # Ensure the image has an alpha channel (RGBA)62    if image.mode != "RGBA":63        image = image.convert("RGBA")64 65    # Create a mask for the rounded corners (start with fully transparent)66    rounded_mask = Image.new("L", image.size, 0)67 68    # Create a rectangular mask (fully opaque)69    rectangular_mask = Image.new("L", image.size, 255)70 71    # Process each corner72    for i, radius in enumerate(clamped_radii):73        if radius > 0:  # Only process if radius is positive74            # Create a circle for this radius75            circle = Image.new("L", (radius * 2, radius * 2), 0)76            draw = ImageDraw.Draw(circle)77            draw.ellipse((0, 0, radius * 2 - 1, radius * 2 - 1), fill=255)78 79            # Calculate position based on corner index80            if i == 0:  # top-left81                rounded_mask.paste(circle.crop((0, 0, radius, radius)), (0, 0))82                rectangular_mask.paste(0, (0, 0, radius, radius))83            elif i == 1:  # top-right84                rounded_mask.paste(85                    circle.crop((radius, 0, radius * 2, radius)), (w - radius, 0)86                )87                rectangular_mask.paste(0, (w - radius, 0, w, radius))88            elif i == 2:  # bottom-right89                rounded_mask.paste(90                    circle.crop((radius, radius, radius * 2, radius * 2)),91                    (w - radius, h - radius),92                )93                rectangular_mask.paste(0, (w - radius, h - radius, w, h))94            else:  # bottom-left95                rounded_mask.paste(96                    circle.crop((0, radius, radius, radius * 2)), (0, h - radius)97                )98                rectangular_mask.paste(0, (0, h - radius, radius, h))99 100    # Get the original alpha channel101    original_alpha = image.getchannel("A")102 103    # Combine the rectangular mask with the rounded corners104    corner_mask = Image.composite(rounded_mask, rectangular_mask, rounded_mask)105 106    # Combine the corner mask with the original alpha channel107    final_alpha = Image.composite(108        original_alpha, Image.new("L", image.size, 0), corner_mask109    )110 111    # Create a new image with the modified alpha channel112    result = Image.new("RGBA", image.size)113    result.paste(image.convert("RGB"), (0, 0))114    result.putalpha(final_alpha)115 116    return result117 118 119def invert_image(img: Image.Image) -> Image.Image:120    # Get image data121    data = img.getdata()122 123    # Process each pixel124    new_data = []125    for item in data:126        # Get current pixel values127        r, g, b, a = item128 129        # Invert RGB values while preserving transparency130        if a != 0:  # Skip fully transparent pixels131            new_data.append((255 - r, 255 - g, 255 - b, a))132        else:133            new_data.append((0, 0, 0, 0))134 135    # Create new image with modified data136    new_img = Image.new("RGBA", img.size)137    new_img.putdata(new_data)138    return new_img139 140 141def create_circle_image(142    image: Image.Image,143) -> Image.Image:144    # Convert to RGBA if not already145    img = image.convert("RGBA")146    # Get the original image size147    size = img.size148    # Use the smaller dimension for the circle149    circle_size = min(size)150    # Create a transparent image of the same size as original151    mask = Image.new("RGBA", size, color=(0, 0, 0, 0))152    draw = ImageDraw.Draw(mask)153 154    # Calculate center position155    center_x = size[0] // 2156    center_y = size[1] // 2157    radius = circle_size // 2158 159    # Create a circular mask160    draw.ellipse(161        (162            center_x - radius,163            center_y - radius,164            center_x + radius,165            center_y + radius,166        ),167        fill=(255, 255, 255, 255),168    )169 170    # Apply the circular mask171    result = Image.composite(img, mask, mask)172    return result173 174 175def set_image_opacity(image: Image.Image, opacity: float) -> Image.Image:176    # Clamp opacity to valid range177    opacity = max(0.0, min(1.0, opacity))178 179    # Convert to RGBA if not already180    if image.mode != "RGBA":181        image = image.convert("RGBA")182 183    # Get the original alpha channel184    original_alpha = image.getchannel("A")185 186    # Create new alpha channel with adjusted opacity187    new_alpha = original_alpha.point(lambda x: int(x * opacity))188 189    # Create new image with modified alpha channel190    result = Image.new("RGBA", image.size)191    result.paste(image.convert("RGB"), (0, 0))192    result.putalpha(new_alpha)193 194    return result195 196 197def fit_image(198    image: Image.Image, width: int, height: int, object_fit: PptxObjectFitModel199) -> Image.Image:200    if not object_fit.fit:201        return image202 203    img_width, img_height = image.size204    img_aspect = img_width / img_height205    box_aspect = width / height206 207    if object_fit.fit == PptxObjectFitEnum.CONTAIN:208        # Scale image to fit within the box while maintaining aspect ratio209        if img_aspect > box_aspect:210            new_width = width211            new_height = int(width / img_aspect)212        else:213            new_height = height214            new_width = int(height * img_aspect)215        resized_image = image.resize((new_width, new_height), Image.LANCZOS)216 217        # Use focus point for positioning if available218        focus_x = 50.0219        focus_y = 50.0220        if object_fit.focus and len(object_fit.focus) == 2:221            focus_x, focus_y = object_fit.focus[0], object_fit.focus[1]222 223        # Calculate paste position based on focus224        paste_x = int((width - new_width) * (focus_x / 100.0))225        paste_y = int((height - new_height) * (focus_y / 100.0))226 227        result = Image.new("RGBA", (width, height), (0, 0, 0, 0))228        result.paste(resized_image, (paste_x, paste_y))229        return result230 231    elif object_fit.fit == PptxObjectFitEnum.COVER:232        # Scale image to cover the box while maintaining aspect ratio233        if img_aspect > box_aspect:234            new_height = height235            new_width = int(height * img_aspect)236        else:237            new_width = width238            new_height = int(width / img_aspect)239        resized_image = image.resize((new_width, new_height), Image.LANCZOS)240 241        # Use focus point for positioning if available242        focus_x = 50.0243        focus_y = 50.0244        if object_fit.focus and len(object_fit.focus) == 2:245            focus_x, focus_y = object_fit.focus[0], object_fit.focus[1]246 247        # Calculate paste position based on focus248        paste_x = int((new_width - width) * (focus_x / 100.0))249        paste_y = int((new_height - height) * (focus_y / 100.0))250 251        # Clip the image to the box size252        return resized_image.crop((paste_x, paste_y, paste_x + width, paste_y + height))253 254    elif object_fit.fit == PptxObjectFitEnum.FILL:255        # Stretch image to fill the box exactly256        return image.resize((width, height), Image.LANCZOS)257 258    return image259