xrvg 0.0.2 → 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +15 -3
- data/README +1 -1
- data/Rakefile +1 -1
- data/examples/palette_circle.rb +1 -1
- data/examples/sample.rb +3 -3
- data/lib/bezier.rb +83 -8
- data/lib/bezierspline.rb +19 -17
- data/lib/color.rb +9 -9
- data/lib/geometry2D.rb +6 -6
- data/lib/interpolation.rb +100 -3
- data/lib/render.rb +4 -1
- data/lib/shape.rb +14 -1
- data/lib/utils.rb +6 -2
- data/lib/xrvg.rb +1 -1
- data/test/test_bezier.rb +22 -0
- data/test/test_color.rb +4 -4
- data/test/test_frame.rb +2 -2
- data/test/test_geometry2D.rb +5 -0
- data/test/test_interpolation.rb +12 -0
- data/test/test_shape.rb +8 -0
- metadata +3 -2
data/CHANGES
CHANGED
@@ -1,5 +1,17 @@
|
|
1
1
|
= CHANGES
|
2
|
-
|
2
|
+
|
3
|
+
== 0.0.3 (2008.02.17)
|
4
|
+
=== Content
|
5
|
+
- just bug correction, optims and refactoring
|
6
|
+
- quad tree implementation
|
7
|
+
=== Testing
|
8
|
+
- test_interpolator
|
9
|
+
- some minors other additions
|
10
|
+
==== Refactoring
|
11
|
+
- implementing bezier length interpolator as quad tree => +60% perf sur sample( 50000 ) !!!
|
12
|
+
- change order of list in interpolation ! now index before value (as nominal way of describing key,value pairs)
|
13
|
+
|
14
|
+
== 0.0.2 (2008.02.10)
|
3
15
|
=== Content
|
4
16
|
==== New features
|
5
17
|
- bezier curve !!
|
@@ -14,13 +26,13 @@
|
|
14
26
|
- range examples correction, and added to unitary tests
|
15
27
|
- Range .complement correction
|
16
28
|
- Range split correction
|
17
|
-
=== TODO
|
29
|
+
=== TODO for 0.0.3
|
18
30
|
- unitary tests to be completed (generate them from doc ?)
|
19
31
|
- Shape Line and Circle interfaces must be completed to fill Curve abstract interface.
|
20
32
|
- mabe bezier tutorial
|
21
33
|
|
22
34
|
|
23
|
-
==
|
35
|
+
== 0.0.1 (2008.02.02)
|
24
36
|
=== Content
|
25
37
|
- First delivery. Contains utilitary classes (utils, samplable, interpolation, attributable), render, color, style, shape.
|
26
38
|
- But no bezier yet.
|
data/README
CHANGED
data/Rakefile
CHANGED
@@ -72,7 +72,7 @@ EXAMPLE_FILES = FileList["#{EXAMPLE_DIR}/*.rb"]
|
|
72
72
|
|
73
73
|
# Filelist with Test::Unit test cases.
|
74
74
|
TEST_DIR = "test"
|
75
|
-
PRE_TEST_FILES = FileList["test_bezier.rb", "test_attributable.rb", "test_color.rb", "test_frame.rb", "test_geometry2D.rb", "test_render.rb", "test_style.rb", "test_utils.rb", "test_shape.rb"]
|
75
|
+
PRE_TEST_FILES = FileList["test_bezier.rb", "test_attributable.rb", "test_color.rb", "test_frame.rb", "test_geometry2D.rb", "test_render.rb", "test_style.rb", "test_utils.rb", "test_shape.rb", "test_interpolation.rb"]
|
76
76
|
TEST_FILES = FileList["test/*.rb"]
|
77
77
|
|
78
78
|
# Executable scripts, all non-garbage files under bin/.
|
data/examples/palette_circle.rb
CHANGED
@@ -2,7 +2,7 @@ require 'xrvg'
|
|
2
2
|
|
3
3
|
render = SVGRender[ :filename, "palette_circle.svg", :background, "white" ]
|
4
4
|
|
5
|
-
palette = Palette[ :colorlist, [
|
5
|
+
palette = Palette[ :colorlist, [ 0.0, Color.black, 1.0 , Color.blue] ]
|
6
6
|
[Circle[], palette, (0.1..0.02).random()].samples(25) do |point, color, radius|
|
7
7
|
render.add( Circle[ :center, point, :radius, radius ], Style[ :fill, color ])
|
8
8
|
end
|
data/examples/sample.rb
CHANGED
@@ -2,9 +2,9 @@ require 'xrvg'
|
|
2
2
|
|
3
3
|
render = SVGRender[ :filename, "sample.svg" ]
|
4
4
|
|
5
|
-
palette = Palette[ :colorlist, [
|
6
|
-
|
7
|
-
|
5
|
+
palette = Palette[ :colorlist, [ 0.0, Color.blue, 0.25, Color.orange,
|
6
|
+
0.5, Color.yellow, 0.75, Color.green,
|
7
|
+
1.0, Color.blue] ]
|
8
8
|
[Circle[], palette, (0.1..0.02).random()].samples(25) do |point, color, radius|
|
9
9
|
render.add( Circle[ :center, point, :radius, radius ], Style[ :fill, color ])
|
10
10
|
end
|
data/lib/bezier.rb
CHANGED
@@ -49,22 +49,64 @@ class Bezier < Curve
|
|
49
49
|
self.new( *args )
|
50
50
|
end
|
51
51
|
|
52
|
+
# Uni Bezier builder
|
53
|
+
#
|
54
|
+
# type can be :raw or :vector
|
52
55
|
def Bezier.single( type, p1, p2, p3, p4 )
|
53
56
|
return Bezier.new( :pieces, [BezierSpline[type, p1, p2, p3, p4]] )
|
54
57
|
end
|
55
58
|
|
59
|
+
# Uni Bezier builder in :raw format
|
56
60
|
def Bezier.raw( p1, pc1, pc2, p2 )
|
57
61
|
return Bezier.single( :raw, p1, pc1, pc2, p2 )
|
58
62
|
end
|
59
63
|
|
64
|
+
# Uni Bezier builder in :vector format
|
60
65
|
def Bezier.vector( p1, v1, p2, v2 )
|
61
66
|
return Bezier.single( :vector, p1, v1, p2, v2 )
|
62
67
|
end
|
63
68
|
|
69
|
+
# Basic Multi Bezier builder
|
70
|
+
#
|
71
|
+
# raw pieces must be an array of arrays of the form [type, p1, p2, p3, p4] as defined for single
|
64
72
|
def Bezier.multi( rawpieces )
|
65
73
|
return Bezier.new( :pieces, rawpieces.map {|piece| BezierSpline[*piece]} )
|
66
74
|
end
|
67
75
|
|
76
|
+
# "regular" Multi Bezier :raw specification
|
77
|
+
#
|
78
|
+
# args is a list of points and control points as [p1, pc1, p2, pc2, p3, pc3]
|
79
|
+
#
|
80
|
+
# Beware that
|
81
|
+
# Bezier.raw( p1, pc1, pc2, p2 ) == Bezier.raws( p1, pc1, p2, p2 + (p2-pc2))
|
82
|
+
def Bezier.raws( *args )
|
83
|
+
rawpieces = []
|
84
|
+
args.foreach(2).pairs do |pair1, pair2|
|
85
|
+
p1, pc1 = pair1
|
86
|
+
p2, pc2 = pair2
|
87
|
+
rawpieces << [:raw, p1, pc1, p2+(p2-pc2), p2]
|
88
|
+
end
|
89
|
+
return Bezier.multi( rawpieces )
|
90
|
+
end
|
91
|
+
|
92
|
+
# "regular" Multi Bezier :vector specification
|
93
|
+
#
|
94
|
+
# args is a list of points and control points as [p1, v1, p2, v2, p3, v3]
|
95
|
+
#
|
96
|
+
# Beware that
|
97
|
+
# Bezier.vector( p1, v1, p2, v2 ) == Bezier.raws( p1, v1, p2, -v2)
|
98
|
+
def Bezier.vectors( *args )
|
99
|
+
rawpieces = []
|
100
|
+
args.foreach(2).pairs do |pair1, pair2|
|
101
|
+
p1, v1 = pair1
|
102
|
+
p2, v2 = pair2
|
103
|
+
rawpieces << [:vector, p1, v1, p2, -v2]
|
104
|
+
end
|
105
|
+
return Bezier.multi( rawpieces )
|
106
|
+
end
|
107
|
+
|
108
|
+
|
109
|
+
|
68
110
|
|
69
111
|
# bezier point, as
|
70
112
|
# Bezier[:raw, V2D::O, V2D::O, V2D::O, V2D::O]
|
@@ -117,6 +159,7 @@ class Bezier < Curve
|
|
117
159
|
def pointlist( type=:raw )
|
118
160
|
result = []
|
119
161
|
@pieces.each {|piece| result = result + piece.pointlist(type)}
|
162
|
+
# Trace("Bezier.pointlist result #{result.inspect}")
|
120
163
|
return result
|
121
164
|
end
|
122
165
|
|
@@ -135,6 +178,12 @@ class Bezier < Curve
|
|
135
178
|
return self.pieces.map{ |piece| Bezier.single( *piece.data ) }
|
136
179
|
end
|
137
180
|
|
181
|
+
# shortcut method to retrieve a piece as an Bezier object
|
182
|
+
def bezier( index )
|
183
|
+
return Bezier.single( *self.piece( index ).data )
|
184
|
+
end
|
185
|
+
|
186
|
+
|
138
187
|
# -------------------------------------------------------------
|
139
188
|
# piece delegation computation
|
140
189
|
# -------------------------------------------------------------
|
@@ -142,7 +191,7 @@ class Bezier < Curve
|
|
142
191
|
# with index (must be Float) and parametertype as inputs, must compute :
|
143
192
|
# - the index of the piece on which the computation must take place
|
144
193
|
# - the new parameter value corresponding to bezier computation input
|
145
|
-
def parametermapping( index, parametertype=:length ) #:nodoc:
|
194
|
+
def parametermapping( index, parametertype=:length, side=:right ) #:nodoc:
|
146
195
|
check_parametertype( parametertype )
|
147
196
|
result = []
|
148
197
|
if parametertype == :length
|
@@ -151,7 +200,7 @@ class Bezier < Curve
|
|
151
200
|
elsif index > 1.0
|
152
201
|
index = 1.0
|
153
202
|
end
|
154
|
-
result = length_parameter_mapping( index )
|
203
|
+
result = length_parameter_mapping( index, side )
|
155
204
|
else # no need to test parametertype value, as check_parametertype already do it
|
156
205
|
if index < 0.0
|
157
206
|
index = 0.0
|
@@ -217,8 +266,10 @@ class Bezier < Curve
|
|
217
266
|
|
218
267
|
# generalize Bezier method
|
219
268
|
def subpieces (t1, t2) #:nodoc:
|
220
|
-
|
221
|
-
|
269
|
+
# Trace("subpieces t1 #{t1} t2 #{t2}")
|
270
|
+
pieceindex1, t1 = parametermapping( t1, :length, :left )
|
271
|
+
pieceindex2, t2 = parametermapping( t2, :length, :right )
|
272
|
+
# Trace("after translation pieceindex1 #{pieceindex1} t1 #{t1} pieceindex2 #{pieceindex2} t2 #{t2}")
|
222
273
|
result = []
|
223
274
|
|
224
275
|
if pieceindex1 == pieceindex2
|
@@ -228,7 +279,7 @@ class Bezier < Curve
|
|
228
279
|
if pieceindex1 + 1 != pieceindex2
|
229
280
|
result += self.pieces[pieceindex1+1..pieceindex2-1]
|
230
281
|
end
|
231
|
-
result << self.piece(
|
282
|
+
result << self.piece( pieceindex2 ).subpiece( 0.0, t2 )
|
232
283
|
end
|
233
284
|
return result
|
234
285
|
end
|
@@ -386,6 +437,7 @@ class Bezier < Curve
|
|
386
437
|
# must use an interpolator ?
|
387
438
|
def compute_length #:nodoc:
|
388
439
|
lengths = self.pieces.map {|piece| piece.length}
|
440
|
+
Trace("pieces #{self.pieces.inspect} lenghts #{lengths.inspect}")
|
389
441
|
result = lengths.sum
|
390
442
|
if result == 0.0
|
391
443
|
lengths = [1.0]
|
@@ -396,27 +448,50 @@ class Bezier < Curve
|
|
396
448
|
lmin = 0.0
|
397
449
|
@lengthranges = []
|
398
450
|
lengths.each do |llength|
|
451
|
+
Trace("lmin #{lmin} llength #{llength}")
|
399
452
|
@lengthranges << (lmin..llength)
|
400
453
|
lmin = llength
|
401
454
|
end
|
455
|
+
@lengthranges[-1] = @lengthranges[-1].begin..1.0
|
456
|
+
return result
|
457
|
+
end
|
458
|
+
|
459
|
+
# utilitary method for interbezier
|
460
|
+
#
|
461
|
+
# return list of piece lengths relatives to total bezier lengths, and cumulated
|
462
|
+
# Bezier.new( :pieces, [piece1, piece2] ).piecelengths; => [0.0, piece1.length / bezier.length, 1.0]
|
463
|
+
def piecelengths
|
464
|
+
result = self.lengthranges.map {|range| range.begin}
|
465
|
+
result << 1.0
|
402
466
|
return result
|
403
467
|
end
|
404
468
|
|
405
|
-
|
469
|
+
# private method, to compute lengthranges if not existing
|
470
|
+
def lengthranges #:nodoc:
|
406
471
|
if not @lengthranges
|
407
472
|
compute_length
|
408
473
|
end
|
474
|
+
return @lengthranges
|
475
|
+
end
|
476
|
+
|
477
|
+
def length_parameter_mapping( t, side ) #:nodoc:
|
409
478
|
pieceindex = -1
|
410
|
-
|
479
|
+
Trace("self.lengthranges #{self.lengthranges.inspect}")
|
480
|
+
self.lengthranges.each_with_index do |lrange,i|
|
411
481
|
if lrange.include?( t )
|
412
482
|
pieceindex = i
|
413
483
|
t = lrange.abscissa( t )
|
414
484
|
t = self.piece( i ).parameterfromlength( t )
|
485
|
+
Trace("pieceindex #{pieceindex} t #{t}")
|
415
486
|
break
|
416
487
|
end
|
417
488
|
end
|
489
|
+
if ((side == :left) and t.fequal?(1.0) and (pieceindex < self.piecenumber - 1))
|
490
|
+
pieceindex += 1
|
491
|
+
t = 0.0
|
492
|
+
end
|
418
493
|
if pieceindex == -1
|
419
|
-
Kernel::raise("length_parameter_mapping error : t #{t} is not in length range #{
|
494
|
+
Kernel::raise("length_parameter_mapping error : t #{t} is not in length range #{self.lengthranges.inspect}")
|
420
495
|
end
|
421
496
|
return [pieceindex, t]
|
422
497
|
end
|
data/lib/bezierspline.rb
CHANGED
@@ -1,5 +1,8 @@
|
|
1
1
|
# +BezierSpline+ source
|
2
2
|
|
3
|
+
require 'interpolation'
|
4
|
+
|
5
|
+
|
3
6
|
# BezierSpline class
|
4
7
|
#
|
5
8
|
# Internal class to represent a single-piece cubic bezier curve, defined by four points or two point + two vectors.
|
@@ -18,8 +21,8 @@ class BezierSpline #:nodoc:
|
|
18
21
|
|
19
22
|
def checkvalues( v1, v2, v3, v4 )
|
20
23
|
[v1, v2, v3, v4].each do |v|
|
21
|
-
if not v.
|
22
|
-
Kernel::raise( "BezierSpline : init value #{v.inspect}
|
24
|
+
if not (v.respond_to?(:x) || v.respond_to?(:y))
|
25
|
+
Kernel::raise( "BezierSpline : init value #{v.inspect} does not respond to :x or :y" )
|
23
26
|
end
|
24
27
|
end
|
25
28
|
end
|
@@ -204,34 +207,34 @@ class BezierSpline #:nodoc:
|
|
204
207
|
samplelist = [0.0, 0.0]
|
205
208
|
new = V2D[0.0,0.0]
|
206
209
|
previous = nil
|
207
|
-
(0.0..1.0).samples(
|
210
|
+
(0.0..1.0).samples( 129 ) do |abs|
|
208
211
|
self.point( abs, new )
|
209
212
|
if previous
|
210
213
|
sum+= (new - previous).r
|
211
|
-
samplelist += [
|
214
|
+
samplelist += [sum, abs]
|
212
215
|
else
|
213
216
|
previous = V2D[0.0,0.0]
|
214
217
|
end
|
215
218
|
previous.x = new.x
|
216
219
|
previous.y = new.y
|
217
220
|
end
|
218
|
-
@length = samplelist[-
|
221
|
+
@length = samplelist[-2]
|
219
222
|
|
220
223
|
length_interpolator = nil
|
221
224
|
if @length == 0.0
|
222
|
-
newsamplelist = [0.0,0.0,
|
223
|
-
invsamplelist = [0.0,0.0,
|
225
|
+
newsamplelist = [0.0,0.0,0.0,1.0]
|
226
|
+
invsamplelist = [0.0,0.0,1.0,0.0]
|
224
227
|
else
|
225
228
|
newsamplelist = []
|
226
229
|
invsamplelist = []
|
227
|
-
samplelist.foreach do |
|
228
|
-
newsamplelist += [
|
229
|
-
invsamplelist += [sum / @length
|
230
|
+
samplelist.foreach do |sum, abs|
|
231
|
+
newsamplelist += [sum / @length, abs ]
|
232
|
+
invsamplelist += [abs, sum / @length ]
|
230
233
|
end
|
231
234
|
samplelist = newsamplelist
|
232
235
|
end
|
233
|
-
@abs_interpolator =
|
234
|
-
return
|
236
|
+
@abs_interpolator = InterpolatorQuad.new( :samplelist, invsamplelist )
|
237
|
+
return InterpolatorQuad.new( :samplelist, samplelist )
|
235
238
|
end
|
236
239
|
|
237
240
|
def length_interpolator() #:nodoc:
|
@@ -257,10 +260,9 @@ class BezierSpline #:nodoc:
|
|
257
260
|
return @length
|
258
261
|
end
|
259
262
|
|
260
|
-
def parameterfromlength(
|
261
|
-
result = self.length_interpolator.interpolate(
|
263
|
+
def parameterfromlength( lvalue ) #:nodoc:
|
264
|
+
result = self.length_interpolator.interpolate( lvalue )
|
262
265
|
return result
|
263
|
-
end
|
264
|
-
|
265
|
-
|
266
|
+
end
|
266
267
|
end
|
268
|
+
|
data/lib/color.rb
CHANGED
@@ -251,7 +251,7 @@ end
|
|
251
251
|
# Palette defines color palettes, as interpolation between color points. As such, use Interpolation module, so uses for the moment only linear interpolation.
|
252
252
|
# But once built with interpolation, palette provides a continuous color "interval", and so is Samplable !
|
253
253
|
# = Use
|
254
|
-
# palette = Palette[ :colorlist, [
|
254
|
+
# palette = Palette[ :colorlist, [ 0.0, Color.blue, 0.5, Color.orange, 1.0, Color.yellow ] ]
|
255
255
|
# palette.rand( 10 ) # => return 10 random colors in palette
|
256
256
|
# palette.color( 0.5 ) # => Color.orange
|
257
257
|
class Palette
|
@@ -259,7 +259,7 @@ class Palette
|
|
259
259
|
attribute :colorlist
|
260
260
|
|
261
261
|
# compute color given float pourcentage.
|
262
|
-
# Palette[ :colorlist, [ Color.black,
|
262
|
+
# Palette[ :colorlist, [ 0.0, Color.black, 1.0, Color.white ] ].sample( 0.5 ) => Color[0.5,0.5,0.5,1.O]
|
263
263
|
# "sample" method as defined in Samplable module
|
264
264
|
def color(dindex)
|
265
265
|
result = self.interpolate(dindex)
|
@@ -269,8 +269,8 @@ class Palette
|
|
269
269
|
# return a new palette by reversing the current one
|
270
270
|
def reverse()
|
271
271
|
newcolorlist = []
|
272
|
-
self.colorlist.reverse.foreach do |index
|
273
|
-
newcolorlist += [
|
272
|
+
self.colorlist.reverse.foreach do |color, index|
|
273
|
+
newcolorlist += [(0.0..1.0).complement( index ), color]
|
274
274
|
end
|
275
275
|
return Palette[ :colorlist, newcolorlist ]
|
276
276
|
end
|
@@ -304,7 +304,7 @@ class LinearGradient < Gradient #:nodoc:
|
|
304
304
|
stoptemplate = '<stop offset="%offset%" stop-color="%color%" stop-opacity="%opacity%"/>'
|
305
305
|
|
306
306
|
stops = "\n"
|
307
|
-
self.colorlist.foreach do |
|
307
|
+
self.colorlist.foreach do |index, color|
|
308
308
|
stops += stoptemplate.subreplace( {"%offset%" => index, "%color%" => color.svg, "%opacity%" => color.a} )
|
309
309
|
stops += "\n"
|
310
310
|
end
|
@@ -321,14 +321,14 @@ class CircularGradient < Gradient #:nodoc:
|
|
321
321
|
stoptemplate = '<stop offset="%offset%" stop-color="%color%" stop-opacity="%opacity%"/>'
|
322
322
|
|
323
323
|
stops = "\n"
|
324
|
-
self.colorlist.foreach do |
|
324
|
+
self.colorlist.foreach do |index, color|
|
325
325
|
stops += stoptemplate.subreplace( {"%offset%" => index, "%color%" => color.svg, "%opacity%" => color.a} )
|
326
326
|
stops += "\n"
|
327
327
|
end
|
328
328
|
|
329
329
|
return template.subreplace( {"%stops%" => stops,
|
330
|
-
"%cx%"
|
331
|
-
"%cy%"
|
332
|
-
"%r%"
|
330
|
+
"%cx%" => circle.center.x,
|
331
|
+
"%cy%" => circle.center.y,
|
332
|
+
"%r%" => circle.radius} )
|
333
333
|
end
|
334
334
|
end
|
data/lib/geometry2D.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
#
|
2
|
-
# Contains Ruby geometric
|
2
|
+
# Contains Ruby geometric V2D 2D class.
|
3
3
|
# See :
|
4
4
|
# - +V2D+
|
5
5
|
|
@@ -10,7 +10,7 @@ require 'Attributable'
|
|
10
10
|
#
|
11
11
|
# V2D class definition, to provide several useful "geometrical" services
|
12
12
|
#
|
13
|
-
# Extends and somehow redefines
|
13
|
+
# Extends and somehow redefines V2D class, to speed up computation, as less generic
|
14
14
|
#
|
15
15
|
# For example :
|
16
16
|
# V2D[0.0,2.0].norm => V2D[0.0,1.0]
|
@@ -103,7 +103,7 @@ class V2D
|
|
103
103
|
return V2D[ self.x.succ, self.y.succ ]
|
104
104
|
end
|
105
105
|
|
106
|
-
# compute length of 2D vector (r notation to be compatible with
|
106
|
+
# compute length of 2D vector (r notation to be compatible with V2D)
|
107
107
|
def r()
|
108
108
|
return Math.hypot( self.x, self.y )
|
109
109
|
end
|
@@ -118,7 +118,7 @@ class V2D
|
|
118
118
|
if r != 0.0
|
119
119
|
return self / r
|
120
120
|
else
|
121
|
-
return
|
121
|
+
return V2D::O
|
122
122
|
end
|
123
123
|
end
|
124
124
|
|
@@ -210,7 +210,7 @@ class V2D
|
|
210
210
|
end
|
211
211
|
|
212
212
|
# compute the coord box enclosing every 2D elements considered as points
|
213
|
-
#
|
213
|
+
# V2D.viewbox( [V2D[1.0,2.0], V2D[2.0,1.0]] ) => [1.0, 1.0, 2.0, 2.0]
|
214
214
|
def V2D.viewbox (pointlist)
|
215
215
|
if pointlist.size == 0
|
216
216
|
return [0.0, 0.0, 0.0, 0.0]
|
@@ -223,7 +223,7 @@ class V2D
|
|
223
223
|
end
|
224
224
|
|
225
225
|
# compute dimension of the box enclosing 2D elemnts considered as points
|
226
|
-
#
|
226
|
+
# V2D.viewbox( [V2D[1.0,2.0], V2D[10.0,1.0]] ) => [9.0, 1.0]
|
227
227
|
# use +viewvox+
|
228
228
|
def V2D.size (pointlist)
|
229
229
|
xmin, ymin, xmax, ymax = V2D.viewbox( pointlist )
|
data/lib/interpolation.rb
CHANGED
@@ -3,6 +3,7 @@
|
|
3
3
|
# See
|
4
4
|
# - +Interpolation+
|
5
5
|
# - +Interpolator+
|
6
|
+
# - +InterpolatorQuad+
|
6
7
|
|
7
8
|
require 'utils'
|
8
9
|
require 'attributable'
|
@@ -33,8 +34,8 @@ module Interpolation
|
|
33
34
|
# interpolate uses + and * scalar operators on interpolation values
|
34
35
|
def interpolate( dindex )
|
35
36
|
result = nil
|
36
|
-
|
37
|
-
self.samplelist.foreach do |
|
37
|
+
pindex, pvalue = self.samplelist[0..1]
|
38
|
+
self.samplelist.foreach do |index, value|
|
38
39
|
if dindex <= index
|
39
40
|
if dindex == index
|
40
41
|
return value
|
@@ -45,7 +46,7 @@ module Interpolation
|
|
45
46
|
pvalue, pindex = value, index
|
46
47
|
end
|
47
48
|
if not result
|
48
|
-
result = self.samplelist[-
|
49
|
+
result = self.samplelist[-1]
|
49
50
|
end
|
50
51
|
return result
|
51
52
|
end
|
@@ -59,3 +60,99 @@ class Interpolator
|
|
59
60
|
attribute :samplelist
|
60
61
|
include Interpolation
|
61
62
|
end
|
63
|
+
|
64
|
+
class QuadRange
|
65
|
+
|
66
|
+
def initialize( limit, quadleft, quadright, range=nil )
|
67
|
+
@limit = limit
|
68
|
+
@quadleft = quadleft
|
69
|
+
@quadright = quadright
|
70
|
+
@range = range
|
71
|
+
end
|
72
|
+
|
73
|
+
def limit
|
74
|
+
return @limit
|
75
|
+
end
|
76
|
+
|
77
|
+
def range( index )
|
78
|
+
if @limit
|
79
|
+
if index < @limit
|
80
|
+
return @quadleft.range( index )
|
81
|
+
else
|
82
|
+
return @quadright.range( index )
|
83
|
+
end
|
84
|
+
else
|
85
|
+
return @range
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
# QuadTree class
|
91
|
+
# = Intro
|
92
|
+
# Optim class to look for predefine ranges for a value. Is actually a binary tree data structure, but used as unlinear space partitioner.
|
93
|
+
# = Example
|
94
|
+
# quad = QuadTree.new( [0.0,1.0, 0.2,0.0, 0.6,1.0, 0.8,0.0, 1.0,1.0] )
|
95
|
+
# quad.range( 0.5 ); #=> [0.2,0.0,0.6,1.0]
|
96
|
+
class QuadTree
|
97
|
+
|
98
|
+
def initialize( samplelist ) #:nodoc:
|
99
|
+
quads = []
|
100
|
+
ends = []
|
101
|
+
samplelist.foreach(2).pairs do |ppair, pair|
|
102
|
+
pindex, pvalue = ppair
|
103
|
+
index, value = pair
|
104
|
+
quads << QuadRange.new( nil, nil, nil, [pindex, pvalue, index, value] )
|
105
|
+
ends << index
|
106
|
+
end
|
107
|
+
@root = build_quads( quads, ends )
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_quads( quads, ends ) #:nodoc:
|
111
|
+
newquads = []
|
112
|
+
newends = []
|
113
|
+
index = 0
|
114
|
+
quads.foreach do |quad1, quad2|
|
115
|
+
newquads << QuadRange.new( ends[2*index], quad1, quad2, nil)
|
116
|
+
newends << ends[2*index + 1]
|
117
|
+
index += 1
|
118
|
+
end
|
119
|
+
if newquads.size == 1
|
120
|
+
return newquads[0]
|
121
|
+
else
|
122
|
+
return build_quads( newquads, newends )
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
# utilitary method to retrieve range of index
|
127
|
+
# QuadTree.new( [0.0,1.0, 0.2,0.0, 0.6,1.0, 0.8,0.0, 1.0,1.0] ).range( 0.5 ); #=> [0.2,0.0,0.6,1.0]
|
128
|
+
def range( index )
|
129
|
+
return @root.range( index )
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# Quad tree interpolator
|
134
|
+
#
|
135
|
+
# Use QuadTree to retrieve range of values between linear interpolation.
|
136
|
+
#
|
137
|
+
# Used in BezierSpline to compute parameter from length.
|
138
|
+
class InterpolatorQuad < Interpolator
|
139
|
+
|
140
|
+
def compute_quad #:nodoc:
|
141
|
+
@quad = QuadTree.new( self.samplelist )
|
142
|
+
end
|
143
|
+
|
144
|
+
# - first use QuadTree range to retrieve range of dindex,
|
145
|
+
# - then linearly interpolate
|
146
|
+
def interpolate( dindex )
|
147
|
+
if not @quad
|
148
|
+
compute_quad
|
149
|
+
end
|
150
|
+
pindex, pvalue, index, value = @quad.range( dindex )
|
151
|
+
if pindex.fequal?(index)
|
152
|
+
result = value
|
153
|
+
else
|
154
|
+
result = pvalue + ((value + pvalue * (-1.0) ) * ((dindex - pindex) / (index - pindex )))
|
155
|
+
end
|
156
|
+
return result
|
157
|
+
end
|
158
|
+
end
|
data/lib/render.rb
CHANGED
@@ -65,6 +65,9 @@ class SVGRender < Render
|
|
65
65
|
|
66
66
|
def layers=( backtofront ) #:nodoc:
|
67
67
|
@sortlayers = backtofront
|
68
|
+
@sortlayers.each do |key|
|
69
|
+
@layers[ key ] = ""
|
70
|
+
end
|
68
71
|
end
|
69
72
|
|
70
73
|
def add_content (string, layer) #:nodoc:
|
@@ -181,7 +184,7 @@ class SVGRender < Render
|
|
181
184
|
xmin, ymin, width, height = get_carre_viewbox( get_final_viewbox() )
|
182
185
|
template = '<rect x="%x%" y="%y%" width="%width%" height="%height%" fill="%fill%"/>'
|
183
186
|
bg = self.background
|
184
|
-
if bg.
|
187
|
+
if bg.respond_to? :svg
|
185
188
|
bg = bg.svg
|
186
189
|
end
|
187
190
|
return template.subreplace( {"%x%" => xmin,
|
data/lib/shape.rb
CHANGED
@@ -318,11 +318,20 @@ end
|
|
318
318
|
# = Attributes
|
319
319
|
# attribute :center, V2D[0.0,0.0]
|
320
320
|
# attribute :radius, 1.0
|
321
|
+
# attribute :initangle, 0.0
|
321
322
|
# = Example
|
322
323
|
# c = Circle[ :center, V2D::O, :radius, 1.0 ] # equiv Circle[]
|
323
324
|
class Circle < Curve
|
324
325
|
attribute :center, V2D[0.0,0.0]
|
325
326
|
attribute :radius, 1.0
|
327
|
+
attribute :initangle, 0.0
|
328
|
+
|
329
|
+
# Circle builder from points diametraly opposed
|
330
|
+
# Circle.diameter( V2D[-1.0,0.0], V2D[1.0,0.0] ) == Circle[ :center, V2D::O, :radius, 1.0 ]
|
331
|
+
def Circle.diameter( p1, p2 )
|
332
|
+
initangle = ( p1 - p2 ).angle
|
333
|
+
return Circle[ :center, (p1 + p2)/2.0, :radius, (p1 - p2).r/2.0, :initangle, initangle ]
|
334
|
+
end
|
326
335
|
|
327
336
|
# compute length of the circle
|
328
337
|
def length
|
@@ -354,6 +363,10 @@ class Circle < Curve
|
|
354
363
|
def size
|
355
364
|
return [ self.radius, self.radius ]
|
356
365
|
end
|
366
|
+
|
367
|
+
def rotate( angle )
|
368
|
+
@initangle += angle
|
369
|
+
end
|
357
370
|
|
358
371
|
# svg description of the circle
|
359
372
|
def svg
|
@@ -365,7 +378,7 @@ class Circle < Curve
|
|
365
378
|
|
366
379
|
# compute point at abscissa
|
367
380
|
def point (abscissa, container=nil)
|
368
|
-
angle = Range::Angle.sample( abscissa )
|
381
|
+
angle = Range::Angle.sample( abscissa ) + @initangle
|
369
382
|
container ||=V2D[]
|
370
383
|
container.x = self.cx + self.radius * Math.cos( angle )
|
371
384
|
container.y = self.cy + self.radius * Math.sin( angle )
|
data/lib/utils.rb
CHANGED
@@ -168,7 +168,7 @@ class Array
|
|
168
168
|
# return the sum of the elements of the Array
|
169
169
|
# works for array whose content defines the + operator
|
170
170
|
# [1.0, 2.0].sum => 3.0
|
171
|
-
# [
|
171
|
+
# [V2D[-1.0,-1.0], V2D[1.0,1.0]].sum => V2D[0.0,0.0]
|
172
172
|
# [curve1, curve2].sum => concatenation of curves
|
173
173
|
def sum
|
174
174
|
sum = self[0]
|
@@ -177,7 +177,7 @@ class Array
|
|
177
177
|
end
|
178
178
|
|
179
179
|
# returns the mean of the array content
|
180
|
-
# [
|
180
|
+
# [V2D[0.0,0.0], V2D[1.0,1.0]].mean => V2D[0.5,0.5]
|
181
181
|
def mean
|
182
182
|
return self.sum / self.size
|
183
183
|
end
|
@@ -342,6 +342,10 @@ class Float #:nodoc:
|
|
342
342
|
return ( max - ( self.to_f - min ) )
|
343
343
|
end
|
344
344
|
|
345
|
+
def fequal?( other )
|
346
|
+
return ((self - other).abs < 0.0000000001)
|
347
|
+
end
|
348
|
+
|
345
349
|
def randsplit(minsize=0.0)
|
346
350
|
v = self.to_f
|
347
351
|
rand = minsize + Kernel::rand * (1.0 - minsize )
|
data/lib/xrvg.rb
CHANGED
data/test/test_bezier.rb
CHANGED
@@ -30,8 +30,19 @@ class BezierTest < Test::Unit::TestCase
|
|
30
30
|
assert_equal( V2D[0.0, 1.0], b.firstpoint )
|
31
31
|
end
|
32
32
|
|
33
|
+
def test_builder2
|
34
|
+
b = Bezier.raws( V2D[0.0, 1.0], V2D[1.0, 1.0], V2D[1.0, 0.0], V2D[2.0, 0.0] )
|
35
|
+
assert_equal( V2D[0.0, 1.0], b.firstpoint )
|
36
|
+
assert( V2D.vequal?( @@bezier.point( 0.1, nil, :parameter ), b.point( 0.1, nil, :parameter ) ) )
|
37
|
+
|
38
|
+
b = Bezier.vectors( V2D[0.0, 1.0], V2D[1.0, 0.0], V2D[1.0, 0.0], V2D[1.0, 0.0] )
|
39
|
+
assert_equal( V2D[0.0, 1.0], b.firstpoint )
|
40
|
+
assert( V2D.vequal?( @@bezier.point( 0.1, nil, :parameter ), b.point( 0.1, nil, :parameter ) ) )
|
41
|
+
end
|
42
|
+
|
33
43
|
def test_O
|
34
44
|
assert_equal( [V2D::O, V2D::O, V2D::O, V2D::O], Bezier::O.pointlist )
|
45
|
+
assert_equal( V2D::O, Bezier::O.sample(0.5) )
|
35
46
|
end
|
36
47
|
|
37
48
|
def test_piece
|
@@ -144,6 +155,17 @@ class BezierTest < Test::Unit::TestCase
|
|
144
155
|
assert_equal( [(0.0..1.0), (1.0..2.0)], @@multibezier.ranges )
|
145
156
|
end
|
146
157
|
|
158
|
+
def test_split1
|
159
|
+
assert( V2D.vequal?( V2D[0.5, 0.5], @@beziersym.split(0.0,0.5).lastpoint ) )
|
160
|
+
assert( V2D.vequal?( V2D[0.5, 0.5], @@beziersym.split(0.5,1.0).firstpoint ) )
|
161
|
+
end
|
162
|
+
|
163
|
+
def test_split2
|
164
|
+
assert( V2D.vequal?( V2D::O, Bezier::O.split( 0.0,0.5 ).firstpoint ) )
|
165
|
+
assert( V2D.vequal?( V2D::O, Bezier::O.split( 0.5,0.8 ).lastpoint ) )
|
166
|
+
end
|
167
|
+
|
168
|
+
|
147
169
|
end
|
148
170
|
|
149
171
|
|
data/test/test_color.rb
CHANGED
@@ -38,7 +38,7 @@ end
|
|
38
38
|
class PaletteTest < Test::Unit::TestCase
|
39
39
|
|
40
40
|
def test_palette
|
41
|
-
palette = Palette.new( :colorlist, [ Color.black,
|
41
|
+
palette = Palette.new( :colorlist, [ 0.0, Color.black, 1.0, Color.white ] )
|
42
42
|
assert_equal( Color[0.5, 0.5, 0.5, 1.0], palette.color( 0.5 ) )
|
43
43
|
end
|
44
44
|
end
|
@@ -46,7 +46,7 @@ end
|
|
46
46
|
class GradientTest < Test::Unit::TestCase
|
47
47
|
|
48
48
|
def test_gradient
|
49
|
-
gradient = LinearGradient.new( :colorlist, [Color.black,
|
49
|
+
gradient = LinearGradient.new( :colorlist, [0.0, Color.black, 1.0, Color.white] )
|
50
50
|
assert_equal( gradient.svgdef,
|
51
51
|
"<linearGradient id=\"%ID%\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n<stop offset=\"0.0\" stop-color=\"rgb(0,0,0)\" stop-opacity=\"1.0\"/>\n<stop offset=\"1.0\" stop-color=\"rgb(255,255,255)\" stop-opacity=\"1.0\"/>\n</linearGradient>")
|
52
52
|
|
@@ -57,7 +57,7 @@ class GradientTest < Test::Unit::TestCase
|
|
57
57
|
require 'render'
|
58
58
|
require 'shape'
|
59
59
|
render = SVGRender.new( :filename, "gradient1.svg" )
|
60
|
-
render.add( Circle.new, Style.new( :stroke, "none", :fill, LinearGradient.new( :colorlist, [Color.black,
|
60
|
+
render.add( Circle.new, Style.new( :stroke, "none", :fill, LinearGradient.new( :colorlist, [0.0, Color.black, 1.0, Color.white] ) ) )
|
61
61
|
render.end
|
62
62
|
assert( File.exist?( "gradient1.png" ) )
|
63
63
|
end
|
@@ -69,7 +69,7 @@ class GradientTest < Test::Unit::TestCase
|
|
69
69
|
require 'shape'
|
70
70
|
render = SVGRender.new( :filename, "gradient2.svg" )
|
71
71
|
circle = Circle.new
|
72
|
-
style = Style.new( :stroke, "none", :fill, CircularGradient.new( :colorlist, [Color.black(1.0),
|
72
|
+
style = Style.new( :stroke, "none", :fill, CircularGradient.new( :colorlist, [0.0, Color.black(1.0), 1.0, Color.black(0.0)], :circle, circle ) )
|
73
73
|
render.add( circle, style )
|
74
74
|
render.end
|
75
75
|
assert( File.exist?( "gradient2.png" ) )
|
data/test/test_frame.rb
CHANGED
@@ -5,8 +5,8 @@ require 'geometry2D'
|
|
5
5
|
class FrameTest < Test::Unit::TestCase
|
6
6
|
|
7
7
|
def test_frame1
|
8
|
-
frame = Frame[ :center,
|
9
|
-
assert_equal( 0.0, frame.center
|
8
|
+
frame = Frame[ :center, V2D[0.0,0.0], :vector, V2D[1.0,0.0], :rotation, 0.0, :scale, 1.0 ]
|
9
|
+
assert_equal( 0.0, frame.center.x )
|
10
10
|
end
|
11
11
|
|
12
12
|
end
|
data/test/test_geometry2D.rb
CHANGED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require 'interpolation'
|
3
|
+
|
4
|
+
class InterpolatorTest < Test::Unit::TestCase
|
5
|
+
|
6
|
+
def test_quadtree
|
7
|
+
quad = QuadTree.new( [0.0,1.0, 0.2,0.0, 0.6,1.0, 0.8,0.0, 1.0,1.0] )
|
8
|
+
assert_equal( [0.2,0.0,0.6,1.0], quad.range( 0.5 ) )
|
9
|
+
assert_equal( [0.0,1.0, 0.2,0.0], quad.range( 0.0 ) )
|
10
|
+
assert_equal( [0.8,0.0, 1.0,1.0], quad.range( 1.0 ) )
|
11
|
+
end
|
12
|
+
end
|
data/test/test_shape.rb
CHANGED
@@ -64,4 +64,12 @@ class CircleTest < Test::Unit::TestCase
|
|
64
64
|
assert( V2D.vequal?( v1, v2 ) )
|
65
65
|
end
|
66
66
|
end
|
67
|
+
|
68
|
+
def test_diameter
|
69
|
+
cd = Circle.diameter( V2D[0.0,1.0], V2D[0.0,-1.0] )
|
70
|
+
c2 = Circle[ :center, V2D::O, :radius, 1.0 ]
|
71
|
+
assert( V2D.vequal?( cd.center, c2.center ) )
|
72
|
+
assert_equal( cd.radius, c2.radius )
|
73
|
+
assert( V2D.vequal?( V2D[0.0,1.0], cd.point( 0.0 ) ) )
|
74
|
+
end
|
67
75
|
end
|
metadata
CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.0
|
|
3
3
|
specification_version: 1
|
4
4
|
name: xrvg
|
5
5
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 0.0.
|
7
|
-
date: 2008-02-
|
6
|
+
version: 0.0.3
|
7
|
+
date: 2008-02-17 00:00:00 +01:00
|
8
8
|
summary: Ruby vector graphics library
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -72,6 +72,7 @@ test_files:
|
|
72
72
|
- test/test_color.rb
|
73
73
|
- test/test_frame.rb
|
74
74
|
- test/test_geometry2D.rb
|
75
|
+
- test/test_interpolation.rb
|
75
76
|
- test/test_render.rb
|
76
77
|
- test/test_shape.rb
|
77
78
|
- test/test_style.rb
|