2gif-maker / app.py
Jeffgold's picture
Update app.py
1a618e0 verified
raw
history blame
6.69 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
# Global variable to store generated GIFs
generated_gifs = []
def preprocess_image(image_data):
# Extract the image and crop data from the ImageEditor output
image = image_data["composite"].convert('RGBA')
crop_data = image_data.get("crop")
if crop_data:
# Apply cropping if crop data is available
left, top, width, height = crop_data
image = image.crop((left, top, left + width, top + height))
# 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))
return new_image
def create_gif(editor1_output, editor2_output, transition_type):
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 list of generated GIFs
global generated_gifs
generated_gifs.append((output_path, f"{transition_type.capitalize()} transition"))
# Return the updated list of generated GIFs
return generated_gifs
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="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_gallery = gr.Gallery(
label="Generated GIFs",
show_label=True,
elem_id="output_gallery",
columns=3,
rows=2,
height=400,
object_fit="contain"
)
generate_button.click(
create_gif,
inputs=[image_editor1, image_editor2, transition_type],
outputs=[output_gallery]
)
# Launch the interface
iface.launch(share=True)