Skip to content

Commit f7457d1

Browse files
committed
improve antialiasing of polylines
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.
1 parent ed8dcb6 commit f7457d1

File tree

9 files changed

+343
-213
lines changed

9 files changed

+343
-213
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ The MIT License (MIT). Please see [License File](LICENSE) for more information.
7171
[ico-downloads]: https://img.shields.io/packagist/dt/runalyze/static-maps.svg?style=flat-square
7272

7373
[link-packagist]: https://packagist.org/packages/runalyze/static-maps
74-
[link-travis]: https://travis-ci.org/runalyze/static-maps
74+
[link-travis]: https://travis-ci.org/Runalyze/static-maps
7575
[link-scrutinizer]: https://scrutinizer-ci.com/g/runalyze/static-maps/code-structure
7676
[link-code-quality]: https://scrutinizer-ci.com/g/runalyze/static-maps
7777
[link-downloads]: https://packagist.org/packages/runalyze/static-maps

docs/examples/example-3.png

-1.31 KB
Loading

src/Drawer/AntialiasDrawer.php

Lines changed: 0 additions & 148 deletions
This file was deleted.
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the StaticMaps.
7+
*
8+
* (c) RUNALYZE <[email protected]>
9+
*
10+
* This source file is subject to the MIT license that is bundled
11+
* with this source code in the file LICENSE.
12+
*/
13+
14+
namespace Runalyze\StaticMaps\Drawer;
15+
16+
interface PainterAwareInterface
17+
{
18+
/**
19+
* Set painter for drawing
20+
*
21+
* Set color specification in rgb-code as well as opacity and line width for later drawing.
22+
*
23+
* @param int $r [0 .. 255]
24+
* @param int $g [0 .. 255]
25+
* @param int $b [0 .. 255]
26+
* @param float $alpha (0.0 .. 100.0]
27+
* @param int $lineWidth [px]
28+
*/
29+
public function setPainter(int $r, int $g, int $b, float $alpha = 100.0, int $lineWidth = 1);
30+
}

src/Drawer/PainterAwareTrait.php

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the StaticMaps.
7+
*
8+
* (c) RUNALYZE <[email protected]>
9+
*
10+
* This source file is subject to the MIT license that is bundled
11+
* with this source code in the file LICENSE.
12+
*/
13+
14+
namespace Runalyze\StaticMaps\Drawer;
15+
16+
trait PainterAwareTrait
17+
{
18+
/** @var int[] [r, g, b] */
19+
protected $PainterColor = [0, 0, 0];
20+
21+
/** @var float (0.0 .. 100.0) */
22+
protected $PainterAlpha = 100.0;
23+
24+
/** @var int [px] */
25+
protected $LineWidth = 1;
26+
27+
public function setPainter(int $r, int $g, int $b, float $alpha = 100.0, int $lineWidth = 1)
28+
{
29+
$this->PainterColor = [$r, $g, $b];
30+
$this->PainterAlpha = $alpha;
31+
$this->LineWidth = $lineWidth;
32+
}
33+
34+
protected function allocateColor($resource, float $alpha = null): int
35+
{
36+
$alpha = null !== $alpha ? $alpha : $this->PainterAlpha;
37+
38+
return imagecolorallocatealpha($resource, $this->PainterColor[0], $this->PainterColor[1], $this->PainterColor[2], (int)(127.0 - $alpha * 127.0 / 100.0));
39+
}
40+
}
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of the StaticMaps.
7+
*
8+
* (c) RUNALYZE <[email protected]>
9+
*
10+
* This source file is subject to the MIT license that is bundled
11+
* with this source code in the file LICENSE.
12+
*/
13+
14+
namespace Runalyze\StaticMaps\Drawer\Polyline;
15+
16+
use Runalyze\StaticMaps\Drawer\PainterAwareTrait;
17+
18+
class AntialiasPolylineDrawer implements PolylineDrawerInterface
19+
{
20+
use PainterAwareTrait;
21+
22+
/** @var array [x][y] => alpha */
23+
protected $AlphaPixels = [];
24+
25+
public function addPolyline(array $points)
26+
{
27+
$numPoints = count($points);
28+
29+
if (1 == $numPoints) {
30+
$this->drawAntialiasPixelToBuffer($points[0][0], $points[0][1]);
31+
32+
return;
33+
}
34+
35+
$previousAngle = $this->getAngle($points[0][0], $points[0][1], $points[1][0], $points[1][1]);
36+
$currentAngle = $previousAngle;
37+
38+
for ($i = 0; $i < $numPoints - 1; ++$i) {
39+
$nextAngle = $i < $numPoints - 2 ? $this->getAngle($points[$i + 1][0], $points[$i + 1][1], $points[$i + 2][0], $points[$i + 2][1]) : $currentAngle;
40+
41+
list($Ax, $Ay) = $points[$i];
42+
list($Bx, $By) = $points[$i + 1];
43+
44+
$transitionAngleAtA = ($currentAngle + $previousAngle) / 2.0 - 90.0;
45+
$transitionAngleAtB = ($currentAngle + $nextAngle) / 2.0 - 90.0;
46+
$weightFactorAtA = 1.0 / cos(deg2rad(($previousAngle - $currentAngle) / 2.0));
47+
$weightFactorAtB = 1.0 / cos(deg2rad(($currentAngle - $nextAngle) / 2.0));
48+
49+
$weights = range(0.5 - $this->LineWidth / 2, $this->LineWidth / 2 - 0.5, 1.0);
50+
51+
foreach ($weights as $weight) {
52+
$this->drawAntialiasLineToBuffer(
53+
$Ax + $weight * $weightFactorAtA * cos(deg2rad($transitionAngleAtA)),
54+
$Ay + $weight * $weightFactorAtA * sin(deg2rad($transitionAngleAtA)),
55+
$Bx + $weight * $weightFactorAtB * cos(deg2rad($transitionAngleAtB)),
56+
$By + $weight * $weightFactorAtB * sin(deg2rad($transitionAngleAtB)),
57+
1
58+
);
59+
}
60+
61+
$previousAngle = $currentAngle;
62+
$currentAngle = $nextAngle;
63+
}
64+
}
65+
66+
public function drawPolylines($resource)
67+
{
68+
foreach ($this->AlphaPixels as $x => $yPixels) {
69+
foreach ($yPixels as $y => $alpha) {
70+
imagesetpixel($resource, $x, $y, $this->allocateColor($resource, $alpha));
71+
}
72+
}
73+
74+
$this->AlphaPixels = [];
75+
}
76+
77+
protected function drawAntialiasLineToBuffer($x1, $y1, $x2, $y2, int $lineWidth = 1)
78+
{
79+
if (1 == $lineWidth) {
80+
$this->drawAntialiasLinePixelwiseToBuffer($x1, $y1, $x2, $y2);
81+
82+
return;
83+
}
84+
85+
$angle = deg2rad($this->getAngle($x1, $y1, $x2, $y2) - 90.0);
86+
$weights = range(0.5 - $lineWidth / 2, $lineWidth / 2 - 0.5, 1.0);
87+
88+
foreach ($weights as $weight) {
89+
$this->drawAntialiasLineToBuffer(
90+
$x1 + $weight * cos($angle),
91+
$y1 + $weight * sin($angle),
92+
$x2 + $weight * cos($angle),
93+
$y2 + $weight * sin($angle)
94+
);
95+
}
96+
}
97+
98+
protected function drawAntialiasLinePixelwiseToBuffer($x1, $y1, $x2, $y2)
99+
{
100+
$distance = max(1.0, sqrt(($x2 - $x1) * ($x2 - $x1) + ($y2 - $y1) * ($y2 - $y1)));
101+
102+
$xStep = ($x2 - $x1) / $distance;
103+
$yStep = ($y2 - $y1) / $distance;
104+
105+
for ($i = 0; $i <= $distance; ++$i) {
106+
$this->drawAntialiasPixelToBuffer($i * $xStep + $x1, $i * $yStep + $y1);
107+
}
108+
}
109+
110+
protected function drawAntialiasPixelToBuffer(float $x, float $y)
111+
{
112+
$xi = (int)floor($x);
113+
$yi = (int)floor($y);
114+
115+
if ($xi == $x && $yi == $y) {
116+
$this->drawAlphaPixelToBuffer($xi, $yi, $this->PainterAlpha);
117+
} else {
118+
$deltaX = $x - (float)$xi;
119+
$deltaY = $y - (float)$yi;
120+
$alpha1 = (1.0 - $deltaX) * (1.0 - $deltaY) * $this->PainterAlpha;
121+
$alpha2 = $deltaX * (1.0 - $deltaY) * $this->PainterAlpha;
122+
$alpha3 = (1.0 - $deltaX) * $deltaY * $this->PainterAlpha;
123+
$alpha4 = $deltaX * $deltaY * $this->PainterAlpha;
124+
125+
$this->drawAlphaPixelToBuffer($xi, $yi, $alpha1);
126+
$this->drawAlphaPixelToBuffer($xi + 1, $yi, $alpha2);
127+
$this->drawAlphaPixelToBuffer($xi, $yi + 1, $alpha3);
128+
$this->drawAlphaPixelToBuffer($xi + 1, $yi + 1, $alpha4);
129+
}
130+
}
131+
132+
protected function drawAlphaPixelToBuffer(int $x, int $y, float $alpha)
133+
{
134+
if (isset($this->AlphaPixels[$x][$y])) {
135+
$this->AlphaPixels[$x][$y] = min($this->PainterAlpha, $this->AlphaPixels[$x][$y] + $alpha);
136+
} else {
137+
$this->AlphaPixels[$x][$y] = $alpha;
138+
}
139+
}
140+
141+
protected function getAngle($x1, $y1, $x2, $y2): float
142+
{
143+
$angle = rad2deg(atan2($y2 - $y1, $x2 - $x1));
144+
145+
if ($angle <= 0.0) {
146+
return 360.0 - abs($angle);
147+
}
148+
149+
return $angle;
150+
}
151+
}

0 commit comments

Comments
 (0)