init commit
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Module\EdgeIterator;
|
||||
|
||||
final class Edge
|
||||
{
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $positive;
|
||||
|
||||
/**
|
||||
* @var array<int[]>
|
||||
*/
|
||||
private $points = [];
|
||||
|
||||
/**
|
||||
* @var array<int[]>|null
|
||||
*/
|
||||
private $simplifiedPoints;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $minX = PHP_INT_MAX;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $minY = PHP_INT_MAX;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $maxX = -1;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $maxY = -1;
|
||||
|
||||
public function __construct(bool $positive)
|
||||
{
|
||||
$this->positive = $positive;
|
||||
}
|
||||
|
||||
public function addPoint(int $x, int $y) : void
|
||||
{
|
||||
$this->points[] = [$x, $y];
|
||||
$this->minX = min($this->minX, $x);
|
||||
$this->minY = min($this->minY, $y);
|
||||
$this->maxX = max($this->maxX, $x);
|
||||
$this->maxY = max($this->maxY, $y);
|
||||
}
|
||||
|
||||
public function isPositive() : bool
|
||||
{
|
||||
return $this->positive;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int[]>
|
||||
*/
|
||||
public function getPoints() : array
|
||||
{
|
||||
return $this->points;
|
||||
}
|
||||
|
||||
public function getMaxX() : int
|
||||
{
|
||||
return $this->maxX;
|
||||
}
|
||||
|
||||
public function getSimplifiedPoints() : array
|
||||
{
|
||||
if (null !== $this->simplifiedPoints) {
|
||||
return $this->simplifiedPoints;
|
||||
}
|
||||
|
||||
$points = [];
|
||||
$length = count($this->points);
|
||||
|
||||
for ($i = 0; $i < $length; ++$i) {
|
||||
$previousPoint = $this->points[(0 === $i ? $length : $i) - 1];
|
||||
$nextPoint = $this->points[($length - 1 === $i ? -1 : $i) + 1];
|
||||
$currentPoint = $this->points[$i];
|
||||
|
||||
if (($previousPoint[0] === $currentPoint[0] && $currentPoint[0] === $nextPoint[0])
|
||||
|| ($previousPoint[1] === $currentPoint[1] && $currentPoint[1] === $nextPoint[1])
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$points[] = $currentPoint;
|
||||
}
|
||||
|
||||
return $this->simplifiedPoints = $points;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,169 @@
|
||||
<?php
|
||||
declare(strict_types = 1);
|
||||
|
||||
namespace BaconQrCode\Renderer\Module\EdgeIterator;
|
||||
|
||||
use BaconQrCode\Encoder\ByteMatrix;
|
||||
use IteratorAggregate;
|
||||
use Traversable;
|
||||
|
||||
/**
|
||||
* Edge iterator based on potrace.
|
||||
*/
|
||||
final class EdgeIterator implements IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
private $bytes = [];
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $size;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $width;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $height;
|
||||
|
||||
public function __construct(ByteMatrix $matrix)
|
||||
{
|
||||
$this->bytes = iterator_to_array($matrix->getBytes());
|
||||
$this->size = count($this->bytes);
|
||||
$this->width = $matrix->getWidth();
|
||||
$this->height = $matrix->getHeight();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Edge[]
|
||||
*/
|
||||
public function getIterator() : Traversable
|
||||
{
|
||||
$originalBytes = $this->bytes;
|
||||
$point = $this->findNext(0, 0);
|
||||
|
||||
while (null !== $point) {
|
||||
$edge = $this->findEdge($point[0], $point[1]);
|
||||
$this->xorEdge($edge);
|
||||
|
||||
yield $edge;
|
||||
|
||||
$point = $this->findNext($point[0], $point[1]);
|
||||
}
|
||||
|
||||
$this->bytes = $originalBytes;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]|null
|
||||
*/
|
||||
private function findNext(int $x, int $y) : ?array
|
||||
{
|
||||
$i = $this->width * $y + $x;
|
||||
|
||||
while ($i < $this->size && 1 !== $this->bytes[$i]) {
|
||||
++$i;
|
||||
}
|
||||
|
||||
if ($i < $this->size) {
|
||||
return $this->pointOf($i);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function findEdge(int $x, int $y) : Edge
|
||||
{
|
||||
$edge = new Edge($this->isSet($x, $y));
|
||||
$startX = $x;
|
||||
$startY = $y;
|
||||
$dirX = 0;
|
||||
$dirY = 1;
|
||||
|
||||
while (true) {
|
||||
$edge->addPoint($x, $y);
|
||||
$x += $dirX;
|
||||
$y += $dirY;
|
||||
|
||||
if ($x === $startX && $y === $startY) {
|
||||
break;
|
||||
}
|
||||
|
||||
$left = $this->isSet($x + ($dirX + $dirY - 1 ) / 2, $y + ($dirY - $dirX - 1) / 2);
|
||||
$right = $this->isSet($x + ($dirX - $dirY - 1) / 2, $y + ($dirY + $dirX - 1) / 2);
|
||||
|
||||
if ($right && ! $left) {
|
||||
$tmp = $dirX;
|
||||
$dirX = -$dirY;
|
||||
$dirY = $tmp;
|
||||
} elseif ($right) {
|
||||
$tmp = $dirX;
|
||||
$dirX = -$dirY;
|
||||
$dirY = $tmp;
|
||||
} elseif (! $left) {
|
||||
$tmp = $dirX;
|
||||
$dirX = $dirY;
|
||||
$dirY = -$tmp;
|
||||
}
|
||||
}
|
||||
|
||||
return $edge;
|
||||
}
|
||||
|
||||
private function xorEdge(Edge $path) : void
|
||||
{
|
||||
$points = $path->getPoints();
|
||||
$y1 = $points[0][1];
|
||||
$length = count($points);
|
||||
$maxX = $path->getMaxX();
|
||||
|
||||
for ($i = 1; $i < $length; ++$i) {
|
||||
$y = $points[$i][1];
|
||||
|
||||
if ($y === $y1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$x = $points[$i][0];
|
||||
$minY = min($y1, $y);
|
||||
|
||||
for ($j = $x; $j < $maxX; ++$j) {
|
||||
$this->flip($j, $minY);
|
||||
}
|
||||
|
||||
$y1 = $y;
|
||||
}
|
||||
}
|
||||
|
||||
private function isSet(int $x, int $y) : bool
|
||||
{
|
||||
return (
|
||||
$x >= 0
|
||||
&& $x < $this->width
|
||||
&& $y >= 0
|
||||
&& $y < $this->height
|
||||
) && 1 === $this->bytes[$this->width * $y + $x];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[]
|
||||
*/
|
||||
private function pointOf(int $i) : array
|
||||
{
|
||||
$y = intdiv($i, $this->width);
|
||||
return [$i - $y * $this->width, $y];
|
||||
}
|
||||
|
||||
private function flip(int $x, int $y) : void
|
||||
{
|
||||
$this->bytes[$this->width * $y + $x] = (
|
||||
$this->isSet($x, $y) ? 0 : 1
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user