Mercurial > repos > dereeper > sniploid
comparison GD/Polyline.pm @ 7:98e4922caaa9 draft default tip
Uploaded
| author | dereeper |
|---|---|
| date | Wed, 26 Jun 2013 09:39:53 -0400 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| 6:cc85d97aeb55 | 7:98e4922caaa9 |
|---|---|
| 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 # |
