123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169 |
- <?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
- );
- }
- }
|