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 #