xrvg 0.0.1
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/LICENCE +21 -0
- data/README +88 -0
- data/Rakefile +263 -0
- data/examples/foreach.rb +9 -0
- data/examples/hellocrown.rb +7 -0
- data/examples/hellocrown2.rb +7 -0
- data/examples/hellocrownrecurse.rb +9 -0
- data/examples/helloworld.rb +5 -0
- data/examples/helloworldcompact.rb +5 -0
- data/examples/helloworldexpanded.rb +5 -0
- data/examples/palette_circle.rb +10 -0
- data/examples/sample.rb +10 -0
- data/examples/uplets.rb +9 -0
- data/lib/assertion.rb +14 -0
- data/lib/attributable.rb +152 -0
- data/lib/color.rb +295 -0
- data/lib/frame.rb +32 -0
- data/lib/geometry2D.rb +207 -0
- data/lib/interpolation.rb +59 -0
- data/lib/render.rb +269 -0
- data/lib/samplation.rb +416 -0
- data/lib/shape.rb +338 -0
- data/lib/style.rb +74 -0
- data/lib/trace.rb +28 -0
- data/lib/utils.rb +404 -0
- data/lib/xrvg.rb +37 -0
- data/test/test_attributable.rb +24 -0
- data/test/test_color.rb +79 -0
- data/test/test_frame.rb +12 -0
- data/test/test_geometry2D.rb +76 -0
- data/test/test_render.rb +18 -0
- data/test/test_style.rb +16 -0
- data/test/test_utils.rb +207 -0
- metadata +80 -0
data/lib/render.rb
ADDED
@@ -0,0 +1,269 @@
|
|
1
|
+
#
|
2
|
+
# Render file. See
|
3
|
+
# - +Render+ for render "abstraction"
|
4
|
+
# - +SVGRender+ for effective SVG render
|
5
|
+
|
6
|
+
require 'color'
|
7
|
+
require 'attributable'
|
8
|
+
require 'style'
|
9
|
+
|
10
|
+
# Render abstract class
|
11
|
+
#
|
12
|
+
# Is pretty useless for the moment
|
13
|
+
class Render
|
14
|
+
include Attributable
|
15
|
+
attr_accessor :width
|
16
|
+
attr_accessor :height
|
17
|
+
end
|
18
|
+
|
19
|
+
# SVG Render class
|
20
|
+
#
|
21
|
+
# In charge of generating a svg output file from different object passed to it
|
22
|
+
# = Use
|
23
|
+
# Canonical use of the class
|
24
|
+
# render = SVGRender[ :filename, "essai.svg" ]
|
25
|
+
# render.add( Circle[] )
|
26
|
+
# render.end
|
27
|
+
# = Improvements
|
28
|
+
# Allows also the "with" syntax
|
29
|
+
# = Attributes
|
30
|
+
# attribute :filename, "", String
|
31
|
+
# attribute :imagesize, "2cm", String
|
32
|
+
# attribute :background, Color.white, [Color, String]
|
33
|
+
class SVGRender < Render
|
34
|
+
attribute :filename, "", String
|
35
|
+
attribute :imagesize, "2cm", String
|
36
|
+
attribute :background, Color.white, [Color, String]
|
37
|
+
attr_reader :viewbox
|
38
|
+
|
39
|
+
# SVGRender builder
|
40
|
+
#
|
41
|
+
# Allows to pass a block, to avoid using .end
|
42
|
+
# SVGRender.[] do |render|
|
43
|
+
# render.add( Circle[] )
|
44
|
+
# end
|
45
|
+
def SVGRender.[](*args,&block)
|
46
|
+
result = self.new( *args )
|
47
|
+
if block
|
48
|
+
yield result
|
49
|
+
result.end
|
50
|
+
end
|
51
|
+
return result
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def initialize ( *args, &block ) #:nodoc:
|
56
|
+
super( *args )
|
57
|
+
@layers = {}
|
58
|
+
@defs = ""
|
59
|
+
@ngradients = 0
|
60
|
+
if @filename.length == 0
|
61
|
+
@filename = $0.split(".")[0..-2].join(".") + ".svg"
|
62
|
+
Trace("filename is #{filename}")
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def layers=( backtofront ) #:nodoc:
|
67
|
+
@sortlayers = backtofront
|
68
|
+
end
|
69
|
+
|
70
|
+
def add_content (string, layer) #:nodoc:
|
71
|
+
if not @layers.key? layer
|
72
|
+
@layers[ layer ] = ""
|
73
|
+
end
|
74
|
+
@layers[ layer ] += string
|
75
|
+
end
|
76
|
+
|
77
|
+
def svg_template #:nodoc:
|
78
|
+
return '<?xml version="1.0" standalone="no"?>
|
79
|
+
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
80
|
+
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
81
|
+
<svg width="%SIZE%" height="%SIZE%" %VIEWBOX% version="1.1"
|
82
|
+
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
83
|
+
%DEFS%
|
84
|
+
%BACKGROUND%
|
85
|
+
%CONTENT%
|
86
|
+
</svg>'
|
87
|
+
end
|
88
|
+
|
89
|
+
def add_def (object) #:nodoc:
|
90
|
+
@defs += object
|
91
|
+
end
|
92
|
+
|
93
|
+
def add_gradient( gradient ) #:nodoc:
|
94
|
+
id = "gradient#{@ngradients}"
|
95
|
+
add_def( gradient.svgdef.subreplace( {"%ID%" => id} ) )
|
96
|
+
@ngradients += 1
|
97
|
+
return id
|
98
|
+
end
|
99
|
+
|
100
|
+
# render fundamental method
|
101
|
+
#
|
102
|
+
# used to render an object, with a particular style, on an optional layer.
|
103
|
+
# render.add( Circle[], Style[ :fill, Color.black ], 1 )
|
104
|
+
# If style is not provided, render asked to object its default_style, if any
|
105
|
+
def add(object, style=nil, layer=0, type=:object)
|
106
|
+
add_content( render( object, style ), layer)
|
107
|
+
refresh_viewbox( object )
|
108
|
+
end
|
109
|
+
|
110
|
+
def adds (objects) #:nodoc:
|
111
|
+
objects.each { |object| add( object )}
|
112
|
+
end
|
113
|
+
|
114
|
+
def render (object, style=nil) #:nodoc:
|
115
|
+
owidth, oheight = object.size
|
116
|
+
|
117
|
+
res = 0.0000001
|
118
|
+
if owidth < res and oheight < res
|
119
|
+
return ""
|
120
|
+
end
|
121
|
+
|
122
|
+
if not style
|
123
|
+
style = object.default_style
|
124
|
+
end
|
125
|
+
|
126
|
+
result = "<g #{style.svgline}>\n"
|
127
|
+
result += object.svg + "\n"
|
128
|
+
result += "</g>\n"
|
129
|
+
|
130
|
+
# puts "result #{result}"
|
131
|
+
|
132
|
+
if style.fill.is_a? Gradient
|
133
|
+
gradientID = add_gradient( style.fill )
|
134
|
+
result = result.subreplace( {"%fillgradient%" => "url(##{gradientID})"} )
|
135
|
+
end
|
136
|
+
|
137
|
+
if style.stroke.is_a? Gradient
|
138
|
+
gradientID = add_gradient( style.stroke )
|
139
|
+
result = result.subreplace( {"%strokegradient%" => "url(##{gradientID})"} )
|
140
|
+
end
|
141
|
+
return result
|
142
|
+
end
|
143
|
+
|
144
|
+
def viewbox #:nodoc:
|
145
|
+
return @viewbox
|
146
|
+
end
|
147
|
+
|
148
|
+
def size #:nodoc:
|
149
|
+
xmin, ymin, xmax, ymax = viewbox
|
150
|
+
return [xmax - xmin, ymax - ymin]
|
151
|
+
end
|
152
|
+
|
153
|
+
def refresh_viewbox (object) #:nodoc:
|
154
|
+
newviewbox = object.viewbox
|
155
|
+
if newviewbox.length > 0
|
156
|
+
if @viewbox == nil
|
157
|
+
@viewbox = newviewbox
|
158
|
+
else
|
159
|
+
newxmin, newymin, newxmax, newymax = newviewbox
|
160
|
+
xmin, ymin, xmax, ymax = viewbox
|
161
|
+
|
162
|
+
if newxmin < xmin
|
163
|
+
xmin = newxmin
|
164
|
+
end
|
165
|
+
if newymin < ymin
|
166
|
+
ymin = newymin
|
167
|
+
end
|
168
|
+
if newxmax > xmax
|
169
|
+
xmax = newxmax
|
170
|
+
end
|
171
|
+
if newymax > ymax
|
172
|
+
ymax = newymax
|
173
|
+
end
|
174
|
+
|
175
|
+
@viewbox = [xmin, ymin, xmax, ymax]
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def get_background_svg #:nodoc:
|
181
|
+
xmin, ymin, width, height = get_carre_viewbox( get_final_viewbox() )
|
182
|
+
template = '<rect x="%x%" y="%y%" width="%width%" height="%height%" fill="%fill%"/>'
|
183
|
+
bg = self.background
|
184
|
+
if bg.is_a? Color
|
185
|
+
bg = bg.svg
|
186
|
+
end
|
187
|
+
return template.subreplace( {"%x%" => xmin,
|
188
|
+
"%y%" => ymin,
|
189
|
+
"%width%" => width,
|
190
|
+
"%height%" => height,
|
191
|
+
"%fill%" => bg} )
|
192
|
+
end
|
193
|
+
|
194
|
+
def get_final_viewbox #:nodoc:
|
195
|
+
marginfactor = 0.2
|
196
|
+
xmin, ymin, xmax, ymax = viewbox()
|
197
|
+
width, height = size()
|
198
|
+
|
199
|
+
xcenter = (xmin + xmax)/2.0
|
200
|
+
ycenter = (ymin + ymax)/2.0
|
201
|
+
|
202
|
+
width *= 1.0 + marginfactor
|
203
|
+
height *= 1.0 + marginfactor
|
204
|
+
|
205
|
+
if width == 0.0
|
206
|
+
width = 1.0
|
207
|
+
end
|
208
|
+
if height == 0.0
|
209
|
+
height = 1.0
|
210
|
+
end
|
211
|
+
|
212
|
+
xmin = xcenter - width / 2.0
|
213
|
+
ymin = ycenter - height / 2.0
|
214
|
+
|
215
|
+
return xmin, ymin, width, height
|
216
|
+
end
|
217
|
+
|
218
|
+
def get_viewbox_svg #:nodoc:
|
219
|
+
return viewbox_svg( get_final_viewbox() )
|
220
|
+
end
|
221
|
+
|
222
|
+
def get_carre_viewbox( viewbox ) #:nodoc:
|
223
|
+
xmin, ymin, width, height = viewbox
|
224
|
+
xcenter = xmin + width / 2.0
|
225
|
+
ycenter = ymin + height / 2.0
|
226
|
+
maxsize = width < height ? height : width
|
227
|
+
return [xcenter - maxsize/2.0, ycenter - maxsize/2.0, maxsize, maxsize]
|
228
|
+
end
|
229
|
+
|
230
|
+
def viewbox_svg( viewbox ) #:nodoc:
|
231
|
+
xmin, ymin, width, height = viewbox
|
232
|
+
return "viewBox=\"#{xmin} #{ymin} #{width} #{height}\""
|
233
|
+
end
|
234
|
+
|
235
|
+
def content #:nodoc:
|
236
|
+
keys = @sortlayers ? @sortlayers : @layers.keys.sort
|
237
|
+
return keys.inject("") {|result,key| result += @layers[key]}
|
238
|
+
end
|
239
|
+
|
240
|
+
def svgdef #:nodoc:
|
241
|
+
return "<defs>\n#{@defs}\n</defs>\n"
|
242
|
+
end
|
243
|
+
|
244
|
+
def end () #:nodoc:
|
245
|
+
svgcontent = content()
|
246
|
+
svgviewbox = get_viewbox_svg()
|
247
|
+
svgbackground = get_background_svg()
|
248
|
+
|
249
|
+
content = svg_template().subreplace( {"%VIEWBOX%" => svgviewbox,
|
250
|
+
"%SIZE%" => @imagesize,
|
251
|
+
"%DEFS%" => svgdef,
|
252
|
+
"%BACKGROUND%" => svgbackground,
|
253
|
+
"%CONTENT%" => svgcontent})
|
254
|
+
|
255
|
+
File.open(filename(), "w") do |f|
|
256
|
+
f << content
|
257
|
+
end
|
258
|
+
|
259
|
+
puts "render #{filename()} OK"; # necessary for Emacs to get output name !!!!
|
260
|
+
end
|
261
|
+
|
262
|
+
def raster () #:nodoc:
|
263
|
+
# bg = background.format255
|
264
|
+
|
265
|
+
# Kernel.system( "ruby", "svg2png.rb", filename(), "2.0" )
|
266
|
+
# Kernel.system( "i_view32", filename().subreplace( ".svg" => ".png" ), "/fs" )
|
267
|
+
end
|
268
|
+
|
269
|
+
end
|
data/lib/samplation.rb
ADDED
@@ -0,0 +1,416 @@
|
|
1
|
+
#
|
2
|
+
# Define float series functional processing
|
3
|
+
# The base module is FloatFunctor, used by Samplable and Splittable
|
4
|
+
#
|
5
|
+
|
6
|
+
#
|
7
|
+
# Base module to define float lists processing and computation.
|
8
|
+
# = Design principle
|
9
|
+
# Composite design pattern, to be able to chain functors
|
10
|
+
# = Concepts
|
11
|
+
# on each functor object :
|
12
|
+
# - "trigger" method triggers float processing, that is
|
13
|
+
# * if arg is integer, call "generate" to get values
|
14
|
+
# * else (array float values), just keep them
|
15
|
+
# - pass inputs to "process" method, which wraps "compute" method to be able to call block and iterate
|
16
|
+
# - "compute" method first call "modify" which is the recursive processing function
|
17
|
+
# - then call "apply" which is specific processing method of the functor
|
18
|
+
# - "apply" then dispatchs processing according to the processing "type", ie "sample" or "split"
|
19
|
+
# - by default, FloatFunctor provides "transforms" and "transform" to be used by "type" processing
|
20
|
+
module FloatFunctor
|
21
|
+
# -------------------------------------------------------------
|
22
|
+
# essential recursivity chaining method
|
23
|
+
# -------------------------------------------------------------
|
24
|
+
|
25
|
+
# building recursivity method
|
26
|
+
#
|
27
|
+
# is private ?
|
28
|
+
def addfilter( newfilter )
|
29
|
+
# Trace("Sampler addfilter method self #{self.inspect}")
|
30
|
+
if not @subfilter
|
31
|
+
@subfilter = newfilter
|
32
|
+
else
|
33
|
+
# Trace("Sampler addfilter recurse on subfilter #{@subfilter.inspect}")
|
34
|
+
@subfilter.addfilter( newfilter )
|
35
|
+
end
|
36
|
+
return self
|
37
|
+
end
|
38
|
+
|
39
|
+
# -------------------------------------------------------------
|
40
|
+
# generic generator method
|
41
|
+
# -------------------------------------------------------------
|
42
|
+
|
43
|
+
# must not be overloaded
|
44
|
+
def trigger( nsamples, type, &block )
|
45
|
+
if nsamples.is_a? Integer
|
46
|
+
indata = self.generate( nsamples )
|
47
|
+
else
|
48
|
+
indata = nsamples
|
49
|
+
end
|
50
|
+
return self.process( indata, type, &block )
|
51
|
+
end
|
52
|
+
|
53
|
+
# hook for Array
|
54
|
+
def compute( indata, type )
|
55
|
+
return self.apply( self.modify( indata ), type )
|
56
|
+
end
|
57
|
+
|
58
|
+
# hook for rand()
|
59
|
+
def process( indata, type, &block )
|
60
|
+
outdata = self.compute( indata, type )
|
61
|
+
if not block
|
62
|
+
# Trace("Samplable#trigger object #{self.inspect} indata #{indata.inspect} outdata #{outdata.inspect}")
|
63
|
+
return outdata
|
64
|
+
else
|
65
|
+
outdata.foreach(nil,&block)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# recursive method to compose modifications.
|
70
|
+
#
|
71
|
+
# must not be overloaded
|
72
|
+
def modify( inputs )
|
73
|
+
# Trace("Samplable#modify object #{self.inspect} inputs #{inputs.inspect}")
|
74
|
+
if @subfilter
|
75
|
+
inputs = @subfilter.modify( inputs )
|
76
|
+
end
|
77
|
+
result = self.transforms( inputs )
|
78
|
+
# Trace("Samplable#filter object #{self.inspect} inputs #{inputs.inspect} result #{result.inspect}")
|
79
|
+
return result
|
80
|
+
end
|
81
|
+
|
82
|
+
# to be overloaded if needed
|
83
|
+
def transforms( inputs )
|
84
|
+
return inputs.map {|abs| self.transform( abs )}
|
85
|
+
end
|
86
|
+
|
87
|
+
# to be overloaded
|
88
|
+
def transform( abs )
|
89
|
+
return abs
|
90
|
+
end
|
91
|
+
|
92
|
+
# default generator method
|
93
|
+
def generate( nsamples )
|
94
|
+
# Trace("Samplable#generate object #{self.inspect} nsamples #{nsamples}")
|
95
|
+
return (0.0..1.0).generate( nsamples )
|
96
|
+
end
|
97
|
+
|
98
|
+
#
|
99
|
+
# apply interface
|
100
|
+
#
|
101
|
+
# it is a registration interface
|
102
|
+
#
|
103
|
+
# must not be overloaded
|
104
|
+
# must be refactored with some meta hooks
|
105
|
+
def applyhash
|
106
|
+
result = {}
|
107
|
+
result[:sample] = :apply_samples
|
108
|
+
result[:split] = :apply_splits
|
109
|
+
return result
|
110
|
+
end
|
111
|
+
|
112
|
+
# must not be overloaded (apart from Samplable containers)
|
113
|
+
def apply( data, type )
|
114
|
+
@applyhash = self.applyhash
|
115
|
+
if not @applyhash.key? type
|
116
|
+
Kernel::Raise("FloatFunctor::apply no regsitration for type #{type} and object #{object.inspect}")
|
117
|
+
else
|
118
|
+
return self.send(@applyhash[type], data )
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# -------------------------------------------------------------
|
123
|
+
# filters management
|
124
|
+
# -------------------------------------------------------------
|
125
|
+
|
126
|
+
# geometric filter
|
127
|
+
def geo( speed )
|
128
|
+
return self.addfilter( Filter.with {|x| 1.0 - Math.exp(-speed * x)} )
|
129
|
+
end
|
130
|
+
|
131
|
+
# random filter
|
132
|
+
def random()
|
133
|
+
return self.addfilter( RandomFilter.new )
|
134
|
+
end
|
135
|
+
|
136
|
+
# sorting filter
|
137
|
+
def ssort()
|
138
|
+
return self.addfilter( SortFilter.new )
|
139
|
+
end
|
140
|
+
|
141
|
+
# shortcut method to build a sampler from self and a block
|
142
|
+
def filter(samplemethod=:sample,&block)
|
143
|
+
return Filter.new( self, samplemethod, &block )
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
# -------------------------------------------------------------
|
148
|
+
# old methods to be refactored
|
149
|
+
# -------------------------------------------------------------
|
150
|
+
|
151
|
+
# deprecated
|
152
|
+
#
|
153
|
+
# ratios sum must be equal to 1.0
|
154
|
+
def multisamples( nsamples, ratios )
|
155
|
+
ratiosum = ratios.sum
|
156
|
+
samplesum = nsamples.sum
|
157
|
+
ratios = ratios.map {|ratio| ratio / ratiosum}
|
158
|
+
|
159
|
+
rratios = ratios
|
160
|
+
index = 0
|
161
|
+
ratios.each do |ratio|
|
162
|
+
rratios[index] = ratio / nsamples[index]
|
163
|
+
index += 1
|
164
|
+
end
|
165
|
+
|
166
|
+
sum = 0.0
|
167
|
+
samples = [0.0]
|
168
|
+
periodindex = 0
|
169
|
+
while sum <= 1.0
|
170
|
+
sum += rratios[ periodindex ]
|
171
|
+
if sum > 1.0
|
172
|
+
break
|
173
|
+
end
|
174
|
+
samples += [sum]
|
175
|
+
periodindex += 1
|
176
|
+
if periodindex >= rratios.length
|
177
|
+
periodindex = 0.0
|
178
|
+
end
|
179
|
+
end
|
180
|
+
return self.samples( samples )
|
181
|
+
end
|
182
|
+
|
183
|
+
# deprecated
|
184
|
+
#
|
185
|
+
# TODO : must add gauss parameters
|
186
|
+
def randgauss()
|
187
|
+
begin
|
188
|
+
x1 = 2.0 * Kernel::rand - 1.0
|
189
|
+
x2 = 2.0 * Kernel::rand - 1.0
|
190
|
+
w = x1 * x1 + x2 * x2
|
191
|
+
end while w >= 1.0
|
192
|
+
w = Math.sqrt( ( -2.0 * Math.log( w ) ) / w )
|
193
|
+
return 1.0/2.0 * ( 1.0 + x1 * w )
|
194
|
+
end
|
195
|
+
|
196
|
+
end
|
197
|
+
|
198
|
+
# -------------------------------------------------------------
|
199
|
+
# Samplable interface
|
200
|
+
# -------------------------------------------------------------
|
201
|
+
|
202
|
+
#
|
203
|
+
# Samplable module, based on FloatFunctor
|
204
|
+
# = Concept
|
205
|
+
# Basically allows advanced item computations from an continuous "Interval" object.
|
206
|
+
# Is used by :
|
207
|
+
# - Range for computing float values
|
208
|
+
# - Curve for computing points
|
209
|
+
# - Palette for computing colors
|
210
|
+
# - ...
|
211
|
+
module Samplable
|
212
|
+
include FloatFunctor
|
213
|
+
|
214
|
+
# -------------------------------------------------------------
|
215
|
+
# sampling generator methods
|
216
|
+
# -------------------------------------------------------------
|
217
|
+
|
218
|
+
# fundamental method of the module
|
219
|
+
#
|
220
|
+
# call the FloatFunctor trigger method, with type :sample
|
221
|
+
#
|
222
|
+
# (0.0..1.0).samples( 3 ) => [0.0, 0.5, 1.0]
|
223
|
+
def samples( nsamples, &block )
|
224
|
+
return self.trigger( nsamples, :sample, &block )
|
225
|
+
end
|
226
|
+
|
227
|
+
# shortcut method to call float processing for one input
|
228
|
+
#
|
229
|
+
# is basically .samples([abs]).pop
|
230
|
+
def sample( abs )
|
231
|
+
return self.apply( self.modify( [abs] ), :sample ).pop
|
232
|
+
end
|
233
|
+
|
234
|
+
# alias for sample( 0.5 )
|
235
|
+
def mean()
|
236
|
+
return self.sample( 0.5 )
|
237
|
+
end
|
238
|
+
|
239
|
+
# alias for mean
|
240
|
+
alias middle mean
|
241
|
+
|
242
|
+
# alias for .random.samples
|
243
|
+
def rand(nsamples=1,&block)
|
244
|
+
inputs = []
|
245
|
+
nsamples.times {|v| inputs.push( Kernel::rand )}
|
246
|
+
result = self.process( inputs, :sample, &block )
|
247
|
+
return nsamples == 1 ? result[0] : result
|
248
|
+
end
|
249
|
+
|
250
|
+
# to be overloaded if needed
|
251
|
+
#
|
252
|
+
# called by FloatFunctor apply method
|
253
|
+
def apply_samples( inputs )
|
254
|
+
return inputs.map {|abs| self.apply_sample( abs ) }
|
255
|
+
end
|
256
|
+
|
257
|
+
# to be overloaded if needed
|
258
|
+
def apply_sample( abs )
|
259
|
+
return abs
|
260
|
+
end
|
261
|
+
|
262
|
+
# apply_register( :sample, :apply_samples )
|
263
|
+
|
264
|
+
end
|
265
|
+
|
266
|
+
# -------------------------------------------------------------
|
267
|
+
# Splittable interface
|
268
|
+
# -------------------------------------------------------------
|
269
|
+
|
270
|
+
#
|
271
|
+
# Splittable module, based on FloatFunctor
|
272
|
+
# = Concept
|
273
|
+
# Basically allows advanced subdivision computations from an continuous "Interval" object.
|
274
|
+
# = Example
|
275
|
+
# (1.0..2.0).splits(2) => [(1.0..1.5), (1.5..2.0)]
|
276
|
+
module Splittable
|
277
|
+
include FloatFunctor
|
278
|
+
|
279
|
+
# generator method for split interface.
|
280
|
+
# Must not be overriden
|
281
|
+
#
|
282
|
+
# Basically call FloatFunctor trigger method with at least 2 data !!
|
283
|
+
def splits( nsamples, &block )
|
284
|
+
if nsamples.is_a? Integer
|
285
|
+
nsamples += 1
|
286
|
+
if nsamples < 2
|
287
|
+
Kernel::Raise("Samplable#split method needs at least two data, instead of #{nsamples}")
|
288
|
+
end
|
289
|
+
else
|
290
|
+
if nsamples.size < 2
|
291
|
+
Kernel::Raise("Samplable#split method needs at least two data, instead of #{nsamples.inspect}")
|
292
|
+
end
|
293
|
+
end
|
294
|
+
return self.trigger( nsamples, :split, &block )
|
295
|
+
end
|
296
|
+
|
297
|
+
# must not be overriden
|
298
|
+
def split( abs1, abs2 )
|
299
|
+
return self.apply( self.filter( [abs1,abs2] ), :split ).pop
|
300
|
+
end
|
301
|
+
|
302
|
+
# to be overloaded if needed
|
303
|
+
def apply_splits( inputs )
|
304
|
+
return inputs.pairs.map {|t1,t2| self.apply_split( t1, t2 )}
|
305
|
+
end
|
306
|
+
|
307
|
+
# to be overloaded if needed
|
308
|
+
def apply_split( t1, t2 )
|
309
|
+
return self.class.new( t1, t2 )
|
310
|
+
end
|
311
|
+
|
312
|
+
# apply_register( :split, :apply_splits )
|
313
|
+
end
|
314
|
+
|
315
|
+
|
316
|
+
# Filter class
|
317
|
+
# = Intro
|
318
|
+
# Filter class allows to call generically a method with arg between 0.0..0.1 on a given object, for use in a +FloatFunctor+ context.
|
319
|
+
# = Example
|
320
|
+
# this allow to do for example
|
321
|
+
# [bezier1.filter(:point), bezier1.filter(:tangent), palette].samples( 10 ) do |point, tangent, color|
|
322
|
+
class Filter
|
323
|
+
|
324
|
+
include Samplable
|
325
|
+
|
326
|
+
# identity functor
|
327
|
+
def Filter.identity
|
328
|
+
return Filter.new(nil,nil) {|x| x}
|
329
|
+
end
|
330
|
+
|
331
|
+
# to define a filter on "object" with method "samplemethod", or with a block (exclusive)
|
332
|
+
#
|
333
|
+
# from these two alternatives, returns a proc to call for float processing
|
334
|
+
def initialize(object=nil, samplemethod=:sample, &block)
|
335
|
+
if object
|
336
|
+
# Trace("Filter::initialize object #{object.inspect} method #{samplemethod.inspect} ")
|
337
|
+
@proc = object.method( samplemethod )
|
338
|
+
else
|
339
|
+
@proc = Proc.new( &block )
|
340
|
+
end
|
341
|
+
end
|
342
|
+
|
343
|
+
# Samplable redefinition : call previous proc built from initialize to process float "abs"
|
344
|
+
def transform( abs )
|
345
|
+
result = @proc.call( abs )
|
346
|
+
# Trace("Filter::transform abs #{abs} result #{result.inspect}")
|
347
|
+
return result
|
348
|
+
end
|
349
|
+
|
350
|
+
# shortcut method aliasing Filter.new( nil, nil, &block)
|
351
|
+
#
|
352
|
+
# must change the name, because "with" seems to refer to LISP "with" design pattern, and this is not the case here
|
353
|
+
def Filter.with( &block )
|
354
|
+
return Filter.new( nil, nil, &block)
|
355
|
+
end
|
356
|
+
|
357
|
+
end
|
358
|
+
|
359
|
+
# RandomFilter class, to resample randomly
|
360
|
+
class RandomFilter < Filter
|
361
|
+
def initialize(*args) #:nodoc:
|
362
|
+
end
|
363
|
+
|
364
|
+
def transform(abs) #:nodoc:
|
365
|
+
return (0.0..1.0).rand
|
366
|
+
end
|
367
|
+
|
368
|
+
def transforms( inputs ) #:nodoc:
|
369
|
+
result = inputs.map { |v| self.transform( v ) }
|
370
|
+
result[0] = 0.0
|
371
|
+
result[-1] = 1.0
|
372
|
+
return result
|
373
|
+
end
|
374
|
+
end
|
375
|
+
|
376
|
+
# SortFilter, to sort inputs (in particular from Random)
|
377
|
+
class SortFilter < Filter
|
378
|
+
def initialize(*args) #:nodoc:
|
379
|
+
end
|
380
|
+
|
381
|
+
def transforms( inputs ) #:nodoc:
|
382
|
+
return inputs.sort
|
383
|
+
end
|
384
|
+
|
385
|
+
end
|
386
|
+
|
387
|
+
# Experimental class to add discrete processing in float processing chains
|
388
|
+
# Roller["white","black"].samples(3) => ["white","black","white"]
|
389
|
+
class Roller
|
390
|
+
include Samplable
|
391
|
+
|
392
|
+
def initialize( *args ) #:nodoc:
|
393
|
+
@index = 0
|
394
|
+
@items = args
|
395
|
+
end
|
396
|
+
|
397
|
+
def Roller.[](*args) #:nodoc:
|
398
|
+
return self.new(*args)
|
399
|
+
end
|
400
|
+
|
401
|
+
def transform( abs ) #:nodoc:
|
402
|
+
result = @items[@index]
|
403
|
+
@index += 1
|
404
|
+
if @index >= @items.size
|
405
|
+
@index = 0
|
406
|
+
end
|
407
|
+
return result
|
408
|
+
end
|
409
|
+
|
410
|
+
# TODO : must be generalized
|
411
|
+
def rand() #:nodoc:
|
412
|
+
index = (0.0..@items.size).rand.to_i
|
413
|
+
return @items[index]
|
414
|
+
end
|
415
|
+
|
416
|
+
end
|