Animate_SVG_v2 / src /postprocessing /insert_animation.py
Daniel Gil-U Fuhge
add model files
e17e8cc
raw
history blame
13.4 kB
import numpy as np
from xml.dom import minidom
from pathlib import Path
from src.postprocessing.get_svg_size_pos import get_midpoint_of_path_bbox, get_begin_values_by_starting_pos
from src.postprocessing.transform_animation_predictor_output import transform_animation_predictor_output
def create_animated_svg(file, animation_ids, model_output, filename_suffix="", save=True):
""" Insert multiple animation statements.
Args:
file (str): Path of SVG file.
animation_ids (list[int]): List of element IDs that get animated.
model_output (ndarray): Array of 13 dimensional arrays with animation predictor model output.
filename_suffix (str): Suffix of animated SVG.
Returns:
list(float): List of begin values of elements in SVG.
xml.dom.minidom.Document: Parsed file with inserted animation statements.
"""
doc = svg_to_doc(file)
begin_values = get_begin_values_by_starting_pos(file, animation_ids, start=1, step=0.25)
for i in range(len(animation_ids)):
if not (model_output[i][:6] == np.array([0] * 6)).all():
try: # there are some paths that can't be embedded and don't have style attributes
output_dict = transform_animation_predictor_output(file, animation_ids[i], model_output[i])
output_dict["begin"] = begin_values[i]
if output_dict["type"] == "translate":
doc = insert_translate_statement(doc, animation_ids[i], output_dict)
if output_dict["type"] == "scale":
doc = insert_scale_statement(doc, animation_ids[i], output_dict, file)
if output_dict["type"] == "rotate":
doc = insert_rotate_statement(doc, animation_ids[i], output_dict)
if output_dict["type"] in ["skewX", "skewY"]:
doc = insert_skew_statement(doc, animation_ids[i], output_dict)
if output_dict["type"] == "fill":
doc = insert_fill_statement(doc, animation_ids[i], output_dict)
if output_dict["type"] in ["opacity"]:
doc = insert_opacity_statement(doc, animation_ids[i], output_dict)
except Exception as e:
print(f"File {file}, animation ID {animation_ids[i]} can't be animated. {e}")
pass
if save:
filename = file.split('/')[-1].replace(".svg", "") + "_animated"
save_animated_svg(doc, filename)
return begin_values, doc
def svg_to_doc(file):
""" Parse an SVG file.
Args:
file (string): Path of SVG file.
Returns:
xml.dom.minidom.Document: Parsed file with inserted animation statement.
"""
return minidom.parse(file)
def save_animated_svg(doc, filename):
""" Save animated SVGs to folder animated_svgs.
Args:
doc (xml.dom.minidom.Document): Parsed file.
filename (str): Name of output file.
"""
Path("data/animated_svgs").mkdir(parents=True, exist_ok=True)
with open('data/animated_svgs/' + filename + '.svg', 'wb') as f:
f.write(doc.toprettyxml(encoding="iso-8859-1"))
def insert_translate_statement(doc, animation_id, model_output_dict):
""" Insert translate statement.
Args:
doc (xml.dom.minidom.Document): Parsed file.
animation_id (int): ID of element that gets animated.
model_output_dict (dict): Dictionary containing animation statement.
Returns:
xml.dom.minidom.Document: Parsed file with inserted animation statement.
"""
pre_animations = []
opacity_dict_1, opacity_dict_2 = create_opacity_pre_animation_dicts(model_output_dict)
pre_animations.append(create_animation_statement(opacity_dict_1))
pre_animations.append(create_animation_statement(opacity_dict_2))
animation = create_animation_statement(model_output_dict)
doc = insert_animation(doc, animation_id, animation, pre_animations)
return doc
def insert_scale_statement(doc, animation_id, model_output_dict, file):
""" Insert scale statement.
Args:
doc (xml.dom.minidom.Document): Parsed file.
animation_id (int): ID of element that gets animated.
model_output_dict (dict): Dictionary containing animation statement.
file (str): Path of SVG file. Needed to get midpoint of path bbox to suppress simultaneous translate movement.
Returns:
xml.dom.minidom.Document: Parsed file with inserted animation statement.
"""
pre_animations = []
opacity_dict_1, opacity_dict_2 = create_opacity_pre_animation_dicts(model_output_dict)
pre_animations.append(create_animation_statement(opacity_dict_1))
pre_animations.append(create_animation_statement(opacity_dict_2))
x_midpoint, y_midpoint = get_midpoint_of_path_bbox(file, animation_id)
if model_output_dict["from_"] > 1:
model_output_dict["from_"] = 2
pre_animation_from = f"-{x_midpoint} -{y_midpoint}" # negative midpoint
else:
model_output_dict["from_"] = 0
pre_animation_from = f"{x_midpoint} {y_midpoint}" # positive midpoint
translate_pre_animation_dict = {"type": "translate",
"begin": model_output_dict["begin"],
"dur": model_output_dict["dur"],
"from_": pre_animation_from,
"to": "0 0",
"fill": "freeze"}
pre_animations.append(create_animation_statement(translate_pre_animation_dict))
animation = create_animation_statement(model_output_dict) + ' additive="sum" '
doc = insert_animation(doc, animation_id, animation, pre_animations)
return doc
def insert_rotate_statement(doc, animation_id, model_output_dict):
""" Insert rotate statement.
Args:
doc (xml.dom.minidom.Document): Parsed file.
animation_id (int): ID of element that gets animated.
model_output_dict (dict): Dictionary containing animation statement.
Returns:
xml.dom.minidom.Document: Parsed file with inserted animation statement.
"""
pre_animations = []
opacity_dict_1, opacity_dict_2 = create_opacity_pre_animation_dicts(model_output_dict)
pre_animations.append(create_animation_statement(opacity_dict_1))
pre_animations.append(create_animation_statement(opacity_dict_2))
animation = create_animation_statement(model_output_dict)
doc = insert_animation(doc, animation_id, animation, pre_animations)
return doc
def insert_skew_statement(doc, animation_id, model_output_dict):
""" Insert skew statement.
Args:
doc (xml.dom.minidom.Document): Parsed file.
animation_id (int): ID of element that gets animated.
model_output_dict (dict): Dictionary containing animation statement.
Returns:
xml.dom.minidom.Document: Parsed file with inserted animation statement.
"""
pre_animations = []
opacity_dict_1, opacity_dict_2 = create_opacity_pre_animation_dicts(model_output_dict)
pre_animations.append(create_animation_statement(opacity_dict_1))
pre_animations.append(create_animation_statement(opacity_dict_2))
animation = create_animation_statement(model_output_dict)
doc = insert_animation(doc, animation_id, animation, pre_animations)
return doc
def insert_fill_statement(doc, animation_id, model_output_dict):
""" Insert fill statement.
Args:
doc (xml.dom.minidom.Document): Parsed file
animation_id (int): ID of element that gets animated.
model_output_dict (dict): Dictionary containing animation statement.
Returns:
xml.dom.minidom.Document: Parsed file with inserted animation statement.
"""
pre_animations = []
model_output_dict['dur'] = 2
if model_output_dict['begin'] < 2:
model_output_dict['begin'] = 0
else: # Wave
pre_animation_dict = {"type": "fill",
"begin": 0,
"dur": model_output_dict["begin"],
"from_": model_output_dict["to"],
"to": model_output_dict["from_"],
"fill": "remove"}
pre_animations.append(create_animation_statement(pre_animation_dict))
animation = create_animation_statement(model_output_dict)
doc = insert_animation(doc, animation_id, animation, pre_animations)
return doc
def insert_opacity_statement(doc, animation_id, model_output_dict):
""" Insert opacity statement.
Args:
doc (xml.dom.minidom.Document): Parsed file.
animation_id (int): ID of element that gets animated.
model_output_dict (dict): Dictionary containing animation statement.
Returns:
xml.dom.minidom.Document: Parsed file with inserted animation statement.
"""
pre_animations = []
opacity_pre_animation_dict = {"type": "opacity",
"begin": "0",
"dur": model_output_dict["begin"],
"from_": "0",
"to": "0",
"fill": "remove"}
pre_animations.append(create_animation_statement(opacity_pre_animation_dict))
animation = create_animation_statement(model_output_dict)
doc = insert_animation(doc, animation_id, animation, pre_animations)
return doc
def insert_animation(doc, animation_id, animation, pre_animations=None):
""" Insert animation statements including pre-animation statements.
Args:
doc (xml.dom.minidom.Document): Parsed file.
animation_id (int): ID of element that gets animated.
animation (string): Animation that needs to be inserted.
pre_animations (list): List of animations that needs to be inserted before actual animation.
Returns:
xml.dom.minidom.Document: Parsed file with inserted animation statement.
"""
elements = doc.getElementsByTagName('path') + doc.getElementsByTagName('circle') + doc.getElementsByTagName(
'ellipse') + doc.getElementsByTagName('line') + doc.getElementsByTagName(
'polygon') + doc.getElementsByTagName('polyline') + doc.getElementsByTagName(
'rect') + doc.getElementsByTagName('text')
for element in elements:
if element.getAttribute('animation_id') == str(animation_id):
if pre_animations is not None:
for i in range(len(pre_animations)):
element.appendChild(doc.createElement(pre_animations[i]))
element.appendChild(doc.createElement(animation))
return doc
def create_animation_statement(animation_dict):
""" Set up animation statement from a dictionary.
Args:
animation_dict (dict): Dictionary that is transformed into animation statement.
Returns:
str: Animation statement.
"""
if animation_dict["type"] in ["translate", "scale", "rotate", "skewX", "skewY"]:
return _create_animate_transform_statement(animation_dict)
elif animation_dict["type"] in ["fill", "opacity"]:
return _create_animate_statement(animation_dict)
def _create_animate_transform_statement(animation_dict):
""" Set up animation statement from model output for ANIMATETRANSFORM animations """
animation = f'animateTransform attributeName = "transform" attributeType = "XML" ' \
f'type = "{animation_dict["type"]}" ' \
f'begin = "{str(animation_dict["begin"])}" ' \
f'dur = "{str(animation_dict["dur"])}" ' \
f'from = "{str(animation_dict["from_"])}" ' \
f'to = "{str(animation_dict["to"])}" ' \
f'fill = "{str(animation_dict["fill"])}"'
return animation
def _create_animate_statement(animation_dict):
""" Set up animation statement from model output for ANIMATE animations """
animation = f'animate attributeName = "{animation_dict["type"]}" ' \
f'begin = "{str(animation_dict["begin"])}" ' \
f'dur = "{str(animation_dict["dur"])}" ' \
f'from = "{str(animation_dict["from_"])}" ' \
f'to = "{str(animation_dict["to"])}" ' \
f'fill = "{str(animation_dict["fill"])}"'
return animation
def create_opacity_pre_animation_dicts(animation_dict):
""" Set up pre_animation statements.
Args:
animation_dict (dict): Dictionary from animation that is needed to set up opacity pre-animations.
Returns:
str: Animation Statement.
"""
opacity_pre_animation_dict_1 = {"type": "opacity",
"begin": "0",
"dur": animation_dict["begin"],
"from_": "0",
"to": "0",
"fill": "remove"}
opacity_pre_animation_dict_2 = {"type": "opacity",
"begin": animation_dict["begin"],
"dur": "0.5",
"from_": "0",
"to": "1",
"fill": "remove"}
return opacity_pre_animation_dict_1, opacity_pre_animation_dict_2