Passed
Push — v5 ( 6b9eea...33c1e2 )
by smiley
09:46
created

PerspectiveTransform::transformPoints()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 22
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 15
c 1
b 0
f 0
nc 4
nop 2
dl 0
loc 22
rs 9.7666
1
<?php
2
/**
3
 * Class PerspectiveTransform
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 function count;
15
16
/**
17
 * <p>This class implements a perspective transform in two dimensions. Given four source and four
18
 * destination points, it will compute the transformation implied between them. The code is based
19
 * directly upon section 3.4.2 of George Wolberg's "Digital Image Warping"; see pages 54-56.</p>
20
 *
21
 * @author Sean Owen
22
 */
23
final class PerspectiveTransform{
24
25
	private float $a11;
26
	private float $a12;
27
	private float $a13;
28
	private float $a21;
29
	private float $a22;
30
	private float $a23;
31
	private float $a31;
32
	private float $a32;
33
	private float $a33;
34
35
	private function set(
36
		float $a11, float $a21, float $a31,
37
		float $a12, float $a22, float $a32,
38
		float $a13, float $a23, float $a33
39
	):PerspectiveTransform{
40
		$this->a11 = $a11;
41
		$this->a12 = $a12;
42
		$this->a13 = $a13;
43
		$this->a21 = $a21;
44
		$this->a22 = $a22;
45
		$this->a23 = $a23;
46
		$this->a31 = $a31;
47
		$this->a32 = $a32;
48
		$this->a33 = $a33;
49
50
		return $this;
51
	}
52
53
	public function quadrilateralToQuadrilateral(
54
		float $x0, float $y0, float $x1, float $y1, float $x2, float $y2, float $x3, float $y3,
55
		float $x0p, float $y0p, float $x1p, float $y1p, float $x2p, float $y2p, float $x3p, float $y3p
56
	):PerspectiveTransform{
57
		return (new self)
58
			->squareToQuadrilateral($x0p, $y0p, $x1p, $y1p, $x2p, $y2p, $x3p, $y3p)
59
			->times($this->quadrilateralToSquare($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3));
60
	}
61
62
	private function quadrilateralToSquare(
63
		float $x0, float $y0, float $x1, float $y1,
64
		float $x2, float $y2, float $x3, float $y3
65
	):PerspectiveTransform{
66
		// Here, the adjoint serves as the inverse:
67
		return $this
68
			->squareToQuadrilateral($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3)
69
			->buildAdjoint();
70
	}
71
72
	private function buildAdjoint():PerspectiveTransform{
73
		// Adjoint is the transpose of the cofactor matrix:
74
		return $this->set(
75
			$this->a22 * $this->a33 - $this->a23 * $this->a32,
76
			$this->a23 * $this->a31 - $this->a21 * $this->a33,
77
			$this->a21 * $this->a32 - $this->a22 * $this->a31,
78
			$this->a13 * $this->a32 - $this->a12 * $this->a33,
79
			$this->a11 * $this->a33 - $this->a13 * $this->a31,
80
			$this->a12 * $this->a31 - $this->a11 * $this->a32,
81
			$this->a12 * $this->a23 - $this->a13 * $this->a22,
82
			$this->a13 * $this->a21 - $this->a11 * $this->a23,
83
			$this->a11 * $this->a22 - $this->a12 * $this->a21
84
		);
85
	}
86
87
	private function squareToQuadrilateral(
88
		float $x0, float $y0, float $x1, float $y1,
89
		float $x2, float $y2, float $x3, float $y3
90
	):PerspectiveTransform{
91
		$dx3 = $x0 - $x1 + $x2 - $x3;
92
		$dy3 = $y0 - $y1 + $y2 - $y3;
93
94
		if($dx3 === 0.0 && $dy3 === 0.0){
0 ignored issues
show
introduced by
The condition $dx3 === 0.0 is always false.
Loading history...
95
			// Affine
96
			return $this->set($x1 - $x0, $x2 - $x1, $x0, $y1 - $y0, $y2 - $y1, $y0, 0.0, 0.0, 1.0);
97
		}
98
99
		$dx1         = $x1 - $x2;
100
		$dx2         = $x3 - $x2;
101
		$dy1         = $y1 - $y2;
102
		$dy2         = $y3 - $y2;
103
		$denominator = $dx1 * $dy2 - $dx2 * $dy1;
104
		$a13         = ($dx3 * $dy2 - $dx2 * $dy3) / $denominator;
105
		$a23         = ($dx1 * $dy3 - $dx3 * $dy1) / $denominator;
106
107
		return $this->set(
108
			$x1 - $x0 + $a13 * $x1, $x3 - $x0 + $a23 * $x3, $x0,
109
			$y1 - $y0 + $a13 * $y1, $y3 - $y0 + $a23 * $y3, $y0,
110
			$a13, $a23, 1.0
111
		);
112
	}
113
114
	private function times(PerspectiveTransform $other):PerspectiveTransform{
115
		return $this->set(
116
			$this->a11 * $other->a11 + $this->a21 * $other->a12 + $this->a31 * $other->a13,
117
			$this->a11 * $other->a21 + $this->a21 * $other->a22 + $this->a31 * $other->a23,
118
			$this->a11 * $other->a31 + $this->a21 * $other->a32 + $this->a31 * $other->a33,
119
			$this->a12 * $other->a11 + $this->a22 * $other->a12 + $this->a32 * $other->a13,
120
			$this->a12 * $other->a21 + $this->a22 * $other->a22 + $this->a32 * $other->a23,
121
			$this->a12 * $other->a31 + $this->a22 * $other->a32 + $this->a32 * $other->a33,
122
			$this->a13 * $other->a11 + $this->a23 * $other->a12 + $this->a33 * $other->a13,
123
			$this->a13 * $other->a21 + $this->a23 * $other->a22 + $this->a33 * $other->a23,
124
			$this->a13 * $other->a31 + $this->a23 * $other->a32 + $this->a33 * $other->a33
125
		);
126
	}
127
128
	public function transformPoints(array &$xValues, array &$yValues = null):void{
129
		$max = count($xValues);
130
131
		if($yValues !== null){
132
133
			for($i = 0; $i < $max; $i++){
134
				$x           = $xValues[$i];
135
				$y           = $yValues[$i];
136
				$denominator = $this->a13 * $x + $this->a23 * $y + $this->a33;
137
				$xValues[$i] = ($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator;
138
				$yValues[$i] = ($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator;
139
			}
140
141
			return;
142
		}
143
144
		for($i = 0; $i < $max; $i += 2){
145
			$x               = $xValues[$i];
146
			$y               = $xValues[$i + 1];
147
			$denominator     = $this->a13 * $x + $this->a23 * $y + $this->a33;
148
			$xValues[$i]     = ($this->a11 * $x + $this->a21 * $y + $this->a31) / $denominator;
149
			$xValues[$i + 1] = ($this->a12 * $x + $this->a22 * $y + $this->a32) / $denominator;
150
		}
151
	}
152
153
}
154