Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 77 |
|
0.00% |
0 / 3 |
CRAP | |
0.00% |
0 / 1 |
Skew | |
0.00% |
0 / 77 |
|
0.00% |
0 / 3 |
1190 | |
0.00% |
0 / 1 |
autoRotate | |
0.00% |
0 / 43 |
|
0.00% |
0 / 1 |
306 | |||
rotatePixelMatrix | |
0.00% |
0 / 14 |
|
0.00% |
0 / 1 |
20 | |||
getNearestValue | |
0.00% |
0 / 20 |
|
0.00% |
0 / 1 |
182 |
1 | <?php |
2 | /** |
3 | * Jingga |
4 | * |
5 | * PHP Version 8.1 |
6 | * |
7 | * @package phpOMS\Image |
8 | * @copyright Dennis Eichhorn |
9 | * @license OMS License 2.0 |
10 | * @version 1.0.0 |
11 | * @link https://jingga.app |
12 | */ |
13 | declare(strict_types=1); |
14 | |
15 | namespace phpOMS\Image; |
16 | |
17 | /** |
18 | * Skew image |
19 | * |
20 | * @package phpOMS\Image |
21 | * @license OMS License 2.0 |
22 | * @link https://jingga.app |
23 | * @since 1.0.0 |
24 | */ |
25 | final class Skew |
26 | { |
27 | /** |
28 | * Automatically rotate image based on projection profile |
29 | * |
30 | * @param string $inPath Binary input image (black/white) |
31 | * @param string $outPath Output image |
32 | * @param int $maxDegree Max degree to consider for rotation |
33 | * @param array $start Start coordinates for analysis (e.g. ignore top/border of image) |
34 | * @param array $end End coordinates for analysis (e.g. ignore bottom/border of image) |
35 | */ |
36 | public static function autoRotate(string $inPath, string $outPath, int $maxDegree = 45, array $start = [], array $end = []) : void |
37 | { |
38 | $im = null; |
39 | if (\strripos($inPath, 'png') !== false) { |
40 | $im = \imagecreatefrompng($inPath); |
41 | } elseif (\strripos($inPath, 'jpg') !== false || \strripos($inPath, 'jpeg') !== false) { |
42 | $im = \imagecreatefromjpeg($inPath); |
43 | } else { |
44 | $im = \imagecreatefromgif($inPath); |
45 | } |
46 | |
47 | if ($im === false) { |
48 | return; |
49 | } |
50 | |
51 | $dim = [\imagesx($im), \imagesy($im)]; |
52 | |
53 | $start = [\max(0, $start[0] ?? 0), \max(0, $start[1] ?? 0)]; |
54 | $end = [\min($dim[0], $end[0] ?? $dim[0]), \min($dim[1], $end[1] ?? $dim[1])]; |
55 | |
56 | // Pixelmatrix [width][height] |
57 | // This is important since it makes the hist calculation further down easier |
58 | $imMatrix = [[]]; |
59 | |
60 | $avg = 0; |
61 | |
62 | for ($i = $start[0]; $i < $end[0]; ++$i) { |
63 | for ($j = $start[1]; $j < $end[1]; ++$j) { |
64 | $imMatrix[$j - $start[1]][$i - $start[0]] = \imagecolorat($im, $i, $j) < 0.5 ? 1 : 0; |
65 | $avg += $imMatrix[$j - $start[1]][$i - $start[0]]; |
66 | } |
67 | } |
68 | |
69 | $avg /= $start[1] - $end[1]; |
70 | |
71 | $dimImMatrix = [\count($imMatrix), \count($imMatrix[0])]; |
72 | $bestScore = 0; |
73 | $bestDegree = 0; |
74 | |
75 | for ($i = -$maxDegree; $i < $maxDegree; ++$i) { |
76 | if ($i === 0) { |
77 | continue; |
78 | } |
79 | |
80 | $rotated = self::rotatePixelMatrix($imMatrix, $dimImMatrix, $i); |
81 | $hist = []; |
82 | |
83 | for ($j = 0; $j < $dimImMatrix[0]; ++$j) { |
84 | $hist[$j] = \array_sum($rotated[$j]); |
85 | |
86 | // cleanup for score function |
87 | // we want to see how many lines are above avg. and how much they are above avg. |
88 | // a different score function may not need this line |
89 | $hist[$j] = $hist[$j] > $avg ? $hist[$j] : 0; |
90 | } |
91 | |
92 | $score = \array_sum($hist); |
93 | if ($bestScore < $score) { |
94 | $bestScore = $score; |
95 | $bestDegree = $i; |
96 | } |
97 | } |
98 | |
99 | $im = \imagerotate($im, $bestDegree, 1); |
100 | if ($im === false) { |
101 | return; |
102 | } |
103 | |
104 | if (\strripos($outPath, 'png') !== false) { |
105 | \imagepng($im, $outPath); |
106 | } elseif (\strripos($outPath, 'jpg') !== false || \strripos($outPath, 'jpeg') !== false) { |
107 | \imagejpeg($im, $outPath); |
108 | } else { |
109 | \imagegif($im, $outPath); |
110 | } |
111 | |
112 | \imagedestroy($im); |
113 | } |
114 | |
115 | /** |
116 | * Rotate the pixel matrix by a certain degree |
117 | * |
118 | * @param array $pixel Pixel matrix (0 index = y, 1 index = x) |
119 | * @param array $dim Matrix dimension (0 index = y, 1 index = x) |
120 | * @param int $deg Degree to rotate |
121 | * |
122 | * @return array |
123 | * |
124 | * @since 1.0.0 |
125 | */ |
126 | public static function rotatePixelMatrix(array $pixel, array $dim, int $deg) : array |
127 | { |
128 | $rad = \deg2rad($deg); |
129 | |
130 | $sin = \sin(-$rad); |
131 | $cos = \cos(-$rad); |
132 | |
133 | $rotated = [[]]; |
134 | |
135 | $cXArr = []; |
136 | for ($j = 0; $j < $dim[1]; ++$j) { |
137 | $cXArr[] = $j - $dim[1] / 2.0; // center |
138 | } |
139 | |
140 | for ($i = 0; $i < $dim[0]; ++$i) { |
141 | $cY = $i - $dim[0] / 2.0; // center |
142 | |
143 | foreach ($cXArr as $j => $cX) { |
144 | $x = $cos * $cX + $sin * $cY + $dim[1] / 2.0; |
145 | $y = -$sin * $cX + $cos * $cY + $dim[0] / 2.0; |
146 | |
147 | $rotated[$i][$j] = self::getNearestValue($pixel, $dim, $x, $y); |
148 | } |
149 | } |
150 | |
151 | return $rotated; |
152 | } |
153 | |
154 | /** |
155 | * Find the closes pixel based on floating points |
156 | * |
157 | * @param array $pixel Pixel matrix (0 index = y, 1 index = x) |
158 | * @param array $dim Matrix dimension (0 index = y, 1 index = x) |
159 | * @param float $x X coordinate |
160 | * @param float $y Y coordinate |
161 | * |
162 | * @return int |
163 | * |
164 | * @since 1.0.0 |
165 | */ |
166 | private static function getNearestValue(array $pixel, array $dim, float $x, float $y) : int |
167 | { |
168 | $xLow = ($x < 0) ? 0 : (($x > ($dim[1] - 1)) ? ($dim[1] - 1) : (int) $x); |
169 | $xHigh = ($xLow === $dim[1] - 1) ? $xLow : ($xLow + 1); |
170 | |
171 | $yLow = ($y < 0) ? 0 : (($y > ($dim[0] - 1)) ? ($dim[0] - 1) : (int) $y); |
172 | $yHigh = ($yLow === $dim[0] - 1) ? $yLow : ($yLow + 1); |
173 | |
174 | $points = [ |
175 | [$xLow, $yLow], |
176 | [$xLow, $yHigh], |
177 | [$xHigh, $yLow], |
178 | [$xHigh, $yHigh], |
179 | ]; |
180 | |
181 | $minDistance = \PHP_FLOAT_MAX; |
182 | $minValue = 0; |
183 | |
184 | foreach ($points as $point) { |
185 | $distance = ($point[0] - $x) * ($point[0] - $x) + ($point[1] - $y) * ($point[1] - $y); |
186 | |
187 | if ($distance < $minDistance) { |
188 | $minDistance = $distance; |
189 | |
190 | $minValue = $point[0] >= 0 && $point[0] < $dim[0] && $point[1] >= 0 && $point[1] < $dim[1] |
191 | ? $pixel[$point[1]][$point[0]] |
192 | : 0; |
193 | } |
194 | } |
195 | |
196 | return $minValue; |
197 | } |
198 | } |