Completed
Push — master ( a20dca...1a08b2 )
by Julien
01:21 queued 11s
created

CropBalanced::getOffsetBalancedForImage()   B

Complexity

Conditions 5
Paths 8

Size

Total Lines 71

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 71
rs 8.3216
c 0
b 0
f 0
cc 5
nc 8
nop 3

How to fix   Long Method   

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
namespace stojg\crop;
4
5
/**
6
 * CropBalanced
7
 *
8
 * This class calculates the most interesting point in the image by:
9
 *
10
 * 1. Dividing the image into four equally squares
11
 * 2. Find the most energetic point per square
12
 * 3. Finding the images weighted mean interest point
13
 *
14
 * @todo Refactor to make cleaner
15
 * @todo Rename the class to something more sensible
16
 */
17
class CropBalanced extends Crop
18
{
19
    /**
20
     * get special offset for class
21
     *
22
     * @param  \Imagick $original
23
     * @param  int      $targetWidth
24
     * @param  int      $targetHeight
25
     * @return array
26
     */
27
    protected function getSpecialOffset(\Imagick $original, $targetWidth, $targetHeight)
28
    {
29
        return $this->getRandomEdgeOffset($original, $targetWidth, $targetHeight);
30
    }
31
32
    /**
33
     *
34
     * @param  \Imagick $original
35
     * @param  int      $targetWidth
36
     * @param  int      $targetHeight
37
     * @return array
38
     */
39
    protected function getRandomEdgeOffset(\Imagick $original, $targetWidth, $targetHeight)
40
    {
41
        $measureImage = clone($original);
42
        // Enhance edges with radius 1
43
        $measureImage->edgeimage(1);
44
        // Turn image into a grayscale
45
        $measureImage->modulateImage(100, 0, 100);
46
        // Turn everything darker than this to pitch black
47
        $measureImage->blackThresholdImage("#070707");
48
        // Get the calculated offset for cropping
49
        return $this->getOffsetBalancedForImage($measureImage, $targetWidth, $targetHeight);
50
    }
51
52
    /**
53
     *
54
     * @param  int   $targetWidth
55
     * @param  int   $targetHeight
56
     * @return array
57
     * @todo refactor so it follows DRY
58
     */
59
    public function getOffsetBalanced($targetWidth, $targetHeight)
60
    {
61
        return $this->getOffsetBalancedForImage($this->originalImage, $targetWidth, $targetHeight);
62
    }
63
64
    /**
65
     * @param \Imagick $image
66
     * @param $targetWidth
67
     * @param $targetHeight
68
     * @return array
69
     * @throws \Exception
70
     */
71
    private function getOffsetBalancedForImage(\Imagick $image, $targetWidth, $targetHeight)
72
    {
73
        $size = $image->getImageGeometry();
74
75
        $points = array();
76
77
        $halfWidth = ceil($size['width']/2);
78
        $halfHeight = ceil($size['height']/2);
79
80
        // First quadrant
81
        $clone = clone($image);
82
        $clone->cropimage($halfWidth, $halfHeight, 0, 0);
83
        $point = $this->getHighestEnergyPoint($clone);
84
        $points[] = array('x' => $point['x'], 'y' => $point['y'], 'sum' => $point['sum']);
85
86
        // Second quadrant
87
        $clone = clone($image);
88
        $clone->cropimage($halfWidth, $halfHeight, $halfWidth, 0);
89
        $point = $this->getHighestEnergyPoint($clone);
90
        $points[] = array('x' => $point['x']+$halfWidth, 'y' => $point['y'], 'sum' => $point['sum']);
91
92
        // Third quadrant
93
        $clone = clone($image);
94
        $clone->cropimage($halfWidth, $halfHeight, 0, $halfHeight);
95
        $point = $this->getHighestEnergyPoint($clone);
96
        $points[] = array('x' => $point['x'], 'y' => $point['y']+$halfHeight, 'sum' => $point['sum']);
97
98
        // Fourth quadrant
99
        $clone = clone($image);
100
        $clone->cropimage($halfWidth, $halfHeight, $halfWidth, $halfHeight);
101
        $point = $point = $this->getHighestEnergyPoint($clone);
102
        $points[] = array('x' => $point['x']+$halfWidth, 'y' => $point['y']+$halfHeight, 'sum' => $point['sum']);
103
104
        // get the totalt sum value so we can find out a mean center point
105
        $totalWeight = array_reduce(
106
            $points,
107
            function ($result, $array) {
108
                return $result + $array['sum'];
109
            }
110
        );
111
112
        $centerX = 0;
113
        $centerY = 0;
114
115
        // If we found a center point, made the calculations to found the coords
116
        if ($totalWeight) {
117
            // Calulate the mean weighted center x and y
118
            $totalPoints = count($points);
119
            for ($idx=0; $idx < $totalPoints; $idx++) {
120
                $centerX += $points[$idx]['x'] * ($points[$idx]['sum'] / $totalWeight);
121
                $centerY += $points[$idx]['y'] * ($points[$idx]['sum'] / $totalWeight);
122
            }
123
        }
124
125
        // From the weighted center point to the topleft corner of the crop would be
126
        $topleftX = max(0, ($centerX - $targetWidth / 2));
127
        $topleftY = max(0, ($centerY - $targetHeight / 2));
128
129
        // If we don't have enough width for the crop, back up $topleftX until
130
        // we can make the image meet $targetWidth
131
        if ($topleftX + $targetWidth > $size['width']) {
132
            $topleftX -= ($topleftX+$targetWidth) - $size['width'];
133
        }
134
        // If we don't have enough height for the crop, back up $topleftY until
135
        // we can make the image meet $targetHeight
136
        if ($topleftY+$targetHeight > $size['height']) {
137
            $topleftY -= ($topleftY+$targetHeight) - $size['height'];
138
        }
139
140
        return array('x'=>$topleftX, 'y'=>$topleftY);
141
    }
142
143
    /**
144
     * By doing random sampling from the image, find the most energetic point on the passed in
145
     * image
146
     *
147
     * @param  \Imagick $image
148
     * @return array
149
     */
150
    protected function getHighestEnergyPoint(\Imagick $image)
151
    {
152
        $size = $image->getImageGeometry();
153
        // It's more performant doing random pixel uplook via GD
154
        $im = imagecreatefromstring($image->getImageBlob());
155
        if ($im === false) {
156
            $msg = 'GD failed to create image from string';
157
            throw new \Exception($msg);
158
        }
159
        $xcenter = 0;
160
        $ycenter = 0;
161
        $sum = 0;
162
        // Only sample 1/50 of all the pixels in the image
163
        $sampleSize = round($size['height']*$size['width'])/50;
164
165
        for ($k=0; $k<$sampleSize; $k++) {
166
            $i = mt_rand(0, $size['width']-1);
167
            $j = mt_rand(0, $size['height']-1);
168
169
            $rgb = imagecolorat($im, $i, $j);
170
            $r = ($rgb >> 16) & 0xFF;
171
            $g = ($rgb >> 8) & 0xFF;
172
            $b = $rgb & 0xFF;
173
174
            $val = $this->rgb2bw($r, $g, $b);
175
            $sum += $val;
176
            $xcenter += ($i+1)*$val;
177
            $ycenter += ($j+1)*$val;
178
        }
179
180
        if ($sum) {
181
            $xcenter /= $sum;
182
            $ycenter /= $sum;
183
        }
184
185
        $point = array('x' => $xcenter, 'y' => $ycenter, 'sum' => $sum/round($size['height']*$size['width']));
186
187
        return $point;
188
    }
189
}
190