import gradio as gr from PIL import Image, ImageDraw, ImageOps import io import numpy as np from math import cos, radians, sin def preprocess_image(image, scale, crop_top, crop_left, crop_bottom, crop_right): # Scale the image new_size = (int(image.width * scale), int(image.height * scale)) image = image.resize(new_size, Image.LANCZOS) # Crop the image width, height = image.size left = int(width * crop_left) top = int(height * crop_top) right = int(width * (1 - crop_right)) bottom = int(height * (1 - crop_bottom)) image = image.crop((left, top, right, bottom)) # Ensure the image is square by padding size = max(image.size) new_image = Image.new("RGBA", (size, size), (255, 255, 255, 0)) new_image.paste(image, ((size - image.width) // 2, (size - image.height) // 2)) return new_image def create_gif(img1, img2, transition_type, scale1, crop_top1, crop_left1, crop_bottom1, crop_right1, scale2, crop_top2, crop_left2, crop_bottom2, crop_right2): frames = [] duration = 100 # Duration for each frame in milliseconds total_frames = 18 # Total number of frames try: # Preprocess images with scaling and cropping img1 = preprocess_image(img1, scale1, crop_top1, crop_left1, crop_bottom1, crop_right1) img2 = preprocess_image(img2, scale2, crop_top2, crop_left2, crop_bottom2, crop_right2) # Set size for the GIF size = (256, 256) img1 = img1.resize(size, Image.LANCZOS) img2 = img2.resize(size, Image.LANCZOS) if transition_type == "default": # Default sliding transition full_width = size[0] step = full_width // (total_frames // 2) for i in range(0, full_width, step): frame = Image.new('RGBA', size) frame.paste(img1, (0, 0)) frame.paste(img2.crop((i, 0, full_width, size[1])), (i, 0), mask=img2.crop((i, 0, full_width, size[1]))) draw = ImageDraw.Draw(frame) draw.line((i, 0, i, size[1]), fill=(0, 255, 0), width=2) frame = frame.convert('P', palette=Image.ADAPTIVE) frames.append(frame) for i in range(full_width, step, -step): frame = Image.new('RGBA', size) frame.paste(img1, (0, 0)) frame.paste(img2.crop((i, 0, full_width, size[1])), (i, 0), mask=img2.crop((i, 0, full_width, size[1]))) draw = ImageDraw.Draw(frame) draw.line((i, 0, i, size[1]), fill=(0, 255, 0), width=2) frame = frame.convert('P', palette=Image.ADAPTIVE) frames.append(frame) elif transition_type == "rotate": # Rotating transition mask_size = (size[0] * 2, size[1] * 2) mask = Image.new('L', mask_size, 0) draw = ImageDraw.Draw(mask) draw.rectangle([size[0], 0, mask_size[0], mask_size[1]], fill=255) center_x, center_y = size[0] // 2, size[1] // 2 for angle in range(0, 360, 360 // total_frames): rotated_mask = mask.rotate(angle, center=(mask_size[0] // 2, mask_size[1] // 2), expand=False) cropped_mask = rotated_mask.crop((size[0] // 2, size[1] // 2, size[0] // 2 + size[0], size[1] // 2 + size[1])) frame = Image.composite(img1, img2, cropped_mask) draw = ImageDraw.Draw(frame) reverse_angle = -angle + 90 end_x1 = center_x + int(size[0] * 1.5 * cos(radians(reverse_angle))) end_y1 = center_y + int(size[1] * 1.5 * sin(radians(reverse_angle))) end_x2 = center_x - int(size[0] * 1.5 * cos(radians(reverse_angle))) end_y2 = center_y - int(size[1] * 1.5 * sin(radians(reverse_angle))) draw.line([center_x, center_y, end_x1, end_y1], fill=(0, 255, 0), width=3) draw.line([center_x, center_y, end_x2, end_y2], fill=(0, 255, 0), width=3) frame = frame.convert('P', palette=Image.ADAPTIVE) frames.append(frame) # Save as GIF output = io.BytesIO() frames[0].save(output, format='GIF', save_all=True, append_images=frames[1:], duration=duration, loop=0, optimize=True) output.seek(0) return output.getvalue() except Exception as e: raise ValueError(f"Error creating GIF: {e}") def create_gif_gradio(image1, image2, transition_type, scale1, crop_top1, crop_left1, crop_bottom1, crop_right1, scale2, crop_top2, crop_left2, crop_bottom2, crop_right2): gif_data = create_gif(image1, image2, transition_type, scale1, crop_top1, crop_left1, crop_bottom1, crop_right1, scale2, crop_top2, crop_left2, crop_bottom2, crop_right2) # Save the GIF to a temporary file temp_output_path = "output.gif" with open(temp_output_path, "wb") as f: f.write(gif_data) return temp_output_path # Gradio interface with gr.Blocks() as iface: gr.Markdown("# GIF Generator with Image Scaling and Cropping") with gr.Row(): with gr.Column(): image1 = gr.Image(type="pil", label="Image 1") scale1 = gr.Slider(minimum=0.1, maximum=2.0, value=1.0, step=0.1, label="Scale Image 1") crop_top1 = gr.Slider(minimum=0, maximum=0.5, value=0, step=0.01, label="Crop Top (Image 1)") crop_left1 = gr.Slider(minimum=0, maximum=0.5, value=0, step=0.01, label="Crop Left (Image 1)") crop_bottom1 = gr.Slider(minimum=0, maximum=0.5, value=0, step=0.01, label="Crop Bottom (Image 1)") crop_right1 = gr.Slider(minimum=0, maximum=0.5, value=0, step=0.01, label="Crop Right (Image 1)") with gr.Column(): image2 = gr.Image(type="pil", label="Image 2") scale2 = gr.Slider(minimum=0.1, maximum=2.0, value=1.0, step=0.1, label="Scale Image 2") crop_top2 = gr.Slider(minimum=0, maximum=0.5, value=0, step=0.01, label="Crop Top (Image 2)") crop_left2 = gr.Slider(minimum=0, maximum=0.5, value=0, step=0.01, label="Crop Left (Image 2)") crop_bottom2 = gr.Slider(minimum=0, maximum=0.5, value=0, step=0.01, label="Crop Bottom (Image 2)") crop_right2 = gr.Slider(minimum=0, maximum=0.5, value=0, step=0.01, label="Crop Right (Image 2)") transition_type = gr.Radio(["default", "rotate"], label="Transition Type", value="default") generate_button = gr.Button("Generate GIF") output_gif = gr.Image(type="filepath", label="Generated GIF") generate_button.click( create_gif_gradio, inputs=[image1, image2, transition_type, scale1, crop_top1, crop_left1, crop_bottom1, crop_right1, scale2, crop_top2, crop_left2, crop_bottom2, crop_right2], outputs=output_gif ) # Launch the interface iface.launch()