xrvg 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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