Teiv
Teiv

Reputation: 2635

Draw a line with thickness in a image?

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

Answers (3)

Tolokoban
Tolokoban

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

Stephen Perelson
Stephen Perelson

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

PurkkaKoodari
PurkkaKoodari

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

Related Questions