Passed
Push — v4.3.x ( be3beb )
by smiley
11:42
created

QRMatrix::maskPattern()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 2
rs 10
c 1
b 0
f 0
1
<?php
2
/**
3
 * Class QRMatrix
4
 *
5
 * @filesource   QRMatrix.php
6
 * @created      15.11.2017
7
 * @package      chillerlan\QRCode\Data
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2017 Smiley
10
 * @license      MIT
11
 */
12
13
namespace chillerlan\QRCode\Data;
14
15
use chillerlan\QRCode\QRCode;
16
use Closure;
17
18
use function array_fill, array_key_exists, array_push, array_unshift, count, floor, in_array, max, min, range;
19
20
/**
21
 * Holds a numerical representation of the final QR Code;
22
 * maps the ECC coded binary data and applies the mask pattern
23
 *
24
 * @see http://www.thonky.com/qr-code-tutorial/format-version-information
25
 */
26
final class QRMatrix{
27
28
	/** @var int */
29
	public const M_NULL       = 0x00;
30
	/** @var int */
31
	public const M_DARKMODULE = 0x02;
32
	/** @var int */
33
	public const M_DATA       = 0x04;
34
	/** @var int */
35
	public const M_FINDER     = 0x06;
36
	/** @var int */
37
	public const M_SEPARATOR  = 0x08;
38
	/** @var int */
39
	public const M_ALIGNMENT  = 0x0a;
40
	/** @var int */
41
	public const M_TIMING     = 0x0c;
42
	/** @var int */
43
	public const M_FORMAT     = 0x0e;
44
	/** @var int */
45
	public const M_VERSION    = 0x10;
46
	/** @var int */
47
	public const M_QUIETZONE  = 0x12;
48
	/** @var int */
49
	public const M_LOGO       = 0x14;
50
	/** @var int */
51
	public const M_FINDER_DOT = 0x16;
52
	/** @var int */
53
	public const M_TEST       = 0xff;
54
55
	/**
56
	 * ISO/IEC 18004:2000 Annex E, Table E.1 - Row/column coordinates of center module of Alignment Patterns
57
	 *
58
	 * version -> pattern
59
	 *
60
	 * @var int[][]
61
	 */
62
	protected const alignmentPattern = [
63
		1  => [],
64
		2  => [6, 18],
65
		3  => [6, 22],
66
		4  => [6, 26],
67
		5  => [6, 30],
68
		6  => [6, 34],
69
		7  => [6, 22, 38],
70
		8  => [6, 24, 42],
71
		9  => [6, 26, 46],
72
		10 => [6, 28, 50],
73
		11 => [6, 30, 54],
74
		12 => [6, 32, 58],
75
		13 => [6, 34, 62],
76
		14 => [6, 26, 46, 66],
77
		15 => [6, 26, 48, 70],
78
		16 => [6, 26, 50, 74],
79
		17 => [6, 30, 54, 78],
80
		18 => [6, 30, 56, 82],
81
		19 => [6, 30, 58, 86],
82
		20 => [6, 34, 62, 90],
83
		21 => [6, 28, 50, 72,  94],
84
		22 => [6, 26, 50, 74,  98],
85
		23 => [6, 30, 54, 78, 102],
86
		24 => [6, 28, 54, 80, 106],
87
		25 => [6, 32, 58, 84, 110],
88
		26 => [6, 30, 58, 86, 114],
89
		27 => [6, 34, 62, 90, 118],
90
		28 => [6, 26, 50, 74,  98, 122],
91
		29 => [6, 30, 54, 78, 102, 126],
92
		30 => [6, 26, 52, 78, 104, 130],
93
		31 => [6, 30, 56, 82, 108, 134],
94
		32 => [6, 34, 60, 86, 112, 138],
95
		33 => [6, 30, 58, 86, 114, 142],
96
		34 => [6, 34, 62, 90, 118, 146],
97
		35 => [6, 30, 54, 78, 102, 126, 150],
98
		36 => [6, 24, 50, 76, 102, 128, 154],
99
		37 => [6, 28, 54, 80, 106, 132, 158],
100
		38 => [6, 32, 58, 84, 110, 136, 162],
101
		39 => [6, 26, 54, 82, 110, 138, 166],
102
		40 => [6, 30, 58, 86, 114, 142, 170],
103
	];
104
105
	/**
106
	 * ISO/IEC 18004:2000 Annex D, Table D.1 - Version information bit stream for each version
107
	 *
108
	 * no version pattern for QR Codes < 7
109
	 *
110
	 * @var int[]
111
	 */
112
	protected const versionPattern = [
113
		7  => 0b000111110010010100,
114
		8  => 0b001000010110111100,
115
		9  => 0b001001101010011001,
116
		10 => 0b001010010011010011,
117
		11 => 0b001011101111110110,
118
		12 => 0b001100011101100010,
119
		13 => 0b001101100001000111,
120
		14 => 0b001110011000001101,
121
		15 => 0b001111100100101000,
122
		16 => 0b010000101101111000,
123
		17 => 0b010001010001011101,
124
		18 => 0b010010101000010111,
125
		19 => 0b010011010100110010,
126
		20 => 0b010100100110100110,
127
		21 => 0b010101011010000011,
128
		22 => 0b010110100011001001,
129
		23 => 0b010111011111101100,
130
		24 => 0b011000111011000100,
131
		25 => 0b011001000111100001,
132
		26 => 0b011010111110101011,
133
		27 => 0b011011000010001110,
134
		28 => 0b011100110000011010,
135
		29 => 0b011101001100111111,
136
		30 => 0b011110110101110101,
137
		31 => 0b011111001001010000,
138
		32 => 0b100000100111010101,
139
		33 => 0b100001011011110000,
140
		34 => 0b100010100010111010,
141
		35 => 0b100011011110011111,
142
		36 => 0b100100101100001011,
143
		37 => 0b100101010000101110,
144
		38 => 0b100110101001100100,
145
		39 => 0b100111010101000001,
146
		40 => 0b101000110001101001,
147
	];
148
149
	/**
150
	 * ISO/IEC 18004:2000 Section 8.9 - Format Information
151
	 *
152
	 * ECC level -> mask pattern
153
	 *
154
	 * @var int[][]
155
	 */
156
	protected const formatPattern = [
157
		[ // L
158
			0b111011111000100,
159
			0b111001011110011,
160
			0b111110110101010,
161
			0b111100010011101,
162
			0b110011000101111,
163
			0b110001100011000,
164
			0b110110001000001,
165
			0b110100101110110,
166
		],
167
		[ // M
168
			0b101010000010010,
169
			0b101000100100101,
170
			0b101111001111100,
171
			0b101101101001011,
172
			0b100010111111001,
173
			0b100000011001110,
174
			0b100111110010111,
175
			0b100101010100000,
176
		],
177
		[ // Q
178
			0b011010101011111,
179
			0b011000001101000,
180
			0b011111100110001,
181
			0b011101000000110,
182
			0b010010010110100,
183
			0b010000110000011,
184
			0b010111011011010,
185
			0b010101111101101,
186
		],
187
		[ // H
188
			0b001011010001001,
189
			0b001001110111110,
190
			0b001110011100111,
191
			0b001100111010000,
192
			0b000011101100010,
193
			0b000001001010101,
194
			0b000110100001100,
195
			0b000100000111011,
196
		],
197
	];
198
199
	/**
200
	 * the current QR Code version number
201
	 */
202
	protected int $version;
203
204
	/**
205
	 * the current ECC level
206
	 */
207
	protected int $eclevel;
208
209
	/**
210
	 * the used mask pattern, set via QRMatrix::mapData()
211
	 */
212
	protected int $maskPattern = QRCode::MASK_PATTERN_AUTO;
213
214
	/**
215
	 * the size (side length) of the matrix
216
	 */
217
	protected int $moduleCount;
218
219
	/**
220
	 * the actual matrix data array
221
	 *
222
	 * @var int[][]
223
	 */
224
	protected array $matrix;
225
226
	/**
227
	 * QRMatrix constructor.
228
	 *
229
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
230
	 */
231
	public function __construct(int $version, int $eclevel){
232
233
		if(!in_array($version, range(1, 40), true)){
234
			throw new QRCodeDataException('invalid QR Code version');
235
		}
236
237
		if(!array_key_exists($eclevel, QRCode::ECC_MODES)){
238
			throw new QRCodeDataException('invalid ecc level');
239
		}
240
241
		$this->version     = $version;
242
		$this->eclevel     = $eclevel;
243
		$this->moduleCount = $this->version * 4 + 17;
244
		$this->matrix      = array_fill(0, $this->moduleCount, array_fill(0, $this->moduleCount, $this::M_NULL));
245
	}
246
247
	/**
248
	 * shortcut to initialize the matrix
249
	 */
250
	public function init(int $maskPattern, bool $test = null):QRMatrix{
251
		return $this
252
			->setFinderPattern()
253
			->setSeparators()
254
			->setAlignmentPattern()
255
			->setTimingPattern()
256
			->setVersionNumber($test)
257
			->setFormatInfo($maskPattern, $test)
258
			->setDarkModule()
259
		;
260
	}
261
262
	/**
263
	 * Returns the data matrix, returns a pure boolean representation if $boolean is set to true
264
	 *
265
	 * @return int[][]|bool[][]
266
	 */
267
	public function matrix(bool $boolean = false):array{
268
269
		if(!$boolean){
270
			return $this->matrix;
271
		}
272
273
		$matrix = [];
274
275
		foreach($this->matrix as $y => $row){
276
			$matrix[$y] = [];
277
278
			foreach($row as $x => $val){
279
				$matrix[$y][$x] = ($val >> 8) > 0;
280
			}
281
		}
282
283
		return $matrix;
284
	}
285
286
	/**
287
	 * Returns the current version number
288
	 */
289
	public function version():int{
290
		return $this->version;
291
	}
292
293
	/**
294
	 * Returns the current ECC level
295
	 */
296
	public function eccLevel():int{
297
		return $this->eclevel;
298
	}
299
300
	/**
301
	 * Returns the current mask pattern
302
	 */
303
	public function maskPattern():int{
304
		return $this->maskPattern;
305
	}
306
307
	/**
308
	 * Returns the absoulute size of the matrix, including quiet zone (after setting it).
309
	 *
310
	 * size = version * 4 + 17 [ + 2 * quietzone size]
311
	 */
312
	public function size():int{
313
		return $this->moduleCount;
314
	}
315
316
	/**
317
	 * Returns the value of the module at position [$x, $y]
318
	 */
319
	public function get(int $x, int $y):int{
320
		return $this->matrix[$y][$x];
321
	}
322
323
	/**
324
	 * Sets the $M_TYPE value for the module at position [$x, $y]
325
	 *
326
	 *   true  => $M_TYPE << 8
327
	 *   false => $M_TYPE
328
	 */
329
	public function set(int $x, int $y, bool $value, int $M_TYPE):QRMatrix{
330
		$this->matrix[$y][$x] = $M_TYPE << ($value ? 8 : 0);
331
332
		return $this;
333
	}
334
335
	/**
336
	 * Checks whether a module is true (dark) or false (light)
337
	 *
338
	 *   true  => $value >> 8 === $M_TYPE
339
	 *            $value >> 8 > 0
340
	 *
341
	 *   false => $value === $M_TYPE
342
	 *            $value >> 8 === 0
343
	 */
344
	public function check(int $x, int $y):bool{
345
		return ($this->matrix[$y][$x] >> 8) > 0;
346
	}
347
348
349
	/**
350
	 * Sets the "dark module", that is always on the same position 1x1px away from the bottom left finder
351
	 */
352
	public function setDarkModule():QRMatrix{
353
		$this->set(8, 4 * $this->version + 9, true, $this::M_DARKMODULE);
354
355
		return $this;
356
	}
357
358
	/**
359
	 * Draws the 7x7 finder patterns in the corners top left/right and bottom left
360
	 *
361
	 * ISO/IEC 18004:2000 Section 7.3.2
362
	 */
363
	public function setFinderPattern():QRMatrix{
364
365
		$pos = [
366
			[0, 0], // top left
367
			[$this->moduleCount - 7, 0], // bottom left
368
			[0, $this->moduleCount - 7], // top right
369
		];
370
371
		foreach($pos as $c){
372
			for($y = 0; $y < 7; $y++){
373
				for($x = 0; $x < 7; $x++){
374
					// outer (dark) 7*7 square
375
					if($x === 0 || $x === 6 || $y === 0 || $y === 6){
376
						$this->set($c[0] + $y, $c[1] + $x, true, $this::M_FINDER);
377
					}
378
					// inner (light) 5*5 square
379
					elseif($x === 1 || $x === 5 || $y === 1 || $y === 5){
380
						$this->set($c[0] + $y, $c[1] + $x, false, $this::M_FINDER);
381
					}
382
					// 3*3 dot
383
					else{
384
						$this->set($c[0] + $y, $c[1] + $x, true, $this::M_FINDER_DOT);
385
					}
386
				}
387
			}
388
		}
389
390
		return $this;
391
	}
392
393
	/**
394
	 * Draws the separator lines around the finder patterns
395
	 *
396
	 * ISO/IEC 18004:2000 Section 7.3.3
397
	 */
398
	public function setSeparators():QRMatrix{
399
400
		$h = [
401
			[7, 0],
402
			[$this->moduleCount - 8, 0],
403
			[7, $this->moduleCount - 8],
404
		];
405
406
		$v = [
407
			[7, 7],
408
			[$this->moduleCount - 1, 7],
409
			[7, $this->moduleCount - 8],
410
		];
411
412
		for($c = 0; $c < 3; $c++){
413
			for($i = 0; $i < 8; $i++){
414
				$this->set($h[$c][0]     , $h[$c][1] + $i, false, $this::M_SEPARATOR);
415
				$this->set($v[$c][0] - $i, $v[$c][1]     , false, $this::M_SEPARATOR);
416
			}
417
		}
418
419
		return $this;
420
	}
421
422
423
	/**
424
	 * Draws the 5x5 alignment patterns
425
	 *
426
	 * ISO/IEC 18004:2000 Section 7.3.5
427
	 */
428
	public function setAlignmentPattern():QRMatrix{
429
430
		foreach($this::alignmentPattern[$this->version] as $y){
431
			foreach($this::alignmentPattern[$this->version] as $x){
432
433
				// skip existing patterns
434
				if($this->matrix[$y][$x] !== $this::M_NULL){
435
					continue;
436
				}
437
438
				for($ry = -2; $ry <= 2; $ry++){
439
					for($rx = -2; $rx <= 2; $rx++){
440
						$v = ($ry === 0 && $rx === 0) || $ry === 2 || $ry === -2 || $rx === 2 || $rx === -2;
441
442
						$this->set($x + $rx, $y + $ry, $v, $this::M_ALIGNMENT);
443
					}
444
				}
445
446
			}
447
		}
448
449
		return $this;
450
	}
451
452
453
	/**
454
	 * Draws the timing pattern (h/v checkered line between the finder patterns)
455
	 *
456
	 * ISO/IEC 18004:2000 Section 7.3.4
457
	 */
458
	public function setTimingPattern():QRMatrix{
459
460
		foreach(range(8, $this->moduleCount - 8 - 1) as $i){
461
462
			if($this->matrix[6][$i] !== $this::M_NULL || $this->matrix[$i][6] !== $this::M_NULL){
463
				continue;
464
			}
465
466
			$v = $i % 2 === 0;
467
468
			$this->set($i, 6, $v, $this::M_TIMING); // h
469
			$this->set(6, $i, $v, $this::M_TIMING); // v
470
		}
471
472
		return $this;
473
	}
474
475
	/**
476
	 * Draws the version information, 2x 3x6 pixel
477
	 *
478
	 * ISO/IEC 18004:2000 Section 8.10
479
	 */
480
	public function setVersionNumber(bool $test = null):QRMatrix{
481
		$bits = $this::versionPattern[$this->version] ?? false;
482
483
		if($bits !== false){
484
485
			for($i = 0; $i < 18; $i++){
486
				$a = (int)floor($i / 3);
487
				$b = $i % 3 + $this->moduleCount - 8 - 3;
488
				$v = !$test && (($bits >> $i) & 1) === 1;
0 ignored issues
show
Bug Best Practice introduced by
The expression $test of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
489
490
				$this->set($b, $a, $v, $this::M_VERSION); // ne
491
				$this->set($a, $b, $v, $this::M_VERSION); // sw
492
			}
493
494
		}
495
496
		return $this;
497
	}
498
499
	/**
500
	 * Draws the format info along the finder patterns
501
	 *
502
	 * ISO/IEC 18004:2000 Section 8.9
503
	 */
504
	public function setFormatInfo(int $maskPattern, bool $test = null):QRMatrix{
505
		$bits = $this::formatPattern[QRCode::ECC_MODES[$this->eclevel]][$maskPattern] ?? 0;
506
507
		for($i = 0; $i < 15; $i++){
508
			$v = !$test && (($bits >> $i) & 1) === 1;
0 ignored issues
show
Bug Best Practice introduced by
The expression $test of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
509
510
			if($i < 6){
511
				$this->set(8, $i, $v, $this::M_FORMAT);
512
			}
513
			elseif($i < 8){
514
				$this->set(8, $i + 1, $v, $this::M_FORMAT);
515
			}
516
			else{
517
				$this->set(8, $this->moduleCount - 15 + $i, $v, $this::M_FORMAT);
518
			}
519
520
			if($i < 8){
521
				$this->set($this->moduleCount - $i - 1, 8, $v, $this::M_FORMAT);
522
			}
523
			elseif($i < 9){
524
				$this->set(15 - $i, 8, $v, $this::M_FORMAT);
525
			}
526
			else{
527
				$this->set(15 - $i - 1, 8, $v, $this::M_FORMAT);
528
			}
529
530
		}
531
532
		$this->set(8, $this->moduleCount - 8, !$test, $this::M_FORMAT);
533
534
		return $this;
535
	}
536
537
	/**
538
	 * Draws the "quiet zone" of $size around the matrix
539
	 *
540
	 * ISO/IEC 18004:2000 Section 7.3.7
541
	 *
542
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
543
	 */
544
	public function setQuietZone(int $size = null):QRMatrix{
545
546
		if($this->matrix[$this->moduleCount - 1][$this->moduleCount - 1] === $this::M_NULL){
547
			throw new QRCodeDataException('use only after writing data');
548
		}
549
550
		$size = $size !== null
551
			? max(0, min($size, floor($this->moduleCount / 2)))
552
			: 4;
553
554
		for($y = 0; $y < $this->moduleCount; $y++){
555
			for($i = 0; $i < $size; $i++){
556
				array_unshift($this->matrix[$y], $this::M_QUIETZONE);
557
				array_push($this->matrix[$y], $this::M_QUIETZONE);
558
			}
559
		}
560
561
		$this->moduleCount += ($size * 2);
562
563
		$r = array_fill(0, $this->moduleCount, $this::M_QUIETZONE);
564
565
		for($i = 0; $i < $size; $i++){
566
			array_unshift($this->matrix, $r);
567
			array_push($this->matrix, $r);
568
		}
569
570
		return $this;
571
	}
572
573
	/**
574
	 * Clears a space of $width * $height in order to add a logo or text.
575
	 *
576
	 * Additionally, the logo space can be positioned within the QR Code - respecting the main functional patterns -
577
	 * using $startX and $startY. If either of these are null, the logo space will be centered in that direction.
578
	 * ECC level "H" (30%) is required.
579
	 *
580
	 * Please note that adding a logo space minimizes the error correction capacity of the QR Code and
581
	 * created images may become unreadable, especially when printed with a chance to receive damage.
582
	 * Please test thoroughly before using this feature in production.
583
	 *
584
	 * This method should be called from within an output module (after the matrix has been filled with data).
585
	 * Note that there is no restiction on how many times this method could be called on the same matrix instance.
586
	 *
587
	 * @link https://github.com/chillerlan/php-qrcode/issues/52
588
	 *
589
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
590
	 */
591
	public function setLogoSpace(int $width, int $height, int $startX = null, int $startY = null):QRMatrix{
592
593
		// for logos we operate in ECC H (30%) only
594
		if($this->eclevel !== QRCode::ECC_H){
595
			throw new QRCodeDataException('ECC level "H" required to add logo space');
596
		}
597
598
		// we need uneven sizes to center the logo space, adjust if needed
599
		if($startX === null && ($width % 2) === 0){
600
			$width++;
601
		}
602
603
		if($startY === null && ($height % 2) === 0){
604
			$height++;
605
		}
606
607
		// $this->moduleCount includes the quiet zone (if created), we need the QR size here
608
		$length = $this->version * 4 + 17;
609
610
		// throw if the logo space exceeds the maximum error correction capacity
611
		if($width * $height > floor($length * $length * 0.2)){
612
			throw new QRCodeDataException('logo space exceeds the maximum error correction capacity');
613
		}
614
615
		// quiet zone size
616
		$qz    = ($this->moduleCount - $length) / 2;
617
		// skip quiet zone and the first 9 rows/columns (finder-, mode-, version- and timing patterns)
618
		$start = $qz + 9;
619
		// skip quiet zone
620
		$end   = $this->moduleCount - $qz;
621
622
		// determine start coordinates
623
		$startX = ($startX !== null ? $startX : ($length - $width) / 2) + $qz;
624
		$startY = ($startY !== null ? $startY : ($length - $height) / 2) + $qz;
625
626
		// clear the space
627
		foreach($this->matrix as $y => $row){
628
			foreach($row as $x => $val){
629
				// out of bounds, skip
630
				if($x < $start || $y < $start ||$x >= $end || $y >= $end){
631
					continue;
632
				}
633
				// a match
634
				if($x >= $startX && $x < ($startX + $width) && $y >= $startY && $y < ($startY + $height)){
635
					$this->set($x, $y, false, $this::M_LOGO);
636
				}
637
			}
638
		}
639
640
		return $this;
641
	}
642
643
	/**
644
	 * Maps the binary $data array from QRDataInterface::maskECC() on the matrix,
645
	 * masking the data using $maskPattern (ISO/IEC 18004:2000 Section 8.8)
646
	 *
647
	 * @see \chillerlan\QRCode\Data\QRDataAbstract::maskECC()
648
	 *
649
	 * @param int[] $data
650
	 * @param int   $maskPattern
651
	 *
652
	 * @return \chillerlan\QRCode\Data\QRMatrix
653
	 */
654
	public function mapData(array $data, int $maskPattern):QRMatrix{
655
		$this->maskPattern = $maskPattern;
656
		$byteCount         = count($data);
657
		$y                 = $this->moduleCount - 1;
658
		$inc               = -1;
659
		$byteIndex         = 0;
660
		$bitIndex          = 7;
661
		$mask              = $this->getMask($this->maskPattern);
662
663
		for($i = $y; $i > 0; $i -= 2){
664
665
			if($i === 6){
666
				$i--;
667
			}
668
669
			while(true){
670
				for($c = 0; $c < 2; $c++){
671
					$x = $i - $c;
672
673
					if($this->matrix[$y][$x] === $this::M_NULL){
674
						$v = false;
675
676
						if($byteIndex < $byteCount){
677
							$v = (($data[$byteIndex] >> $bitIndex) & 1) === 1;
678
						}
679
680
						if($mask($x, $y) === 0){
681
							$v = !$v;
682
						}
683
684
						$this->matrix[$y][$x] = $this::M_DATA << ($v ? 8 : 0);
685
						$bitIndex--;
686
687
						if($bitIndex === -1){
688
							$byteIndex++;
689
							$bitIndex = 7;
690
						}
691
692
					}
693
				}
694
695
				$y += $inc;
696
697
				if($y < 0 || $this->moduleCount <= $y){
698
					$y   -=  $inc;
699
					$inc  = -$inc;
700
701
					break;
702
				}
703
704
			}
705
		}
706
707
		return $this;
708
	}
709
710
	/**
711
	 * ISO/IEC 18004:2000 Section 8.8.1
712
	 *
713
	 * Note that some versions of the QR code standard have had errors in the section about mask patterns.
714
	 * The information below has been corrected. (https://www.thonky.com/qr-code-tutorial/mask-patterns)
715
	 *
716
	 * @see \chillerlan\QRCode\QRMatrix::mapData()
717
	 *
718
	 * @internal
719
	 *
720
	 * @throws \chillerlan\QRCode\Data\QRCodeDataException
721
	 */
722
	protected function getMask(int $maskPattern):Closure{
723
724
		if((0b111 & $maskPattern) !== $maskPattern){
725
			throw new QRCodeDataException('invalid mask pattern'); // @codeCoverageIgnore
726
		}
727
728
		return [
729
			0b000 => fn($x, $y):int => ($x + $y) % 2,
730
			0b001 => fn($x, $y):int => $y % 2,
731
			0b010 => fn($x, $y):int => $x % 3,
0 ignored issues
show
Unused Code introduced by
The parameter $y is not used and could be removed. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unused  annotation

731
			0b010 => fn($x, /** @scrutinizer ignore-unused */ $y):int => $x % 3,

This check looks for parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
732
			0b011 => fn($x, $y):int => ($x + $y) % 3,
733
			0b100 => fn($x, $y):int => ((int)($y / 2) + (int)($x / 3)) % 2,
734
			0b101 => fn($x, $y):int => (($x * $y) % 2) + (($x * $y) % 3),
735
			0b110 => fn($x, $y):int => ((($x * $y) % 2) + (($x * $y) % 3)) % 2,
736
			0b111 => fn($x, $y):int => ((($x * $y) % 3) + (($x + $y) % 2)) % 2,
737
		][$maskPattern];
738
	}
739
740
}
741