Passed
Push — master ( 6ebac0...a0be99 )
by Michael
19:32
created

Image::__construct()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 32
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 5
eloc 21
c 1
b 0
f 0
nc 7
nop 1
dl 0
loc 32
rs 9.2728
1
<?php
2
/**
3
 * Styx::Image - Provides an Interface to the GD-Library for image manipulation
4
 *
5
 * @package    Styx
6
 * @subpackage Utility
7
 *
8
 * @license    MIT-style License
9
 * @author     Christoph Pojer <[email protected]>
10
 *
11
 * @link       http://www.bin-co.com/php/scripts/classes/gd_image/ Based on work by "Binny V A"
12
 */
13
14
class Image
15
{
16
    /**
17
     * The path to the image file
18
     *
19
     * @var string
20
     */
21
    private $file;
22
    /**
23
     * The image resource
24
     *
25
     * @var resource
26
     */
27
    private $image;
28
    /**
29
     * Metadata regarding the image
30
     *
31
     * @var array
32
     */
33
    private $meta;
34
35
    /**
36
     * @param string $file The path to the image file
37
     */
38
    public function __construct($file)
39
    {
40
        $file = realpath($file);
41
        if (!file_exists($file)) {
42
            return;
43
        }
44
45
        $this->file = $file;
46
        $img        = getimagesize($file);
47
48
        $this->meta = [
49
            'width'  => $img[0],
50
            'height' => $img[1],
51
            'mime'   => $img['mime'],
52
            'ext'    => end(explode('/', $img['mime'])),
53
        ];
54
        if ('jpg' == $this->meta['ext']) {
55
            $this->meta['ext'] = 'jpeg';
56
        }
57
58
        if (!in_array($this->meta['ext'], ['gif', 'png', 'jpeg'])) {
59
            return;
60
        }
61
62
        if (in_array($this->meta['ext'], ['gif', 'png'])) {
63
            $this->image = $this->create();
64
65
            $fn       = 'imagecreatefrom' . $this->meta['ext'];
66
            $original = $fn($file);
67
            imagecopyresampled($this->image, $original, 0, 0, 0, 0, $this->meta['width'], $this->meta['height'], $this->meta['width'], $this->meta['height']);
68
        } else {
69
            $this->image = imagecreatefromjpeg($file);
0 ignored issues
show
Documentation Bug introduced by
It seems like imagecreatefromjpeg($file) can also be of type GdImage. However, the property $image is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
70
        }
71
    }
72
73
    public function __destruct()
74
    {
75
        if (!empty($this->image)) {
76
            imagedestroy($this->image);
77
        }
78
    }
79
80
    /**
81
     * Returns the size of the image
82
     *
83
     * @return array
84
     */
85
    public function getSize()
86
    {
87
        return [
88
            'width'  => $this->meta['width'],
89
            'height' => $this->meta['height'],
90
        ];
91
    }
92
93
    /**
94
     * Creates a new, empty image with the desired size
95
     *
96
     * @param int    $x
97
     * @param int    $y
98
     * @param string $ext
99
     * @return resource
100
     */
101
    private function create($x = null, $y = null, $ext = null)
102
    {
103
        if (!$x) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $x of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
104
            $x = $this->meta['width'];
105
        }
106
        if (!$y) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $y of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
107
            $y = $this->meta['height'];
108
        }
109
110
        $image = imagecreatetruecolor($x, $y);
111
        if (!$ext) {
112
            $ext = $this->meta['ext'];
113
        }
114
        if ('png' == $ext) {
115
            imagealphablending($image, false);
116
            imagefilledrectangle($image, 0, 0, $x, $y, imagecolorallocatealpha($image, 0, 0, 0, 127));
117
        }
118
119
        return $image;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $image also could return the type GdImage which is incompatible with the documented return type resource.
Loading history...
120
    }
121
122
    /**
123
     * Replaces the image resource with the given parameter
124
     *
125
     * @param resource $new
126
     */
127
    private function set($new)
128
    {
129
        imagedestroy($this->image);
130
        $this->image = $new;
131
132
        $this->meta['width']  = imagesx($this->image);
133
        $this->meta['height'] = imagesy($this->image);
134
    }
135
136
    /**
137
     * Returns the path to the image file
138
     *
139
     * @return string
140
     */
141
    public function getPathname()
142
    {
143
        return $this->file;
144
    }
145
146
    /**
147
     * Rotates the image by the given angle
148
     *
149
     * @param int   $angle
150
     * @param array $bgcolor An indexed array with red/green/blue/alpha values
151
     * @return Image
152
     */
153
    public function rotate($angle, $bgcolor = null)
154
    {
155
        if (empty($this->image) || !$angle || $angle >= 360) {
156
            return $this;
157
        }
158
159
        $this->set(imagerotate($this->image, $angle, is_array($bgcolor) ? imagecolorallocatealpha($this->image, $bgcolor[0], $bgcolor[1], $bgcolor[2], !empty($bgcolor[3]) ? $bgcolor[3] : null) : $bgcolor));
0 ignored issues
show
Bug introduced by
It seems like imagerotate($this->image...[3] : null) : $bgcolor) can also be of type GdImage; however, parameter $new of Image::set() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

159
        $this->set(/** @scrutinizer ignore-type */ imagerotate($this->image, $angle, is_array($bgcolor) ? imagecolorallocatealpha($this->image, $bgcolor[0], $bgcolor[1], $bgcolor[2], !empty($bgcolor[3]) ? $bgcolor[3] : null) : $bgcolor));
Loading history...
160
161
        return $this;
162
    }
163
164
    /**
165
     * Resizes the image to the given size, automatically calculates
166
     * the new ratio if parameter {@link $ratio} is set to true
167
     *
168
     * @param int  $x
169
     * @param int  $y
170
     * @param bool $ratio
171
     * @return Image
172
     */
173
    public function resize($x = null, $y = null, $ratio = true)
174
    {
175
        if (empty($this->image) || (!$x && !$y)) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $x of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
Bug Best Practice introduced by
The expression $y of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
176
            return $this;
177
        }
178
179
        if (!$y) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $y of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
180
            $y = $ratio ? $this->meta['height'] * $x / $this->meta['width'] : $this->meta['height'];
181
        }
182
        if (!$x) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $x of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
183
            $x = $ratio ? $this->meta['width'] * $y / $this->meta['height'] : $this->meta['width'];
184
        }
185
186
        $new = $this->create($x, $y);
187
        imagecopyresampled($new, $this->image, 0, 0, 0, 0, $x, $y, $this->meta['width'], $this->meta['height']);
188
        $this->set($new);
189
190
        return $this;
191
    }
192
193
    /**
194
     * Crops the image. The values are given like margin/padding values in css
195
     *
196
     * <b>Example</b>
197
     * <ul>
198
     * <li>crop(10) - Crops by 10px on all sides</li>
199
     * <li>crop(10, 5) - Crops by 10px on top and bottom and by 5px on left and right sides</li>
200
     * <li>crop(10, 5, 5) - Crops by 10px on top and by 5px on left, right and bottom sides</li>
201
     * <li>crop(10, 5, 3, 2) - Crops by 10px on top, 5px by right, 3px by bottom and 2px by left sides</li>
202
     * </ul>
203
     *
204
     * @param int $top
205
     * @param int $right
206
     * @param int $bottom
207
     * @param int $left
208
     * @return Image
209
     */
210
    public function crop($top, $right = null, $bottom = null, $left = null)
211
    {
212
        if (empty($this->image)) {
213
            return $this;
214
        }
215
216
        if (!is_numeric($right) && !is_numeric($bottom) && !is_numeric($left)) {
217
            $right = $bottom = $left = $top;
218
        }
219
220
        if (!is_numeric($bottom) && !is_numeric($left)) {
221
            $bottom = $top;
222
            $left   = $right;
223
        }
224
225
        if (!is_numeric($left)) {
226
            $left = $right;
227
        }
228
229
        $x = $this->meta['width'] - $left - $right;
230
        $y = $this->meta['height'] - $top - $bottom;
231
232
        if ($x < 0 || $y < 0) {
233
            return $this;
234
        }
235
236
        $new = $this->create($x, $y);
237
        imagecopy($new, $this->image, 0, 0, $left, $top, $x, $y);
238
        $this->set($new);
239
240
        return $this;
241
    }
242
243
    /**
244
     * Flips the image horizontally or vertically. To Flip both just use ->rotate(180)
245
     *
246
     * @param string $type Either horizontal or vertical
247
     * @return Image
248
     * @see Image::rotate()
249
     */
250
    public function flip($type)
251
    {
252
        if (empty($this->image) || !in_array($type, ['horizontal', 'vertical'])) {
253
            return $this;
254
        }
255
256
        $new = $this->create();
257
258
        if ('horizontal' == $type) {
259
            for ($x = 0; $x < $this->meta['width']; $x++) {
260
                imagecopy($new, $this->image, $this->meta['width'] - $x - 1, 0, $x, 0, 1, $this->meta['height']);
261
            }
262
        } elseif ('vertical' == $type) {
263
            for ($y = 0; $y < $this->meta['height']; $y++) {
264
                imagecopy($new, $this->image, 0, $this->meta['height'] - $y - 1, 0, $y, $this->meta['width'], 1);
265
            }
266
        }
267
268
        $this->set($new);
269
270
        return $this;
271
    }
272
273
    /**
274
     * Stores the image in the desired directory or outputs it
275
     *
276
     * @param string $ext
277
     * @param string $file
278
     */
279
    private function process($ext = null, $file = null)
280
    {
281
        if (!$ext) {
282
            $ext = $this->meta['ext'];
283
        }
284
285
        if ('png' == $ext) {
286
            imagesavealpha($this->image, true);
287
        }
288
        $fn = 'image' . $ext;
289
        $fn($this->image, $file);
290
291
        // If there is a new filename change the internal name too
292
        if ($file) {
293
            $this->file = $file;
294
        }
295
    }
296
297
    /**
298
     * Saves the image to the given path
299
     *
300
     * @param string $file Leave empty to replace the original file
301
     * @return Image
302
     */
303
    public function save($file = null)
304
    {
305
        if (empty($this->image)) {
306
            return $this;
307
        }
308
309
        if (!$file) {
310
            $file = $this->file;
311
        }
312
313
        $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
0 ignored issues
show
Bug introduced by
It seems like pathinfo($file, PATHINFO_EXTENSION) can also be of type array; however, parameter $string of strtolower() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

313
        $ext = strtolower(/** @scrutinizer ignore-type */ pathinfo($file, PATHINFO_EXTENSION));
Loading history...
314
        if (!$ext) {
315
            $file .= '.' . $this->meta['ext'];
316
            $ext  = $this->meta['ext'];
317
        }
318
319
        if ('jpg' == $ext) {
320
            $ext = 'jpeg';
321
        }
322
323
        if (!in_array($ext, ['png', 'jpeg', 'gif'])) {
324
            return $this;
325
        }
326
327
        $this->process($ext, $file);
328
329
        return $this;
330
    }
331
332
    /**
333
     * Outputs the manipulated image
334
     *
335
     * @return Image
336
     */
337
    public function show()
338
    {
339
        if (empty($this->image)) {
340
            return $this;
341
        }
342
343
        header('Content-type: ' . $this->meta['mime']);
344
        $this->process();
345
346
        return $this;
347
    }
348
349
}
350