Skip to content

Instantly share code, notes, and snippets.

@OhadRubin
Created December 31, 2024 08:32
Show Gist options
  • Save OhadRubin/27a1a032c0bf538e256c263416970d03 to your computer and use it in GitHub Desktop.
Save OhadRubin/27a1a032c0bf538e256c263416970d03 to your computer and use it in GitHub Desktop.
animate_svg.py
# pip install svgelements
import io
from svgelements import SVG, Path
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
import numpy as np
def read_svg(svg_file_path):
try:
with io.open(svg_file_path, "r", encoding="utf-8") as f:
svg_content = f.read()
try:
svg_bytes = svg_content.encode("utf-8")
except UnicodeDecodeError as e:
return None
svg_obj = SVG.parse(io.BytesIO(svg_bytes))
return svg_obj
except Exception as e:
return None
def extract_path_points(svg_data, num_points=100):
paths = []
for element in svg_data.elements(lambda e: isinstance(e, Path)):
current_segment = []
all_segments = []
for segment in element.segments():
# Start a new segment if this is a Move command
if segment.__class__.__name__ == 'Move':
if current_segment:
all_segments.append(np.array(current_segment))
current_segment = []
point = segment.end
current_segment.append((point.real, point.imag))
else:
# Sample points along each segment
for t in np.linspace(0, 1, num_points):
point = segment.point(t)
current_segment.append((point.real, point.imag))
# Add the last segment if it exists
if current_segment:
all_segments.append(np.array(current_segment))
paths.extend(all_segments)
return paths
def create_animation(paths, duration=5, fps=60):
fig, ax = plt.subplots()
# Find the bounds of all paths
all_points = np.concatenate(paths)
x_min, y_min = np.min(all_points, axis=0)
x_max, y_max = np.max(all_points, axis=0)
# Set up the plot
ax.set_xlim(x_min - 10, x_max + 10)
ax.set_ylim(y_max + 10, y_min - 10)
ax.set_aspect('equal')
# Create line objects for each path segment
lines = [ax.plot([], [], 'k-', linewidth=2)[0] for _ in paths]
def init():
for line in lines:
line.set_data([], [])
return lines
def animate(frame):
progress = frame / (fps * duration)
total_points = sum(len(path) for path in paths)
points_progress = int(total_points * progress)
points_counted = 0
for line, path in zip(lines, paths):
if points_counted + len(path) <= points_progress:
# Show full segment
line.set_data(path[:, 0], path[:, 1])
points_counted += len(path)
elif points_counted < points_progress:
# Show partial segment
points_to_show = points_progress - points_counted
line.set_data(path[:points_to_show, 0], path[:points_to_show, 1])
points_counted += len(path)
else:
# Hide segment
line.set_data([], [])
return lines
anim = FuncAnimation(fig, animate,
init_func=init,
frames=fps * duration,
interval=1000/fps,
blit=True)
return fig, anim
import fire
# usage: python3 animate_svg.py --svg_file=/Users/ohadr/Downloads/03/smoothed_image_pig.svg
# usage: python3 animate_svg.py --svg_file=/Users/ohadr/Downloads/03/smoothed_image_holding.svg
def main(svg_file: str):
"""
Create an animation from an SVG file.
Args:
svg_file: Path to the SVG file to animate
"""
svg_data = read_svg(svg_file)
if svg_data:
# Extract points from the SVG paths
paths = extract_path_points(svg_data)
# Create and display the animation
fig, anim = create_animation(paths)
# Save the animation
svg_file_name = svg_file.replace(".svg", ".gif")
anim.save(svg_file_name, writer='pillow', fps=30)
# Display the animation
# plt.show()
else:
print("Failed to load SVG file")
if __name__ == "__main__":
fire.Fire(main)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment