Skip to content

Commit

Permalink
improve antialiasing of polylines
Browse files Browse the repository at this point in the history
Release v0.2.0

Antialiasing for route feature is not optional anymore.

Antialiasing is done by considering the whole polyline and calculating alpha values in a buffer before drawing single pixels.
  • Loading branch information
laufhannes committed Oct 10, 2018
1 parent ed8dcb6 commit f7457d1
Show file tree
Hide file tree
Showing 9 changed files with 343 additions and 213 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ The MIT License (MIT). Please see [License File](LICENSE) for more information.
[ico-downloads]: https://img.shields.io/packagist/dt/runalyze/static-maps.svg?style=flat-square

[link-packagist]: https://packagist.org/packages/runalyze/static-maps
[link-travis]: https://travis-ci.org/runalyze/static-maps
[link-travis]: https://travis-ci.org/Runalyze/static-maps
[link-scrutinizer]: https://scrutinizer-ci.com/g/runalyze/static-maps/code-structure
[link-code-quality]: https://scrutinizer-ci.com/g/runalyze/static-maps
[link-downloads]: https://packagist.org/packages/runalyze/static-maps
Expand Down
Binary file modified docs/examples/example-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
148 changes: 0 additions & 148 deletions src/Drawer/AntialiasDrawer.php

This file was deleted.

30 changes: 30 additions & 0 deletions src/Drawer/PainterAwareInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<?php

declare(strict_types=1);

/*
* This file is part of the StaticMaps.
*
* (c) RUNALYZE <[email protected]>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Runalyze\StaticMaps\Drawer;

interface PainterAwareInterface
{
/**
* Set painter for drawing
*
* Set color specification in rgb-code as well as opacity and line width for later drawing.
*
* @param int $r [0 .. 255]
* @param int $g [0 .. 255]
* @param int $b [0 .. 255]
* @param float $alpha (0.0 .. 100.0]
* @param int $lineWidth [px]
*/
public function setPainter(int $r, int $g, int $b, float $alpha = 100.0, int $lineWidth = 1);
}
40 changes: 40 additions & 0 deletions src/Drawer/PainterAwareTrait.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?php

declare(strict_types=1);

/*
* This file is part of the StaticMaps.
*
* (c) RUNALYZE <[email protected]>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Runalyze\StaticMaps\Drawer;

trait PainterAwareTrait
{
/** @var int[] [r, g, b] */
protected $PainterColor = [0, 0, 0];

/** @var float (0.0 .. 100.0) */
protected $PainterAlpha = 100.0;

/** @var int [px] */
protected $LineWidth = 1;

public function setPainter(int $r, int $g, int $b, float $alpha = 100.0, int $lineWidth = 1)
{
$this->PainterColor = [$r, $g, $b];
$this->PainterAlpha = $alpha;
$this->LineWidth = $lineWidth;
}

protected function allocateColor($resource, float $alpha = null): int
{
$alpha = null !== $alpha ? $alpha : $this->PainterAlpha;

return imagecolorallocatealpha($resource, $this->PainterColor[0], $this->PainterColor[1], $this->PainterColor[2], (int)(127.0 - $alpha * 127.0 / 100.0));
}
}
151 changes: 151 additions & 0 deletions src/Drawer/Polyline/AntialiasPolylineDrawer.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<?php

declare(strict_types=1);

/*
* This file is part of the StaticMaps.
*
* (c) RUNALYZE <[email protected]>
*
* This source file is subject to the MIT license that is bundled
* with this source code in the file LICENSE.
*/

namespace Runalyze\StaticMaps\Drawer\Polyline;

use Runalyze\StaticMaps\Drawer\PainterAwareTrait;

class AntialiasPolylineDrawer implements PolylineDrawerInterface
{
use PainterAwareTrait;

/** @var array [x][y] => alpha */
protected $AlphaPixels = [];

public function addPolyline(array $points)
{
$numPoints = count($points);

if (1 == $numPoints) {
$this->drawAntialiasPixelToBuffer($points[0][0], $points[0][1]);

return;
}

$previousAngle = $this->getAngle($points[0][0], $points[0][1], $points[1][0], $points[1][1]);
$currentAngle = $previousAngle;

for ($i = 0; $i < $numPoints - 1; ++$i) {
$nextAngle = $i < $numPoints - 2 ? $this->getAngle($points[$i + 1][0], $points[$i + 1][1], $points[$i + 2][0], $points[$i + 2][1]) : $currentAngle;

list($Ax, $Ay) = $points[$i];
list($Bx, $By) = $points[$i + 1];

$transitionAngleAtA = ($currentAngle + $previousAngle) / 2.0 - 90.0;
$transitionAngleAtB = ($currentAngle + $nextAngle) / 2.0 - 90.0;
$weightFactorAtA = 1.0 / cos(deg2rad(($previousAngle - $currentAngle) / 2.0));
$weightFactorAtB = 1.0 / cos(deg2rad(($currentAngle - $nextAngle) / 2.0));

$weights = range(0.5 - $this->LineWidth / 2, $this->LineWidth / 2 - 0.5, 1.0);

foreach ($weights as $weight) {
$this->drawAntialiasLineToBuffer(
$Ax + $weight * $weightFactorAtA * cos(deg2rad($transitionAngleAtA)),
$Ay + $weight * $weightFactorAtA * sin(deg2rad($transitionAngleAtA)),
$Bx + $weight * $weightFactorAtB * cos(deg2rad($transitionAngleAtB)),
$By + $weight * $weightFactorAtB * sin(deg2rad($transitionAngleAtB)),
1
);
}

$previousAngle = $currentAngle;
$currentAngle = $nextAngle;
}
}

public function drawPolylines($resource)
{
foreach ($this->AlphaPixels as $x => $yPixels) {
foreach ($yPixels as $y => $alpha) {
imagesetpixel($resource, $x, $y, $this->allocateColor($resource, $alpha));
}
}

$this->AlphaPixels = [];
}

protected function drawAntialiasLineToBuffer($x1, $y1, $x2, $y2, int $lineWidth = 1)
{
if (1 == $lineWidth) {
$this->drawAntialiasLinePixelwiseToBuffer($x1, $y1, $x2, $y2);

return;
}

$angle = deg2rad($this->getAngle($x1, $y1, $x2, $y2) - 90.0);
$weights = range(0.5 - $lineWidth / 2, $lineWidth / 2 - 0.5, 1.0);

foreach ($weights as $weight) {
$this->drawAntialiasLineToBuffer(
$x1 + $weight * cos($angle),
$y1 + $weight * sin($angle),
$x2 + $weight * cos($angle),
$y2 + $weight * sin($angle)
);
}
}

protected function drawAntialiasLinePixelwiseToBuffer($x1, $y1, $x2, $y2)
{
$distance = max(1.0, sqrt(($x2 - $x1) * ($x2 - $x1) + ($y2 - $y1) * ($y2 - $y1)));

$xStep = ($x2 - $x1) / $distance;
$yStep = ($y2 - $y1) / $distance;

for ($i = 0; $i <= $distance; ++$i) {
$this->drawAntialiasPixelToBuffer($i * $xStep + $x1, $i * $yStep + $y1);
}
}

protected function drawAntialiasPixelToBuffer(float $x, float $y)
{
$xi = (int)floor($x);
$yi = (int)floor($y);

if ($xi == $x && $yi == $y) {
$this->drawAlphaPixelToBuffer($xi, $yi, $this->PainterAlpha);
} else {
$deltaX = $x - (float)$xi;
$deltaY = $y - (float)$yi;
$alpha1 = (1.0 - $deltaX) * (1.0 - $deltaY) * $this->PainterAlpha;
$alpha2 = $deltaX * (1.0 - $deltaY) * $this->PainterAlpha;
$alpha3 = (1.0 - $deltaX) * $deltaY * $this->PainterAlpha;
$alpha4 = $deltaX * $deltaY * $this->PainterAlpha;

$this->drawAlphaPixelToBuffer($xi, $yi, $alpha1);
$this->drawAlphaPixelToBuffer($xi + 1, $yi, $alpha2);
$this->drawAlphaPixelToBuffer($xi, $yi + 1, $alpha3);
$this->drawAlphaPixelToBuffer($xi + 1, $yi + 1, $alpha4);
}
}

protected function drawAlphaPixelToBuffer(int $x, int $y, float $alpha)
{
if (isset($this->AlphaPixels[$x][$y])) {
$this->AlphaPixels[$x][$y] = min($this->PainterAlpha, $this->AlphaPixels[$x][$y] + $alpha);
} else {
$this->AlphaPixels[$x][$y] = $alpha;
}
}

protected function getAngle($x1, $y1, $x2, $y2): float
{
$angle = rad2deg(atan2($y2 - $y1, $x2 - $x1));

if ($angle <= 0.0) {
return 360.0 - abs($angle);
}

return $angle;
}
}
Loading

0 comments on commit f7457d1

Please sign in to comment.