Spaces:
Running
Running
Update app.py
Browse files
app.py
CHANGED
@@ -1,16 +1,9 @@
|
|
1 |
-
from math import cos, radians, sin
|
2 |
-
from flask import Flask, render_template, request, send_file, jsonify
|
3 |
-
from PIL import Image, ImageDraw, ImageOps
|
4 |
import gradio as gr
|
5 |
-
import
|
6 |
import io
|
|
|
7 |
import subprocess
|
8 |
-
import
|
9 |
-
|
10 |
-
app = Flask(__name__)
|
11 |
-
|
12 |
-
UPLOAD_FOLDER = 'uploads'
|
13 |
-
os.makedirs(UPLOAD_FOLDER, exist_ok=True)
|
14 |
|
15 |
def preprocess_image(image):
|
16 |
# Ensure the image is square by padding it
|
@@ -19,47 +12,12 @@ def preprocess_image(image):
|
|
19 |
new_image.paste(image, ((size - image.width) // 2, (size - image.height) // 2))
|
20 |
return new_image
|
21 |
|
22 |
-
def
|
23 |
-
try:
|
24 |
-
image = Image.open(io.BytesIO(image_data))
|
25 |
-
if image.format == 'WEBP':
|
26 |
-
output = io.BytesIO()
|
27 |
-
image.convert('RGB').save(output, format='JPEG')
|
28 |
-
return output.getvalue()
|
29 |
-
return image_data
|
30 |
-
except Exception as e:
|
31 |
-
raise ValueError(f"Error converting WEBP to JPEG: {e}")
|
32 |
-
|
33 |
-
def convert_svg_to_png(image_data):
|
34 |
-
try:
|
35 |
-
png_data = cairosvg.svg2png(bytestring=image_data)
|
36 |
-
return png_data
|
37 |
-
except Exception as e:
|
38 |
-
raise ValueError(f"Error converting SVG to PNG: {e}")
|
39 |
-
|
40 |
-
def create_gif(images):
|
41 |
frames = []
|
42 |
duration = 100 # Duration for each frame in milliseconds
|
43 |
total_frames = 18 # Total number of frames
|
44 |
|
45 |
try:
|
46 |
-
img1_data = images[0].read()
|
47 |
-
img2_data = images[1].read()
|
48 |
-
|
49 |
-
# Convert WebP images to JPEG if necessary
|
50 |
-
img1_data = convert_webp_to_jpeg(img1_data)
|
51 |
-
img2_data = convert_webp_to_jpeg(img2_data)
|
52 |
-
|
53 |
-
# Convert SVG images to PNG if necessary
|
54 |
-
if img1_data.strip().startswith(b'<svg'):
|
55 |
-
img1_data = convert_svg_to_png(img1_data)
|
56 |
-
if img2_data.strip().startswith(b'<svg'):
|
57 |
-
img2_data = convert_svg_to_png(img2_data)
|
58 |
-
|
59 |
-
# Open images
|
60 |
-
img1 = Image.open(io.BytesIO(img1_data)).convert('RGBA')
|
61 |
-
img2 = Image.open(io.BytesIO(img2_data)).convert('RGBA')
|
62 |
-
|
63 |
# Preprocess images to make them square and same size
|
64 |
img1 = preprocess_image(img1)
|
65 |
img2 = preprocess_image(img2)
|
@@ -69,144 +27,79 @@ def create_gif(images):
|
|
69 |
img1 = img1.resize(size, Image.LANCZOS)
|
70 |
img2 = img2.resize(size, Image.LANCZOS)
|
71 |
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
94 |
-
|
95 |
-
|
96 |
-
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
|
102 |
# Save as GIF
|
103 |
output = io.BytesIO()
|
104 |
frames[0].save(output, format='GIF', save_all=True, append_images=frames[1:], duration=duration, loop=0, optimize=True)
|
105 |
output.seek(0)
|
106 |
|
107 |
-
return output
|
108 |
except Exception as e:
|
109 |
raise ValueError(f"Error creating GIF: {e}")
|
110 |
|
111 |
-
def create_rotating_gif(images):
|
112 |
-
frames = []
|
113 |
-
duration = 100 # Duration for each frame in milliseconds
|
114 |
-
total_frames = 18 # Total number of frames for a smooth rotation
|
115 |
-
|
116 |
-
try:
|
117 |
-
img1_data = images[0].read()
|
118 |
-
img2_data = images[1].read()
|
119 |
-
|
120 |
-
# Convert WebP images to JPEG if necessary
|
121 |
-
img1_data = convert_webp_to_jpeg(img1_data)
|
122 |
-
img2_data = convert_webp_to_jpeg(img2_data)
|
123 |
-
|
124 |
-
# Convert SVG images to PNG if necessary
|
125 |
-
if img1_data.strip().startswith(b'<svg'):
|
126 |
-
img1_data = convert_svg_to_png(img1_data)
|
127 |
-
if img2_data.strip().startswith(b'<svg'):
|
128 |
-
img2_data = convert_svg_to_png(img2_data)
|
129 |
-
|
130 |
-
# Open images
|
131 |
-
img1 = Image.open(io.BytesIO(img1_data)).convert('RGBA')
|
132 |
-
img2 = Image.open(io.BytesIO(img2_data)).convert('RGBA')
|
133 |
-
|
134 |
-
# Preprocess images to make them square and same size
|
135 |
-
img1 = preprocess_image(img1)
|
136 |
-
img2 = preprocess_image(img2)
|
137 |
-
|
138 |
-
# Set size for the GIF
|
139 |
-
size = (256, 256)
|
140 |
-
img1 = img1.resize(size, Image.LANCZOS)
|
141 |
-
img2 = img2.resize(size, Image.LANCZOS)
|
142 |
-
|
143 |
-
# Create a mask that is 2x the size of the image
|
144 |
-
mask_size = (size[0] * 2, size[1] * 2)
|
145 |
-
mask = Image.new('L', mask_size, 0)
|
146 |
-
draw = ImageDraw.Draw(mask)
|
147 |
-
draw.rectangle([size[0], 0, mask_size[0], mask_size[1]], fill=255)
|
148 |
-
|
149 |
-
center_x, center_y = size[0] // 2, size[1] // 2
|
150 |
-
|
151 |
-
for angle in range(0, 360, 360 // total_frames):
|
152 |
-
rotated_mask = mask.rotate(angle, center=(mask_size[0] // 2, mask_size[1] // 2), expand=False)
|
153 |
-
cropped_mask = rotated_mask.crop((size[0] // 2, size[1] // 2, size[0] // 2 + size[0], size[1] // 2 + size[1]))
|
154 |
-
frame = Image.composite(img1, img2, cropped_mask)
|
155 |
-
|
156 |
-
# Draw the green dividing lines in the reverse direction
|
157 |
-
draw = ImageDraw.Draw(frame)
|
158 |
-
reverse_angle = -angle + 90 # Reverse the direction of the line and add 90 degrees
|
159 |
-
end_x1 = center_x + int(size[0] * 1.5 * cos(radians(reverse_angle)))
|
160 |
-
end_y1 = center_y + int(size[1] * 1.5 * sin(radians(reverse_angle)))
|
161 |
-
end_x2 = center_x - int(size[0] * 1.5 * cos(radians(reverse_angle)))
|
162 |
-
end_y2 = center_y - int(size[1] * 1.5 * sin(radians(reverse_angle)))
|
163 |
-
draw.line([center_x, center_y, end_x1, end_y1], fill=(0, 255, 0), width=3) # Line in one direction
|
164 |
-
draw.line([center_x, center_y, end_x2, end_y2], fill=(0, 255, 0), width=3) # Mirrored tail
|
165 |
-
|
166 |
-
# Convert frame to P mode which is a palette-based image
|
167 |
-
frame = frame.convert('P', palette=Image.ADAPTIVE)
|
168 |
-
frames.append(frame)
|
169 |
-
|
170 |
-
# Save as GIF
|
171 |
-
output = io.BytesIO()
|
172 |
-
frames[0].save(output, format='GIF', save_all=True, append_images=frames[1:], duration=duration, loop=0, optimize=True)
|
173 |
-
output.seek(0)
|
174 |
-
|
175 |
-
return output
|
176 |
-
except Exception as e:
|
177 |
-
raise ValueError(f"Error creating rotating GIF: {e}")
|
178 |
-
|
179 |
-
def compress_gif(input_path, output_path, lossy=80, colors=256):
|
180 |
-
# Optimize the GIF using gifsicle with lossy compression
|
181 |
-
subprocess.run(['gifsicle', '--lossy={}'.format(lossy), '--colors', str(colors), input_path, '-o', output_path])
|
182 |
-
print(f"Compressed GIF saved to {output_path}")
|
183 |
-
|
184 |
def create_gif_gradio(image1, image2, transition_type):
|
185 |
-
|
186 |
-
|
187 |
-
img2_io = io.BytesIO()
|
188 |
-
image1.save(img1_io, format='PNG')
|
189 |
-
image2.save(img2_io, format='PNG')
|
190 |
-
img1_io.seek(0)
|
191 |
-
img2_io.seek(0)
|
192 |
-
|
193 |
-
images = [img1_io, img2_io]
|
194 |
-
|
195 |
-
if transition_type == 'rotate':
|
196 |
-
gif_data = create_rotating_gif(images)
|
197 |
-
else:
|
198 |
-
gif_data = create_gif(images)
|
199 |
-
|
200 |
# Save the GIF to a temporary file
|
201 |
-
|
202 |
-
with open(
|
203 |
-
f.write(gif_data
|
204 |
-
|
205 |
-
|
206 |
-
compressed_output_path = "compressed_output.gif"
|
207 |
-
compress_gif(temp_input_path, compressed_output_path)
|
208 |
-
|
209 |
-
return compressed_output_path
|
210 |
|
211 |
# Gradio interface
|
212 |
iface = gr.Interface(
|
@@ -221,56 +114,5 @@ iface = gr.Interface(
|
|
221 |
description="Upload two images and select a transition type to generate an animated GIF."
|
222 |
)
|
223 |
|
224 |
-
|
225 |
-
|
226 |
-
return render_template('index.html')
|
227 |
-
|
228 |
-
@app.route('/gradio', methods=['GET', 'POST'])
|
229 |
-
def gradio_interface():
|
230 |
-
return gr.routes.App.create_app(iface)()
|
231 |
-
|
232 |
-
@app.route('/upload_image', methods=['POST'])
|
233 |
-
def upload_image():
|
234 |
-
try:
|
235 |
-
image = request.files['image']
|
236 |
-
if image:
|
237 |
-
filepath = os.path.join(UPLOAD_FOLDER, image.filename)
|
238 |
-
image.save(filepath)
|
239 |
-
return jsonify({'success': True, 'filepath': filepath})
|
240 |
-
else:
|
241 |
-
return jsonify({'success': False, 'error': 'No image uploaded.'})
|
242 |
-
except Exception as e:
|
243 |
-
return jsonify({'success': False, 'error': str(e)})
|
244 |
-
|
245 |
-
@app.route('/generate_gif', methods=['POST'])
|
246 |
-
def generate_gif():
|
247 |
-
try:
|
248 |
-
images = request.files.getlist('images')
|
249 |
-
if len(images) != 2:
|
250 |
-
raise ValueError("Two images are required.")
|
251 |
-
|
252 |
-
# Determine the type of transition
|
253 |
-
transition_type = request.form.get('transition_type', 'default')
|
254 |
-
|
255 |
-
# Create the GIF based on the transition type
|
256 |
-
if transition_type == 'rotate':
|
257 |
-
gif_data = create_rotating_gif(images)
|
258 |
-
else:
|
259 |
-
gif_data = create_gif(images)
|
260 |
-
|
261 |
-
# Save the GIF to a temporary file
|
262 |
-
temp_input_path = "temp_input.gif"
|
263 |
-
with open(temp_input_path, "wb") as f:
|
264 |
-
f.write(gif_data.getbuffer())
|
265 |
-
|
266 |
-
# Compress the GIF
|
267 |
-
compressed_output_path = "compressed_output.gif"
|
268 |
-
compress_gif(temp_input_path, compressed_output_path)
|
269 |
-
|
270 |
-
# Send the compressed GIF as the response
|
271 |
-
return send_file(compressed_output_path, mimetype='image/gif', as_attachment=True, download_name='animation.gif')
|
272 |
-
except Exception as e:
|
273 |
-
return jsonify({'error': str(e)}), 400
|
274 |
-
|
275 |
-
if __name__ == '__main__':
|
276 |
-
app.run(debug=True)
|
|
|
|
|
|
|
|
|
1 |
import gradio as gr
|
2 |
+
from PIL import Image, ImageDraw, ImageOps
|
3 |
import io
|
4 |
+
import os
|
5 |
import subprocess
|
6 |
+
from math import cos, radians, sin
|
|
|
|
|
|
|
|
|
|
|
7 |
|
8 |
def preprocess_image(image):
|
9 |
# Ensure the image is square by padding it
|
|
|
12 |
new_image.paste(image, ((size - image.width) // 2, (size - image.height) // 2))
|
13 |
return new_image
|
14 |
|
15 |
+
def create_gif(img1, img2, transition_type):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
16 |
frames = []
|
17 |
duration = 100 # Duration for each frame in milliseconds
|
18 |
total_frames = 18 # Total number of frames
|
19 |
|
20 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
21 |
# Preprocess images to make them square and same size
|
22 |
img1 = preprocess_image(img1)
|
23 |
img2 = preprocess_image(img2)
|
|
|
27 |
img1 = img1.resize(size, Image.LANCZOS)
|
28 |
img2 = img2.resize(size, Image.LANCZOS)
|
29 |
|
30 |
+
if transition_type == "default":
|
31 |
+
# Calculate step size for consistent speed
|
32 |
+
full_width = size[0]
|
33 |
+
step = full_width // (total_frames // 2) # Divide by 2 as we have 2 parts to the animation
|
34 |
+
|
35 |
+
# Generate frames from left to right
|
36 |
+
for i in range(0, full_width, step):
|
37 |
+
frame = Image.new('RGBA', size)
|
38 |
+
frame.paste(img1, (0, 0))
|
39 |
+
frame.paste(img2.crop((i, 0, full_width, size[1])), (i, 0), mask=img2.crop((i, 0, full_width, size[1])))
|
40 |
+
|
41 |
+
draw = ImageDraw.Draw(frame)
|
42 |
+
draw.line((i, 0, i, size[1]), fill=(0, 255, 0), width=2)
|
43 |
+
|
44 |
+
frame = frame.convert('P', palette=Image.ADAPTIVE)
|
45 |
+
frames.append(frame)
|
46 |
+
|
47 |
+
# Generate frames from right to left
|
48 |
+
for i in range(full_width, step, -step):
|
49 |
+
frame = Image.new('RGBA', size)
|
50 |
+
frame.paste(img1, (0, 0))
|
51 |
+
frame.paste(img2.crop((i, 0, full_width, size[1])), (i, 0), mask=img2.crop((i, 0, full_width, size[1])))
|
52 |
+
|
53 |
+
draw = ImageDraw.Draw(frame)
|
54 |
+
draw.line((i, 0, i, size[1]), fill=(0, 255, 0), width=2)
|
55 |
+
|
56 |
+
frame = frame.convert('P', palette=Image.ADAPTIVE)
|
57 |
+
frames.append(frame)
|
58 |
+
|
59 |
+
elif transition_type == "rotate":
|
60 |
+
# Create a mask that is 2x the size of the image
|
61 |
+
mask_size = (size[0] * 2, size[1] * 2)
|
62 |
+
mask = Image.new('L', mask_size, 0)
|
63 |
+
draw = ImageDraw.Draw(mask)
|
64 |
+
draw.rectangle([size[0], 0, mask_size[0], mask_size[1]], fill=255)
|
65 |
+
|
66 |
+
center_x, center_y = size[0] // 2, size[1] // 2
|
67 |
+
|
68 |
+
for angle in range(0, 360, 360 // total_frames):
|
69 |
+
rotated_mask = mask.rotate(angle, center=(mask_size[0] // 2, mask_size[1] // 2), expand=False)
|
70 |
+
cropped_mask = rotated_mask.crop((size[0] // 2, size[1] // 2, size[0] // 2 + size[0], size[1] // 2 + size[1]))
|
71 |
+
frame = Image.composite(img1, img2, cropped_mask)
|
72 |
+
|
73 |
+
draw = ImageDraw.Draw(frame)
|
74 |
+
reverse_angle = -angle + 90
|
75 |
+
end_x1 = center_x + int(size[0] * 1.5 * cos(radians(reverse_angle)))
|
76 |
+
end_y1 = center_y + int(size[1] * 1.5 * sin(radians(reverse_angle)))
|
77 |
+
end_x2 = center_x - int(size[0] * 1.5 * cos(radians(reverse_angle)))
|
78 |
+
end_y2 = center_y - int(size[1] * 1.5 * sin(radians(reverse_angle)))
|
79 |
+
draw.line([center_x, center_y, end_x1, end_y1], fill=(0, 255, 0), width=3)
|
80 |
+
draw.line([center_x, center_y, end_x2, end_y2], fill=(0, 255, 0), width=3)
|
81 |
+
|
82 |
+
frame = frame.convert('P', palette=Image.ADAPTIVE)
|
83 |
+
frames.append(frame)
|
84 |
|
85 |
# Save as GIF
|
86 |
output = io.BytesIO()
|
87 |
frames[0].save(output, format='GIF', save_all=True, append_images=frames[1:], duration=duration, loop=0, optimize=True)
|
88 |
output.seek(0)
|
89 |
|
90 |
+
return output.getvalue()
|
91 |
except Exception as e:
|
92 |
raise ValueError(f"Error creating GIF: {e}")
|
93 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
94 |
def create_gif_gradio(image1, image2, transition_type):
|
95 |
+
gif_data = create_gif(image1, image2, transition_type)
|
96 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
97 |
# Save the GIF to a temporary file
|
98 |
+
temp_output_path = "output.gif"
|
99 |
+
with open(temp_output_path, "wb") as f:
|
100 |
+
f.write(gif_data)
|
101 |
+
|
102 |
+
return temp_output_path
|
|
|
|
|
|
|
|
|
103 |
|
104 |
# Gradio interface
|
105 |
iface = gr.Interface(
|
|
|
114 |
description="Upload two images and select a transition type to generate an animated GIF."
|
115 |
)
|
116 |
|
117 |
+
# Launch the interface
|
118 |
+
iface.launch()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|