Passed
Push — v5 ( 93618e...84eb31 )
by smiley
01:52
created

GridSampler::checkAndNudgePoints()   D

Complexity

Conditions 21
Paths 111

Size

Total Lines 61
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 21
eloc 40
c 1
b 0
f 0
nc 111
nop 2
dl 0
loc 61
rs 4.075

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * Class GridSampler
4
 *
5
 * @created      17.01.2021
6
 * @author       ZXing Authors
7
 * @author       Smiley <[email protected]>
8
 * @copyright    2021 Smiley
9
 * @license      Apache-2.0
10
 */
11
12
namespace chillerlan\QRCode\Detector;
13
14
use Exception, RuntimeException;
15
use chillerlan\QRCode\Decoder\BitMatrix;
16
use function array_fill, count, sprintf;
17
18
/**
19
 * Implementations of this class can, given locations of finder patterns for a QR code in an
20
 * image, sample the right points in the image to reconstruct the QR code, accounting for
21
 * perspective distortion. It is abstracted since it is relatively expensive and should be allowed
22
 * to take advantage of platform-specific optimized implementations, like Sun's Java Advanced
23
 * Imaging library, but which may not be available in other environments such as J2ME, and vice
24
 * versa.
25
 *
26
 * The implementation used can be controlled by calling {@link #setGridSampler(GridSampler)}
27
 * with an instance of a class which implements this interface.
28
 *
29
 * @author Sean Owen
30
 */
31
final class GridSampler{
32
33
	/**
34
	 * <p>Checks a set of points that have been transformed to sample points on an image against
35
	 * the image's dimensions to see if the point are even within the image.</p>
36
	 *
37
	 * <p>This method will actually "nudge" the endpoints back onto the image if they are found to be
38
	 * barely (less than 1 pixel) off the image. This accounts for imperfect detection of finder
39
	 * patterns in an image where the QR Code runs all the way to the image border.</p>
40
	 *
41
	 * <p>For efficiency, the method will check points from either end of the line until one is found
42
	 * to be within the image. Because the set of points are assumed to be linear, this is valid.</p>
43
	 *
44
	 * @param \chillerlan\QRCode\Decoder\BitMatrix $bitMatrix image into which the points should map
45
	 * @param float[]                  $points    actual points in x1,y1,...,xn,yn form
46
	 *
47
	 * @throws \RuntimeException if an endpoint is lies outside the image boundaries
48
	 */
49
	private function checkAndNudgePoints(BitMatrix $bitMatrix, array $points):void{
50
		$dimension = $bitMatrix->getDimension();
51
		$nudged    = true;
52
		$max       = count($points);
53
54
		// Check and nudge points from start until we see some that are OK:
55
		for($offset = 0; $offset < $max && $nudged; $offset += 2){
56
			$x = (int)$points[$offset];
57
			$y = (int)$points[$offset + 1];
58
59
			if($x < -1 || $x > $dimension || $y < -1 || $y > $dimension){
60
				throw new RuntimeException(sprintf('checkAndNudgePoints 1, x: %s, y: %s, d: %s', $x, $y, $dimension));
61
			}
62
63
			$nudged = false;
64
65
			if($x === -1){
66
				$points[$offset] = 0.0;
67
				$nudged          = true;
68
			}
69
			elseif($x === $dimension){
70
				$points[$offset] = $dimension - 1;
71
				$nudged          = true;
72
			}
73
			if($y === -1){
74
				$points[$offset + 1] = 0.0;
75
				$nudged              = true;
76
			}
77
			elseif($y === $dimension){
78
				$points[$offset + 1] = $dimension - 1;
79
				$nudged              = true;
80
			}
81
		}
82
		// Check and nudge points from end:
83
		$nudged = true;
84
85
		for($offset = count($points) - 2; $offset >= 0 && $nudged; $offset -= 2){
86
			$x = (int)$points[$offset];
87
			$y = (int)$points[$offset + 1];
88
89
			if($x < -1 || $x > $dimension || $y < -1 || $y > $dimension){
90
				throw new RuntimeException(sprintf('checkAndNudgePoints 2, x: %s, y: %s, d: %s', $x, $y, $dimension));
91
			}
92
93
			$nudged = false;
94
95
			if($x === -1){
96
				$points[$offset] = 0.0;
97
				$nudged          = true;
98
			}
99
			elseif($x === $dimension){
100
				$points[$offset] = $dimension - 1;
101
				$nudged          = true;
102
			}
103
			if($y === -1){
104
				$points[$offset + 1] = 0.0;
105
				$nudged              = true;
106
			}
107
			elseif($y === $dimension){
108
				$points[$offset + 1] = $dimension - 1;
109
				$nudged              = true;
110
			}
111
		}
112
	}
113
114
	/**
115
	 * Samples an image for a rectangular matrix of bits of the given dimension. The sampling
116
	 * transformation is determined by the coordinates of 4 points, in the original and transformed
117
	 * image space.
118
	 *
119
	 * @return \chillerlan\QRCode\Decoder\BitMatrix representing a grid of points sampled from the image within a region
120
	 *   defined by the "from" parameters
121
	 * @throws \RuntimeException if image can't be sampled, for example, if the transformation defined
122
	 *   by the given points is invalid or results in sampling outside the image boundaries
123
	 */
124
	public function sampleGrid(BitMatrix $image, int $dimension, PerspectiveTransform $transform):BitMatrix{
125
126
		if($dimension <= 0){
127
			throw new RuntimeException('invalid matrix size');
128
		}
129
130
		$bits   = new BitMatrix($dimension);
131
		$points = array_fill(0, 2 * $dimension, 0.0);
132
133
		for($y = 0; $y < $dimension; $y++){
134
			$max    = count($points);
135
			$iValue = (float)$y + 0.5;
136
137
			for($x = 0; $x < $max; $x += 2){
138
				$points[$x]     = (float)($x / 2) + 0.5;
139
				$points[$x + 1] = $iValue;
140
			}
141
142
			$transform->transformPoints($points);
143
			// Quick check to see if points transformed to something inside the image;
144
			// sufficient to check the endpoints
145
			$this->checkAndNudgePoints($image, $points);
146
147
			try{
148
				for($x = 0; $x < $max; $x += 2){
149
					if($image->get((int)$points[$x], (int)$points[$x + 1])){
150
						// Black(-ish) pixel
151
						$bits->set($x / 2, $y);
152
					}
153
				}
154
			}
155
			catch(Exception $aioobe){//ArrayIndexOutOfBoundsException
156
				// This feels wrong, but, sometimes if the finder patterns are misidentified, the resulting
157
				// transform gets "twisted" such that it maps a straight line of points to a set of points
158
				// whose endpoints are in bounds, but others are not. There is probably some mathematical
159
				// way to detect this about the transformation that I don't know yet.
160
				// This results in an ugly runtime exception despite our clever checks above -- can't have
161
				// that. We could check each point's coordinates but that feels duplicative. We settle for
162
				// catching and wrapping ArrayIndexOutOfBoundsException.
163
				throw new RuntimeException('ArrayIndexOutOfBoundsException');
164
			}
165
166
		}
167
168
		return $bits;
169
	}
170
171
}
172