Completed
Push — 2.0 ( 49430a...312d76 )
by Stig
01:41
created

CropEntropy::getVerticalSlice()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 5
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 5
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
1
<?php
2
3
namespace Stojg\Crop;
4
5
use Imagick;
6
use ImagickPixel;
7
use InvalidArgumentException;
8
9
/**
10
 * Class CropEntropy
11
 *
12
 * This class will help on finding the most energetic part of an image.
13
 *
14
 * @package Stojg\Crop
15
 */
16
class CropEntropy
17
{
18
    /**
19
     * @var Imagick
20
     */
21
    protected $image = null;
22
23
    /**
24
     * CropEntropy constructor
25
     *
26
     * @param Imagick|null $image
27
     */
28
    public function __construct(Imagick $image = null)
29
    {
30
        if ($image !== null) {
31
            $this->image = $image;
32
        } else {
33
            $this->image = new Imagick();
34
        }
35
    }
36
37
    /**
38
     * @return Imagick
39
     */
40
    public function getImage()
41
    {
42
        return $this->image;
43
    }
44
45
    /**
46
     * @param int $width - The width of the region to be extracted
47
     * @param int $height - The height of the region to be extracted
48
     * @param int $x - X-coordinate of the top-left corner of the region to be extracted
49
     * @param int $y -Y-coordinate of the top-left corner of the region to be extracted
50
     * @return CropEntropy
51
     */
52
    public function getRegion($width, $height, $x, $y)
53
    {
54
        return new CropEntropy($this->image->getImageRegion($width, $height, $x, $y));
55
    }
56
57
    /**
58
     * Get the area in pixels for this image
59
     *
60
     * @return int
61
     */
62
    public function area()
63
    {
64
        $size = $this->image->getImageGeometry();
65
        return $size['height'] * $size['width'];
66
    }
67
68
    /**
69
     * @param CropEntropy $b
70
     * @return int
71
     */
72
    public function compare(CropEntropy $b)
73
    {
74
        $aValue = $this->getGrayScaleEntropy();
75
        $bValue = $b->getGrayScaleEntropy();
76
77
        if ($aValue == $bValue) {
78
            return 0;
79
        }
80
81
        return ($aValue < $bValue) ? -1 : 1;
82
    }
83
84
    /**
85
     * Calculate the entropy for this image.
86
     *
87
     * A higher value of entropy means more noise / liveliness / color / business
88
     *
89
     * @return float
90
     *
91
     * @see http://brainacle.com/calculating-image-entropy-with-python-how-and-why.html
92
     * @see http://www.mathworks.com/help/toolbox/images/ref/entropy.html
93
     */
94
    public function getGrayScaleEntropy()
95
    {
96
        $histogram = $this->image->getImageHistogram();
97
        return $this->getEntropy($histogram, $this->area());
98
    }
99
100
    /**
101
     *
102
     * @param string $axis - must be either 'x' or 'y'
103
     * @param int $sliceSize
104
     * @return int
105
     */
106
    public function getMidPoint($axis, $sliceSize = 20)
107
    {
108
        if (!in_array($axis, ['x', 'y'])) {
109
            throw new InvalidArgumentException('argument $axis must be either "x" or "y"');
110
        }
111
112
        $targetSize = $sliceSize;
113
114
        $clone = clone($this->image);
115
        $clone->modulateImage(100, 0, 100);
116
        $clone->setImageColorspace(Imagick::COLORSPACE_GRAY);
117
        $image = new CropEntropy($clone);
118
119
        $size = $clone->getImageGeometry();
120
        $min = 0;
121
122
        if ($axis === 'x') {
123
            $max = $size['width'];
124
        } else {
125
            $max = $size['height'];
126
        }
127
128
        $a = null;
129
        $b = null;
130
131
        // until we have a slice that would fit inside the target size
132
        do {
133
            // Make sure that we don't try to slice outside the image
134
            $sliceSize = min($max - $min - $targetSize, $sliceSize);
135
136
            if ($a === null) {
137
                if ($axis === 'x') {
138
                    $a = $image->getVerticalSlice($min - 1, $sliceSize);
139
                } else {
140
                    $a = $image->getHorizontalSlice($min - 1, $sliceSize);
141
                }
142
            }
143
144
            if ($b === null) {
145
                if ($axis === 'x') {
146
                    $b = $image->getVerticalSlice($max - $sliceSize - 1, $sliceSize);
147
                } else {
148
                    $b = $image->getHorizontalSlice($max - $sliceSize - 1, $sliceSize);
149
                }
150
            }
151
152
            // a has higher energy, so create a new b slice, i.e move b $sliceSize against a
153
            if ($a->compare($b) > 0) {
154
                $max -= $sliceSize;
155
                $b = null;
156
            } else {
157
                $min += $sliceSize;
158
                $a = null;
159
            }
160
        } while ($max - $min > $targetSize);
161
162
        return $min + ($sliceSize / 2);
163
    }
164
165
    /**
166
     *
167
     * @param  ImagickPixel[] $histogram
168
     * @param  int $area
169
     * @return float
170
     */
171
    protected function getEntropy($histogram, $area)
172
    {
173
        $value = 0.0;
174
        foreach ($histogram as $pixel) {
175
            // calculates the percentage of pixels having this color value
176
            $p = $pixel->getColorCount() / $area;
177
            // A common way of representing entropy in scalar
178
            $value += $p * log($p, 2);
179
        }
180
        // $value is always 0.0 or negative, so transform into positive scalar value
181
        return -$value;
182
    }
183
184
    public function getVerticalSlice($x, $sliceSize)
185
    {
186
        $size = $this->image->getImageGeometry();
187
        return $this->getRegion($sliceSize, $size['height'], $x, 0);
188
    }
189
190
    public function getHorizontalSlice($y, $sliceSize)
191
    {
192
        $size = $this->image->getImageGeometry();
193
        return $this->getRegion($size['width'], $sliceSize, 0, $y);
194
    }
195
}
196