xrvg 0.0.6 → 0.0.7

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/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