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/CHANGES +12 -1
- data/Rakefile +30 -8
- data/lib/samplation.rb +10 -2
- data/lib/trace.rb +1 -0
- data/lib/utils.rb +24 -1
- data/test/test_color.rb +19 -13
- data/test/test_utils.rb +10 -0
- metadata +2 -17
- data/lib/bezier.rb +0 -536
- data/lib/bezierbuilders.rb +0 -210
- data/lib/beziermotifs.rb +0 -121
- data/lib/bezierspline.rb +0 -235
- data/lib/beziertools.rb +0 -245
- data/lib/color.rb +0 -401
- data/lib/fitting.rb +0 -203
- data/lib/frame.rb +0 -33
- data/lib/geovariety.rb +0 -128
- data/lib/interbezier.rb +0 -87
- data/lib/render.rb +0 -266
- data/lib/shape.rb +0 -421
- data/lib/spiral.rb +0 -72
- data/lib/style.rb +0 -76
- data/lib/xrvg.rb +0 -46
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
|