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 CHANGED
@@ -1,5 +1,17 @@
1
1
  = CHANGES
2
- == => v 0.0.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
- == => 0.0.1
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
@@ -1,6 +1,6 @@
1
1
  = XRVG -- X Ruby Vector Graphics
2
2
 
3
- Supporting XRVG version: 0.0.2
3
+ Supporting XRVG version: 0.0.3
4
4
 
5
5
  This package contains XRVG, a Ruby vector graphic programming library.
6
6
 
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/.
@@ -2,7 +2,7 @@ require 'xrvg'
2
2
 
3
3
  render = SVGRender[ :filename, "palette_circle.svg", :background, "white" ]
4
4
 
5
- palette = Palette[ :colorlist, [ Color.black, 0.0, Color.blue, 1.0 ] ]
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, [ Color.blue, 0.0, Color.orange, 0.25,
6
- Color.yellow, 0.5, Color.green, 0.75,
7
- Color.blue, 1.0 ] ]
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
- pieceindex1, t1 = parametermapping( t1 )
221
- pieceindex2, t2 = parametermapping( t2 )
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( pieceindex1 ).subpiece( 0, t2 )
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
- def length_parameter_mapping( t ) #:nodoc:
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
- @lengthranges.each_with_index do |lrange,i|
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 #{@lengthranges.inspect}")
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.is_a? V2D
22
- Kernel::raise( "BezierSpline : init value #{v.inspect} is not a V2D" )
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( 100 ) do |abs|
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 += [abs, sum]
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[-1]
221
+ @length = samplelist[-2]
219
222
 
220
223
  length_interpolator = nil
221
224
  if @length == 0.0
222
- newsamplelist = [0.0,0.0,1.0,0.0]
223
- invsamplelist = [0.0,0.0,0.0,1.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 |abs, sum|
228
- newsamplelist += [abs, sum / @length]
229
- invsamplelist += [sum / @length, abs]
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 = Interpolator.new( :samplelist, invsamplelist )
234
- return Interpolator.new( :samplelist, samplelist )
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( l ) #:nodoc:
261
- result = self.length_interpolator.interpolate( l )
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, [ Color.blue, 0.0, Color.orange, 0.5, Color.yellow, 1.0 ] ]
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, 0.0, Color.white, 1.0 ] ].sample( 0.5 ) => Color[0.5,0.5,0.5,1.O]
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,color|
273
- newcolorlist += [color, (0.0..1.0).complement( index )]
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 |color, index|
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 |color, index|
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%" => circle.center.x,
331
- "%cy%" => circle.center.y,
332
- "%r%" => circle.radius} )
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 Vector 2D class.
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 Vector class, to speed up computation, as less generic
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 Vector)
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 V::O
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
- # Vector.viewbox( [Vector[1.0,2.0], Vector[2.0,1.0]] ) => [1.0, 1.0, 2.0, 2.0]
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
- # Vector.viewbox( [Vector[1.0,2.0], Vector[10.0,1.0]] ) => [9.0, 1.0]
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
- pvalue, pindex = self.samplelist[0..1]
37
- self.samplelist.foreach do |value, index|
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[-2]
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.is_a? Color
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
- # [Vector[-1.0,-1.0], Vector[1.0,1.0]].sum => Vector[0.0,0.0]
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
- # [Vector[0.0,0.0], Vector[1.0,1.0]].mean => Vector[0.5,0.5]
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
@@ -3,7 +3,7 @@
3
3
  # Please refer to README for XRVG introduction
4
4
 
5
5
  # XRVG version (used in rakefile)
6
- XRVG_VERSION = "0.0.2"
6
+ XRVG_VERSION = "0.0.3"
7
7
 
8
8
  # Standard Ruby extensions
9
9
  require 'enumerator'
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, 0.0, Color.white, 1.0 ] )
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, 0.0, Color.white, 1.0] )
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, 0.0, Color.white, 1.0] ) ) )
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), 0.0, Color.black(0.0), 1.0], :circle, circle ) )
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, Vector[0.0,0.0], :vector, Vector[1.0,0.0], :rotation, 0.0, :scale, 1.0 ]
9
- assert_equal( 0.0, frame.center[0] )
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
@@ -45,6 +45,11 @@ class V2DTest < Test::Unit::TestCase
45
45
 
46
46
  end
47
47
 
48
+ def test_norm
49
+ assert_equal( V2D[0.0,1.0], V2D[0.0,2.0].norm )
50
+ assert_equal( V2D[0.0,0.0], V2D[0.0,0.0].norm )
51
+ end
52
+
48
53
  def test_ortho
49
54
  assert_equal(V2D[-0.6,0.5],
50
55
  V2D[0.5,0.6].ortho)
@@ -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.2
7
- date: 2008-02-10 00:00:00 +01:00
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