xrvg 0.0.2 → 0.0.3
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 +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
|