EdgeIterator.php 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. <?php
  2. declare(strict_types = 1);
  3. namespace BaconQrCode\Renderer\Module\EdgeIterator;
  4. use BaconQrCode\Encoder\ByteMatrix;
  5. use IteratorAggregate;
  6. use Traversable;
  7. /**
  8. * Edge iterator based on potrace.
  9. */
  10. final class EdgeIterator implements IteratorAggregate
  11. {
  12. /**
  13. * @var int[]
  14. */
  15. private $bytes = [];
  16. /**
  17. * @var int
  18. */
  19. private $size;
  20. /**
  21. * @var int
  22. */
  23. private $width;
  24. /**
  25. * @var int
  26. */
  27. private $height;
  28. public function __construct(ByteMatrix $matrix)
  29. {
  30. $this->bytes = iterator_to_array($matrix->getBytes());
  31. $this->size = count($this->bytes);
  32. $this->width = $matrix->getWidth();
  33. $this->height = $matrix->getHeight();
  34. }
  35. /**
  36. * @return Edge[]
  37. */
  38. public function getIterator() : Traversable
  39. {
  40. $originalBytes = $this->bytes;
  41. $point = $this->findNext(0, 0);
  42. while (null !== $point) {
  43. $edge = $this->findEdge($point[0], $point[1]);
  44. $this->xorEdge($edge);
  45. yield $edge;
  46. $point = $this->findNext($point[0], $point[1]);
  47. }
  48. $this->bytes = $originalBytes;
  49. }
  50. /**
  51. * @return int[]|null
  52. */
  53. private function findNext(int $x, int $y) : ?array
  54. {
  55. $i = $this->width * $y + $x;
  56. while ($i < $this->size && 1 !== $this->bytes[$i]) {
  57. ++$i;
  58. }
  59. if ($i < $this->size) {
  60. return $this->pointOf($i);
  61. }
  62. return null;
  63. }
  64. private function findEdge(int $x, int $y) : Edge
  65. {
  66. $edge = new Edge($this->isSet($x, $y));
  67. $startX = $x;
  68. $startY = $y;
  69. $dirX = 0;
  70. $dirY = 1;
  71. while (true) {
  72. $edge->addPoint($x, $y);
  73. $x += $dirX;
  74. $y += $dirY;
  75. if ($x === $startX && $y === $startY) {
  76. break;
  77. }
  78. $left = $this->isSet($x + ($dirX + $dirY - 1 ) / 2, $y + ($dirY - $dirX - 1) / 2);
  79. $right = $this->isSet($x + ($dirX - $dirY - 1) / 2, $y + ($dirY + $dirX - 1) / 2);
  80. if ($right && ! $left) {
  81. $tmp = $dirX;
  82. $dirX = -$dirY;
  83. $dirY = $tmp;
  84. } elseif ($right) {
  85. $tmp = $dirX;
  86. $dirX = -$dirY;
  87. $dirY = $tmp;
  88. } elseif (! $left) {
  89. $tmp = $dirX;
  90. $dirX = $dirY;
  91. $dirY = -$tmp;
  92. }
  93. }
  94. return $edge;
  95. }
  96. private function xorEdge(Edge $path) : void
  97. {
  98. $points = $path->getPoints();
  99. $y1 = $points[0][1];
  100. $length = count($points);
  101. $maxX = $path->getMaxX();
  102. for ($i = 1; $i < $length; ++$i) {
  103. $y = $points[$i][1];
  104. if ($y === $y1) {
  105. continue;
  106. }
  107. $x = $points[$i][0];
  108. $minY = min($y1, $y);
  109. for ($j = $x; $j < $maxX; ++$j) {
  110. $this->flip($j, $minY);
  111. }
  112. $y1 = $y;
  113. }
  114. }
  115. private function isSet(int $x, int $y) : bool
  116. {
  117. return (
  118. $x >= 0
  119. && $x < $this->width
  120. && $y >= 0
  121. && $y < $this->height
  122. ) && 1 === $this->bytes[$this->width * $y + $x];
  123. }
  124. /**
  125. * @return int[]
  126. */
  127. private function pointOf(int $i) : array
  128. {
  129. $y = intdiv($i, $this->width);
  130. return [$i - $y * $this->width, $y];
  131. }
  132. private function flip(int $x, int $y) : void
  133. {
  134. $this->bytes[$this->width * $y + $x] = (
  135. $this->isSet($x, $y) ? 0 : 1
  136. );
  137. }
  138. }