malepati/custom_template_working
0
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 