http://www.perlmonks.org?node_id=418142

revdiablo has asked for the wisdom of the Perl Monks concerning the following question:

I am playing around with Imager, attempting to make "fat lines." In other words, I want to draw a straight line, but be able to make it more than 1 pixel thick. I did not see any way to set a thickness for the primitive line() method, so I thought about drawing a rectangle instead, rotated and scaled appropriately. But I could not find a way to rotate the box() once I had it drawn.

So I fell back to the hammer of polygon(). My algorithm is thus: determine the angle of the line to be drawn. Rotate that angle by 90 degrees and project a point from each of the line's endpoints, in either direction. Connect the four projected points together as a polygon, and voila, a fat line. I made a simple diagram to demonstrate the idea. [The fat line is a filled polygon, of course; this diagram just shows the outline.]

Now, an algorithm is one thing, but actual code is another. It has been a long time since I've worked with trigonometry in any significant way, so I was fumbling around quite a bit. I've got something that works, but it doesn't strike me as particularly elegant. Here's the subroutine I came up with:

use Math::Trig qw(atan deg2rad rad2deg); sub _thick_line { my ($image, $thickness, $color, $x1, $y1, $x2, $y2) = @_; my $angle = _angle($x1, $y1, $x2, $y2); my $mirror1 = $angle + 90; my $mirror2 = $angle - 90; my $m1c = $thickness*cos(deg2rad $mirror1)/2; my $m1s = $thickness*sin(deg2rad $mirror1)/2; my $m2c = $thickness*cos(deg2rad $mirror2)/2; my $m2s = $thickness*sin(deg2rad $mirror2)/2; my $x1a = $x1 + $m1c; # bottom left my $y1a = $y1 + $m1s; my $x1b = $x1 + $m2c; # top left my $y1b = $y1 + $m2s; my $x2a = $x2 + $m1c; # bottom right my $y2a = $y2 + $m1s; my $x2b = $x2 + $m2c; # top right my $y2b = $y2 + $m2s; $image->polygon( aa => 1, color => $color, points => [ [$x1a, $y1a], [$x2a, $y2a], [$x2b, $y2b], [$x1b, $y1b], ], ); } sub _angle { my ($x1, $y1, $x2, $y2) = @_; my $x = $x2 - $x1; my $y = $y2 - $y1; # short circuit the extreme cases return 0 if $x > 0 and $y == 0; return 180 if $x < 0 and $y == 0; return 90 if $x == 0 and $y > 0; return 270 if $x == 0 and $y < 0; # calculate the rest return rad2deg atan($y/$x); }

Any ideas or suggestions? Any way I can make this code easier to follow, in any way? I'm looking mainly for refactoring advice, but algorithmic suggestions would be welcome, too.

Update: here's the updated version, taking everyone's advice into consideration. I think it's quite a bit nicer. Many thanks.

sub _thick_line { my ($image, $thickness, $color, $x1, $y1, $x2, $y2) = @_; my $angle = atan2 $x2-$x1, $y2-$y1; my $mirror = atan2 $y1-$y2, $x2-$x1; # perpendicular angle my $dx = $thickness*sin($mirror)/2; my $dy = $thickness*cos($mirror)/2; $image->polygon( aa => 1, color => $color, points => [ [$x1 + $dx, $y1 + $dy], # top left [$x2 + $dx, $y2 + $dy], # top right [$x2 - $dx, $y2 - $dy], # bottom right [$x1 - $dx, $y1 - $dy], # bottom left ], ); }