Completed
Push — master ( a06a95...a1d542 )
by dan
15s
created

FocusCropDataCalculator   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 283
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 1

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 1
dl 0
loc 283
rs 10
c 0
b 0
f 0

9 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 7 1
A getFocusCropData() 0 51 3
B calculateAxisCropData() 0 23 5
A getFloatingOffset() 0 7 1
A getFocusPointForAxis() 0 21 2
B findFocusOffset() 0 41 4
A getOptimalOffset() 0 12 2
B getValidOffsets() 0 29 5
A isInBounds() 0 9 4
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
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
63
        // 2: $imageAspectRatio < $styleAspectRatio
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
64
        // 3: $imageAspectRatio === $styleAspectRatio
0 ignored issues
show
Unused Code Comprehensibility introduced by
45% of this comment could be valid code. Did you maybe forget this after debugging?

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.

Loading history...
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
}