Balanced::getOffsetBalanced()   B
last analyzed

Complexity

Conditions 4
Paths 8

Size

Total Lines 68

Duplication

Lines 0
Ratio 0 %

Importance

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