Reputation: 2635
I want to draw a line with thickness in a image with PHP using GD library. I have found some solutions in this page PHP: imageline - Manual but none of them seems to work correctly when the (x, y) position of the line changes.
There are 3 functions I found in the page
function dickelinie($img, $start_x, $start_y, $end_x, $end_y, $color, $thickness)
{
$angle = (atan2(($start_y - $end_y), ($end_x - $start_x)));
$dist_x = $thickness * (sin($angle));
$dist_y = $thickness * (cos($angle));
$p1x = ceil(($start_x + $dist_x));
$p1y = ceil(($start_y + $dist_y));
$p2x = ceil(($end_x + $dist_x));
$p2y = ceil(($end_y + $dist_y));
$p3x = ceil(($end_x - $dist_x));
$p3y = ceil(($end_y - $dist_y));
$p4x = ceil(($start_x - $dist_x));
$p4y = ceil(($start_y - $dist_y));
$array = array(0 => $p1x, $p1y, $p2x, $p2y, $p3x, $p3y, $p4x, $p4y);
imagefilledpolygon ($img, $array, (count($array) / 2), $color);
}
function imagelinethick($image, $x1, $y1, $x2, $y2, $color, $thick = 1)
{
if ($thick == 1)
{
return imageline($image, $x1, $y1, $x2, $y2, $color);
}
$t = $thick / 2 - 0.5;
if ($x1 == $x2 || $y1 == $y2)
{
return imagefilledrectangle($image, round(min($x1, $x2) - $t), round(min($y1, $y2) - $t), round(max($x1, $x2) + $t), round(max($y1, $y2) + $t), $color);
}
$k = ($y2 - $y1) / ($x2 - $x1);
$a = $t / sqrt(1 + pow($k, 2));
$points = array(
round($x1 - (1+$k)*$a), round($y1 + (1-$k)*$a),
round($x1 - (1-$k)*$a), round($y1 - (1+$k)*$a),
round($x2 + (1+$k)*$a), round($y2 - (1-$k)*$a),
round($x2 + (1-$k)*$a), round($y2 + (1+$k)*$a),
);
imagefilledpolygon($image, $points, 4, $color);
return imagepolygon($image, $points, 4, $color);
}
function imagelinethick1($image, $x1, $y1, $x2, $y2, $color, $thick = 1)
{
imagesetthickness($image, $thick);
imageline($image, $x1, $y1, $x2, $y2, $color);
}
And my test case is
header("Content-Type: image/png");
$image = @imagecreatetruecolor(500, 500) or die("Cannot initialize new GD image stream");
$color = imagecolorallocate($image, 255, 255, 255);
# Line thickness equals to 18 pixels
$thickness = 18;
# OK
dickelinie($image, 0, 0, 0, 500, $color, $thickness);
# Wrong: The thickness of the line is doubled
dickelinie($image, 200, 0, 200, 500, $color, $thickness);
# Wrong: The thickness of the line is halved
imagelinethick($image, 0, 0, 0, 500, $color, $thickness);
# OK
imagelinethick($image, 200, 0, 200, 500, $color, $thickness);
# Wrong: The thickness of the line is halved
imagelinethick1($image, 0, 0, 0, 500, $color, $thickness);
# OK
imagelinethick1($image, 200, 0, 200, 500, $color, $thickness);
imagepng($image);
imagedestroy($image);
Can anybody tell me what is wrong please?
Upvotes: 0
Views: 4721
Reputation: 2339
This is not the fastest way to solve your problem, but I use this function in a project of mine:
function thickline( $img, $x1, $y1, $x2, $y2, $color, $thickness ) {
$radius = $thickness * .5;
$vx = $x2 - $x1;
$vy = $y2 - $y1;
$steps = ceil( .5 + max( abs($vx), abs($vy) ) );
$vx /= $steps;
$vy /= $steps;
$x = $x1;
$y = $y1;
while( $steps --> 0 ) {
imagefilledellipse( $img, $x, $y, $radius, $radius, $color );
$x += $vx;
$y += $vy;
}
}
The idea is to plot a circle on each point of a line.
Upvotes: 1
Reputation: 6773
Some of your test cases are wrong. Every test case that renders with x = 0 will be cut in half as the routines will centre the new thicker line onto the given xy coordinates. Thus half of the rendered line is rendered outside of the bitmap and is lost.
Given that your test cases are a little flawed we can see that the "dickelinie" method always doubles the expected thickness.
I still used the "dickelinie" method for rendering a simple analog clock as it produced the best results for my needs. I just passed it half the thickness to ensure it worked. I also added the following code onto the end of the "dickelinie" method to ensure that the resulting line was rendered anti-aliased when told to be:
return imagepolygon($img, $array, (count($array) / 2), $color);
You will want this if you need to render the lines at different angles.
Upvotes: 0
Reputation: 6809
php.net's example #1 for imageline
is "Drawing a thick line". It suggests using either imagesetthickness
or the following code:
<?php
function imagelinethick($image, $x1, $y1, $x2, $y2, $color, $thick = 1)
{
/* this way it works well only for orthogonal lines
imagesetthickness($image, $thick);
return imageline($image, $x1, $y1, $x2, $y2, $color);
*/
if ($thick == 1) {
return imageline($image, $x1, $y1, $x2, $y2, $color);
}
$t = $thick / 2 - 0.5;
if ($x1 == $x2 || $y1 == $y2) {
return imagefilledrectangle($image, round(min($x1, $x2) - $t), round(min($y1, $y2) - $t), round(max($x1, $x2) + $t), round(max($y1, $y2) + $t), $color);
}
$k = ($y2 - $y1) / ($x2 - $x1); //y = kx + q
$a = $t / sqrt(1 + pow($k, 2));
$points = array(
round($x1 - (1+$k)*$a), round($y1 + (1-$k)*$a),
round($x1 - (1-$k)*$a), round($y1 - (1+$k)*$a),
round($x2 + (1+$k)*$a), round($y2 - (1-$k)*$a),
round($x2 + (1-$k)*$a), round($y2 + (1+$k)*$a),
);
imagefilledpolygon($image, $points, 4, $color);
return imagepolygon($image, $points, 4, $color);
}
?>
Upvotes: 0