BitArray.php
11 months ago
BitMatrix.php
11 months ago
BitUtils.php
11 months ago
CharacterSetEci.php
11 months ago
EcBlock.php
11 months ago
EcBlocks.php
11 months ago
ErrorCorrectionLevel.php
11 months ago
FormatInformation.php
11 months ago
Mode.php
11 months ago
ReedSolomonCodec.php
11 months ago
Version.php
11 months ago
BitMatrix.php
259 lines
| 1 | <?php |
| 2 | |
| 3 | declare (strict_types=1); |
| 4 | namespace WP2FA_Vendor\BaconQrCode\Common; |
| 5 | |
| 6 | use WP2FA_Vendor\BaconQrCode\Exception\InvalidArgumentException; |
| 7 | use SplFixedArray; |
| 8 | /** |
| 9 | * Bit matrix. |
| 10 | * |
| 11 | * Represents a 2D matrix of bits. In function arguments below, and throughout |
| 12 | * the common module, x is the column position, and y is the row position. The |
| 13 | * ordering is always x, y. The origin is at the top-left. |
| 14 | */ |
| 15 | class BitMatrix |
| 16 | { |
| 17 | /** |
| 18 | * Width of the bit matrix. |
| 19 | * |
| 20 | * @var int |
| 21 | */ |
| 22 | private $width; |
| 23 | /** |
| 24 | * Height of the bit matrix. |
| 25 | * |
| 26 | * @var int |
| 27 | */ |
| 28 | private $height; |
| 29 | /** |
| 30 | * Size in bits of each individual row. |
| 31 | * |
| 32 | * @var int |
| 33 | */ |
| 34 | private $rowSize; |
| 35 | /** |
| 36 | * Bits representation. |
| 37 | * |
| 38 | * @var SplFixedArray<int> |
| 39 | */ |
| 40 | private $bits; |
| 41 | /** |
| 42 | * @throws InvalidArgumentException if a dimension is smaller than zero |
| 43 | */ |
| 44 | public function __construct(int $width, int $height = null) |
| 45 | { |
| 46 | if (null === $height) { |
| 47 | $height = $width; |
| 48 | } |
| 49 | if ($width < 1 || $height < 1) { |
| 50 | throw new InvalidArgumentException('Both dimensions must be greater than zero'); |
| 51 | } |
| 52 | $this->width = $width; |
| 53 | $this->height = $height; |
| 54 | $this->rowSize = $width + 31 >> 5; |
| 55 | $this->bits = SplFixedArray::fromArray(\array_fill(0, $this->rowSize * $height, 0)); |
| 56 | } |
| 57 | /** |
| 58 | * Gets the requested bit, where true means black. |
| 59 | */ |
| 60 | public function get(int $x, int $y) : bool |
| 61 | { |
| 62 | $offset = $y * $this->rowSize + ($x >> 5); |
| 63 | return 0 !== (BitUtils::unsignedRightShift($this->bits[$offset], $x & 0x1f) & 1); |
| 64 | } |
| 65 | /** |
| 66 | * Sets the given bit to true. |
| 67 | */ |
| 68 | public function set(int $x, int $y) : void |
| 69 | { |
| 70 | $offset = $y * $this->rowSize + ($x >> 5); |
| 71 | $this->bits[$offset] = $this->bits[$offset] | 1 << ($x & 0x1f); |
| 72 | } |
| 73 | /** |
| 74 | * Flips the given bit. |
| 75 | */ |
| 76 | public function flip(int $x, int $y) : void |
| 77 | { |
| 78 | $offset = $y * $this->rowSize + ($x >> 5); |
| 79 | $this->bits[$offset] = $this->bits[$offset] ^ 1 << ($x & 0x1f); |
| 80 | } |
| 81 | /** |
| 82 | * Clears all bits (set to false). |
| 83 | */ |
| 84 | public function clear() : void |
| 85 | { |
| 86 | $max = \count($this->bits); |
| 87 | for ($i = 0; $i < $max; ++$i) { |
| 88 | $this->bits[$i] = 0; |
| 89 | } |
| 90 | } |
| 91 | /** |
| 92 | * Sets a square region of the bit matrix to true. |
| 93 | * |
| 94 | * @throws InvalidArgumentException if left or top are negative |
| 95 | * @throws InvalidArgumentException if width or height are smaller than 1 |
| 96 | * @throws InvalidArgumentException if region does not fit into the matix |
| 97 | */ |
| 98 | public function setRegion(int $left, int $top, int $width, int $height) : void |
| 99 | { |
| 100 | if ($top < 0 || $left < 0) { |
| 101 | throw new InvalidArgumentException('Left and top must be non-negative'); |
| 102 | } |
| 103 | if ($height < 1 || $width < 1) { |
| 104 | throw new InvalidArgumentException('Width and height must be at least 1'); |
| 105 | } |
| 106 | $right = $left + $width; |
| 107 | $bottom = $top + $height; |
| 108 | if ($bottom > $this->height || $right > $this->width) { |
| 109 | throw new InvalidArgumentException('The region must fit inside the matrix'); |
| 110 | } |
| 111 | for ($y = $top; $y < $bottom; ++$y) { |
| 112 | $offset = $y * $this->rowSize; |
| 113 | for ($x = $left; $x < $right; ++$x) { |
| 114 | $index = $offset + ($x >> 5); |
| 115 | $this->bits[$index] = $this->bits[$index] | 1 << ($x & 0x1f); |
| 116 | } |
| 117 | } |
| 118 | } |
| 119 | /** |
| 120 | * A fast method to retrieve one row of data from the matrix as a BitArray. |
| 121 | */ |
| 122 | public function getRow(int $y, BitArray $row = null) : BitArray |
| 123 | { |
| 124 | if (null === $row || $row->getSize() < $this->width) { |
| 125 | $row = new BitArray($this->width); |
| 126 | } |
| 127 | $offset = $y * $this->rowSize; |
| 128 | for ($x = 0; $x < $this->rowSize; ++$x) { |
| 129 | $row->setBulk($x << 5, $this->bits[$offset + $x]); |
| 130 | } |
| 131 | return $row; |
| 132 | } |
| 133 | /** |
| 134 | * Sets a row of data from a BitArray. |
| 135 | */ |
| 136 | public function setRow(int $y, BitArray $row) : void |
| 137 | { |
| 138 | $bits = $row->getBitArray(); |
| 139 | for ($i = 0; $i < $this->rowSize; ++$i) { |
| 140 | $this->bits[$y * $this->rowSize + $i] = $bits[$i]; |
| 141 | } |
| 142 | } |
| 143 | /** |
| 144 | * This is useful in detecting the enclosing rectangle of a 'pure' barcode. |
| 145 | * |
| 146 | * @return int[]|null |
| 147 | */ |
| 148 | public function getEnclosingRectangle() : ?array |
| 149 | { |
| 150 | $left = $this->width; |
| 151 | $top = $this->height; |
| 152 | $right = -1; |
| 153 | $bottom = -1; |
| 154 | for ($y = 0; $y < $this->height; ++$y) { |
| 155 | for ($x32 = 0; $x32 < $this->rowSize; ++$x32) { |
| 156 | $bits = $this->bits[$y * $this->rowSize + $x32]; |
| 157 | if (0 !== $bits) { |
| 158 | if ($y < $top) { |
| 159 | $top = $y; |
| 160 | } |
| 161 | if ($y > $bottom) { |
| 162 | $bottom = $y; |
| 163 | } |
| 164 | if ($x32 * 32 < $left) { |
| 165 | $bit = 0; |
| 166 | while ($bits << 31 - $bit === 0) { |
| 167 | $bit++; |
| 168 | } |
| 169 | if ($x32 * 32 + $bit < $left) { |
| 170 | $left = $x32 * 32 + $bit; |
| 171 | } |
| 172 | } |
| 173 | } |
| 174 | if ($x32 * 32 + 31 > $right) { |
| 175 | $bit = 31; |
| 176 | while (0 === BitUtils::unsignedRightShift($bits, $bit)) { |
| 177 | --$bit; |
| 178 | } |
| 179 | if ($x32 * 32 + $bit > $right) { |
| 180 | $right = $x32 * 32 + $bit; |
| 181 | } |
| 182 | } |
| 183 | } |
| 184 | } |
| 185 | $width = $right - $left; |
| 186 | $height = $bottom - $top; |
| 187 | if ($width < 0 || $height < 0) { |
| 188 | return null; |
| 189 | } |
| 190 | return [$left, $top, $width, $height]; |
| 191 | } |
| 192 | /** |
| 193 | * Gets the most top left set bit. |
| 194 | * |
| 195 | * This is useful in detecting a corner of a 'pure' barcode. |
| 196 | * |
| 197 | * @return int[]|null |
| 198 | */ |
| 199 | public function getTopLeftOnBit() : ?array |
| 200 | { |
| 201 | $bitsOffset = 0; |
| 202 | while ($bitsOffset < \count($this->bits) && 0 === $this->bits[$bitsOffset]) { |
| 203 | ++$bitsOffset; |
| 204 | } |
| 205 | if (\count($this->bits) === $bitsOffset) { |
| 206 | return null; |
| 207 | } |
| 208 | $x = \intdiv($bitsOffset, $this->rowSize); |
| 209 | $y = $bitsOffset % $this->rowSize << 5; |
| 210 | $bits = $this->bits[$bitsOffset]; |
| 211 | $bit = 0; |
| 212 | while (0 === $bits << 31 - $bit) { |
| 213 | ++$bit; |
| 214 | } |
| 215 | $x += $bit; |
| 216 | return [$x, $y]; |
| 217 | } |
| 218 | /** |
| 219 | * Gets the most bottom right set bit. |
| 220 | * |
| 221 | * This is useful in detecting a corner of a 'pure' barcode. |
| 222 | * |
| 223 | * @return int[]|null |
| 224 | */ |
| 225 | public function getBottomRightOnBit() : ?array |
| 226 | { |
| 227 | $bitsOffset = \count($this->bits) - 1; |
| 228 | while ($bitsOffset >= 0 && 0 === $this->bits[$bitsOffset]) { |
| 229 | --$bitsOffset; |
| 230 | } |
| 231 | if ($bitsOffset < 0) { |
| 232 | return null; |
| 233 | } |
| 234 | $x = \intdiv($bitsOffset, $this->rowSize); |
| 235 | $y = $bitsOffset % $this->rowSize << 5; |
| 236 | $bits = $this->bits[$bitsOffset]; |
| 237 | $bit = 0; |
| 238 | while (0 === BitUtils::unsignedRightShift($bits, $bit)) { |
| 239 | --$bit; |
| 240 | } |
| 241 | $x += $bit; |
| 242 | return [$x, $y]; |
| 243 | } |
| 244 | /** |
| 245 | * Gets the width of the matrix, |
| 246 | */ |
| 247 | public function getWidth() : int |
| 248 | { |
| 249 | return $this->width; |
| 250 | } |
| 251 | /** |
| 252 | * Gets the height of the matrix. |
| 253 | */ |
| 254 | public function getHeight() : int |
| 255 | { |
| 256 | return $this->height; |
| 257 | } |
| 258 | } |
| 259 |