xrvg 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
data/lib/beziertools.rb DELETED
@@ -1,245 +0,0 @@
1
- # Some BezierBuilder implementations
2
- # - old-fashioned SimpleBezier
3
- # - useful BezierLevel
4
- # - powerful Offset
5
- # - interesting Ondulation
6
- # - simple ClosureBezier
7
-
8
- require 'bezierbuilders'
9
-
10
- module XRVG
11
- # = SimpleBezier
12
- # == Content
13
- # Simple Bezier interpolator, that builds a multipiece "regular" bezier curve from a list of points
14
- # == Algo
15
- # For each point triplet :
16
- # - tangent vector of the middle point is the vector mean of vector from first to middle and vector frommiddle to last.
17
- # First and last tangents are computed by symetry
18
- # == Note
19
- # FittingBezier is a better class for point bezier interpolation. However, this class is kept mainly for historical reasons.
20
- class SimpleBezier < BezierBuilder
21
- attribute :support, nil, Array
22
-
23
- # BezierBuilder overloading: see SimpleBezier description for algorithm
24
- def compute( )
25
- points = @support
26
- if points.length < 2
27
- Kernel::raise("SimpleBezier support must have at least two points")
28
- elsif points.length == 2
29
- return LinearBezier.build( :support, points).data
30
- end
31
-
32
- result = Array.new
33
- p1 = points[0]
34
- v1 = V2D::O
35
- cpiece = [:vector, p1, v1]
36
-
37
- points.triplets do |p1, p2, p3|
38
- v = ((p2 - p1)..( p3 - p2 )).middle * 1.0 / 3.0
39
- result.push( cpiece + [p2, v.reverse] )
40
- cpiece = [:vector, p2, v]
41
- end
42
-
43
- pr2 = points[-1]
44
- vr2 = V2D::O
45
- result.push( cpiece + [pr2, vr2] )
46
-
47
- # compute first and last piece again, by symetry
48
- piece0 = Bezier.single( *result[0] )
49
- p1, v1, p2, v2 = piece0.pointlist(:vector)
50
- pv = (p2 - p1)
51
- angle = v2.angle - pv.angle
52
- v1 = (-v2).rotate( -2.0 * angle )
53
- result[0] = [:vector, p1, v1, p2, v2]
54
-
55
- piecel = Bezier.single( *result[-1] )
56
- p1, v1, p2, v2 = piecel.pointlist(:vector)
57
- pv = (p2 - p1)
58
- angle = v1.angle - pv.angle
59
- v2 = (-v1).rotate( -2.0 * angle )
60
- result[-1] = [:vector, p1, v1, p2, v2]
61
-
62
- return result
63
- end
64
- end
65
-
66
- #
67
- # Interpolation extension with SimpleBezier
68
- #
69
- module Interpolation
70
-
71
- def compute_simplebezier
72
- points = self.samplelist.foreach(2).map { |index, value| V2D[index,value] }
73
- @simplebezier = SimpleBezier[ :support, points ]
74
- end
75
-
76
- def getcurve
77
- if not @simplebezier
78
- self.compute_simplebezier
79
- end
80
- return @simplebezier
81
- end
82
-
83
- def simplebezier( dindex )
84
- return self.getcurve.sample( dindex ).y
85
- end
86
-
87
- end
88
-
89
-
90
- # = Offset bezier builder
91
- # == Content
92
- # Generic offset bezier builder.
93
- # == Attributes
94
- # attribute :support, nil, Curve
95
- # attribute :abscissasampler, (0.0..1.0), Samplable
96
- # attribute :ampl, 0.5, :samplable
97
- # attribute :nsamples, 100
98
- class Offset < FitBezierBuilder
99
- attribute :support, nil, Curve
100
- attribute :abscissasampler, (0.0..1.0), Samplable
101
- attribute :ampl, 0.5, :samplable
102
- attribute :nsamples, 100
103
-
104
- # overload FitBezierBuilder.points to compute Offset points
105
- #
106
- # Algo: for each sample, compute point, normal and amp, and newpoint = point + normal.norm * ampl
107
- def points
108
- result = []
109
- [self.abscissasampler, self.ampl].samples( self.nsamples) do |abscissa, amplsample|
110
- frame = self.support.frame( abscissa )
111
- result << frame.center + frame.vector.ortho.norm * amplsample
112
- end
113
- return result
114
- end
115
- end
116
-
117
- # = Fuseau bezier builder
118
- # == Content
119
- # Just shortcut class for Offset with :ampl = (1.0..0.0)
120
- # == Attributes
121
- # attribute :maxwidth, 0.1
122
- class Fuseau < Offset
123
- attribute :maxwidth, 0.1
124
-
125
- # overload Offset.ampl method by returning (self.maxwidth..0.0)
126
- def ampl
127
- return (self.maxwidth..0.0)
128
- end
129
- end
130
-
131
-
132
-
133
- # = BezierLevel bezier builder
134
- # == Content
135
- # Compute "roller coaster" bezier curves
136
- #
137
- # Can be used as a x-progressing curve, that is as an interpolation curve
138
- # == Attributes
139
- # attribute :samplelist, [], Array
140
- # :samplelist must contain pairs of cartesien coords [x1,y1,x2,y2,...], x between 0.0 and 1.0 (as for interpolator)
141
- class BezierLevel < BezierBuilder
142
- attribute :samplelist, [], Array
143
-
144
- # Overload BezierBuilder build method
145
- #
146
- # Algo: simply interpolate [x,y] couples as V2D, with SimpleBezier bezier builder
147
- def BezierLevel.build( *args )
148
- builder = BezierLevel.new( *args )
149
- points = []
150
- builder.samplelist.foreach do |x,y|
151
- points << V2D[x,y]
152
- end
153
- return SimpleBezier[ :support, points ]
154
- end
155
- end
156
-
157
- # = ClosureBezier bezier builder
158
- # == Content
159
- # Simple bezier operator that take a list of beziers and produce a concatenate multipieces closed bezier curve.
160
- # Missing segments are completed with lines
161
- class ClosureBezier < BezierBuilder
162
- attribute :bezierlist
163
-
164
- # BezierBuilder compute overloading
165
- def compute
166
- result = []
167
- result += self.bezierlist[0].pieces
168
- self.bezierlist[1..-1].each do |bezier|
169
- lastpoint = result[-1].lastpoint
170
- newpoint = bezier.firstpoint
171
- if not V2D.vequal?( lastpoint, newpoint )
172
- result += LinearBezier[ :support, [lastpoint, newpoint]].pieces
173
- end
174
- result += bezier.pieces
175
- end
176
- lastpoint = result[-1].lastpoint
177
- newpoint = result[0].firstpoint
178
- if not V2D.vequal?( lastpoint, newpoint )
179
- result += LinearBezier[ :support, [lastpoint, newpoint]].pieces
180
- end
181
- result = result.map {|piece| piece.data}
182
- # Trace("result #{result.inspect}")
183
- return result
184
- end
185
- end
186
-
187
- # = Ondulation bezier builder
188
- # == Content
189
- # Generic ondulation bezier builder.
190
- # == Attributes
191
- # attribute :support, nil, Curve
192
- # attribute :ampl, 0.5, :samplable
193
- # attribute :abscissasampler, (0.0..1.0), Samplable
194
- # attribute :freq, 10
195
- # :support is a Curve
196
- # :abscissas must be a Float Samplable, as (0.0..1.0).geo(3.0)
197
- # :ampl can be a constant or a sampler
198
- # :freq is the number of oscillations to be computed
199
- class Ondulation < BezierBuilder
200
- attribute :support, nil, Curve
201
- attribute :ampl, 0.5, :samplable
202
- attribute :abscissasampler, (0.0..1.0), Samplable
203
- attribute :freq, 10
204
-
205
- # atomic pattern computation
206
- #
207
- def compute_arc( abs1, abs2, amplitude, sens )
208
- mabs = (abs1 + abs2)/2.0
209
- p1, halfpoint, p2 = self.support.points( [abs1, mabs, abs2] )
210
- # Trace("mabs #{mabs} abs1 #{abs1} abs2 #{abs2} halfpoint #{halfpoint.inspect} p1 #{p1.inspect} p2 #{p2.inspect}")
211
- # Trace("normal #{@support.normal( mabs )}")
212
- halfnormal = self.support.normal( mabs ).norm * ( sens * amplitude * (p2 - p1).length)
213
- # Trace("halfnormal #{halfnormal.inspect}")
214
- newpoint = halfpoint + halfnormal
215
- tpoint = halfpoint + halfnormal * 3.0
216
- t1 = (tpoint - p1 ) / 6.0
217
- t2 = (tpoint - p2 ) / 6.0
218
- # Trace("newpoint #{newpoint.inspect} p1 #{p1.inspect} (newpoint - p1) #{(newpoint - p1).inspect}")
219
- halftangent = self.support.tangent( mabs ).norm * (newpoint - p1).length / 3.0
220
- # halftangent = self.support.tangent( mabs ).norm * (p2 - p1).length / 3.0
221
- return [[:vector, p1, t1, newpoint, -halftangent], [:vector, newpoint, halftangent, p2, t2]]
222
- end
223
-
224
- def compute_interpol( abs1, abs2, amplitude, sens )
225
- arc = Bezier.multi( self.compute_arc( abs1, abs2, 1.0, sens ) )
226
- subsupport = self.support.subbezier( abs1, abs2 )
227
- return InterBezier[ :bezierlist, [0.0, subsupport, 1.0, arc] ].sample( amplitude ).data
228
- end
229
-
230
- # algo : for each abscissa, 0.0 of the curve (given the normal)
231
- # and for each mean abscissa, :amp normal
232
- def compute
233
- abscissas = self.abscissasampler.samples( self.freq + 1 )
234
- sens = 1.0
235
- pieces = []
236
- [abscissas.pairs, self.ampl.samples( self.freq )].forzip do |abspair, amplitude|
237
- abs1, abs2 = abspair
238
- pieces += self.compute_interpol( abs1, abs2, amplitude, sens )
239
- sens *= -1.0
240
- end
241
- return pieces
242
- end
243
- end
244
-
245
- end # XRVG
data/lib/color.rb DELETED
@@ -1,401 +0,0 @@
1
- # Color functionalities file. See
2
- # - +Color+
3
- # - +Palette+
4
- # - +Gradient+
5
-
6
- require 'utils'
7
- require 'interpolation'
8
- require 'shape'; # for gradient
9
-
10
- module XRVG
11
- #
12
- # Color class
13
- #
14
- # = Basics
15
- # Class Color consists in a 4D vector of (0.0..1.0) values, for red, blue, green, and opacity
16
- # = Utilities
17
- # Conversion from hsv and hsl color spaces available (see this link[http://en.wikipedia.org/wiki/HSV_color_space])
18
- # = Future
19
- # - Must use this library[https://rubyforge.org/projects/color/], to avoid effort duplication
20
- # - Must add relative color operations as Nodebox wants to
21
- # - Must optimize 4D vector operations (as C extension ?)
22
- class Color
23
-
24
- # Color builder
25
- #
26
- # only allows to build 4D vector, with composants between 0.0 and 1.0
27
- def Color.[]( *args )
28
- # TODO : check args number
29
- Color.new( *args )
30
- end
31
-
32
- # builder
33
- #
34
- # r, g, b, a must be between 0.0 and 1.0
35
- def initialize( r, g, b, a)
36
- # cannot trim because otherwise color interpolation cannot be right !!
37
- # r = Range.O.trim( r )
38
- # g = Range.O.trim( g )
39
- # b = Range.O.trim( b )
40
- # a = Range.O.trim( a )
41
- @items = [r,g,b,a]
42
- end
43
-
44
- # delegation componant indexation method
45
- def [](index)
46
- return @items[index]
47
- end
48
-
49
- # define addition operation, for interpolation
50
- def +( other )
51
- return Color[ self.r + other.r,
52
- self.g + other.g,
53
- self.b + other.b,
54
- self.a + other.a ]
55
- end
56
-
57
- # define scalar multiplication, for interpolation
58
- def *( scalar )
59
- return Color[ self.r * scalar,
60
- self.g * scalar,
61
- self.b * scalar,
62
- self.a * scalar ]
63
- end
64
-
65
- # return [r,g,b,a]
66
- def composants
67
- return @items
68
- end
69
-
70
- # equality operator
71
- def ==( other )
72
- return (self.composants == other.composants)
73
- end
74
-
75
- # return the red composant
76
- # Color[0.1,0.2,0.3,0.4].r => 0.1
77
- def r
78
- return self[0]
79
- end
80
-
81
- # return the green composant
82
- # Color[0.1,0.2,0.3,0.4].r => 0.2
83
- def g
84
- return self[1]
85
- end
86
-
87
- # return the blue composant
88
- # Color[0.1,0.2,0.3,0.4].r => 0.3
89
- def b
90
- return self[2]
91
- end
92
-
93
- # return the opacity composant
94
- # Color[0.1,0.2,0.3,0.4].r => 0.4
95
- def a
96
- return self[3]
97
- end
98
-
99
- # set the red composant
100
- # Color[0.1,0.2,0.3,0.4].r = 0.5 => Color[0.5,0.2,0.3,0.4]
101
- def r=(n)
102
- @items[0]= n
103
- end
104
-
105
- # set the green composant
106
- # Color[0.1,0.2,0.3,0.4].g = 0.5 => Color[0.1,0.5,0.3,0.4]
107
- def g=(n)
108
- @items[1] = n
109
- end
110
-
111
- # set the blue composant
112
- # Color[0.1,0.2,0.3,0.4].b = 0.5 => Color[0.1,0.2,0.5,0.4]
113
- def b=(n)
114
- @items[2] = n
115
- end
116
-
117
- # set the opacity composant
118
- # Color[0.1,0.2,0.3,0.4].a = 0.5 => Color[0.1,0.2,0.3,0.5]
119
- def a=(n)
120
- @items[3] = n
121
- end
122
-
123
- # return an array containing colors on 255 integer format
124
- # Color[0.0,1.0,0.0,1.0].format255 => [0,255,0,255]
125
- def format255()
126
- return @items.map {|v| (v * 255.0).to_i}
127
- end
128
-
129
- # return a random color vector, with 1.0 opacity !!
130
- # Color.rand => Color[0.2345, 0.987623, 0.4123, 1.0]
131
- def Color.rand( opacity=1.0 )
132
- return Color[Kernel::rand,Kernel::rand,Kernel::rand,opacity]
133
- end
134
-
135
- # return a black color vector
136
- def Color.black(opacity=1.0)
137
- return Color[0.0, 0.0, 0.0, opacity]
138
- end
139
-
140
- # return a blue color vector
141
- def Color.blue(opacity=1.0)
142
- return Color[0.0, 0.0, 1.0, opacity]
143
- end
144
-
145
- # return a red color vector
146
- def Color.red(opacity=1.0)
147
- return Color[1.0, 0.0, 0.0, opacity]
148
- end
149
-
150
- # return a yellow color vector
151
- def Color.yellow(opacity=1.0)
152
- return Color[1.0, 1.0, 0.0, opacity]
153
- end
154
-
155
- # return a orange color vector
156
- def Color.orange(opacity=1.0)
157
- return Color[1.0, 0.5, 0.0, opacity]
158
- end
159
-
160
- # return a green color vector
161
- def Color.green(opacity=1.0)
162
- return Color[0.0, 1.0, 0.0, opacity]
163
- end
164
-
165
- # return a white color vector
166
- def Color.white(opacity=1.0)
167
- return Color[1.0, 1.0, 1.0, opacity]
168
- end
169
-
170
- # return a grey color vector
171
- def Color.grey(light,opacity=1.0)
172
- return Color[light, light, light, opacity]
173
- end
174
-
175
-
176
- # build a color vector from hsv parametrization (convert from hsv to rgb) h, s, v being between 0.0 and 1.0
177
- #
178
- # taken from wikipedia[http://en.wikipedia.org/wiki/HSV_color_space]
179
- #
180
- # error on algo with h = 1.0 => hi == 6 mustbe taken into account
181
- def Color.hsv( h, s, v, a)
182
- h = Range.O.trim( h )
183
- s = Range.O.trim( s )
184
- v = Range.O.trim( v )
185
- a = Range.O.trim( a )
186
- if s == 0.0
187
- return Color[v, v, v, a]
188
- end
189
- h *= 360.0
190
- hi = (h/60.0).floor
191
- if hi == 6
192
- hi = 5
193
- end
194
- f = (h/60.0) - hi
195
- p = v * ( 1 - s )
196
- q = v * ( 1 - f * s )
197
- t = v * ( 1 - ( 1 - f ) * s )
198
- if hi == 0
199
- return Color[ v, t, p, a]
200
- end
201
- if hi == 1
202
- return Color[ q, v, p, a]
203
- end
204
- if hi == 2
205
- return Color[ p, v, t, a]
206
- end
207
- if hi == 3
208
- return Color[ p, q, v, a]
209
- end
210
- if hi == 4
211
- return Color[ t, p, v, a]
212
- end
213
- if hi == 5
214
- return Color[ v, p, q, a]
215
- end
216
- end
217
-
218
- def Color.getHSLcomponent( tC, p, q ) #:nodoc:
219
- while tC < 0.0
220
- tC = tC + 1.0
221
- end
222
- while tC > 1.0
223
- tC = tC - 1.0
224
- end
225
-
226
- if tC < (1.0 / 6.0)
227
- tC = p + ( (q-p) * 6.0 * tC )
228
- elsif tC >=(1.0 / 6.0) and tC < 0.5
229
- tC = q
230
- elsif tC >= 0.5 and tC < (2.0 / 3.0)
231
- tC = p + ( (q-p) * 6.0 * ((2.0 / 3.0) - tC) )
232
- else
233
- tC = p
234
- end
235
- return tC
236
- end
237
-
238
- # build a color vector from hsl parametrization (convert from hsl to rgb) h, s, l being between 0.0 and 1.0
239
- # taken from [[http://en.wikipedia.org/wiki/HSV_color_space]]
240
- # h, s, l must be between 0.0 and 1.0
241
- def Color.hsl( h, s, l, a)
242
- h = Range.O.trim( h )
243
- s = Range.O.trim( s )
244
- l = Range.O.trim( l )
245
- a = Range.O.trim( a )
246
- h *= 360.0
247
- if l < 0.5
248
- q = l * (1.0 + s)
249
- else
250
- q = l+ s - (l * s)
251
- end
252
- p = 2 * l - q
253
- hk = h / 360.0
254
- tR = hk + 1.0 / 3.0
255
- tG = hk
256
- tB = hk - 1.0 / 3.0
257
-
258
- tR = self.getHSLcomponent( tR, p, q )
259
- tG = self.getHSLcomponent( tG, p, q )
260
- tB = self.getHSLcomponent( tB, p, q )
261
- return Color[tR, tG, tB, a]
262
- end
263
-
264
- # get svg description of a color
265
- def svg
266
- values = self[0..2].map do |v|
267
- v = Range.O.trim( v )
268
- (255.0 * v).to_i
269
- end
270
- return "rgb(#{values.join(",")})"
271
- end
272
-
273
- end
274
-
275
- # class Palette
276
- # = Intro
277
- # Palette defines color palettes, as interpolation between color points. As such, use Interpolation module, so uses for the moment only linear interpolation.
278
- # But once built with interpolation, palette provides a continuous color "interval", and so is Samplable !
279
- # = Use
280
- # palette = Palette[ :colorlist, [ 0.0, Color.blue, 0.5, Color.orange, 1.0, Color.yellow ] ]
281
- # palette.rand( 10 ) # => return 10 random colors in palette
282
- # palette.color( 0.5 ) # => Color.orange
283
- class Palette
284
- include Attributable
285
- attribute :colorlist, nil, Array
286
- attribute :interpoltype, :linear
287
-
288
- include Samplable
289
- include Interpolation
290
-
291
- def initialize( *args )
292
- super( *args )
293
- build_interpolators
294
- end
295
-
296
- # build an interpolator by color componant
297
- def build_interpolators()
298
- vlists = [[],[],[],[]]
299
- self.colorlist.foreach do |index, color|
300
- vlists[0] += [index, color.r ]
301
- vlists[1] += [index, color.g ]
302
- vlists[2] += [index, color.b ]
303
- vlists[3] += [index, color.a ]
304
- end
305
- @interpolators = vlists.map {|samplelist| Interpolator[ :samplelist, samplelist, :interpoltype, self.interpoltype]}
306
- end
307
-
308
- # interpolators accessor (for debugging)
309
- def interpolators()
310
- return @interpolators
311
- end
312
-
313
- # overloading to reset interpolators if interpoltype changes
314
- def interpoltype=(value)
315
- @interpoltype = value
316
- if @interpolators
317
- self.build_interpolators
318
- end
319
- end
320
-
321
- # method overloading to delegate computation to componant interpolators
322
- def interpolate( dindex )
323
- vs = self.interpolators.map {|inter| inter.interpolate( dindex )}
324
- return Color[ *vs ]
325
- end
326
-
327
- # compute color given float pourcentage.
328
- # Palette[ :colorlist, [ 0.0, Color.black, 1.0, Color.white ] ].sample( 0.5 ) => Color[0.5,0.5,0.5,1.O]
329
- # "sample" method as defined in Samplable module
330
- def color(dindex)
331
- result = self.interpolate(dindex)
332
- return result
333
- end
334
-
335
- # return a new palette by reversing the current one
336
- def reverse()
337
- newcolorlist = []
338
- self.colorlist.reverse.foreach do |color, index|
339
- newcolorlist += [(0.0..1.0).complement( index ), color]
340
- end
341
- return Palette[ :colorlist, newcolorlist ]
342
- end
343
-
344
-
345
-
346
- def apply_sample( abs ) #:nodoc:
347
- # Trace("Palette#apply_sample abs #{abs}")
348
- return self.color( abs )
349
- end
350
-
351
-
352
-
353
- # alias apply_sample color
354
- # alias apply_split ? => TODO
355
- alias samplelist colorlist
356
- alias colors samples
357
- end
358
-
359
- class Gradient < Palette #:nodoc:
360
- def defsvg()
361
- raise NotImplementedError.new("#{self.class.name}#defsvg is an abstract method.")
362
- end
363
- end
364
-
365
-
366
- class LinearGradient < Gradient #:nodoc:
367
-
368
- def svgdef
369
- template = '<linearGradient id="%ID%" x1="0%" y1="0%" x2="0%" y2="100%">%stops%</linearGradient>'
370
- stoptemplate = '<stop offset="%offset%" stop-color="%color%" stop-opacity="%opacity%"/>'
371
-
372
- stops = "\n"
373
- self.colorlist.foreach do |index, color|
374
- stops += stoptemplate.subreplace( {"%offset%" => index, "%color%" => color.svg, "%opacity%" => color.a} )
375
- stops += "\n"
376
- end
377
-
378
- return template.subreplace( {"%stops%" => stops} )
379
- end
380
- end
381
-
382
- class CircularGradient < Gradient #:nodoc:
383
- attribute :circle, nil, Circle
384
-
385
- def svgdef
386
- template = '<radialGradient id="%ID%" gradientUnits="userSpaceOnUse" cx="%cx%" cy="%cy%" r="%r%">%stops%</radialGradient>'
387
- stoptemplate = '<stop offset="%offset%" stop-color="%color%" stop-opacity="%opacity%"/>'
388
-
389
- stops = "\n"
390
- self.colorlist.foreach do |index, color|
391
- stops += stoptemplate.subreplace( {"%offset%" => index, "%color%" => color.svg, "%opacity%" => color.a} )
392
- stops += "\n"
393
- end
394
-
395
- return template.subreplace( {"%stops%" => stops,
396
- "%cx%" => circle.center.x,
397
- "%cy%" => circle.center.y,
398
- "%r%" => circle.radius} )
399
- end
400
- end
401
- end