package Polyplus; use GD (); use strict; use vars qw($VERSION); @Polyplus::ISA = qw( GD::Polygon ); $VERSION = "0.01"; # addPt, except accepts a list of coordinates sub Polyplus::addPts { my($self, @points) = @_; my($x, $y); while (scalar(@points)) { $x = shift @points; $y = shift @points; $self->addPt( $x, $y); } } # getPt, except returns the whole list of coordinates sub Polyplus::getPts { my($self) = shift; my $size = $self->length; my @points = (); my($i); for ($i=0;$i<$size;$i++) { my($x,$y)=$self->getPt($i); push (@points, $x, $y); } return @points; } # an ugly hack to allow a polygon not to close on itself # Update: Guess what? you can just use $self->openPolygon ! sub Polyplus::open { my $self = shift; my (@oldpoints) = $self->getPts; my $i; foreach $i (0.. (scalar (@oldpoints) / 2) - 1 ) { splice (@oldpoints, 2*$i, 2, reverse @oldpoints[2*$i,2*$i+1]); } $self->addPts(reverse(@oldpoints)); } # rotate the polygon $angle DEGREES about $x_center, $y_center sub Polyplus::rotate { my($self, $angle, $x_center ,$y_center) = @_; my $size = $self->length; # convert to radians $angle *= atan2(1,1) / 45 ; my ($cos_theta, $sin_theta) = ( cos($angle), sin($angle) ); # shift so that $x_center, $y_center is the "origin" $self->offset( -1*$x_center, -1*$y_center ); # transform--this rotates ('theta' args) AND "undoes" # the previous shift ('center' args) $self->transform( $cos_theta, $sin_theta, -1 * $sin_theta, $cos_theta, $x_center, $y_center, ); } # rotate the polygon $angle DEGREES about the center of its # bounding rectangle sub Polyplus::spin { my($self, $angle) = @_; $self->rotate($angle, $self->bounds_center); } # rotate the polygon $angle DEGREES about the center of mass of its # points (nice for regular polygons) sub Polyplus::centroid_spin { my($self, $angle) = @_; $self->rotate($angle, $self->point_center); } #returns the point at the center of the bounding rectangle sub Polyplus::bounds_center { my($self) = @_; my $size = $self->length; my ($left, $top, $right, $bottom) = $self->bounds; return ( ($left + $right) / 2, ($top + $bottom) / 2 ); } # returns the point at the "center of mass" of the POINTS in the # polygon. In some (few!) situations it's the same as the center of mass # of the polygon itself, but not in general (e.g., if you make a # an equilateral triangle with an extra point along one of the sides, # then this "centroid" will be shifted away from the center and # toward that extra point) sub Polyplus::point_center { my($self) = @_; my $size = $self->length; my ($x_center, $y_center); my($i); for ($i=0;$i<$size;$i++) { my($x,$y)=$self->getPt($i); $x_center += $x / $size; $y_center += $y / $size; } return ($x_center, $y_center); } # scale the polygon, but keep its (bounding rectangle's) center in same place sub Polyplus::expand { my($self, $sx, $sy) = @_; # allow one-argument form for automatic symmetric scaling unless (defined $sy) { $sy = $sx }; my($x_center, $y_center) = $self->bounds_center; $self->transform( 1, 0, 0 , 1, -1 * $x_center, -1 * $y_center ); $self->transform( $sx, 0, 0 , $sy, $x_center, $y_center ); } # scale the polygon, but keep its point_center in same place sub Polyplus::centroid_expand { my($self, $sx, $sy) = @_; # allow one-argument form for automatic symmetric scaling unless (defined $sy) { $sy = $sx }; my($x_center, $y_center) = $self->point_center; $self->transform( 1, 0, 0 , 1, -$x_center, -$y_center, ); $self->transform( $sx, 0, 0 , $sy, $x_center, $y_center, ); } 1; =head1 NAME Polyplus.pm - adds methods to B =head1 SYNOPSIS use Polyplus; $im = new GD::Image(200,200); $black = $im->colorAllocate(0,0,0); $red = $im->colorAllocate(255,0,0); $green = $im->colorAllocate(0,255,0); $blue = $im->colorAllocate(0,0,255); $poly = new Polyplus; $poly->addPts( 0,0, 1,0, .5,.866); $poly->offset(100,100); $poly->expand(50); $im->polygon ($poly, $red); $poly->spin(30); $im->polygon ($poly, $green); ($x, $y) = $poly->point_center; $poly->rotate( 180, $poly->getPt(0) ) ; $im->polygon ($poly, $blue); binmode STDOUT; print $im->png; =head1 DESCRIPTION A subclass of B adding some convenient methods. =over 5 =item C C I Like C, but adds an entire list at once. =item C C I Like C, but returns entire list at once. =item C C I An ugly hack. If you call it after adding all the points, appears to prevent the polygon from closing itself when drawn. What it really does is add points to the polygon in such a way that the polygon traces back over itself--making it an "infinitely thin" polygon which is, in fact, closed on itself, but looks like it isn't. B Hey, guess what? If you look at the code for the current C method, you will see that there is, in fact, an C method available! Just needs to be added to the POD, from what I can tell. =item C C I Rotates the polygon by deg degrees about the point xcenter, ycenter. =item C C I Returns the X and Y coordinates of the center of the smallest rectangle bounding the polygon. =item C C I Returns the X and Y coordinates of the "centroid" of the points of the polygon. (The centroid is the point (average of all the X coordinates, average of all Y coordinates) .) =item C C I Rotate the polygon deg degrees about the the center of the polygon's bounding rectangle. =item C C I Rotate the polygon deg degrees about the the centroid of the polygon's points. =item C C I Expands the polygon by a factor of sx in the X direction, and sy in the Y direction, but keeps the center of the bounding rectangle in the same place. Calling with one argument scales symmetrically in the X and Y directions. (Note that calling with fractional arguments shrinks the polygon. To a physicist, the brake I an accelerator.) =item C C I Expands the polygon by a factor of sx in the X direction, and sy in the Y direction, but keeps the centroid of the polygon's points in the same place. Calling with one argument scales symmetrically in the X and Y directions. =back An Unnecessarily Long Example: use Polyplus; $im = new GD::Image(200,200); $black = $im->colorAllocate (0,0,0); $red = $im->colorAllocate (255,0,0); $poly = new Polyplus; $poly->addPts(1,0, 1,1, 0,1, 0,0); $poly->offset(50,50); $poly->expand(100); $im->polygon($poly, $red); foreach (1..10){ $poly->expand( (1/50)**(1/10) ); $poly->spin( 5 ); $im->polygon($poly, $red); } #star of David $poly = new Polyplus; $poly->addPts(0,0, 1,0, .5,(sqrt(3) / 2) ); $poly->offset(150,60); $poly->expand(87); $im->polygon($poly, $red); $poly->centroid_spin(180); $im->polygon($poly, $red); # G $gee = new Polyplus; $gee-> addPts( 4,2, 4,1, 3,0, 1,0, 0,1, 0,6, 1,7, 3,7, 4,6, 4,5, 3,5, 5,5, ); $gee->open; #only for entertainment purposes. Read the POD, Luke! #D $dee = new Polyplus; $dee->addPts( 4,1, 3,0, 0,0, 0,7, 3,7, 4,6, ); $gee->offset(66,150); $gee->expand(15); &zoom($gee); $dee->offset(133,150); $dee->expand(15); &zoom($dee); open (PNG, ">test.png") or die "trying: $!\n"; binmode PNG; print PNG $im->png; sub zoom { my $poly = shift; foreach (1..5){ $poly->expand( (1/50)**(1/10) ); $im->polygon($poly, $red); } }