PluginProbe
WCPOS – Point of Sale (POS) plugin for WooCommerce / 1.10.18
WCPOS – Point of Sale (POS) plugin for WooCommerce v1.10.18
1.10.19 1.10.18 1.10.17 1.10.16 1.10.15 1.10.13 1.10.14 1.10.12 1.10.11 1.10.10 1.10.9 1.10.8 untagged-3d9b7ccddc54df87c672 1.10.7 1.10.6 1.10.5 1.10.3 1.10.4 1.10.2 1.10.1 1.10.0 1.9.17 1.9.15 1.9.16 1.9.14 All 163 releases
woocommerce-pos / vendor_prefixed / chillerlan / php-qrcode / src / Detector / AlignmentPatternFinder.php

AlignmentPatternFinder.php in WCPOS – Point of Sale (POS) plugin for WooCommerce 1.10.18, at vendor_prefixed/chillerlan/php-qrcode/src/Detector/AlignmentPatternFinder.php

250 lines 10.0 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Class AlignmentPatternFinder
5 *
6 * @created 17.01.2021
7 * @author ZXing Authors
8 * @author Smiley <smiley@chillerlan.net>
9 * @copyright 2021 Smiley
10 * @license Apache-2.0
11 */
12 namespace WCPOS\Vendor\chillerlan\QRCode\Detector;
13
14 use WCPOS\Vendor\chillerlan\QRCode\Decoder\BitMatrix;
15 use function abs, count;
16 /**
17 * This class attempts to find alignment patterns in a QR Code. Alignment patterns look like finder
18 * patterns but are smaller and appear at regular intervals throughout the image.
19 *
20 * At the moment this only looks for the bottom-right alignment pattern.
21 *
22 * This is mostly a simplified copy of FinderPatternFinder. It is copied,
23 * pasted and stripped down here for maximum performance but does unfortunately duplicate
24 * some code.
25 *
26 * This class is thread-safe but not reentrant. Each thread must allocate its own object.
27 *
28 * @author Sean Owen
29 */
30 final class AlignmentPatternFinder
31 {
32 private BitMatrix $matrix;
33 private float $moduleSize;
34 /** @var \chillerlan\QRCode\Detector\AlignmentPattern[] */
35 private array $possibleCenters;
36 /**
37 * Creates a finder that will look in a portion of the whole image.
38 *
39 * @param \chillerlan\QRCode\Decoder\BitMatrix $matrix image to search
40 * @param float $moduleSize estimated module size so far
41 */
42 public function __construct(BitMatrix $matrix, float $moduleSize)
43 {
44 $this->matrix = $matrix;
45 $this->moduleSize = $moduleSize;
46 $this->possibleCenters = [];
47 }
48 /**
49 * This method attempts to find the bottom-right alignment pattern in the image. It is a bit messy since
50 * it's pretty performance-critical and so is written to be fast foremost.
51 *
52 * @param int $startX left column from which to start searching
53 * @param int $startY top row from which to start searching
54 * @param int $width width of region to search
55 * @param int $height height of region to search
56 *
57 * @return \chillerlan\QRCode\Detector\AlignmentPattern|null
58 */
59 public function find(int $startX, int $startY, int $width, int $height) : ?AlignmentPattern
60 {
61 $maxJ = $startX + $width;
62 $middleI = $startY + $height / 2;
63 $stateCount = [];
64 // We are looking for black/white/black modules in 1:1:1 ratio;
65 // this tracks the number of black/white/black modules seen so far
66 for ($iGen = 0; $iGen < $height; $iGen++) {
67 // Search from middle outwards
68 $i = (int) ($middleI + (($iGen & 0x1) === 0 ? ($iGen + 1) / 2 : -(($iGen + 1) / 2)));
69 $stateCount[0] = 0;
70 $stateCount[1] = 0;
71 $stateCount[2] = 0;
72 $j = $startX;
73 // Burn off leading white pixels before anything else; if we start in the middle of
74 // a white run, it doesn't make sense to count its length, since we don't know if the
75 // white run continued to the left of the start point
76 while ($j < $maxJ && !$this->matrix->check($j, $i)) {
77 $j++;
78 }
79 $currentState = 0;
80 while ($j < $maxJ) {
81 if ($this->matrix->check($j, $i)) {
82 // Black pixel
83 if ($currentState === 1) {
84 // Counting black pixels
85 $stateCount[$currentState]++;
86 } else {
87 // A winner?
88 if ($currentState === 2) {
89 // Yes
90 if ($this->foundPatternCross($stateCount)) {
91 $confirmed = $this->handlePossibleCenter($stateCount, $i, $j);
92 if ($confirmed !== null) {
93 return $confirmed;
94 }
95 }
96 $stateCount[0] = $stateCount[2];
97 $stateCount[1] = 1;
98 $stateCount[2] = 0;
99 $currentState = 1;
100 } else {
101 $stateCount[++$currentState]++;
102 }
103 }
104 } else {
105 // Counting black pixels
106 if ($currentState === 1) {
107 $currentState++;
108 }
109 $stateCount[$currentState]++;
110 }
111 $j++;
112 }
113 if ($this->foundPatternCross($stateCount)) {
114 $confirmed = $this->handlePossibleCenter($stateCount, $i, $maxJ);
115 if ($confirmed !== null) {
116 return $confirmed;
117 }
118 }
119 }
120 // Hmm, nothing we saw was observed and confirmed twice. If we had
121 // any guess at all, return it.
122 if (count($this->possibleCenters)) {
123 return $this->possibleCenters[0];
124 }
125 return null;
126 }
127 /**
128 * @param int[] $stateCount count of black/white/black pixels just read
129 *
130 * @return bool true if the proportions of the counts is close enough to the 1/1/1 ratios
131 * used by alignment patterns to be considered a match
132 */
133 private function foundPatternCross(array $stateCount) : bool
134 {
135 $maxVariance = $this->moduleSize / 2.0;
136 for ($i = 0; $i < 3; $i++) {
137 if (abs($this->moduleSize - $stateCount[$i]) >= $maxVariance) {
138 return \false;
139 }
140 }
141 return \true;
142 }
143 /**
144 * This is called when a horizontal scan finds a possible alignment pattern. It will
145 * cross-check with a vertical scan, and if successful, will see if this pattern had been
146 * found on a previous horizontal scan. If so, we consider it confirmed and conclude we have
147 * found the alignment pattern.
148 *
149 * @param int[] $stateCount reading state module counts from horizontal scan
150 * @param int $i row where alignment pattern may be found
151 * @param int $j end of possible alignment pattern in row
152 *
153 * @return \chillerlan\QRCode\Detector\AlignmentPattern|null if we have found the same pattern twice, or null if not
154 */
155 private function handlePossibleCenter(array $stateCount, int $i, int $j) : ?AlignmentPattern
156 {
157 $stateCountTotal = $stateCount[0] + $stateCount[1] + $stateCount[2];
158 $centerJ = $this->centerFromEnd($stateCount, $j);
159 $centerI = $this->crossCheckVertical($i, (int) $centerJ, 2 * $stateCount[1], $stateCountTotal);
160 if ($centerI !== null) {
161 $estimatedModuleSize = ($stateCount[0] + $stateCount[1] + $stateCount[2]) / 3.0;
162 foreach ($this->possibleCenters as $center) {
163 // Look for about the same center and module size:
164 if ($center->aboutEquals($estimatedModuleSize, $centerI, $centerJ)) {
165 return $center->combineEstimate($centerI, $centerJ, $estimatedModuleSize);
166 }
167 }
168 // Hadn't found this before; save it
169 $point = new AlignmentPattern($centerJ, $centerI, $estimatedModuleSize);
170 $this->possibleCenters[] = $point;
171 }
172 return null;
173 }
174 /**
175 * Given a count of black/white/black pixels just seen and an end position,
176 * figures the location of the center of this black/white/black run.
177 *
178 * @param int[] $stateCount
179 * @param int $end
180 *
181 * @return float
182 */
183 private function centerFromEnd(array $stateCount, int $end) : float
184 {
185 return (float) ($end - $stateCount[2] - $stateCount[1] / 2);
186 }
187 /**
188 * After a horizontal scan finds a potential alignment pattern, this method
189 * "cross-checks" by scanning down vertically through the center of the possible
190 * alignment pattern to see if the same proportion is detected.
191 *
192 * @param int $startI row where an alignment pattern was detected
193 * @param int $centerJ center of the section that appears to cross an alignment pattern
194 * @param int $maxCount maximum reasonable number of modules that should be
195 * observed in any reading state, based on the results of the horizontal scan
196 * @param int $originalStateCountTotal
197 *
198 * @return float|null vertical center of alignment pattern, or null if not found
199 */
200 private function crossCheckVertical(int $startI, int $centerJ, int $maxCount, int $originalStateCountTotal) : ?float
201 {
202 $maxI = $this->matrix->getSize();
203 $stateCount = [];
204 $stateCount[0] = 0;
205 $stateCount[1] = 0;
206 $stateCount[2] = 0;
207 // Start counting up from center
208 $i = $startI;
209 while ($i >= 0 && $this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount) {
210 $stateCount[1]++;
211 $i--;
212 }
213 // If already too many modules in this state or ran off the edge:
214 if ($i < 0 || $stateCount[1] > $maxCount) {
215 return null;
216 }
217 while ($i >= 0 && !$this->matrix->check($centerJ, $i) && $stateCount[0] <= $maxCount) {
218 $stateCount[0]++;
219 $i--;
220 }
221 if ($stateCount[0] > $maxCount) {
222 return null;
223 }
224 // Now also count down from center
225 $i = $startI + 1;
226 while ($i < $maxI && $this->matrix->check($centerJ, $i) && $stateCount[1] <= $maxCount) {
227 $stateCount[1]++;
228 $i++;
229 }
230 if ($i === $maxI || $stateCount[1] > $maxCount) {
231 return null;
232 }
233 while ($i < $maxI && !$this->matrix->check($centerJ, $i) && $stateCount[2] <= $maxCount) {
234 $stateCount[2]++;
235 $i++;
236 }
237 if ($stateCount[2] > $maxCount) {
238 return null;
239 }
240 // phpcs:ignore
241 if (5 * abs($stateCount[0] + $stateCount[1] + $stateCount[2] - $originalStateCountTotal) >= 2 * $originalStateCountTotal) {
242 return null;
243 }
244 if (!$this->foundPatternCross($stateCount)) {
245 return null;
246 }
247 return $this->centerFromEnd($stateCount, $i);
248 }
249 }
250