import gradio as gr from PIL import Image, ImageDraw import io import numpy as np from math import cos, radians, sin import os import time import json import random # Load emojis from JSON file with open("emojis.json") as f: emojis = json.load(f)["emojis"] # Directory where emoji PNGs are stored emoji_dir = "emoji_pngs" def preprocess_image(image): # Ensure the image is square by padding it 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)) new_image = new_image.resize((256, 256), Image.LANCZOS) # Resize to match crop size # Add a random emoji to the image random_emoji = random.choice(list(emojis.keys())) emoji_path = os.path.join(emoji_dir, f"{random_emoji}.png") if os.path.exists(emoji_path): emoji_image = Image.open(emoji_path).convert("RGBA") emoji_image = emoji_image.resize((256, 256), Image.LANCZOS) new_image.paste(emoji_image, (0, 0), emoji_image) return new_image, random_emoji def create_gif(editor1_output, editor2_output, transition_type, history): frames = [] duration = 100 # Duration for each frame in milliseconds total_frames = 18 # Total number of frames try: # Open images img1 = editor1_output["composite"].convert('RGBA') img2 = editor2_output["composite"].convert('RGBA') # Preprocess images to make them square and same size img1, random_emoji1 = preprocess_image(img1) img2, random_emoji2 = preprocess_image(img2) # Set size for the GIF size = (256, 256) img1 = img1.resize(size, Image.LANCZOS) img2 = img2.resize(size, Image.LANCZOS) if transition_type == "slide": # Calculate step size for consistent speed full_width = size[0] step = full_width // (total_frames // 2) # Divide by 2 as we have 2 parts to the animation # Generate frames from left to right 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) # Convert frame to P mode which is a palette-based image frame = frame.convert('P', palette=Image.ADAPTIVE) frames.append(frame) # Generate frames from right to left 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) # Convert frame to P mode which is a palette-based image frame = frame.convert('P', palette=Image.ADAPTIVE) frames.append(frame) 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) 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) # Save the GIF to a file with a unique name os.makedirs("outputs", exist_ok=True) timestamp = time.strftime("%Y%m%d%H%M%S", time.localtime(time.time())) nonce = int((time.time() * 2) % 2) # create a nonce to the 1/2 second output_path = f"outputs/{random_emoji1}-{random_emoji2}-{timestamp}{nonce}.gif" with open(output_path, "wb") as f: f.write(output.getvalue()) # Add the new GIF to the history history.append((output_path, f"{transition_type.capitalize()} transition")) # Return the updated list of generated GIFs return history, history except Exception as e: raise ValueError(f"Error creating GIF: {e}") # 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=400, crop_size=(256, 256), # Specify exact crop size 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=400, crop_size=(256, 256), # Specify exact crop size 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_gallery = gr.Gallery( label="Generated GIFs", show_label=True, elem_id="output_gallery", columns=3, rows=2, height=400, object_fit="contain" ) history = gr.State([]) generate_button.click( create_gif, inputs=[image_editor1, image_editor2, transition_type, history], outputs=[output_gallery, history] ) # Launch the interface iface.launch(share=True)