Mercurial > repos > dereeper > sniploid2
comparison GD/Polyline.pm @ 0:e94de0ea3351 draft default tip
Uploaded
author | dereeper |
---|---|
date | Wed, 11 Sep 2013 09:08:15 -0400 |
parents | |
children |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 0:e94de0ea3351 |
---|---|
1 ############################################################################ | |
2 # | |
3 # Polyline.pm | |
4 # | |
5 # Author: Dan Harasty | |
6 # Email: harasty@cpan.org | |
7 # Version: 0.2 | |
8 # Date: 2002/08/06 | |
9 # | |
10 # For usage documentation: see POD at end of file | |
11 # | |
12 # For changes: see "Changes" file included with distribution | |
13 # | |
14 | |
15 use strict; | |
16 | |
17 package GD::Polyline; | |
18 | |
19 ############################################################################ | |
20 # | |
21 # GD::Polyline | |
22 # | |
23 ############################################################################ | |
24 # | |
25 # What's this? A class with nothing but a $VERSION and and @ISA? | |
26 # Below, this module overrides and adds several modules to | |
27 # the parent class, GD::Polygon. Those updated/new methods | |
28 # act on polygons and polylines, and sometimes those behaviours | |
29 # vary slightly based on whether the object is a polygon or polyine. | |
30 # | |
31 | |
32 use vars qw($VERSION @ISA); | |
33 $VERSION = "0.2"; | |
34 @ISA = qw(GD::Polygon); | |
35 | |
36 | |
37 package GD::Polygon; | |
38 | |
39 ############################################################################ | |
40 # | |
41 # new methods on GD::Polygon | |
42 # | |
43 ############################################################################ | |
44 | |
45 use GD; | |
46 use Carp 'croak','carp'; | |
47 | |
48 use vars qw($bezSegs $csr); | |
49 $bezSegs = 20; # number of bezier segs -- number of segments in each portion of the spline produces by toSpline() | |
50 $csr = 1/3; # control seg ratio -- the one possibly user-tunable parameter in the addControlPoints() algorithm | |
51 | |
52 | |
53 sub rotate { | |
54 my ($self, $angle, $cx, $cy) = @_; | |
55 $self->offset(-$cx,-$cy) if $cx or $cy; | |
56 $self->transform(cos($angle),sin($angle),-sin($angle),cos($angle),$cx,$cy); | |
57 } | |
58 | |
59 sub centroid { | |
60 my ($self, $scale) = @_; | |
61 my ($cx,$cy); | |
62 $scale = 1 unless defined $scale; | |
63 | |
64 map {$cx += $_->[0]; $cy += $_->[1]} $self->vertices(); | |
65 | |
66 $cx *= $scale / $self->length(); | |
67 $cy *= $scale / $self->length(); | |
68 | |
69 return ($cx, $cy); | |
70 } | |
71 | |
72 | |
73 sub segLength { | |
74 my $self = shift; | |
75 my @points = $self->vertices(); | |
76 | |
77 my ($p1, $p2, @segLengths); | |
78 | |
79 $p1 = shift @points; | |
80 | |
81 # put the first vertex on the end to "close" a polygon, but not a polyline | |
82 push @points, $p1 unless $self->isa('GD::Polyline'); | |
83 | |
84 while ($p2 = shift @points) { | |
85 push @segLengths, _len($p1, $p2); | |
86 $p1 = $p2; | |
87 } | |
88 | |
89 return @segLengths if wantarray; | |
90 | |
91 my $sum; | |
92 map {$sum += $_} @segLengths; | |
93 return $sum; | |
94 } | |
95 | |
96 sub segAngle { | |
97 my $self = shift; | |
98 my @points = $self->vertices(); | |
99 | |
100 my ($p1, $p2, @segAngles); | |
101 | |
102 $p1 = shift @points; | |
103 | |
104 # put the first vertex on the end to "close" a polygon, but not a polyline | |
105 push @points, $p1 unless $self->isa('GD::Polyline'); | |
106 | |
107 while ($p2 = shift @points) { | |
108 push @segAngles, _angle_reduce2(_angle($p1, $p2)); | |
109 $p1 = $p2; | |
110 } | |
111 | |
112 return @segAngles; | |
113 } | |
114 | |
115 sub vertexAngle { | |
116 my $self = shift; | |
117 my @points = $self->vertices(); | |
118 | |
119 my ($p1, $p2, $p3, @vertexAngle); | |
120 | |
121 $p1 = $points[$#points]; # last vertex | |
122 $p2 = shift @points; # current point -- the first vertex | |
123 | |
124 # put the first vertex on the end to "close" a polygon, but not a polyline | |
125 push @points, $p2 unless $self->isa('GD::Polyline'); | |
126 | |
127 while ($p3 = shift @points) { | |
128 push @vertexAngle, _angle_reduce2(_angle($p1, $p2, $p3)); | |
129 ($p1, $p2) = ($p2, $p3); | |
130 } | |
131 | |
132 $vertexAngle[0] = undef if defined $vertexAngle[0] and $self->isa("GD::Polyline"); | |
133 | |
134 return @vertexAngle if wantarray; | |
135 | |
136 } | |
137 | |
138 | |
139 | |
140 sub toSpline { | |
141 my $self = shift; | |
142 my @points = $self->vertices(); | |
143 | |
144 # put the first vertex on the end to "close" a polygon, but not a polyline | |
145 push @points, [$self->getPt(0)] unless $self->isa('GD::Polyline'); | |
146 | |
147 unless (@points > 1 and @points % 3 == 1) { | |
148 carp "Attempt to call toSpline() with invalid set of control points"; | |
149 return undef; | |
150 } | |
151 | |
152 my ($ap1, $dp1, $dp2, $ap2); # ap = anchor point, dp = director point | |
153 $ap1 = shift @points; | |
154 | |
155 my $bez = new ref($self); | |
156 | |
157 $bez->addPt(@$ap1); | |
158 | |
159 while (@points) { | |
160 ($dp1, $dp2, $ap2) = splice(@points, 0, 3); | |
161 | |
162 for (1..$bezSegs) { | |
163 my ($t0, $t1, $c1, $c2, $c3, $c4, $x, $y); | |
164 | |
165 $t1 = $_/$bezSegs; | |
166 $t0 = (1 - $t1); | |
167 | |
168 # possible optimization: | |
169 # these coefficient could be calculated just once and | |
170 # cached in an array for a given value of $bezSegs | |
171 | |
172 $c1 = $t0 * $t0 * $t0; | |
173 $c2 = 3 * $t0 * $t0 * $t1; | |
174 $c3 = 3 * $t0 * $t1 * $t1; | |
175 $c4 = $t1 * $t1 * $t1; | |
176 | |
177 $x = $c1 * $ap1->[0] + $c2 * $dp1->[0] + $c3 * $dp2->[0] + $c4 * $ap2->[0]; | |
178 $y = $c1 * $ap1->[1] + $c2 * $dp1->[1] + $c3 * $dp2->[1] + $c4 * $ap2->[1]; | |
179 | |
180 $bez->addPt($x, $y); | |
181 } | |
182 | |
183 $ap1 = $ap2; | |
184 } | |
185 | |
186 # remove the last anchor point if this is a polygon -- since it will autoclose without it | |
187 $bez->deletePt($bez->length()-1) unless $self->isa('GD::Polyline'); | |
188 | |
189 return $bez; | |
190 } | |
191 | |
192 sub addControlPoints { | |
193 my $self = shift; | |
194 my @points = $self->vertices(); | |
195 | |
196 unless (@points > 1) { | |
197 carp "Attempt to call addControlPoints() with too few vertices in polyline"; | |
198 return undef; | |
199 } | |
200 | |
201 my $points = scalar(@points); | |
202 my @segAngles = $self->segAngle(); | |
203 my @segLengths = $self->segLength(); | |
204 | |
205 my ($prevLen, $nextLen, $prevAngle, $thisAngle, $nextAngle); | |
206 my ($controlSeg, $pt, $ptX, $ptY, @controlSegs); | |
207 | |
208 # this loop goes about creating polylines -- here called control segments -- | |
209 # that hold the control points for the final set of control points | |
210 | |
211 # each control segment has three points, and these are colinear | |
212 | |
213 # the first and last will ultimately be "director points", and | |
214 # the middle point will ultimately be an "anchor point" | |
215 | |
216 for my $i (0..$#points) { | |
217 | |
218 $controlSeg = new GD::Polyline; | |
219 | |
220 $pt = $points[$i]; | |
221 ($ptX, $ptY) = @$pt; | |
222 | |
223 if ($self->isa('GD::Polyline') and ($i == 0 or $i == $#points)) { | |
224 $controlSeg->addPt($ptX, $ptY); # director point | |
225 $controlSeg->addPt($ptX, $ptY); # anchor point | |
226 $controlSeg->addPt($ptX, $ptY); # director point | |
227 next; | |
228 } | |
229 | |
230 $prevLen = $segLengths[$i-1]; | |
231 $nextLen = $segLengths[$i]; | |
232 $prevAngle = $segAngles[$i-1]; | |
233 $nextAngle = $segAngles[$i]; | |
234 | |
235 # make a control segment with control points (director points) | |
236 # before and after the point from the polyline (anchor point) | |
237 | |
238 $controlSeg->addPt($ptX - $csr * $prevLen, $ptY); # director point | |
239 $controlSeg->addPt($ptX , $ptY); # anchor point | |
240 $controlSeg->addPt($ptX + $csr * $nextLen, $ptY); # director point | |
241 | |
242 # note that: | |
243 # - the line is parallel to the x-axis, as the points have a common $ptY | |
244 # - the points are thus clearly colinear | |
245 # - the director point is a distance away from the anchor point in proportion to the length of the segment it faces | |
246 | |
247 # now, we must come up with a reasonable angle for the control seg | |
248 # first, "unwrap" $nextAngle w.r.t. $prevAngle | |
249 $nextAngle -= 2*pi() until $nextAngle < $prevAngle + pi(); | |
250 $nextAngle += 2*pi() until $nextAngle > $prevAngle - pi(); | |
251 # next, use seg lengths as an inverse weighted average | |
252 # to "tip" the control segment toward the *shorter* segment | |
253 $thisAngle = ($nextAngle * $prevLen + $prevAngle * $nextLen) / ($prevLen + $nextLen); | |
254 | |
255 # rotate the control segment to $thisAngle about it's anchor point | |
256 $controlSeg->rotate($thisAngle, $ptX, $ptY); | |
257 | |
258 } continue { | |
259 # save the control segment for later | |
260 push @controlSegs, $controlSeg; | |
261 | |
262 } | |
263 | |
264 # post process | |
265 | |
266 my $controlPoly = new ref($self); | |
267 | |
268 # collect all the control segments' points in to a single control poly | |
269 | |
270 foreach my $cs (@controlSegs) { | |
271 foreach my $pt ($cs->vertices()) { | |
272 $controlPoly->addPt(@$pt); | |
273 } | |
274 } | |
275 | |
276 # final clean up based on poly type | |
277 | |
278 if ($controlPoly->isa('GD::Polyline')) { | |
279 # remove the first and last control point | |
280 # since they are director points ... | |
281 $controlPoly->deletePt(0); | |
282 $controlPoly->deletePt($controlPoly->length()-1); | |
283 } else { | |
284 # move the first control point to the last control point | |
285 # since it is supposed to end with two director points ... | |
286 $controlPoly->addPt($controlPoly->getPt(0)); | |
287 $controlPoly->deletePt(0); | |
288 } | |
289 | |
290 return $controlPoly; | |
291 } | |
292 | |
293 | |
294 # The following helper functions are for internal | |
295 # use of this module. Input arguments of "points" | |
296 # refer to an array ref of two numbers, [$x, $y] | |
297 # as is used internally in the GD::Polygon | |
298 # | |
299 # _len() | |
300 # Find the length of a segment, passing in two points. | |
301 # Internal function; NOT a class or object method. | |
302 # | |
303 sub _len { | |
304 # my ($p1, $p2) = @_; | |
305 # return sqrt(($p2->[0]-$p1->[0])**2 + ($p2->[1]-$p1->[1])**2); | |
306 my $pt = _subtract(@_); | |
307 return sqrt($pt->[0] ** 2 + $pt->[1] **2); | |
308 } | |
309 | |
310 use Math::Trig; | |
311 | |
312 # _angle() | |
313 # Find the angle of... well, depends on the number of arguments: | |
314 # - one point: the angle from x-axis to the point (origin is the center) | |
315 # - two points: the angle of the vector defined from point1 to point2 | |
316 # - three points: | |
317 # Internal function; NOT a class or object method. | |
318 # | |
319 sub _angle { | |
320 my ($p1, $p2, $p3) = @_; | |
321 my $angle = undef; | |
322 if (@_ == 1) { | |
323 return atan2($p1->[1], $p1->[0]); | |
324 } | |
325 if (@_ == 2) { | |
326 return _angle(_subtract($p1, $p2)); | |
327 } | |
328 if (@_ == 3) { | |
329 return _angle(_subtract($p2, $p3)) - _angle(_subtract($p2, $p1)); | |
330 } | |
331 } | |
332 | |
333 # _subtract() | |
334 # Find the difference of two points; returns a point. | |
335 # Internal function; NOT a class or object method. | |
336 # | |
337 sub _subtract { | |
338 my ($p1, $p2) = @_; | |
339 # print(_print_point($p2), "-", _print_point($p1), "\n"); | |
340 return [$p2->[0]-$p1->[0], $p2->[1]-$p1->[1]]; | |
341 } | |
342 | |
343 # _print_point() | |
344 # Returns a string suitable for displaying the value of a point. | |
345 # Internal function; NOT a class or object method. | |
346 # | |
347 sub _print_point { | |
348 my ($p1) = @_; | |
349 return "[" . join(", ", @$p1) . "]"; | |
350 } | |
351 | |
352 # _angle_reduce1() | |
353 # "unwraps" angle to interval -pi < angle <= +pi | |
354 # Internal function; NOT a class or object method. | |
355 # | |
356 sub _angle_reduce1 { | |
357 my ($angle) = @_; | |
358 $angle += 2 * pi() while $angle <= -pi(); | |
359 $angle -= 2 * pi() while $angle > pi(); | |
360 return $angle; | |
361 } | |
362 | |
363 # _angle_reduce2() | |
364 # "unwraps" angle to interval 0 <= angle < 2 * pi | |
365 # Internal function; NOT a class or object method. | |
366 # | |
367 sub _angle_reduce2 { | |
368 my ($angle) = @_; | |
369 $angle += 2 * pi() while $angle < 0; | |
370 $angle -= 2 * pi() while $angle >= 2 * pi(); | |
371 return $angle; | |
372 } | |
373 | |
374 ############################################################################ | |
375 # | |
376 # new methods on GD::Image | |
377 # | |
378 ############################################################################ | |
379 | |
380 sub GD::Image::polyline { | |
381 my $self = shift; # the GD::Image | |
382 my $p = shift; # the GD::Polyline (or GD::Polygon) | |
383 my $c = shift; # the color | |
384 | |
385 my @points = $p->vertices(); | |
386 my $p1 = shift @points; | |
387 my $p2; | |
388 while ($p2 = shift @points) { | |
389 $self->line(@$p1, @$p2, $c); | |
390 $p1 = $p2; | |
391 } | |
392 } | |
393 | |
394 sub GD::Image::polydraw { | |
395 my $self = shift; # the GD::Image | |
396 my $p = shift; # the GD::Polyline or GD::Polygon | |
397 my $c = shift; # the color | |
398 | |
399 return $self->polyline($p, $c) if $p->isa('GD::Polyline'); | |
400 return $self->polygon($p, $c); | |
401 } | |
402 | |
403 | |
404 1; | |
405 __END__ | |
406 | |
407 =pod | |
408 | |
409 =head1 NAME | |
410 | |
411 GD::Polyline - Polyline object and Polygon utilities (including splines) for use with GD | |
412 | |
413 =head1 SYNOPSIS | |
414 | |
415 use GD; | |
416 use GD::Polyline; | |
417 | |
418 # create an image | |
419 $image = new GD::Image (500,300); | |
420 $white = $image->colorAllocate(255,255,255); | |
421 $black = $image->colorAllocate( 0, 0, 0); | |
422 $red = $image->colorAllocate(255, 0, 0); | |
423 | |
424 # create a new polyline | |
425 $polyline = new GD::Polyline; | |
426 | |
427 # add some points | |
428 $polyline->addPt( 0, 0); | |
429 $polyline->addPt( 0,100); | |
430 $polyline->addPt( 50,125); | |
431 $polyline->addPt(100, 0); | |
432 | |
433 # polylines can use polygon methods (and vice versa) | |
434 $polyline->offset(200,100); | |
435 | |
436 # rotate 60 degrees, about the centroid | |
437 $polyline->rotate(3.14159/3, $polyline->centroid()); | |
438 | |
439 # scale about the centroid | |
440 $polyline->scale(1.5, 2, $polyline->centroid()); | |
441 | |
442 # draw the polyline | |
443 $image->polydraw($polyline,$black); | |
444 | |
445 # create a spline, which is also a polyine | |
446 $spline = $polyline->addControlPoints->toSpline; | |
447 $image->polydraw($spline,$red); | |
448 | |
449 # output the png | |
450 binmode STDOUT; | |
451 print $image->png; | |
452 | |
453 =head1 DESCRIPTION | |
454 | |
455 B<Polyline.pm> extends the GD module by allowing you to create polylines. Think | |
456 of a polyline as "an open polygon", that is, the last vertex is not connected | |
457 to the first vertex (unless you expressly add the same value as both points). | |
458 | |
459 For the remainder of this doc, "polyline" will refer to a GD::Polyline, | |
460 "polygon" will refer to a GD::Polygon that is not a polyline, and | |
461 "polything" and "$poly" may be either. | |
462 | |
463 The big feature added to GD by this module is the means | |
464 to create splines, which are approximations to curves. | |
465 | |
466 =head1 The Polyline Object | |
467 | |
468 GD::Polyline defines the following class: | |
469 | |
470 =over 5 | |
471 | |
472 =item C<GD::Polyline> | |
473 | |
474 A polyline object, used for storing lists of vertices prior to | |
475 rendering a polyline into an image. | |
476 | |
477 =item C<new> | |
478 | |
479 C<GD::Polyline-E<gt>new> I<class method> | |
480 | |
481 Create an empty polyline with no vertices. | |
482 | |
483 $polyline = new GD::Polyline; | |
484 | |
485 $polyline->addPt( 0, 0); | |
486 $polyline->addPt( 0,100); | |
487 $polyline->addPt( 50,100); | |
488 $polyline->addPt(100, 0); | |
489 | |
490 $image->polydraw($polyline,$black); | |
491 | |
492 In fact GD::Polyline is a subclass of GD::Polygon, | |
493 so all polygon methods (such as B<offset> and B<transform>) | |
494 may be used on polylines. | |
495 Some new methods have thus been added to GD::Polygon (such as B<rotate>) | |
496 and a few updated/modified/enhanced (such as B<scale>) I<in this module>. | |
497 See section "New or Updated GD::Polygon Methods" for more info. | |
498 | |
499 =back | |
500 | |
501 Note that this module is very "young" and should be | |
502 considered subject to change in future releases, and/or | |
503 possibly folded in to the existing polygon object and/or GD module. | |
504 | |
505 =head1 Updated Polygon Methods | |
506 | |
507 The following methods (defined in GD.pm) are OVERRIDDEN if you use this module. | |
508 | |
509 All effort has been made to provide 100% backward compatibility, but if you | |
510 can confirm that has not been achieved, please consider that a bug and let the | |
511 the author of Polyline.pm know. | |
512 | |
513 =over 5 | |
514 | |
515 =item C<scale> | |
516 | |
517 C<$poly-E<gt>scale($sx, $sy, $cx, $cy)> I<object method -- UPDATE to GD::Polygon::scale> | |
518 | |
519 Scale a polything in along x-axis by $sx and along the y-axis by $sy, | |
520 about centery point ($cx, $cy). | |
521 | |
522 Center point ($cx, $cy) is optional -- if these are omitted, the function | |
523 will scale about the origin. | |
524 | |
525 To flip a polything, use a scale factor of -1. For example, to | |
526 flip the polything top to bottom about line y = 100, use: | |
527 | |
528 $poly->scale(1, -1, 0, 100); | |
529 | |
530 =back | |
531 | |
532 =head1 New Polygon Methods | |
533 | |
534 The following methods are added to GD::Polygon, and thus can be used | |
535 by polygons and polylines. | |
536 | |
537 Don't forget: a polyline is a GD::Polygon, so GD::Polygon methods | |
538 like offset() can be used, and they can be used in | |
539 GD::Image methods like filledPolygon(). | |
540 | |
541 =over 5 | |
542 | |
543 =item C<rotate> | |
544 | |
545 C<$poly-E<gt>rotate($angle, $cx, $cy)> I<object method> | |
546 | |
547 Rotate a polything through $angle (clockwise, in radians) about center point ($cx, $cy). | |
548 | |
549 Center point ($cx, $cy) is optional -- if these are omitted, the function | |
550 will rotate about the origin | |
551 | |
552 In this function and other angle-oriented functions in GD::Polyline, | |
553 positive $angle corrensponds to clockwise rotation. This is opposite | |
554 of the usual Cartesian sense, but that is because the raster is opposite | |
555 of the usual Cartesian sense in that the y-axis goes "down". | |
556 | |
557 =item C<centroid> | |
558 | |
559 C<($cx, $cy) = $poly-E<gt>centroid($scale)> I<object method> | |
560 | |
561 Calculate and return ($cx, $cy), the centroid of the vertices of the polything. | |
562 For example, to rotate something 180 degrees about it's centroid: | |
563 | |
564 $poly->rotate(3.14159, $poly->centroid()); | |
565 | |
566 $scale is optional; if supplied, $cx and $cy are multiplied by $scale | |
567 before returning. The main use of this is to shift an polything to the | |
568 origin like this: | |
569 | |
570 $poly->offset($poly->centroid(-1)); | |
571 | |
572 =item C<segLength> | |
573 | |
574 C<@segLengths = $poly-E<gt>segLength()> I<object method> | |
575 | |
576 In array context, returns an array the lengths of the segments in the polything. | |
577 Segment n is the segment from vertex n to vertex n+1. | |
578 Polygons have as many segments as vertices; polylines have one fewer. | |
579 | |
580 In a scalar context, returns the sum of the array that would have been returned | |
581 in the array context. | |
582 | |
583 =item C<segAngle> | |
584 | |
585 C<@segAngles = $poly-E<gt>segAngle()> I<object method> | |
586 | |
587 Returns an array the angles of each segment from the x-axis. | |
588 Segment n is the segment from vertex n to vertex n+1. | |
589 Polygons have as many segments as vertices; polylines have one fewer. | |
590 | |
591 Returned angles will be on the interval 0 <= $angle < 2 * pi and | |
592 angles increase in a clockwise direction. | |
593 | |
594 =item C<vertexAngle> | |
595 | |
596 C<@vertexAngles = $poly-E<gt>vertexAngle()> I<object method> | |
597 | |
598 Returns an array of the angles between the segment into and out of each vertex. | |
599 For polylines, the vertex angle at vertex 0 and the last vertex are not defined; | |
600 however $vertexAngle[0] will be undef so that $vertexAngle[1] will correspond to | |
601 vertex 1. | |
602 | |
603 Returned angles will be on the interval 0 <= $angle < 2 * pi and | |
604 angles increase in a clockwise direction. | |
605 | |
606 Note that this calculation does not attempt to figure out the "interior" angle | |
607 with respect to "inside" or "outside" the polygon, but rather, | |
608 just the angle between the adjacent segments | |
609 in a clockwise sense. Thus a polygon with all right angles will have vertex | |
610 angles of either pi/2 or 3*pi/2, depending on the way the polygon was "wound". | |
611 | |
612 =item C<toSpline> | |
613 | |
614 C<$poly-E<gt>toSpline()> I<object method & factory method> | |
615 | |
616 Create a new polything which is a reasonably smooth curve | |
617 using cubic spline algorithms, often referred to as Bezier | |
618 curves. The "source" polything is called the "control polything". | |
619 If it is a polyline, the control polyline must | |
620 have 4, 7, 10, or some number of vertices of equal to 3n+1. | |
621 If it is a polygon, the control polygon must | |
622 have 3, 6, 9, or some number of vertices of equal to 3n. | |
623 | |
624 $spline = $poly->toSpline(); | |
625 $image->polydraw($spline,$red); | |
626 | |
627 In brief, groups of four points from the control polyline | |
628 are considered "control | |
629 points" for a given portion of the spline: the first and | |
630 fourth are "anchor points", and the spline passes through | |
631 them; the second and third are "director points". The | |
632 spline does not pass through director points, however the | |
633 spline is tangent to the line segment from anchor point to | |
634 adjacent director point. | |
635 | |
636 The next portion of the spline reuses the previous portion's | |
637 last anchor point. The spline will have a cusp | |
638 (non-continuous slope) at an anchor point, unless the anchor | |
639 points and its adjacent director point are colinear. | |
640 | |
641 In the current implementation, toSpline() return a fixed | |
642 number of segments in the returned polyline per set-of-four | |
643 control points. In the future, this and other parameters of | |
644 the algorithm may be configurable. | |
645 | |
646 =item C<addControlPoints> | |
647 | |
648 C<$polyline-E<gt>addControlPoints()> I<object method & factory method> | |
649 | |
650 So you say: "OK. Splines sound cool. But how can I | |
651 get my anchor points and its adjacent director point to be | |
652 colinear so that I have a nice smooth curves from my | |
653 polyline?" Relax! For The Lazy: addControlPoints() to the | |
654 rescue. | |
655 | |
656 addControlPoints() returns a polyline that can serve | |
657 as the control polyline for toSpline(), which returns | |
658 another polyline which is the spline. Is your head spinning | |
659 yet? Think of it this way: | |
660 | |
661 =over 5 | |
662 | |
663 =item + | |
664 | |
665 If you have a polyline, and you have already put your | |
666 control points where you want them, call toSpline() directly. | |
667 Remember, only every third vertex will be "on" the spline. | |
668 | |
669 You get something that looks like the spline "inscribed" | |
670 inside the control polyline. | |
671 | |
672 =item + | |
673 | |
674 If you have a polyline, and you want all of its vertices on | |
675 the resulting spline, call addControlPoints() and then | |
676 toSpline(): | |
677 | |
678 $control = $polyline->addControlPoints(); | |
679 $spline = $control->toSpline(); | |
680 $image->polyline($spline,$red); | |
681 | |
682 You get something that looks like the control polyline "inscribed" | |
683 inside the spline. | |
684 | |
685 =back | |
686 | |
687 Adding "good" control points is subjective; this particular | |
688 algorithm reveals its author's tastes. | |
689 In the future, you may be able to alter the taste slightly | |
690 via parameters to the algorithm. For The Hubristic: please | |
691 build a better one! | |
692 | |
693 And for The Impatient: note that addControlPoints() returns a | |
694 polyline, so you can pile up the the call like this, | |
695 if you'd like: | |
696 | |
697 $image->polyline($polyline->addControlPoints()->toSpline(),$mauve); | |
698 | |
699 =back | |
700 | |
701 =head1 New GD::Image Methods | |
702 | |
703 =over 5 | |
704 | |
705 =item C<polyline> | |
706 | |
707 C<$image-E<gt>polyline(polyline,color)> I<object method> | |
708 | |
709 $image->polyline($polyline,$black) | |
710 | |
711 This draws a polyline with the specified color. | |
712 Both real color indexes and the special | |
713 colors gdBrushed, gdStyled and gdStyledBrushed can be specified. | |
714 | |
715 Neither the polyline() method or the polygon() method are very | |
716 picky: you can call either method with either a GD::Polygon or a GD::Polyline. | |
717 The I<method> determines if the shape is "closed" or "open" as drawn, I<not> | |
718 the object type. | |
719 | |
720 =item C<polydraw> | |
721 | |
722 C<$image-E<gt>polydraw(polything,color)> I<object method> | |
723 | |
724 $image->polydraw($poly,$black) | |
725 | |
726 This method draws the polything as expected (polygons are closed, | |
727 polylines are open) by simply checking the object type and calling | |
728 either $image->polygon() or $image->polyline(). | |
729 | |
730 =back | |
731 | |
732 =head1 Examples | |
733 | |
734 Please see file "polyline-examples.pl" that is included with the distribution. | |
735 | |
736 =head1 See Also | |
737 | |
738 For more info on Bezier splines, see http://www.webreference.com/dlab/9902/bezier.html. | |
739 | |
740 =head1 Future Features | |
741 | |
742 On the drawing board are additional features such as: | |
743 | |
744 - polygon winding algorithms (to determine if a point is "inside" or "outside" the polygon) | |
745 | |
746 - new polygon from bounding box | |
747 | |
748 - find bounding polygon (tightest fitting simple convex polygon for a given set of vertices) | |
749 | |
750 - addPts() method to add many points at once | |
751 | |
752 - clone() method for polygon | |
753 | |
754 - functions to interwork GD with SVG | |
755 | |
756 Please provide input on other possible features you'd like to see. | |
757 | |
758 =head1 Author | |
759 | |
760 This module has been written by Daniel J. Harasty. | |
761 Please send questions, comments, complaints, and kudos to him | |
762 at harasty@cpan.org. | |
763 | |
764 Thanks to Lincoln Stein for input and patience with me and this, | |
765 my first CPAN contribution. | |
766 | |
767 =head1 Copyright Information | |
768 | |
769 The Polyline.pm module is copyright 2002, Daniel J. Harasty. It is | |
770 distributed under the same terms as Perl itself. See the "Artistic | |
771 License" in the Perl source code distribution for licensing terms. | |
772 | |
773 The latest version of Polyline.pm is available at | |
774 your favorite CPAN repository and/or | |
775 along with GD.pm by Lincoln D. Stein at http://stein.cshl.org/WWW/software/GD. | |
776 | |
777 =cut | |
778 | |
779 # future: | |
780 # addPts | |
781 # boundingPolygon | |
782 # addControlPoints('method' => 'fitToSegments', 'numSegs' => 10) | |
783 # toSpline('csr' => 1/4); | |
784 | |
785 # GD::Color | |
786 # colorMap('x11' | 'svg' | <filename> ) | |
787 # colorByName($image, 'orange'); | |
788 # setImage($image); | |
789 # cbn('orange'); | |
790 # | |
791 # | |
792 # |