Passed
Push — v5 ( 33c1e2...f092e8 )
by smiley
01:52
created

MaskPatternTester::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 2
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 1
dl 0
loc 2
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Class MaskPatternTester
4
 *
5
 * @created      22.11.2017
6
 * @author       Smiley <[email protected]>
7
 * @copyright    2017 Smiley
8
 * @license      MIT
9
 *
10
 * @noinspection PhpUnused
11
 */
12
13
namespace chillerlan\QRCode\Common;
14
15
use chillerlan\QRCode\Data\QRData;
16
use function abs, array_search, call_user_func_array, min;
17
18
/**
19
 * Receives a QRData object and runs the mask pattern tests on it.
20
 *
21
 * ISO/IEC 18004:2000 Section 8.8.2 - Evaluation of masking results
22
 *
23
 * @see http://www.thonky.com/qr-code-tutorial/data-masking
24
 */
25
final class MaskPatternTester{
26
27
	/**
28
	 * The data interface that contains the data matrix to test
29
	 */
30
	private QRData $qrData;
31
32
	/**
33
	 * Receives the QRData object
34
	 *
35
	 * @see \chillerlan\QRCode\QROptions::$maskPattern
36
	 * @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
37
	 */
38
	public function __construct(QRData $qrData){
39
		$this->qrData = $qrData;
40
	}
41
42
	/**
43
	 * shoves a QRMatrix through the MaskPatternTester to find the lowest penalty mask pattern
44
	 *
45
	 * @see \chillerlan\QRCode\Data\MaskPatternTester
46
	 */
47
	public function getBestMaskPattern():MaskPattern{
48
		$penalties = [];
49
50
		foreach(MaskPattern::PATTERNS as $pattern){
51
			$penalties[$pattern] = $this->testPattern(new MaskPattern($pattern));
52
		}
53
54
		return new MaskPattern(array_search(min($penalties), $penalties, true));
0 ignored issues
show
Bug introduced by
It seems like array_search(min($penalties), $penalties, true) can also be of type string; however, parameter $maskPattern of chillerlan\QRCode\Common...kPattern::__construct() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

54
		return new MaskPattern(/** @scrutinizer ignore-type */ array_search(min($penalties), $penalties, true));
Loading history...
55
	}
56
57
	/**
58
	 * Returns the penalty for the given mask pattern
59
	 *
60
	 * @see \chillerlan\QRCode\QROptions::$maskPattern
61
	 * @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
62
	 */
63
	public function testPattern(MaskPattern $pattern):int{
64
		$matrix  = $this->qrData->writeMatrix($pattern, true);
65
		$penalty = 0;
66
67
		for($level = 1; $level <= 4; $level++){
68
			$penalty += call_user_func_array([$this, 'testLevel'.$level], [$matrix->matrix(true), $matrix->size()]);
69
		}
70
71
		return (int)$penalty;
72
	}
73
74
	/**
75
	 * Checks for each group of five or more same-colored modules in a row (or column)
76
	 */
77
	private function testLevel1(array $m, int $size):int{
78
		$penalty = 0;
79
80
		foreach($m as $y => $row){
81
			foreach($row as $x => $val){
82
				$count = 0;
83
84
				for($ry = -1; $ry <= 1; $ry++){
85
86
					if($y + $ry < 0 || $size <= $y + $ry){
87
						continue;
88
					}
89
90
					for($rx = -1; $rx <= 1; $rx++){
91
92
						if(($ry === 0 && $rx === 0) || (($x + $rx) < 0 || $size <= ($x + $rx))){
93
							continue;
94
						}
95
96
						if($m[$y + $ry][$x + $rx] === $val){
97
							$count++;
98
						}
99
100
					}
101
				}
102
103
				if($count > 5){
104
					$penalty += (3 + $count - 5);
105
				}
106
107
			}
108
		}
109
110
		return $penalty;
111
	}
112
113
	/**
114
	 * Checks for each 2x2 area of same-colored modules in the matrix
115
	 */
116
	private function testLevel2(array $m, int $size):int{
117
		$penalty = 0;
118
119
		foreach($m as $y => $row){
120
121
			if($y > $size - 2){
122
				break;
123
			}
124
125
			foreach($row as $x => $val){
126
127
				if($x > $size - 2){
128
					break;
129
				}
130
131
				if(
132
					   $val === $m[$y][$x + 1]
133
					&& $val === $m[$y + 1][$x]
134
					&& $val === $m[$y + 1][$x + 1]
135
				){
136
					$penalty++;
137
				}
138
			}
139
		}
140
141
		return 3 * $penalty;
142
	}
143
144
	/**
145
	 * Checks if there are patterns that look similar to the finder patterns (1:1:3:1:1 ratio)
146
	 */
147
	private function testLevel3(array $m, int $size):int{
148
		$penalties = 0;
149
150
		foreach($m as $y => $row){
151
			foreach($row as $x => $val){
152
153
				if(
154
					$x + 6 < $size
155
					&&  $val
156
					&& !$m[$y][$x + 1]
157
					&&  $m[$y][$x + 2]
158
					&&  $m[$y][$x + 3]
159
					&&  $m[$y][$x + 4]
160
					&& !$m[$y][$x + 5]
161
					&&  $m[$y][$x + 6]
162
				){
163
					$penalties++;
164
				}
165
166
				if(
167
					$y + 6 < $size
168
					&&  $val
169
					&& !$m[$y + 1][$x]
170
					&&  $m[$y + 2][$x]
171
					&&  $m[$y + 3][$x]
172
					&&  $m[$y + 4][$x]
173
					&& !$m[$y + 5][$x]
174
					&&  $m[$y + 6][$x]
175
				){
176
					$penalties++;
177
				}
178
179
			}
180
		}
181
182
		return $penalties * 40;
183
	}
184
185
	/**
186
	 * Checks if more than half of the modules are dark or light, with a larger penalty for a larger difference
187
	 */
188
	private function testLevel4(array $m, int $size):float{
189
		$count = 0;
190
191
		foreach($m as $y => $row){
192
			foreach($row as $x => $val){
193
				if($val){
194
					$count++;
195
				}
196
			}
197
		}
198
199
		return (abs(100 * $count / $size / $size - 50) / 5) * 10;
200
	}
201
202
}
203