Spaces:
Running
Running
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 | |
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 | |
return new_image | |
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 = preprocess_image(img1) | |
img2 = 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 = int(time.time()) | |
output_path = f"outputs/output_{timestamp}.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) |