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

CropEntropy::printDebug()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 13
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 13
rs 9.4285
cc 2
eloc 10
nc 2
nop 5
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
     * @var bool
25
     */
26
    protected $debug;
27
28
    /**
29
     * CropEntropy constructor
30
     *
31
     * @param Imagick|null $image
32
     */
33
    public function __construct(Imagick $image = null)
34
    {
35
        if ($image !== null) {
36
            $this->image = $image;
37
        } else {
38
            $this->image = new Imagick();
39
        }
40
    }
41
42
    public function debugOn()
43
    {
44
        $this->debug = true;
45
    }
46
47
    public function debugOff()
48
    {
49
        $this->debug = false;
50
    }
51
52
    /**
53
     * @return Imagick
54
     */
55
    public function getImage()
56
    {
57
        return $this->image;
58
    }
59
60
    /**
61
     * @param int $width - The width of the region to be extracted
62
     * @param int $height - The height of the region to be extracted
63
     * @param int $x - X-coordinate of the top-left corner of the region to be extracted
64
     * @param int $y -Y-coordinate of the top-left corner of the region to be extracted
65
     * @return CropEntropy
66
     */
67
    public function getRegion($width, $height, $x, $y)
68
    {
69
        return new CropEntropy($this->image->getImageRegion($width, $height, $x, $y));
70
    }
71
72
    /**
73
     * Get the area in pixels for this image
74
     *
75
     * @return int
76
     */
77
    public function area()
78
    {
79
        $size = $this->image->getImageGeometry();
80
        return $size['height'] * $size['width'];
81
    }
82
83
    /**
84
     * @param CropEntropy $b
85
     * @return int
86
     */
87
    public function compare(CropEntropy $b)
88
    {
89
        $aValue = $this->getGrayScaleEntropy();
90
        $bValue = $b->getGrayScaleEntropy();
91
92
93
        if ($aValue == $bValue) {
94
            return 0;
95
        }
96
97
        return ($aValue < $bValue) ? -1 : 1;
98
    }
99
100
    /**
101
     * Calculate the entropy for this image.
102
     *
103
     * A higher value of entropy means more noise / liveliness / color / business
104
     *
105
     * @return float
106
     *
107
     * @see http://brainacle.com/calculating-image-entropy-with-python-how-and-why.html
108
     * @see http://www.mathworks.com/help/toolbox/images/ref/entropy.html
109
     */
110
    public function getGrayScaleEntropy()
111
    {
112
        $histogram = $this->image->getImageHistogram();
113
        return $this->getEntropy($histogram, $this->area());
114
    }
115
116
    /**
117
     *
118
     * @param string $axis - must be either 'x' or 'y'
119
     * @param $sliceSize
120
     * @return int
121
     */
122
    public function getMidPoint($axis, $sliceSize)
123
    {
124
        $currentPos = 0;
125
126
        if (!in_array($axis, ['x', 'y'])) {
127
            throw new InvalidArgumentException('argument $axis must be either "x" or "y"');
128
        }
129
130
        $image = new CropEntropy(clone($this->image));
131
        $image->getImage()->modulateImage(100, 0, 100);
132
        $image->getImage()->edgeImage(1);
133
        $image->getImage()->blackThresholdImage("#303030");
134
135
        $size = $this->image->getImageGeometry();
136
137
138
        if ($axis === 'x') {
139
            $max = $size['width'];
140
        } else {
141
            $max = $size['height'];
142
        }
143
144
        $sliceIndex = [];
145
146
        // until we have a slice that would fit inside the target size
147
        $left = $max;
148
        while ($left > 0) {
149
            $sliceSize = min($sliceSize, $left);
150
            if ($axis === 'x') {
151
                $a = $image->getVerticalSlice($currentPos, $sliceSize);
152
            } else {
153
                $a = $image->getHorizontalSlice($currentPos, $sliceSize);
154
            }
155
            $value = $a->getGrayScaleEntropy();
156
            $sliceIndex[] = $value;
157
158
            if ($this->debug) {
159
                $this->printDebug($axis, $sliceSize, $value, $currentPos, $size);
160
            }
161
            $currentPos += $sliceSize;
162
            $left -= $sliceSize;
163
        }
164
        $max = array_keys($sliceIndex, max($sliceIndex));
165
        return $sliceSize * $max[0] + $sliceSize / 2;
166
    }
167
168
    /**
169
     *
170
     * @param  ImagickPixel[] $histogram
171
     * @param  int $area
172
     * @return float
173
     */
174
    protected function getEntropy($histogram, $area)
175
    {
176
        $value = 0.0;
177
        foreach ($histogram as $pixel) {
178
            // calculates the percentage of pixels having this color value
179
            $p = $pixel->getColorCount() / $area;
180
            // A common way of representing entropy in scalar
181
            $value += $p * log($p, 2);
182
        }
183
        // $value is always 0.0 or negative, so transform into positive scalar value
184
        return -$value;
185
    }
186
187
    /**
188
     * @param int $x
189
     * @param int $sliceSize
190
     * @return CropEntropy
191
     */
192
    public function getVerticalSlice($x, $sliceSize)
193
    {
194
        $size = $this->image->getImageGeometry();
195
        return $this->getRegion($sliceSize, $size['height'], $x, 0);
196
    }
197
198
    /**
199
     * @param int $y
200
     * @param int $sliceSize
201
     * @return CropEntropy
202
     */
203
    public function getHorizontalSlice($y, $sliceSize)
204
    {
205
        $size = $this->image->getImageGeometry();
206
        return $this->getRegion($size['width'], $sliceSize, 0, $y);
207
    }
208
209
    /**
210
     * @param int $x1
211
     * @param int $y1
212
     * @param int $x2
213
     * @param int $y2
214
     * @param strint $fillColor
215
     */
216
    public function rectDraw($x1, $y1, $x2, $y2, $fillColor)
217
    {
218
        $draw = new \ImagickDraw();
219
        $draw->setStrokeWidth(1);
220
        $draw->setStrokeColor(new \ImagickPixel('rgba(0%, 0%, 0%, 0.5)'));
221
        $draw->setFillColor(new \ImagickPixel($fillColor));
222
        $draw->rectangle($x1, $y1, $x2, $y2);
223
        $this->image->drawImage($draw);
224
    }
225
226
    /**
227
     * @param int $x
228
     * @param int $y
229
     * @param string $text
230
     * @param int $angle - 0 to 350
231
     */
232
    public function drawText($x, $y, $text, $angle)
233
    {
234
        $draw = new \ImagickDraw();
235
        $draw->setFont('fonts/Hack-Regular.ttf');
236
        $draw->setFontSize(10);
237
        $this->image->annotateImage($draw, $x, $y, $angle, $text);
238
    }
239
240
    /**
241
     * @param string $axis
242
     * @param int $sliceSize
243
     * @param float $value
244
     * @param int $currentPos
245
     * @param array $size
246
     */
247
    protected function printDebug($axis, $sliceSize, $value, $currentPos, $size)
248
    {
249
        $text = sprintf('%05.5f', $value);
250
        if ($axis === 'x') {
251
            $this->rectDraw($currentPos, 0, $currentPos + $sliceSize, $size['height'],
252
                'rgba(100%, 0%, 0%, 0.1)');
0 ignored issues
show
Documentation introduced by
'rgba(100%, 0%, 0%, 0.1)' is of type string, but the function expects a object<Stojg\Crop\strint>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
253
            $this->drawText($currentPos + 5, $size['height'] - 5, $text, 0);
254
        } else {
255
            $this->rectDraw(0, $currentPos, $size['width'], $currentPos + $sliceSize,
256
                'rgba(100%, 0%, 0%, 0.1)');
0 ignored issues
show
Documentation introduced by
'rgba(100%, 0%, 0%, 0.1)' is of type string, but the function expects a object<Stojg\Crop\strint>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
257
            $this->drawText(5, $currentPos + 15, $text, 0);
258
        }
259
    }
260
}
261