import gradio as gr from PIL import Image, ImageDraw import io import numpy as np from math import cos, radians, sin def create_gif(editor1_output, editor2_output, transition_type): img1 = editor1_output["composite"].convert('RGBA') img2 = editor2_output["composite"].convert('RGBA') frames = [] duration = 100 # Duration for each frame in milliseconds total_frames = 18 # Total number of frames size = (256, 256) # Fixed size for both images img1 = img1.resize(size, Image.LANCZOS) img2 = img2.resize(size, Image.LANCZOS) if transition_type == "slide": full_width = size[0] step = full_width // (total_frames // 2) # Create sliding transition for i in range(0, full_width, step): frame = Image.new('RGBA', size) frame.paste(img1, (0, 0)) frame.paste(img2.crop((0, 0, i, size[1])), (full_width - i, 0), mask=img2.crop((0, 0, i, size[1]))) draw = ImageDraw.Draw(frame) draw.line((full_width - i, 0, full_width - i, size[1]), fill=(0, 255, 0), width=2) frames.append(frame.convert('P', palette=Image.ADAPTIVE)) for i in range(full_width, 0, -step): frame = Image.new('RGBA', size) frame.paste(img2, (0, 0)) frame.paste(img1.crop((0, 0, i, size[1])), (full_width - i, 0), mask=img1.crop((0, 0, i, size[1]))) draw = ImageDraw.Draw(frame) draw.line((full_width - i, 0, full_width - i, size[1]), fill=(0, 255, 0), width=2) frames.append(frame.convert('P', palette=Image.ADAPTIVE)) else: # rotate 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) frames.append(frame.convert('P', palette=Image.ADAPTIVE)) # 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) # Save the GIF to a temporary file temp_output_path = "output.gif" with open(temp_output_path, "wb") as f: f.write(output.getvalue()) return temp_output_path # Gradio interface with gr.Blocks() as iface: gr.Markdown("# 2GIF Transition Slider") with gr.Row(): with gr.Column(scale=2): image_editor1 = gr.ImageEditor( label="Edit Image 1", brush=gr.Brush(colors=["#ff0000", "#00ff00", "#0000ff"]), eraser=gr.Eraser(default_size=10), height=400, width="100%", crop_size="1:1", layers=True, type="pil" ) with gr.Column(scale=2): image_editor2 = gr.ImageEditor( label="Edit Image 2", brush=gr.Brush(colors=["#ff0000", "#00ff00", "#0000ff"]), eraser=gr.Eraser(default_size=10), height=400, width="100%", crop_size="1:1", layers=True, type="pil" ) with gr.Row(): transition_type = gr.Radio(["slide", "rotate"], label="Transition Type", value="slide") generate_button = gr.Button("Generate GIF") with gr.Row(): output_gif = gr.Image(type="filepath", label="Generated GIF", height=300) generate_button.click( create_gif, inputs=[image_editor1, image_editor2, transition_type], outputs=[output_gif] ) # Launch the interface iface.launch(share=True)