Passed
Push — master ( 552d15...d4c1db )
by smiley
02:51
created

MaskPatternTester::getBestMaskPattern()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 0
dl 0
loc 8
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Class MaskPatternTester
4
 *
5
 * @filesource   MaskPatternTester.php
6
 * @created      22.11.2017
7
 * @package      chillerlan\QRCode\Data
8
 * @author       Smiley <[email protected]>
9
 * @copyright    2017 Smiley
10
 * @license      MIT
11
 *
12
 * @noinspection PhpUnused
13
 */
14
15
namespace chillerlan\QRCode\Data;
16
17
use function abs, array_search, call_user_func_array, min;
18
19
/**
20
 * Receives a QRDataInterface object and runs the mask pattern tests on it.
21
 *
22
 * ISO/IEC 18004:2000 Section 8.8.2 - Evaluation of masking results
23
 *
24
 * @see http://www.thonky.com/qr-code-tutorial/data-masking
25
 */
26
final class MaskPatternTester{
27
28
	/**
29
	 * The data interface that contains the data matrix to test
30
	 */
31
	protected QRDataInterface $dataInterface;
32
33
	/**
34
	 * Receives the QRDataInterface
35
	 *
36
	 * @see \chillerlan\QRCode\QROptions::$maskPattern
37
	 * @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
38
	 */
39
	public function __construct(QRDataInterface $dataInterface){
40
		$this->dataInterface = $dataInterface;
41
	}
42
43
	/**
44
	 * shoves a QRMatrix through the MaskPatternTester to find the lowest penalty mask pattern
45
	 *
46
	 * @see \chillerlan\QRCode\Data\MaskPatternTester
47
	 */
48
	public function getBestMaskPattern():int{
49
		$penalties = [];
50
51
		for($pattern = 0; $pattern < 8; $pattern++){
52
			$penalties[$pattern] = $this->testPattern($pattern);
53
		}
54
55
		return array_search(min($penalties), $penalties, true);
56
	}
57
58
	/**
59
	 * Returns the penalty for the given mask pattern
60
	 *
61
	 * @see \chillerlan\QRCode\QROptions::$maskPattern
62
	 * @see \chillerlan\QRCode\Data\QRMatrix::$maskPattern
63
	 */
64
	public function testPattern(int $pattern):int{
65
		$matrix  = $this->dataInterface->initMatrix($pattern, true);
66
		$penalty = 0;
67
68
		for($level = 1; $level <= 4; $level++){
69
			$penalty += call_user_func_array([$this, 'testLevel'.$level], [$matrix, $matrix->size()]);
70
		}
71
72
		return (int)$penalty;
73
	}
74
75
	/**
76
	 * Checks for each group of five or more same-colored modules in a row (or column)
77
	 */
78
	protected function testLevel1(QRMatrix $m, int $size):float{
0 ignored issues
show
Bug introduced by
The type chillerlan\QRCode\Data\QRMatrix was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
79
		$penalty = 0;
80
81
		foreach($m->matrix() as $y => $row){
82
			foreach($row as $x => $val){
83
				$count = 0;
84
85
				for($ry = -1; $ry <= 1; $ry++){
86
87
					if($y + $ry < 0 || $size <= $y + $ry){
88
						continue;
89
					}
90
91
					for($rx = -1; $rx <= 1; $rx++){
92
93
						if(($ry === 0 && $rx === 0) || (($x + $rx) < 0 || $size <= ($x + $rx))){
94
							continue;
95
						}
96
97
						if($m->check($x + $rx, $y + $ry) === (($val >> 8) > 0)){
98
							$count++;
99
						}
100
101
					}
102
				}
103
104
				if($count > 5){
105
					$penalty += (3 + $count - 5);
106
				}
107
108
			}
109
		}
110
111
		return $penalty;
112
	}
113
114
	/**
115
	 * Checks for each 2x2 area of same-colored modules in the matrix
116
	 */
117
	protected function testLevel2(QRMatrix $m, int $size):float{
118
		$penalty = 0;
119
120
		foreach($m->matrix() as $y => $row){
121
122
			if($y > $size - 2){
123
				break;
124
			}
125
126
			foreach($row as $x => $val){
127
128
				if($x > $size - 2){
129
					break;
130
				}
131
132
				$count = 0;
133
134
				if($val >> 8 > 0){
135
					$count++;
136
				}
137
138
				if($m->check($y, $x + 1)){
139
					$count++;
140
				}
141
142
				if($m->check($y + 1, $x)){
143
					$count++;
144
				}
145
146
				if($m->check($y + 1, $x + 1)){
147
					$count++;
148
				}
149
150
				if($count === 0 || $count === 4){
151
					$penalty += 3;
152
				}
153
154
			}
155
		}
156
157
		return $penalty;
158
	}
159
160
	/**
161
	 * Checks if there are patterns that look similar to the finder patterns (1:1:3:1:1 ratio)
162
	 */
163
	protected function testLevel3(QRMatrix $m, int $size):float{
164
		$penalty = 0;
165
166
		foreach($m->matrix() as $y => $row){
167
			foreach($row as $x => $val){
168
169
				if($x <= $size - 7){
170
					if(
171
						    $m->check($x    , $y)
172
						&& !$m->check($x + 1, $y)
173
						&&  $m->check($x + 2, $y)
174
						&&  $m->check($x + 3, $y)
175
						&&  $m->check($x + 4, $y)
176
						&& !$m->check($x + 5, $y)
177
						&&  $m->check($x + 6, $y)
178
					){
179
						$penalty += 40;
180
					}
181
				}
182
183
				if($y <= $size - 7){
184
					if(
185
						    $m->check($x, $y)
186
						&& !$m->check($x, $y + 1)
187
						&&  $m->check($x, $y + 2)
188
						&&  $m->check($x, $y + 3)
189
						&&  $m->check($x, $y + 4)
190
						&& !$m->check($x, $y + 5)
191
						&&  $m->check($x, $y + 6)
192
					){
193
						$penalty += 40;
194
					}
195
				}
196
197
			}
198
		}
199
200
		return $penalty;
201
	}
202
203
	/**
204
	 * Checks if more than half of the modules are dark or light, with a larger penalty for a larger difference
205
	 */
206
	protected function testLevel4(QRMatrix $m, int $size):float{
207
		$count = 0;
208
209
		foreach($m->matrix() as $y => $row){
210
			foreach($row as $x => $val){
211
				if(($val >> 8) > 0){
212
					$count++;
213
				}
214
			}
215
		}
216
217
		return (abs(100 * $count / $size / $size - 50) / 5) * 10;
218
	}
219
220
}
221