1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of the IrishDan\ResponsiveImageBundle package. |
4
|
|
|
* |
5
|
|
|
* (c) Daniel Byrne <[email protected]> |
6
|
|
|
* |
7
|
|
|
* For the full copyright and license information, please view the LICENSE file that was distributed with this source |
8
|
|
|
* code. |
9
|
|
|
*/ |
10
|
|
|
|
11
|
|
|
namespace IrishDan\ResponsiveImageBundle\ImageProcessing; |
12
|
|
|
|
13
|
|
|
/** |
14
|
|
|
* Class FocusCropDataCalculator |
15
|
|
|
* |
16
|
|
|
* @package IrishDan\ResponsiveImageBundle\ImageProcessing |
17
|
|
|
*/ |
18
|
|
|
class FocusCropDataCalculator |
19
|
|
|
{ |
20
|
|
|
private $cropCoordinates; |
21
|
|
|
private $focusCoordinates; |
22
|
|
|
private $styleWidth; |
23
|
|
|
private $styleHeight; |
24
|
|
|
private $geometry; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* FocusCropDataCalculator constructor. |
28
|
|
|
* |
29
|
|
|
* @param $cropCoordinates |
30
|
|
|
* @param $focusCoordinates |
31
|
|
|
* @param $styleWidth |
32
|
|
|
* @param $styleHeight |
33
|
|
|
*/ |
34
|
|
|
public function __construct($cropCoordinates, $focusCoordinates, $styleWidth, $styleHeight) |
35
|
|
|
{ |
36
|
|
|
$this->cropCoordinates = $cropCoordinates; |
37
|
|
|
$this->focusCoordinates = $focusCoordinates; |
38
|
|
|
$this->styleWidth = $styleWidth; |
39
|
|
|
$this->styleHeight = $styleHeight; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
/** |
43
|
|
|
* @return array |
44
|
|
|
*/ |
45
|
|
|
public function getFocusCropData() |
46
|
|
|
{ |
47
|
|
|
// If there is a focus rectangle, |
48
|
|
|
// We have the image shape (or crop rectangle), |
49
|
|
|
// and we have the the final image style rectangle. |
50
|
|
|
// |
51
|
|
|
// The image style shape should be as large as possible so, |
52
|
|
|
// there are three possibilities: |
53
|
|
|
// 1: The style rectangle fits inside the crop rectangle vertically. |
54
|
|
|
// The sides of the image will be cropped. |
55
|
|
|
// 2: The style rectangle fits inside the crop rectangle horizontally. |
56
|
|
|
// The top and bottom of the image will be cropped. |
57
|
|
|
// 3: The style rectangle fits inside the crop rectangle exactly. |
58
|
|
|
// no cropping in required |
59
|
|
|
// |
60
|
|
|
// To determine which type of cropping should be used, the aspect-ratio of the image/crop rectangle ($imageAspectRatio) |
61
|
|
|
// and the aspect-ratio of the style ($styleAspectRatio) are compared. |
62
|
|
|
// 1: $imageAspectRatio > $styleAspectRatio |
|
|
|
|
63
|
|
|
// 2: $imageAspectRatio < $styleAspectRatio |
|
|
|
|
64
|
|
|
// 3: $imageAspectRatio === $styleAspectRatio |
|
|
|
|
65
|
|
|
|
66
|
|
|
list($x1, $y1, $x2, $y2) = $this->cropCoordinates; |
67
|
|
|
$this->geometry = new CoordinateGeometry($x1, $y1, $x2, $y2); |
68
|
|
|
|
69
|
|
|
$newWidth = $this->geometry->axisLength('x'); |
70
|
|
|
$newHeight = $this->geometry->axisLength('y'); |
71
|
|
|
|
72
|
|
|
// Find out what type of style crop we are dealing with. |
73
|
|
|
$imageAspectRatio = $this->geometry->getAspectRatio(); |
74
|
|
|
|
75
|
|
|
$styleAspectRatio = $this->styleWidth / $this->styleHeight; |
76
|
|
|
|
77
|
|
|
if ($imageAspectRatio > $styleAspectRatio) { |
78
|
|
|
$axis = 'x'; |
79
|
|
|
} |
80
|
|
|
else { |
81
|
|
|
if ($imageAspectRatio < $styleAspectRatio) { |
82
|
|
|
$axis = 'y'; |
83
|
|
|
} |
84
|
|
|
else { |
85
|
|
|
return [ |
86
|
|
|
'width' => $newWidth, |
87
|
|
|
'height' => $newHeight, |
88
|
|
|
'x' => $this->cropCoordinates[0], |
89
|
|
|
'y' => $this->cropCoordinates[1], |
90
|
|
|
]; |
91
|
|
|
} |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
return $this->calculateAxisCropData($axis, $this->cropCoordinates, $styleAspectRatio, $newWidth, $newHeight); |
95
|
|
|
} |
96
|
|
|
|
97
|
|
|
/** |
98
|
|
|
* @param string $axis |
99
|
|
|
* @param $cropCoordinates |
100
|
|
|
* @param $aspectRatio |
101
|
|
|
* @param $newWidth |
102
|
|
|
* @param $newHeight |
103
|
|
|
* |
104
|
|
|
* @return array |
105
|
|
|
*/ |
106
|
|
|
protected function calculateAxisCropData($axis = 'x', $cropCoordinates, $aspectRatio, $newWidth, $newHeight) |
107
|
|
|
{ |
108
|
|
|
$cropWidth = ($axis == 'y') ? $newWidth : $newHeight * $aspectRatio; |
109
|
|
|
$cropHeight = ($axis == 'y') ? $newWidth / $aspectRatio : $newHeight; |
110
|
|
|
|
111
|
|
|
$cropXOffset = ($axis == 'y') ? $cropCoordinates[0] : $this->getFloatingOffset( |
112
|
|
|
'x', |
113
|
|
|
$cropWidth, |
114
|
|
|
$cropCoordinates[0] |
115
|
|
|
); |
116
|
|
|
$cropYOffset = ($axis == 'y') ? $this->getFloatingOffset( |
117
|
|
|
'y', |
118
|
|
|
$cropHeight, |
119
|
|
|
$cropCoordinates[1] |
120
|
|
|
) : $cropCoordinates[1]; |
121
|
|
|
|
122
|
|
|
return [ |
123
|
|
|
'width' => $cropWidth, |
124
|
|
|
'height' => $cropHeight, |
125
|
|
|
'x' => $cropXOffset, |
126
|
|
|
'y' => $cropYOffset, |
127
|
|
|
]; |
128
|
|
|
} |
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* @param string $axis |
132
|
|
|
* @param $point |
133
|
|
|
* @param $start |
134
|
|
|
* |
135
|
|
|
* @return mixed |
136
|
|
|
*/ |
137
|
|
|
protected function getFloatingOffset($axis = 'y', $point, $start) |
138
|
|
|
{ |
139
|
|
|
$offset = $this->findFocusOffset($axis, $point); |
140
|
|
|
$offset = $offset + $start; |
141
|
|
|
|
142
|
|
|
return $offset; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
/** |
146
|
|
|
* @param $axis |
147
|
|
|
* @param string $type |
148
|
|
|
* |
149
|
|
|
* @return mixed |
150
|
|
|
*/ |
151
|
|
|
protected function getFocusPointForAxis($axis, $type = 'near') |
152
|
|
|
{ |
153
|
|
|
$cropX1 = $this->cropCoordinates[0]; |
154
|
|
|
$cropY1 = $this->cropCoordinates[1]; |
155
|
|
|
$cropX2 = $this->cropCoordinates[2]; |
156
|
|
|
$cropY2 = $this->cropCoordinates[3]; |
157
|
|
|
|
158
|
|
|
$focusX1 = $this->focusCoordinates[0]; |
159
|
|
|
$focusY1 = $this->focusCoordinates[1]; |
160
|
|
|
$focusX2 = $this->focusCoordinates[2]; |
161
|
|
|
$focusY2 = $this->focusCoordinates[3]; |
162
|
|
|
|
163
|
|
|
if ($type == 'near') { |
164
|
|
|
$point = ${'focus' . $axis . '1'} - ${'crop' . $axis . '1'}; |
165
|
|
|
} |
166
|
|
|
else { |
167
|
|
|
$point = ${'focus' . $axis . '2'} - ${'crop' . $axis . '1'}; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
return $point; |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Calculates the offset needed to keep focus rectangle in view with optimal position. |
175
|
|
|
* |
176
|
|
|
* @param string $axis |
177
|
|
|
* @param $cropLength |
178
|
|
|
* |
179
|
|
|
* @return mixed |
180
|
|
|
*/ |
181
|
|
|
protected function findFocusOffset($axis = 'x', $cropLength) |
182
|
|
|
{ |
183
|
|
|
$axis = ucfirst($axis); |
184
|
|
|
|
185
|
|
|
// Get the crop and focus information, sudo iptables -A INPUT -s 142.54.166.218 -j REJECT |
186
|
|
|
// and the length. |
187
|
|
|
$imageLength = $this->geometry->axisLength($axis); |
188
|
|
|
|
189
|
|
|
// If there are no focus coordinates the image should be cropped from center. |
190
|
|
|
if (empty($this->focusCoordinates)) { |
191
|
|
|
return ($imageLength - $cropLength) / 2; |
192
|
|
|
} |
193
|
|
|
|
194
|
|
|
// Offsetting on either the x or the y axis. |
195
|
|
|
// Subtract the crop rectangle. |
196
|
|
|
$focusNear = $this->getFocusPointForAxis($axis, 'near'); |
197
|
|
|
$focusFar = $this->getFocusPointForAxis($axis, 'far'); |
198
|
|
|
|
199
|
|
|
$focusLength = $focusFar - $focusNear; |
200
|
|
|
$focusCenter = round(($focusNear + $focusFar) / 2); |
201
|
|
|
|
202
|
|
|
// There are two possibilities. |
203
|
|
|
// 1: The focus area is longer then the desired crop length. |
204
|
|
|
// In this case we simple center ont he crop area on the the center of the focus area. |
205
|
|
|
// Both sides of the image will be clipped, and both sides of the crop area will be missing a piece. |
206
|
|
|
// 2: In most cases the focus rectangle will fit nicely within the crop area. |
207
|
|
|
// We must find the optimal position to crop from. |
208
|
|
|
$focusType = $focusLength >= $cropLength ? 'in' : 'out'; |
209
|
|
|
|
210
|
|
|
// First case. |
211
|
|
|
if ($focusType == 'in') { |
212
|
|
|
return $focusCenter - ($cropLength / 2); |
213
|
|
|
} // Second case. |
214
|
|
|
else { |
215
|
|
|
// We will find a range of valid offsets, |
216
|
|
|
$validOffsets = $this->getValidOffsets($focusNear, $focusFar, $cropLength, $imageLength); |
217
|
|
|
|
218
|
|
|
// Return the most optimal offset. |
219
|
|
|
return $this->getOptimalOffset($validOffsets); |
220
|
|
|
} |
221
|
|
|
} |
222
|
|
|
|
223
|
|
|
/** |
224
|
|
|
* @param array $validOffsets |
225
|
|
|
* |
226
|
|
|
* @return int|mixed |
227
|
|
|
*/ |
228
|
|
|
protected function getOptimalOffset(array $validOffsets) |
229
|
|
|
{ |
230
|
|
|
$offset = 0; |
231
|
|
|
|
232
|
|
|
if (!empty($validOffsets)) { |
233
|
|
|
asort($validOffsets); |
234
|
|
|
$offsets = array_keys($validOffsets); |
235
|
|
|
$offset = reset($offsets); |
236
|
|
|
} |
237
|
|
|
|
238
|
|
|
return $offset; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* @param $focusNear |
243
|
|
|
* @param $focusFar |
244
|
|
|
* @param $cropLength |
245
|
|
|
* @param $imageLength |
246
|
|
|
* |
247
|
|
|
* @return array |
248
|
|
|
*/ |
249
|
|
|
protected function getValidOffsets($focusNear, $focusFar, $cropLength, $imageLength) |
250
|
|
|
{ |
251
|
|
|
$nearGap = $focusNear; |
252
|
|
|
$farGap = $imageLength - $focusFar; |
253
|
|
|
$offFactor = $nearGap / $farGap; |
254
|
|
|
|
255
|
|
|
// Will need the maximum and minimum offset also. |
256
|
|
|
$maxOffset = $imageLength - $cropLength; |
257
|
|
|
$minOffset = 0; |
258
|
|
|
|
259
|
|
|
$validOffsets = []; |
260
|
|
|
for ($i = $minOffset; $i <= $maxOffset; $i++) { |
261
|
|
|
if ($this->isInBounds($i, $cropLength, $imageLength, $focusNear, $focusFar)) { |
262
|
|
|
// Need a factor of near / far to compare to offFactor. |
263
|
|
|
// Closest to that wins. |
264
|
|
|
$near = $focusNear - $i; |
265
|
|
|
$far = ($i + $cropLength) - $focusFar; |
266
|
|
|
if ($near != 0 && $far != 0) { |
267
|
|
|
$optimalFactor = ($near / $far) / $offFactor; |
268
|
|
|
$optimalFactor = abs($optimalFactor); |
269
|
|
|
|
270
|
|
|
$theTest = abs($optimalFactor - 1); |
271
|
|
|
$validOffsets[$i] = $theTest; |
272
|
|
|
} |
273
|
|
|
} |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
return $validOffsets; |
277
|
|
|
} |
278
|
|
|
|
279
|
|
|
/** |
280
|
|
|
* Tests if a given offset is valid. |
281
|
|
|
* Valid offsets cropped images will include the focus rectangle and will not fall outside of the original image. |
282
|
|
|
* |
283
|
|
|
* @param $point |
284
|
|
|
* @param $cropLength |
285
|
|
|
* @param $imageLength |
286
|
|
|
* @param $focusNear |
287
|
|
|
* @param $focusFar |
288
|
|
|
* |
289
|
|
|
* @return bool |
290
|
|
|
*/ |
291
|
|
|
protected function isInBounds($point, $cropLength, $imageLength, $focusNear, $focusFar) |
292
|
|
|
{ |
293
|
|
|
$inBounds = false; |
294
|
|
|
if ($point + $cropLength <= $imageLength && $point <= $focusNear && $point + $cropLength >= $focusFar) { |
295
|
|
|
$inBounds = true; |
296
|
|
|
} |
297
|
|
|
|
298
|
|
|
return $inBounds; |
299
|
|
|
} |
300
|
|
|
} |
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.