2gif-maker / app.py
Jeffgold's picture
Update app.py
fe0fb2e verified
raw
history blame
6.46 kB
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)