-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathmorph.py
136 lines (109 loc) · 4.31 KB
/
morph.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
"""
Functions to generate attractive 'morphs' between colors
All morphs are simple linear progressions between 2 or more points
in the HSV colorspace. These functions merely produce sequences of
colors - determining the time each color should be shown or
the overall duration of the animation is a detail that the library
user must handle.
All functions take and return Color objects
There are two main functions you'll want to use. The first
generates a simple linear transition between two Colors:
- color_transition(start_color, end_color, steps=20)
The second generates a transition between a list of colors:
- multistep_color_transition(rgb_points, steps=20, continuous=False)
Takes a list of Color points and returns a sequence that
transitions between all points.
Can optionally generate a sequence that cycles, returning an
infinite list of colors useful for indefinite length
animations
"""
import itertools
import colorsys
from math import ceil
from color import Color, HSV
__all__ = ['color_transition', 'multistep_color_transition']
# http://stackoverflow.com/questions/477486/python-decimal-range-step-value
def frange(start, stop = None, step = 1):
"""frange generates a set of floating point values over the
range [start, stop) with step size step
frange([start,] stop [, step ])"""
if stop is None:
for x in range(int(ceil(start))):
yield x
else:
# create a generator expression for the index values
indices = (i for i in range(0, int((stop-start)/step)))
# yield results
for i in indices:
yield start + step*i
def should_wrap(p1,p2):
# is the distance going in a negative direction around the hsv circle shorter?
if p1 > p2:
p1,p2 = p2,p1
return abs(p2-p1) > abs((p1+1)-p2)
def hsv_transition(h1, h2, steps=20, wrap=False):
"""
Transition between two values in even increments
If wrap=True, treat 0.0 == 1.0 and try to determine
the shortest distance around the colorspace
(only for hue in HSV, I don't think anything else
needs to wrap.
"""
# print "transition:", h1, h2, steps
if h1 == h2:
return itertools.repeat(h1, steps+1) # XXX check number of steps!
else:
if wrap and should_wrap(h1,h2):
if h1 < h2:
h1 += 1.0
else:
h2 += 1.0
dh = abs(h1 - h2)
step_size = dh / steps
if h1 > h2:
step_size *= -1
return frange(h1, h2, step_size)
def pairwise(iterable): # from the itertools documentation
"s -> (s0,s1), (s1,s2), (s2, s3), ..."
a, b = itertools.tee(iterable)
next(b, None)
return zip(a, b)
def color_transition(start_color, end_color, steps=20):
"""
Takes two Color objects and produce a sequence of colors that
transition between them. `steps` specifies the number of
intermediate steps in the sequence.
"""
assert isinstance(start_color, Color), "start_color must be a Color instance"
assert isinstance(end_color, Color), "end_color must be a Color instance"
h1,s1,v1 = start_color.hsv
h2,s2,v2 = end_color.hsv
h_seq = hsv_transition(h1,h2,steps,wrap=True)
s_seq = hsv_transition(s1,s2,steps)
v_seq = hsv_transition(v1,v2,steps)
for (h,s,v) in zip(h_seq, s_seq, v_seq):
yield HSV(h%1, s, v)
def multistep_color_transition(color_list, steps=20, continuous=False):
"""
Takes a list of Colors and returns a sequence of Colors that
transitions between them.
`steps` indicates the number of intermediate steps between each
color in the list.
`continuous` will create an infinite sequence
"""
if continuous and color_list[0] != color_list[-1]:
# smooth things out with a transition back to the first color
color_list.append(color_list[0])
transitions = [color_transition(a,b,steps) for (a,b) in pairwise(color_list)]
chain = itertools.chain.from_iterable(transitions)
if continuous:
chain = itertools.cycle(chain)
return chain
# Instead of a generator, just create a list with colors init rather than
# recreating a bunch of objects all the time
def transition_list(start_color, end_color, steps=20):
_list = []
t = color_transition(start_color, end_color, steps=steps)
for i in range(0, steps):
_list.append(next(t))
return _list