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 / Common / MaskPattern.php

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

242 lines 8.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * Class MaskPattern
5 *
6 * @created 19.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\Common;
13
14 use WCPOS\Vendor\chillerlan\QRCode\QRCodeException;
15 use WCPOS\Vendor\chillerlan\QRCode\Data\QRMatrix;
16 use Closure;
17 use function abs, array_column, array_search, intdiv, min;
18 /**
19 * ISO/IEC 18004:2000 Section 8.8.1
20 * ISO/IEC 18004:2000 Section 8.8.2 - Evaluation of masking results
21 *
22 * @see http://www.thonky.com/qr-code-tutorial/data-masking
23 * @see https://github.com/zxing/zxing/blob/e9e2bd280bcaeabd59d0f955798384fe6c018a6c/core/src/main/java/com/google/zxing/qrcode/encoder/MaskUtil.java
24 */
25 final class MaskPattern
26 {
27 /**
28 * @see \chillerlan\QRCode\QROptionsTrait::$maskPattern
29 *
30 * @var int
31 */
32 public const AUTO = -1;
33 public const PATTERN_000 = 0b0;
34 public const PATTERN_001 = 0b1;
35 public const PATTERN_010 = 0b10;
36 public const PATTERN_011 = 0b11;
37 public const PATTERN_100 = 0b100;
38 public const PATTERN_101 = 0b101;
39 public const PATTERN_110 = 0b110;
40 public const PATTERN_111 = 0b111;
41 /**
42 * @var int[]
43 */
44 public const PATTERNS = [self::PATTERN_000, self::PATTERN_001, self::PATTERN_010, self::PATTERN_011, self::PATTERN_100, self::PATTERN_101, self::PATTERN_110, self::PATTERN_111];
45 /*
46 * Penalty scores
47 *
48 * ISO/IEC 18004:2000 Section 8.8.1 - Table 24
49 */
50 private const PENALTY_N1 = 3;
51 private const PENALTY_N2 = 3;
52 private const PENALTY_N3 = 40;
53 private const PENALTY_N4 = 10;
54 /**
55 * The current mask pattern value (0-7)
56 */
57 private int $maskPattern;
58 /**
59 * MaskPattern constructor.
60 *
61 * @throws \chillerlan\QRCode\QRCodeException
62 */
63 public function __construct(int $maskPattern)
64 {
65 if (($maskPattern & 0b111) !== $maskPattern) {
66 throw new QRCodeException('invalid mask pattern');
67 }
68 $this->maskPattern = $maskPattern;
69 }
70 /**
71 * Returns the current mask pattern
72 */
73 public function getPattern() : int
74 {
75 return $this->maskPattern;
76 }
77 /**
78 * Returns a closure that applies the mask for the chosen mask pattern.
79 *
80 * Note that the diagram in section 6.8.1 is misleading since it indicates that $i is column position
81 * and $j is row position. In fact, as the text says, $i is row position and $j is column position.
82 *
83 * @see https://www.thonky.com/qr-code-tutorial/mask-patterns
84 * @see https://github.com/zxing/zxing/blob/e9e2bd280bcaeabd59d0f955798384fe6c018a6c/core/src/main/java/com/google/zxing/qrcode/decoder/DataMask.java#L32-L117
85 */
86 public function getMask() : Closure
87 {
88 // $x = column (width), $y = row (height)
89 return [self::PATTERN_000 => fn(int $x, int $y): bool => ($x + $y) % 2 === 0, self::PATTERN_001 => fn(int $x, int $y): bool => $y % 2 === 0, self::PATTERN_010 => fn(int $x, int $y): bool => $x % 3 === 0, self::PATTERN_011 => fn(int $x, int $y): bool => ($x + $y) % 3 === 0, self::PATTERN_100 => fn(int $x, int $y): bool => (intdiv($y, 2) + intdiv($x, 3)) % 2 === 0, self::PATTERN_101 => fn(int $x, int $y): bool => $x * $y % 6 === 0, self::PATTERN_110 => fn(int $x, int $y): bool => $x * $y % 6 < 3, self::PATTERN_111 => fn(int $x, int $y): bool => ($x + $y + $x * $y % 3) % 2 === 0][$this->maskPattern];
90 }
91 /**
92 * Evaluates the matrix of the given data interface and returns a new mask pattern instance for the best result
93 */
94 public static function getBestPattern(QRMatrix $QRMatrix) : self
95 {
96 $penalties = [];
97 $size = $QRMatrix->getSize();
98 foreach (self::PATTERNS as $pattern) {
99 $mp = new self($pattern);
100 $matrix = (clone $QRMatrix)->setFormatInfo($mp)->mask($mp)->getMatrix(\true);
101 $penalty = 0;
102 for ($level = 1; $level <= 4; $level++) {
103 $penalty += self::{'testRule' . $level}($matrix, $size, $size);
104 }
105 $penalties[$pattern] = (int) $penalty;
106 }
107 return new self(array_search(min($penalties), $penalties, \true));
108 }
109 /**
110 * Apply mask penalty rule 1 and return the penalty. Find repetitive cells with the same color and
111 * give penalty to them. Example: 00000 or 11111.
112 */
113 public static function testRule1(array $matrix, int $height, int $width) : int
114 {
115 $penalty = 0;
116 // horizontal
117 foreach ($matrix as $row) {
118 $penalty += self::applyRule1($row);
119 }
120 // vertical
121 for ($x = 0; $x < $width; $x++) {
122 $penalty += self::applyRule1(array_column($matrix, $x));
123 }
124 return $penalty;
125 }
126 /**
127 *
128 */
129 private static function applyRule1(array $rc) : int
130 {
131 $penalty = 0;
132 $numSameBitCells = 0;
133 $prevBit = null;
134 foreach ($rc as $val) {
135 if ($val === $prevBit) {
136 $numSameBitCells++;
137 } else {
138 if ($numSameBitCells >= 5) {
139 $penalty += self::PENALTY_N1 + $numSameBitCells - 5;
140 }
141 $numSameBitCells = 1;
142 // Include the cell itself.
143 $prevBit = $val;
144 }
145 }
146 if ($numSameBitCells >= 5) {
147 $penalty += self::PENALTY_N1 + $numSameBitCells - 5;
148 }
149 return $penalty;
150 }
151 /**
152 * Apply mask penalty rule 2 and return the penalty. Find 2x2 blocks with the same color and give
153 * penalty to them. This is actually equivalent to the spec's rule, which is to find MxN blocks and give a
154 * penalty proportional to (M-1)x(N-1), because this is the number of 2x2 blocks inside such a block.
155 */
156 public static function testRule2(array $matrix, int $height, int $width) : int
157 {
158 $penalty = 0;
159 foreach ($matrix as $y => $row) {
160 if ($y > $height - 2) {
161 break;
162 }
163 foreach ($row as $x => $val) {
164 if ($x > $width - 2) {
165 break;
166 }
167 if ($val === $row[$x + 1] && $val === $matrix[$y + 1][$x] && $val === $matrix[$y + 1][$x + 1]) {
168 $penalty++;
169 }
170 }
171 }
172 return self::PENALTY_N2 * $penalty;
173 }
174 /**
175 * Apply mask penalty rule 3 and return the penalty. Find consecutive runs of 1:1:3:1:1:4
176 * starting with black, or 4:1:1:3:1:1 starting with white, and give penalty to them. If we
177 * find patterns like 000010111010000, we give penalty once.
178 */
179 public static function testRule3(array $matrix, int $height, int $width) : int
180 {
181 $penalties = 0;
182 foreach ($matrix as $y => $row) {
183 foreach ($row as $x => $val) {
184 if ($x + 6 < $width && $val && !$row[$x + 1] && $row[$x + 2] && $row[$x + 3] && $row[$x + 4] && !$row[$x + 5] && $row[$x + 6] && (self::isWhiteHorizontal($row, $width, $x - 4, $x) || self::isWhiteHorizontal($row, $width, $x + 7, $x + 11))) {
185 $penalties++;
186 }
187 if ($y + 6 < $height && $val && !$matrix[$y + 1][$x] && $matrix[$y + 2][$x] && $matrix[$y + 3][$x] && $matrix[$y + 4][$x] && !$matrix[$y + 5][$x] && $matrix[$y + 6][$x] && (self::isWhiteVertical($matrix, $height, $x, $y - 4, $y) || self::isWhiteVertical($matrix, $height, $x, $y + 7, $y + 11))) {
188 $penalties++;
189 }
190 }
191 }
192 return $penalties * self::PENALTY_N3;
193 }
194 /**
195 *
196 */
197 private static function isWhiteHorizontal(array $row, int $width, int $from, int $to) : bool
198 {
199 if ($from < 0 || $width < $to) {
200 return \false;
201 }
202 for ($x = $from; $x < $to; $x++) {
203 if ($row[$x]) {
204 return \false;
205 }
206 }
207 return \true;
208 }
209 /**
210 *
211 */
212 private static function isWhiteVertical(array $matrix, int $height, int $x, int $from, int $to) : bool
213 {
214 if ($from < 0 || $height < $to) {
215 return \false;
216 }
217 for ($y = $from; $y < $to; $y++) {
218 if ($matrix[$y][$x] === \true) {
219 return \false;
220 }
221 }
222 return \true;
223 }
224 /**
225 * Apply mask penalty rule 4 and return the penalty. Calculate the ratio of dark cells and give
226 * penalty if the ratio is far from 50%. It gives 10 penalty for 5% distance.
227 */
228 public static function testRule4(array $matrix, int $height, int $width) : int
229 {
230 $darkCells = 0;
231 $totalCells = $height * $width;
232 foreach ($matrix as $row) {
233 foreach ($row as $val) {
234 if ($val === \true) {
235 $darkCells++;
236 }
237 }
238 }
239 return intdiv(abs($darkCells * 2 - $totalCells) * 10, $totalCells) * self::PENALTY_N4;
240 }
241 }
242