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