Created
August 4, 2020 20:29
-
-
Save laurent-dinh/8578ed92b2de9de126cc70fb67c48aa2 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import numpy as np | |
# define letters | |
letter_d = [[0, 1, 0, 0], [0, 1, 2, 0]] | |
letter_i = [[1, 1], [2, 0]] | |
letter_n = [[0, 0, 1, 1, 1], [2, 0, 1, 2, 0]] | |
letter_h = [[0, 0, 0, 1, 1, 1], [0, 2, 1, 1, 2, 0]] | |
letter_names = ["d", "i", "n", "h"] | |
letter_paths = [letter_d, letter_i, letter_n, letter_h] | |
letter_shifts = [0, 0.4, 1.8, 3.2] | |
RATIO_YX = 1.3 | |
MULXY = 15 | |
START_X = 20 * MULXY / 100. | |
START_Y = 20 * MULXY / 100. | |
STROKE_WIDTH = 12 * MULXY / 100. | |
CURSOR_WIDTH = 26 * MULXY / 100. | |
def draw_path(xs_, ys_, id_, color_=(51, 51, 51), width=STROKE_WIDTH): | |
coords_ = (["M %f %f" % (xs_[0], ys_[0])] | |
+ ["L %f %f" % (x_, y_) for x_, y_ in zip(xs_[1:], ys_[1:])]) | |
coords_ = " ".join(coords_) | |
res = ("""<path id="%s" d="%s" style="fill:none;""" % (id_, coords_) | |
+ """stroke:rgb(%d, %d, %d);stroke-linecap:round;""" % color_ | |
+ """stroke-width:%f;stroke-linejoin:round;"/>""" % width) | |
return res | |
def draw_circle(x_, y_, id_, color_=(51, 51, 51), radius_=0): | |
res = ("""<circle id="%s" """ % id_ | |
+ """r="%f" cx="%f" cy="%f" """ % (radius_, x_, y_) | |
+ """fill="rgb(%d, %d, %d)" />""" % color_) | |
return res | |
# create red strokes | |
red_strokes = [] | |
reds_cursors = [] | |
for letter_idx, (letter_name, letter_path) in enumerate(zip(letter_names, letter_paths)): | |
reds_cursors.append(draw_circle( | |
(letter_shifts[letter_idx] + letter_path[0][0]) * MULXY * RATIO_YX + START_X, | |
letter_path[1][0] * MULXY + START_Y, | |
"reds_%s_cursor" % letter_name, | |
(51, 51, 51) | |
)) | |
n_lines = len(letter_path[0][:-1]) | |
red_strokes.append( | |
draw_path([(letter_path[0][0] + letter_shifts[letter_idx]) * MULXY * RATIO_YX + START_Y | |
for in_ in letter_path[0]], | |
[letter_path[1][0] * MULXY + START_Y for in_ in letter_path[1]], | |
id_=("red_%s" % letter_name), color_=(140, 35, 35), width=0)) | |
# create black strokes | |
black_strokes = [] | |
blacks_cursors = [] | |
for letter_idx, (letter_name, letter_path) in enumerate(zip(letter_names, letter_paths)): | |
n_lines = len(letter_path[0][:-1]) | |
blacks_cursors.append(draw_circle( | |
letter_path[0][-1] * MULXY * RATIO_YX + START_X, | |
letter_path[1][-1] * MULXY + START_Y, | |
"blacks_%s_cursor" % letter_name, | |
(153, 51, 51) | |
)) | |
black_strokes.append( | |
draw_path([in_ * MULXY * RATIO_YX + START_Y | |
for in_ in letter_path[0]], | |
[in_ * MULXY + START_Y for in_ in letter_path[1]], | |
id_=("black_%s" % letter_name), color_=(51, 51, 51), width=STROKE_WIDTH)) | |
svg_prefix = """<svg id="logo_sign" version="2.0" height="{}pt" width="{}pt">""".format( | |
240 * MULXY / 100., (MULXY * letter_shifts[-1] + 140 * MULXY / 100.) * RATIO_YX) | |
svg_suffix = "</svg>" | |
background = ("""<rect height="{}" width="{}" id="back" """.format( | |
240 * MULXY / 100., (MULXY * letter_shifts[-1] + 140 * MULXY / 100.) * RATIO_YX) | |
+ """style="fill:rgb(255,255,255);stroke-width:0;stroke:rgb(0,0,0)"/>""") | |
clicker = ("""<rect height="{}" width="{}" id="clicker" """.format( | |
240 * MULXY / 100., (MULXY * letter_shifts[-1] + 140 * MULXY / 100.) * RATIO_YX) | |
+ """style="opacity:0%;fill:rgb(255,255,255);stroke-width:0;stroke:rgb(0,0,0)"/>""") | |
SPEED = 7 | |
def get_letter_lens(letter_seq_): | |
letter_seq_x, letter_seq_y = letter_seq_ | |
letter_lens = np.sqrt( | |
(np.array(letter_seq_x)[1:]- np.array(letter_seq_x)[:-1]) ** 2 | |
+ (np.array(letter_seq_y)[1:] - np.array(letter_seq_y)[:-1]) ** 2) | |
# print(stroke_len) | |
return letter_lens | |
path_lens = [list(get_letter_lens(letter_path)) for letter_path in letter_paths] | |
longest_len = max(map(sum, path_lens)) | |
path_lens = list(map(lambda in_: in_ / longest_len, path_lens)) | |
path_cum_lens = [[0] + list(np.cumsum(path_len)) for path_len in path_lens] | |
path_rev_cum_lens = [[0] + list( | |
np.cumsum(path_len[::-1])) for path_len in path_lens] | |
# animations | |
DRAW_DUR = 1.2 | |
CPRESS_DUR = 1 | |
clicker_start = """<animate | |
xlink:href="#clicker" | |
attributeName="fill" | |
from="rgb(255,255,255)" | |
to="rgb(0,0,0)" | |
dur="{dur}s" | |
begin="mouseenter;mouseover;touchstart" | |
end="mouseleave;mouseout;touchend" | |
fill="freeze" | |
id="clicker-start" />""".format(dur=DRAW_DUR) | |
clicker_end = """<animate | |
xlink:href="#clicker" | |
attributeName="fill" | |
from="rgb(0,0,0)" | |
to="rgb(255,255,255)" | |
dur="{dur}s" | |
begin="mouseleave;mouseout;touchend" | |
end="mouseenter;mouseover;touchstart" | |
fill="freeze" | |
id="clicker-end" />""".format(dur=CPRESS_DUR) | |
def animate_svg(id_, attr_, values_, timestamps_, | |
ref_, offset_=0, keysplines_="", extra_id_="", | |
replace_=False): | |
new_timestamps = map(lambda in_: in_ / timestamps_[-1], timestamps_) | |
new_timestamps = ";".join(list(map(str, new_timestamps))) | |
res = ("""<animate | |
xlink:href="#{id_}" | |
attributeName="{attr_}" | |
from="{from_}" | |
to="{to_}" | |
dur="{dur_}s" | |
begin="{ref_}.begin + {offset_}s" | |
end="{ref_}.end" | |
values="{values_}" | |
keyTimes="{timestamps_}" | |
""" + keysplines_ | |
+ """ | |
fill="%s" | |
""" % ("replace" if replace_ else "freeze") | |
+ """id="{id_}-{attr_}-{extra_id_}" />""") | |
res = res.format( | |
id_=id_, attr_=attr_, offset_=offset_, ref_=ref_, | |
from_=values_.split(";")[0], | |
to_=values_.split(";")[-1], | |
dur_=timestamps_[-1], extra_id_=extra_id_, | |
values_=values_, timestamps_=new_timestamps) | |
return res | |
cursors_animations = [] | |
strokes_animations = [] | |
compress_animations = [] | |
for letter_idx, (letter_name, letter_path) in enumerate(zip(letter_names, letter_paths)): | |
# animate cursors (x, y, radius) | |
cursors_animations.append(animate_svg( | |
extra_id_="draw", | |
id_=("reds_%s_cursor" % letter_name), | |
attr_="cx", ref_="clicker-start", values_=";".join( | |
[str(START_X + ( | |
letter_shifts[letter_idx] + in_) * MULXY * RATIO_YX) | |
for in_ in letter_path[0]]), | |
timestamps_=np.array(path_cum_lens[letter_idx]) * DRAW_DUR)) | |
cursors_animations.append(animate_svg( | |
extra_id_="draw", | |
id_=("reds_%s_cursor" % letter_name), | |
attr_="cy", ref_="clicker-start", values_=";".join( | |
[str(START_Y + (in_) * MULXY) | |
for in_ in letter_path[1]]), | |
timestamps_=np.array(path_cum_lens[letter_idx]) * DRAW_DUR)) | |
cursors_animations.append(animate_svg( | |
extra_id_="draw", | |
id_=("reds_%s_cursor" % letter_name), | |
attr_="r", ref_="clicker-start", values_="0; %f; 0" % CURSOR_WIDTH, | |
keysplines_="keySplines=\"0 1 0 1; 1 0 1 0\"", | |
timestamps_=[0, .5 * path_cum_lens[letter_idx][-1] * DRAW_DUR, | |
path_cum_lens[letter_idx][-1] * DRAW_DUR])) | |
cursors_animations.append(animate_svg( | |
extra_id_="draw", | |
id_=("blacks_%s_cursor" % letter_name), | |
attr_="cx", ref_="clicker-start", values_=";".join( | |
[str(START_X + (in_) * MULXY * RATIO_YX) | |
for in_ in letter_path[0]]), | |
timestamps_=np.array(path_cum_lens[letter_idx]) * DRAW_DUR)) | |
cursors_animations.append(animate_svg( | |
extra_id_="draw", | |
id_=("blacks_%s_cursor" % letter_name), | |
attr_="cy", ref_="clicker-start", values_=";".join( | |
[str(START_Y + (in_) * MULXY) | |
for in_ in letter_path[1]]), | |
timestamps_=np.array(path_cum_lens[letter_idx]) * DRAW_DUR)) | |
cursors_animations.append(animate_svg( | |
extra_id_="draw", | |
id_=("blacks_%s_cursor" % letter_name), | |
attr_="r", ref_="clicker-start", values_="0; %f; 0" % CURSOR_WIDTH, | |
keysplines_="keySplines=\"0 1 0 1; 1 0 1 0\"", | |
timestamps_=[0, .5 * path_cum_lens[letter_idx][-1] * DRAW_DUR, | |
path_cum_lens[letter_idx][-1] * DRAW_DUR])) | |
# animate (draw) | |
strokes_animations.append( | |
animate_svg( | |
extra_id_="draw", | |
id_=("red_%s" % letter_name), | |
attr_="stroke-width", | |
values_="0; %f; %f" % (STROKE_WIDTH, STROKE_WIDTH), | |
timestamps_=[0, .001 * DRAW_DUR * path_cum_lens[letter_idx][-1], | |
1 * DRAW_DUR * path_cum_lens[letter_idx][-1]], | |
ref_="clicker-start", | |
offset_=0) | |
) | |
strokes_animations.append( | |
animate_svg( | |
extra_id_="draw", | |
id_=("red_%s" % letter_name), | |
attr_="stroke", | |
values_="rgb(51, 51, 51); rgb(140, 35, 35); rgb(140, 35, 35)", | |
timestamps_=[0, .001 * DRAW_DUR * path_cum_lens[letter_idx][-1], | |
1 * DRAW_DUR * path_cum_lens[letter_idx][-1]], | |
ref_="clicker-start", | |
offset_=0) | |
) | |
strokes_animations.append( | |
animate_svg( | |
extra_id_="draw", | |
id_=("black_%s" % letter_name), | |
attr_="stroke-width", | |
values_="%f; %f; 0" % (STROKE_WIDTH, STROKE_WIDTH), | |
timestamps_=[0, .999 * DRAW_DUR * path_cum_lens[letter_idx][-1], | |
1 * DRAW_DUR * path_cum_lens[letter_idx][-1]], | |
ref_="clicker-start", | |
offset_=0) | |
) | |
n_lines = len(letter_path[0][:-1]) | |
drawing_values = [] | |
for pt_idx in range(len(letter_path[0])): | |
cur_xs = letter_path[0][:(pt_idx + 1)] + [letter_path[0][pt_idx]] * (n_lines - pt_idx + 1) | |
cur_ys = letter_path[1][:(pt_idx + 1)] + [letter_path[1][pt_idx]] * (n_lines - pt_idx + 1) | |
cur_xs = map( | |
lambda in_: str(START_X + ( | |
letter_shifts[letter_idx] + in_) * MULXY * RATIO_YX), cur_xs) | |
cur_ys = map( | |
lambda in_: str(START_Y + in_ * MULXY), cur_ys) | |
drawing_values.append( | |
"M" + " L".join( | |
list(map(lambda in_: " ".join(in_), zip(cur_xs, cur_ys))))) | |
drawing_values = ";".join(drawing_values) | |
strokes_animations.append(animate_svg( | |
extra_id_="draw", | |
id_=("red_%s" % letter_name), | |
attr_="d", ref_="clicker-start", values_=drawing_values, | |
timestamps_=np.array(path_cum_lens[letter_idx]) * DRAW_DUR)) | |
erasing_values = [] | |
for pt_idx in range(len(letter_path[0])): | |
cur_xs = [letter_path[0][pt_idx]] * (pt_idx + 1) + letter_path[0][(pt_idx + 1):] | |
cur_ys = [letter_path[1][pt_idx]] * (pt_idx + 1) + letter_path[1][(pt_idx + 1):] | |
cur_xs = map( | |
lambda in_: str(START_X + in_ * MULXY * RATIO_YX), cur_xs) | |
cur_ys = map( | |
lambda in_: str(START_Y + in_ * MULXY), cur_ys) | |
erasing_values.append( | |
"M" + " L".join( | |
list(map(lambda in_: " ".join(in_), zip(cur_xs, cur_ys))))) | |
erasing_values = ";".join(erasing_values) | |
strokes_animations.append(animate_svg( | |
extra_id_="draw", | |
id_=("black_%s" % letter_name), | |
attr_="d", ref_="clicker-start", values_=erasing_values, | |
timestamps_=np.array(path_rev_cum_lens[letter_idx]) * DRAW_DUR)) | |
# compress animations (positions, color) | |
start_cpress_xs = list(map( | |
lambda in_: str(START_X + ( | |
letter_shifts[letter_idx] + in_) * MULXY * RATIO_YX), | |
letter_path[0])) | |
end_cpress_xs = list(map( | |
lambda in_: str(START_X + in_ * MULXY * RATIO_YX), | |
letter_path[0])) | |
red_ys = list(map( | |
lambda in_: str(START_Y + in_ * MULXY), letter_path[1])) | |
start_coords = "M" + " L".join( | |
list(map(lambda in_: " ".join(in_), zip(start_cpress_xs, red_ys)))) | |
end_coords = "M" + " L".join( | |
list(map(lambda in_: " ".join(in_), zip(end_cpress_xs, red_ys)))) | |
if letter_idx != 0: | |
compress_animations.append(animate_svg( | |
extra_id_="compress", | |
id_=("red_%s" % letter_name), | |
attr_="d", ref_="clicker-end", | |
values_="%s;%s" % (start_coords, end_coords), | |
timestamps_=[0, (letter_shifts[letter_idx] * CPRESS_DUR | |
/ letter_shifts[-1])])) | |
compress_animations.append(animate_svg( | |
extra_id_="compress", | |
id_=("red_%s" % letter_name), | |
attr_="stroke", ref_="clicker-end", | |
values_="rgb(153, 51, 51);rgb(51, 51, 51)", | |
timestamps_=[0, CPRESS_DUR])) | |
compress_animations.append(animate_svg( | |
extra_id_="draw", | |
id_=("reds_%s_cursor" % letter_name), | |
attr_="r", ref_="clicker-end", values_="%f;0;0" % CURSOR_WIDTH, | |
timestamps_=[0, .001 * CPRESS_DUR * path_cum_lens[letter_idx][-1], | |
1 * CPRESS_DUR * path_cum_lens[letter_idx][-1]])) | |
print("\n".join( | |
[svg_prefix] + red_strokes | |
+ black_strokes + blacks_cursors + reds_cursors | |
+ [clicker_start, clicker_end] + cursors_animations | |
+ strokes_animations + compress_animations | |
+ [clicker, svg_suffix])) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment