0
|
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 #
|