xrvg 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENCE +21 -0
- data/README +88 -0
- data/Rakefile +263 -0
- data/examples/foreach.rb +9 -0
- data/examples/hellocrown.rb +7 -0
- data/examples/hellocrown2.rb +7 -0
- data/examples/hellocrownrecurse.rb +9 -0
- data/examples/helloworld.rb +5 -0
- data/examples/helloworldcompact.rb +5 -0
- data/examples/helloworldexpanded.rb +5 -0
- data/examples/palette_circle.rb +10 -0
- data/examples/sample.rb +10 -0
- data/examples/uplets.rb +9 -0
- data/lib/assertion.rb +14 -0
- data/lib/attributable.rb +152 -0
- data/lib/color.rb +295 -0
- data/lib/frame.rb +32 -0
- data/lib/geometry2D.rb +207 -0
- data/lib/interpolation.rb +59 -0
- data/lib/render.rb +269 -0
- data/lib/samplation.rb +416 -0
- data/lib/shape.rb +338 -0
- data/lib/style.rb +74 -0
- data/lib/trace.rb +28 -0
- data/lib/utils.rb +404 -0
- data/lib/xrvg.rb +37 -0
- data/test/test_attributable.rb +24 -0
- data/test/test_color.rb +79 -0
- data/test/test_frame.rb +12 -0
- data/test/test_geometry2D.rb +76 -0
- data/test/test_render.rb +18 -0
- data/test/test_style.rb +16 -0
- data/test/test_utils.rb +207 -0
- metadata +80 -0
data/lib/color.rb
ADDED
@@ -0,0 +1,295 @@
|
|
1
|
+
# Color functionalities file. See
|
2
|
+
# - +Color+
|
3
|
+
# - +Palette+
|
4
|
+
# - +Gradient+
|
5
|
+
|
6
|
+
require 'geometry2D'; # for vector extension
|
7
|
+
require 'interpolation'
|
8
|
+
require 'attributable'
|
9
|
+
require 'utils'
|
10
|
+
require 'shape'; # for gradient
|
11
|
+
|
12
|
+
#
|
13
|
+
# Color class
|
14
|
+
#
|
15
|
+
# = Basics
|
16
|
+
# Class Color derives from Vector, and consists in a 4D vector of (0.0..1.0) values, for red, blue, green, and opacity
|
17
|
+
# = Utilities
|
18
|
+
# Conversion from hsv and hsl color spaces available (see this link[http://en.wikipedia.org/wiki/HSV_color_space])
|
19
|
+
# = Future
|
20
|
+
# - Must use this library[https://rubyforge.org/projects/color/], to avoid effort duplication
|
21
|
+
# - Must add relative color operations as Nodebox wants to
|
22
|
+
# - Must optimize 4D vector operations (as C extension ?)
|
23
|
+
class Color < Vector
|
24
|
+
|
25
|
+
# Color builder
|
26
|
+
#
|
27
|
+
# only allows to build 4D vector, with composants between 0.0 and 1.0
|
28
|
+
def initialize( *args )
|
29
|
+
# TODO : check args number
|
30
|
+
super( *args )
|
31
|
+
end
|
32
|
+
|
33
|
+
# return the red composant
|
34
|
+
# Color[0.1,0.2,0.3,0.4].r => 0.1
|
35
|
+
def r
|
36
|
+
return self[0]
|
37
|
+
end
|
38
|
+
|
39
|
+
# return the green composant
|
40
|
+
# Color[0.1,0.2,0.3,0.4].r => 0.2
|
41
|
+
def g
|
42
|
+
return self[1]
|
43
|
+
end
|
44
|
+
|
45
|
+
# return the blue composant
|
46
|
+
# Color[0.1,0.2,0.3,0.4].r => 0.3
|
47
|
+
def b
|
48
|
+
return self[2]
|
49
|
+
end
|
50
|
+
|
51
|
+
# return the opacity composant
|
52
|
+
# Color[0.1,0.2,0.3,0.4].r => 0.4
|
53
|
+
def a
|
54
|
+
return self[3]
|
55
|
+
end
|
56
|
+
|
57
|
+
# set the red composant
|
58
|
+
# Color[0.1,0.2,0.3,0.4].r = 0.5 => Color[0.5,0.2,0.3,0.4]
|
59
|
+
def r=(n)
|
60
|
+
self[0]= n
|
61
|
+
end
|
62
|
+
|
63
|
+
# set the green composant
|
64
|
+
# Color[0.1,0.2,0.3,0.4].g = 0.5 => Color[0.1,0.5,0.3,0.4]
|
65
|
+
def g=(n)
|
66
|
+
self[1] = n
|
67
|
+
end
|
68
|
+
|
69
|
+
# set the blue composant
|
70
|
+
# Color[0.1,0.2,0.3,0.4].b = 0.5 => Color[0.1,0.2,0.5,0.4]
|
71
|
+
def b=(n)
|
72
|
+
self[2] = n
|
73
|
+
end
|
74
|
+
|
75
|
+
# set the opacity composant
|
76
|
+
# Color[0.1,0.2,0.3,0.4].a = 0.5 => Color[0.1,0.2,0.3,0.5]
|
77
|
+
def a=(n)
|
78
|
+
self[3] = n
|
79
|
+
end
|
80
|
+
|
81
|
+
# return an array containing colors on 255 integer format
|
82
|
+
# Color[0.0,1.0,0.0,1.0].format255 => [0,255,0,255]
|
83
|
+
def format255()
|
84
|
+
return self.map {|v| (v * 255.0).to_i}
|
85
|
+
end
|
86
|
+
|
87
|
+
# return a random color vector, with 1.0 opacity !!
|
88
|
+
# Color.rand => Color[0.2345, 0.987623, 0.4123, 1.0]
|
89
|
+
def Color.rand
|
90
|
+
return Color[Kernel::rand,Kernel::rand,Kernel::rand,1.0]
|
91
|
+
end
|
92
|
+
|
93
|
+
# return a black color vector
|
94
|
+
def Color.black(opacity=1.0)
|
95
|
+
return Color[0.0, 0.0, 0.0, opacity]
|
96
|
+
end
|
97
|
+
|
98
|
+
# return a blue color vector
|
99
|
+
def Color.blue(opacity=1.0)
|
100
|
+
return Color[0.0, 0.0, 1.0, opacity]
|
101
|
+
end
|
102
|
+
|
103
|
+
# return a red color vector
|
104
|
+
def Color.red(opacity=1.0)
|
105
|
+
return Color[1.0, 0.0, 0.0, opacity]
|
106
|
+
end
|
107
|
+
|
108
|
+
# return a yellow color vector
|
109
|
+
def Color.yellow(opacity=1.0)
|
110
|
+
return Color[1.0, 1.0, 0.0, opacity]
|
111
|
+
end
|
112
|
+
|
113
|
+
# return a orange color vector
|
114
|
+
def Color.orange(opacity=1.0)
|
115
|
+
return Color[1.0, 0.5, 0.0, opacity]
|
116
|
+
end
|
117
|
+
|
118
|
+
# return a green color vector
|
119
|
+
def Color.green(opacity=1.0)
|
120
|
+
return Color[0.0, 1.0, 0.0, opacity]
|
121
|
+
end
|
122
|
+
|
123
|
+
# return a white color vector
|
124
|
+
def Color.white(opacity=1.0)
|
125
|
+
return Color[1.0, 1.0, 1.0, opacity]
|
126
|
+
end
|
127
|
+
|
128
|
+
# build a color vector from hsv parametrization (convert from hsv to rgb) h, s, v being between 0.0 and 1.0
|
129
|
+
# taken from wikipedia[http://en.wikipedia.org/wiki/HSV_color_space]
|
130
|
+
def Color.hsv( h, s, v, a)
|
131
|
+
if s == 0.0
|
132
|
+
return Color[v, v, v, a]
|
133
|
+
end
|
134
|
+
h *= 360.0
|
135
|
+
hi = (h/60.0).floor
|
136
|
+
f = (h/60.0) - hi
|
137
|
+
p = v * ( 1 - s )
|
138
|
+
q = v * ( 1 - f * s )
|
139
|
+
t = v * ( 1 - ( 1 - f ) * s )
|
140
|
+
if hi == 0
|
141
|
+
return Color[ v, t, p, a]
|
142
|
+
end
|
143
|
+
if hi == 1
|
144
|
+
return Color[ q, v, p, a]
|
145
|
+
end
|
146
|
+
if hi == 2
|
147
|
+
return Color[ p, v, t, a]
|
148
|
+
end
|
149
|
+
if hi == 3
|
150
|
+
return Color[ p, q, v, a]
|
151
|
+
end
|
152
|
+
if hi == 4
|
153
|
+
return Color[ t, p, v, a]
|
154
|
+
end
|
155
|
+
if hi == 5
|
156
|
+
return Color[ v, p, q, a]
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def Color.getHSLcomponent( tC, p, q ) #:nodoc:
|
161
|
+
while tC < 0.0
|
162
|
+
tC = tC + 1.0
|
163
|
+
end
|
164
|
+
while tC > 1.0
|
165
|
+
tC = tC - 1.0
|
166
|
+
end
|
167
|
+
|
168
|
+
if tC < (1.0 / 6.0)
|
169
|
+
tC = p + ( (q-p) * 6.0 * tC )
|
170
|
+
elsif tC >=(1.0 / 6.0) and tC < 0.5
|
171
|
+
tC = q
|
172
|
+
elsif tC >= 0.5 and tC < (2.0 / 3.0)
|
173
|
+
tC = p + ( (q-p) * 6.0 * ((2.0 / 3.0) - tC) )
|
174
|
+
else
|
175
|
+
tC = p
|
176
|
+
end
|
177
|
+
return tC
|
178
|
+
end
|
179
|
+
|
180
|
+
# build a color vector from hsl parametrization (convert from hsl to rgb) h, s, l being between 0.0 and 1.0
|
181
|
+
# taken from [[http://en.wikipedia.org/wiki/HSV_color_space]]
|
182
|
+
# h, s, l must be between 0.0 and 1.0
|
183
|
+
def Color.hsl( h, s, l, a)
|
184
|
+
h *= 360.0
|
185
|
+
if l < 0.5
|
186
|
+
q = l * (1.0 + s)
|
187
|
+
else
|
188
|
+
q = l+ s - (l * s)
|
189
|
+
end
|
190
|
+
p = 2 * l - q
|
191
|
+
hk = h / 360.0
|
192
|
+
tR = hk + 1.0 / 3.0
|
193
|
+
tG = hk
|
194
|
+
tB = hk - 1.0 / 3.0
|
195
|
+
|
196
|
+
tR = self.getHSLcomponent( tR, p, q )
|
197
|
+
tG = self.getHSLcomponent( tG, p, q )
|
198
|
+
tB = self.getHSLcomponent( tB, p, q )
|
199
|
+
return Color[tR, tG, tB, a]
|
200
|
+
end
|
201
|
+
|
202
|
+
# get svg description of a color
|
203
|
+
def svg
|
204
|
+
values = self[0..2].map {|v| (255.0 * v).to_i }
|
205
|
+
return "rgb(#{values.join(",")})"
|
206
|
+
end
|
207
|
+
|
208
|
+
end
|
209
|
+
|
210
|
+
# class Palette
|
211
|
+
# = Intro
|
212
|
+
# Palette defines color palettes, as interpolation between color points. As such, use Interpolation module, so uses for the moment only linear interpolation.
|
213
|
+
# But once built with interpolation, palette provides a continuous color "interval", and so is Samplable !
|
214
|
+
# = Use
|
215
|
+
# palette = Palette[ :colorlist, [ Color.blue, 0.0, Color.orange, 0.5, Color.yellow, 1.0 ] ]
|
216
|
+
# palette.rand( 10 ) # => return 10 random colors in palette
|
217
|
+
# palette.color( 0.5 ) # => Color.orange
|
218
|
+
class Palette
|
219
|
+
include Attributable
|
220
|
+
attribute :colorlist
|
221
|
+
|
222
|
+
# compute color given float pourcentage.
|
223
|
+
# Palette[ :colorlist, [ Color.black, 0.0, Color.white, 1.0 ] ].sample( 0.5 ) => Color[0.5,0.5,0.5,1.O]
|
224
|
+
# "sample" method as defined in Samplable module
|
225
|
+
def color(dindex)
|
226
|
+
result = self.interpolate(dindex)
|
227
|
+
return Color.elements(result[0..-1],false)
|
228
|
+
end
|
229
|
+
|
230
|
+
# return a new palette by reversing the current one
|
231
|
+
def reverse()
|
232
|
+
newcolorlist = []
|
233
|
+
self.colorlist.reverse.foreach do |index,color|
|
234
|
+
newcolorlist += [color, (0.0..1.0).complement( index )]
|
235
|
+
end
|
236
|
+
return Palette[ :colorlist, newcolorlist ]
|
237
|
+
end
|
238
|
+
|
239
|
+
|
240
|
+
include Samplable
|
241
|
+
include Interpolation
|
242
|
+
|
243
|
+
def apply_sample( abs ) #:nodoc:
|
244
|
+
# Trace("Palette#apply_sample abs #{abs}")
|
245
|
+
return self.color( abs )
|
246
|
+
end
|
247
|
+
|
248
|
+
# alias apply_sample color
|
249
|
+
# alias apply_split ? => TODO
|
250
|
+
alias samplelist colorlist
|
251
|
+
alias colors samples
|
252
|
+
end
|
253
|
+
|
254
|
+
class Gradient < Palette #:nodoc:
|
255
|
+
def defsvg()
|
256
|
+
Kernel::raise("Gradient::defsvg must be redefined in subclasses")
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
|
261
|
+
class LinearGradient < Gradient #:nodoc:
|
262
|
+
|
263
|
+
def svgdef
|
264
|
+
template = '<linearGradient id="%ID%" x1="0%" y1="0%" x2="0%" y2="100%">%stops%</linearGradient>'
|
265
|
+
stoptemplate = '<stop offset="%offset%" stop-color="%color%" stop-opacity="%opacity%"/>'
|
266
|
+
|
267
|
+
stops = "\n"
|
268
|
+
self.colorlist.foreach do |color, index|
|
269
|
+
stops += stoptemplate.subreplace( {"%offset%" => index, "%color%" => color.svg, "%opacity%" => color.a} )
|
270
|
+
stops += "\n"
|
271
|
+
end
|
272
|
+
|
273
|
+
return template.subreplace( {"%stops%" => stops} )
|
274
|
+
end
|
275
|
+
end
|
276
|
+
|
277
|
+
class CircularGradient < Gradient #:nodoc:
|
278
|
+
attribute :circle, nil, Circle
|
279
|
+
|
280
|
+
def svgdef
|
281
|
+
template = '<radialGradient id="%ID%" gradientUnits="userSpaceOnUse" cx="%cx%" cy="%cy%" r="%r%">%stops%</radialGradient>'
|
282
|
+
stoptemplate = '<stop offset="%offset%" stop-color="%color%" stop-opacity="%opacity%"/>'
|
283
|
+
|
284
|
+
stops = "\n"
|
285
|
+
self.colorlist.foreach do |color, index|
|
286
|
+
stops += stoptemplate.subreplace( {"%offset%" => index, "%color%" => color.svg, "%opacity%" => color.a} )
|
287
|
+
stops += "\n"
|
288
|
+
end
|
289
|
+
|
290
|
+
return template.subreplace( {"%stops%" => stops,
|
291
|
+
"%cx%" => circle.center.x,
|
292
|
+
"%cy%" => circle.center.y,
|
293
|
+
"%r%" => circle.radius} )
|
294
|
+
end
|
295
|
+
end
|
data/lib/frame.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
# frame.rb file
|
2
|
+
#
|
3
|
+
# See +Frame+
|
4
|
+
require 'attributable'
|
5
|
+
|
6
|
+
#
|
7
|
+
# Frame class
|
8
|
+
# = Intro
|
9
|
+
# Defines a local geometry. Used by +Curve+ interface.
|
10
|
+
# = Attributes
|
11
|
+
# attribute :center
|
12
|
+
# attribute :vector
|
13
|
+
# attribute :rotation
|
14
|
+
# attribute :scale
|
15
|
+
class Frame
|
16
|
+
include Attributable
|
17
|
+
attribute :center
|
18
|
+
attribute :vector
|
19
|
+
attribute :rotation
|
20
|
+
attribute :scale
|
21
|
+
|
22
|
+
def ==(other)
|
23
|
+
if self.center == other.center and
|
24
|
+
self.vector == other.vector and
|
25
|
+
self.rotation == other.rotation and
|
26
|
+
self.scale == other.scale
|
27
|
+
return true
|
28
|
+
end
|
29
|
+
return false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
data/lib/geometry2D.rb
ADDED
@@ -0,0 +1,207 @@
|
|
1
|
+
#
|
2
|
+
# Contains Ruby Vector class extension.
|
3
|
+
# See :
|
4
|
+
# - +Vector+
|
5
|
+
# - +Point+
|
6
|
+
|
7
|
+
require 'matrix'
|
8
|
+
|
9
|
+
#
|
10
|
+
# Vector class extension, to provide several useful "geometrical" services
|
11
|
+
#
|
12
|
+
# For example :
|
13
|
+
# Vector[0.0,2.0].norm => Vector[0.0,1.0]
|
14
|
+
# Vector[0.0,2.0].angle => 0.0
|
15
|
+
# Vector[0.0,2.0].ortho => Vector[-2.0,0.0]
|
16
|
+
# Vector[0.0,2.0].rotate( Math::PI ) => Vector[0.0,-2.0]
|
17
|
+
#
|
18
|
+
# It could be efficient to define specific 2D vector, to speed up computation.
|
19
|
+
# It is however not compatible with Color definition, as Vector 4D, and prevent from using this lib in 3D or 4D (with time).
|
20
|
+
# Must be checked though.
|
21
|
+
#
|
22
|
+
# Another thing is to make this class C extension, to speed up computation.
|
23
|
+
class Vector
|
24
|
+
include Comparable
|
25
|
+
|
26
|
+
X = Vector[1.0, 0.0]
|
27
|
+
Y = Vector[0.0, 1.0]
|
28
|
+
O = Vector[0.0, 0.0]
|
29
|
+
|
30
|
+
# to optimize for 2D
|
31
|
+
if nil
|
32
|
+
def *( scalar )
|
33
|
+
return Vector[ self[0] * scalar, self[1] * scalar ]
|
34
|
+
end
|
35
|
+
|
36
|
+
def +(other)
|
37
|
+
return Vector[ self[0] + other[0], self[1] + other[1] ]
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
# method necessary to make Vector Ranges
|
43
|
+
#
|
44
|
+
# make <=> on x, then on y
|
45
|
+
def <=>( other )
|
46
|
+
first = self.x <=> other.x
|
47
|
+
if first != 0
|
48
|
+
return first
|
49
|
+
else
|
50
|
+
return self.y <=> other.y
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# method necessary to make Vector Ranges
|
55
|
+
#
|
56
|
+
# simply call succ on each coord
|
57
|
+
def succ()
|
58
|
+
return Vector[ self.x.succ, self.y.succ ]
|
59
|
+
end
|
60
|
+
|
61
|
+
# compute the normalized vector given the current vector
|
62
|
+
# Vector[0.0,2.0].norm => Vector[0.0,1.0]
|
63
|
+
def norm
|
64
|
+
r = r()
|
65
|
+
if r != 0.0
|
66
|
+
return self / r
|
67
|
+
else
|
68
|
+
return Vector::O
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# compute the angle of a vector considering x axis.
|
73
|
+
# Vector[0.0,2.0].angle => 0.0
|
74
|
+
# must be made in C extension, as expensive
|
75
|
+
def angle
|
76
|
+
r = self.r()
|
77
|
+
if r == 0
|
78
|
+
return 0
|
79
|
+
else
|
80
|
+
unitary = self/r
|
81
|
+
cos, sin = unitary[0], unitary[1]
|
82
|
+
angle = Math.acos( cos )
|
83
|
+
if sin < 0.0
|
84
|
+
angle = -angle
|
85
|
+
end
|
86
|
+
return angle
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# compute the angle between two vectors
|
91
|
+
# Vector.angle( Vector[1.0,0.0], Vector[0.0,1.0] ) => Math::PI/2.0
|
92
|
+
def Vector.angle( v1, v2 )
|
93
|
+
return v1.angle - v2.angle
|
94
|
+
end
|
95
|
+
|
96
|
+
# scale a vector with another one
|
97
|
+
# Vector[1.0,2.0].scale( Vector[3.0,4.0] ) => Vector[3.0,8.0]
|
98
|
+
# v.scale( Vector[a,a] ) <=> v * a
|
99
|
+
def scale( scaler )
|
100
|
+
return Vector[self.x * scaler.x, self.y * scaler.y ]
|
101
|
+
end
|
102
|
+
|
103
|
+
# rotate a 2D vector
|
104
|
+
# Vector[1.0,0.0].rotate( Math::PI/2.0 ) => Vector[0.0,1.0]
|
105
|
+
def rotate( angle )
|
106
|
+
newx = self.x * Math.cos( angle ) - self.y * Math.sin( angle )
|
107
|
+
newy = self.x * Math.sin( angle ) + self.y * Math.cos( angle )
|
108
|
+
return Vector[ newx, newy ]
|
109
|
+
end
|
110
|
+
|
111
|
+
# compute the orthogonal vector. Equiv to .rotate( Math::PI/2.0 )
|
112
|
+
# Vector[1.0,0.0].ortho => Vector[0.0,1.0]
|
113
|
+
def ortho
|
114
|
+
return Vector[ -self[1], self[0] ]
|
115
|
+
end
|
116
|
+
|
117
|
+
# return first coord of a vector
|
118
|
+
# Vector[1.0,2.0].x => 1.0
|
119
|
+
def x
|
120
|
+
return self[0]
|
121
|
+
end
|
122
|
+
|
123
|
+
# return second coord of a vector
|
124
|
+
# Vector[1.0,2.0].y => 2.0
|
125
|
+
def y
|
126
|
+
return self[1]
|
127
|
+
end
|
128
|
+
|
129
|
+
# compute the symetric of vector considered as point, with center of symetrie being other considered as point
|
130
|
+
# Vector[1.0,2.0].sym( Vector[0.0,0.0] ) => Vector[-1.0,-2.0]
|
131
|
+
def sym( other )
|
132
|
+
return self * 2.0 - other
|
133
|
+
end
|
134
|
+
|
135
|
+
# shortcut method to do
|
136
|
+
# vector * (1.0 / scalar)
|
137
|
+
def /( scalar )
|
138
|
+
return self * (1.0 / scalar)
|
139
|
+
end
|
140
|
+
|
141
|
+
# more efficient that self * -1.0
|
142
|
+
def -@ ()
|
143
|
+
return Vector[ -self[0], -self[1] ]
|
144
|
+
end
|
145
|
+
|
146
|
+
alias reverse -@
|
147
|
+
|
148
|
+
# coords management between different coord systems (for the moment only euclidian and polar)
|
149
|
+
# Vector[0.0,1.0].coords => [0.0,1.0]
|
150
|
+
# Vector[0.0,1.0].coords(:polar) => [1.0,Math::PI/2.0]
|
151
|
+
def coords(type=:cartesien)
|
152
|
+
if type == :cartesien
|
153
|
+
return [self.x, self.y]
|
154
|
+
elsif type == :polar
|
155
|
+
if @polarcoords == nil
|
156
|
+
@polarcoords = [self.r, self.angle]
|
157
|
+
end
|
158
|
+
return @polarcoords
|
159
|
+
else
|
160
|
+
Kernel.raise( "Unknown coord type #{type}" )
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# build a vector from polar coords
|
165
|
+
def Vector.polar( r, angle )
|
166
|
+
x = r * Math.cos( angle )
|
167
|
+
y = r * Math.sin( angle )
|
168
|
+
return Vector[x,y]
|
169
|
+
end
|
170
|
+
|
171
|
+
alias length r
|
172
|
+
alias translate +
|
173
|
+
end
|
174
|
+
|
175
|
+
#
|
176
|
+
# Point class just to have some more meaningful notation, as Point::O
|
177
|
+
#
|
178
|
+
class Point < Vector
|
179
|
+
|
180
|
+
O = Point[0.0, 0.0]
|
181
|
+
|
182
|
+
# compute the coord box enclosing every 2D elements considered as points
|
183
|
+
# Point.viewbox( [Vector[1.0,2.0], Vector[2.0,1.0]] ) => [1.0, 1.0, 2.0, 2.0]
|
184
|
+
def Point.viewbox (pointlist)
|
185
|
+
if pointlist.size == 0
|
186
|
+
return [0.0, 0.0, 0.0, 0.0]
|
187
|
+
end
|
188
|
+
|
189
|
+
xs = pointlist.map {|p| p.x}
|
190
|
+
ys = pointlist.map {|p| p.y}
|
191
|
+
|
192
|
+
return [xs.min, ys.min, xs.max, ys.max]
|
193
|
+
end
|
194
|
+
|
195
|
+
# compute dimension of the box enclosing 2D elemnts considered as points
|
196
|
+
# Point.viewbox( [Vector[1.0,2.0], Vector[10.0,1.0]] ) => [9.0, 1.0]
|
197
|
+
# use +viewvox+
|
198
|
+
def Point.size (pointlist)
|
199
|
+
xmin, ymin, xmax, ymax = viewbox( pointlist )
|
200
|
+
return [xmax - xmin, ymax - ymin]
|
201
|
+
end
|
202
|
+
|
203
|
+
end
|
204
|
+
|
205
|
+
|
206
|
+
|
207
|
+
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# Interpolation file.
|
2
|
+
#
|
3
|
+
# See
|
4
|
+
# - +Interpolation+
|
5
|
+
# - +Interpolator+
|
6
|
+
|
7
|
+
require 'utils'
|
8
|
+
require 'attributable'
|
9
|
+
|
10
|
+
# Interpolation module
|
11
|
+
# = Intro
|
12
|
+
# Defines an interpolation service from a samplelist that must be a list [value1, index1, value2, index2, ..., valueN, indexN],
|
13
|
+
# with index between 0.0 and 1.0 and in increasing order.
|
14
|
+
# value must be an object with + and * scalar operators defined
|
15
|
+
# = Uses
|
16
|
+
# Used for example by Palette
|
17
|
+
# = Future
|
18
|
+
# Must be extended for all kinds of interpolation (bezier, ...)
|
19
|
+
module Interpolation
|
20
|
+
|
21
|
+
# must be the overloaded method to adapt Interpolation
|
22
|
+
#
|
23
|
+
# for example, Palette redefines samplelist as
|
24
|
+
# alias samplelist colorlist
|
25
|
+
def samplelist()
|
26
|
+
return [0.0, 0.0, 1.0, 1.0]
|
27
|
+
end
|
28
|
+
|
29
|
+
# computing method
|
30
|
+
#
|
31
|
+
# from an input between 0.0 and 1.0, returns linear interpolated value
|
32
|
+
def interpolate( dindex )
|
33
|
+
result = nil
|
34
|
+
pvalue, pindex = self.samplelist[0..1]
|
35
|
+
self.samplelist.foreach do |value, index|
|
36
|
+
if dindex <= index
|
37
|
+
if dindex == index
|
38
|
+
return value
|
39
|
+
end
|
40
|
+
result = pvalue + ((value - pvalue ) * ((dindex - pindex) / (index - pindex )))
|
41
|
+
break
|
42
|
+
end
|
43
|
+
pvalue, pindex = value, index
|
44
|
+
end
|
45
|
+
if not result
|
46
|
+
result = self.samplelist[-2]
|
47
|
+
end
|
48
|
+
return result
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# Buildable interpolator
|
53
|
+
#
|
54
|
+
# Simply instanciated module in a class
|
55
|
+
class Interpolator
|
56
|
+
include Attributable
|
57
|
+
attribute :samplelist
|
58
|
+
include Interpolation
|
59
|
+
end
|