Passed
Push — master ( 2d77cb...280f1a )
by Mathieu
13:51 queued 05:39
created

Image::resize()   A

Complexity

Conditions 5
Paths 7

Size

Total Lines 33
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 6
Bugs 1 Features 0
Metric Value
cc 5
eloc 19
c 6
b 1
f 0
nc 7
nop 2
dl 0
loc 33
ccs 0
cts 21
cp 0
crap 30
rs 9.3222
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Suricate;
6
7
use InvalidArgumentException;
8
use RuntimeException;
9
10
class Image
11
{
12
    use Traits\ImageFilter;
13
14
    public $source;
15
    private $destination;
16
17
    private $width;
18
    private $height;
19
20
    public function load($filename)
21
    {
22
        if (is_file($filename) && ($imgString = file_get_contents($filename))) {
23
            $imgString = @imagecreatefromstring($imgString);
24
            if ($imgString !== false) {
25
                $this->source = $imgString;
26
27
                if (is_callable('exif_read_data')) {
28
                    $exif = exif_read_data($filename, 'IFD0');
29
                    $exifOrientation = $exif['Orientation'] ?? 0;
30
                    $orientation = 0;
31
                    if (in_array($exifOrientation, [3, 6, 8])) {
32
                        $orientation = $exifOrientation;
33
                    }
34
                    if ($orientation == 3) {
35
                        $this->source = imagerotate($this->source, 180, 0);
36
                    }
37
                    if ($orientation == 8) {
38
                        $this->source = imagerotate($this->source, 90, 0);
39
                    }
40
                    if ($orientation == 6) {
41
                        $this->source = imagerotate($this->source, -90, 0);
42
                    }
43
                }
44
                $this->destination = $this->source;
45
                $this->width = imagesx($this->source);
46
                $this->height = imagesy($this->source);
47
                return $this;
48
            }
49
50
            throw new InvalidArgumentException(
51
                'Cannot load ' . $filename . ', not an image'
52
            );
53
        }
54
55
        throw new InvalidArgumentException(
56
            'Cannot load ' . $filename . ', file unreadable'
57
        );
58
    }
59
60
    public function getWidth()
61
    {
62
        return $this->width;
63
    }
64
65
    public function getHeight()
66
    {
67
        return $this->height;
68
    }
69
70
    public function getResource()
71
    {
72
        return $this->source;
73
    }
74
75
    public function setResource($source)
76
    {
77
        if (!is_resource($source) && !($source instanceof \GdImage)) {
78
            throw new InvalidArgumentException("Invalid source");
79
        }
80
81
        $this->source = $source;
82
        $this->width = imagesx($this->source);
83
        $this->height = imagesy($this->source);
84
85
        return $this;
86
    }
87
88
    public function create(int $width, int $height)
89
    {
90
        $this->source = imagecreatetruecolor($width, $height);
91
        $this->destination = $this->source;
92
        $this->width = $width;
93
        $this->height = $height;
94
95
        return $this;
96
    }
97
98
    /**
99
     * Fill an image with a color
100
     *
101
     * @param integer $pointX  X point coordinate
102
     * @param integer $pointY  Y point coordinate
103
     * @param array   $color   RGB color
104
     *
105
     */
106
    public function fill(int $pointX, int $pointY, $color = [0, 0, 0])
107
    {
108
        $color = imagecolorallocate(
109
            $this->destination,
110
            $color[0],
111
            $color[1],
112
            $color[2]
113
        );
114
115
        imagefill($this->destination, $pointX, $pointY, $color);
116
117
        return $this->chain();
118
    }
119
120
    /**
121
     * Return true if image is in portrait mode
122
     *
123
     * @return boolean
124
     */
125
    public function isPortrait(): bool
126
    {
127
        return $this->width < $this->height;
128
    }
129
130
    /**
131
     * Return true if image is in landscape mode
132
     *
133
     * @return boolean
134
     */
135
    public function isLandscape(): bool
136
    {
137
        return $this->height < $this->width;
138
    }
139
140
    /**
141
     * Apply modification to destination image
142
     *
143
     */
144
    public function chain(): self
145
    {
146
        $this->source = $this->destination;
147
        $this->width = imagesx($this->source);
148
        $this->height = imagesy($this->source);
149
150
        return $this;
151
    }
152
153
    public function resize($width = null, $height = null)
154
    {
155
        if ($this->source) {
156
            if ($width == null) {
157
                $width = intval(
158
                    round(($height / $this->height) * $this->width, 0)
159
                );
160
            } elseif ($height == null) {
161
                $height = intval(
162
                    round(($width / $this->width) * $this->height, 0)
163
                );
164
            }
165
166
            $this->destination = imagecreatetruecolor($width, $height);
167
            if ($this->destination === false) {
168
                throw new RuntimeException("Can't create destination image");
169
            }
170
            imagecopyresampled(
171
                $this->destination,
172
                $this->source,
173
                0,
174
                0,
175
                0,
176
                0,
177
                $width,
178
                $height,
179
                $this->width,
180
                $this->height
181
            );
182
183
            return $this->chain();
184
        }
185
        return $this;
186
    }
187
188
    public function crop($width, $height)
189
    {
190
        $centerX = round($this->width / 2);
191
        $centerY = round($this->height / 2);
192
193
        $cropWidthHalf = round($width / 2);
194
        $cropHeightHalf = round($height / 2);
195
196
        $x1 = intval(max(0, $centerX - $cropWidthHalf));
197
        $y1 = intval(max(0, $centerY - $cropHeightHalf));
198
199
        $this->destination = imagecreatetruecolor($width, $height);
200
        if ($this->destination === false) {
201
            throw new RuntimeException("Can't create destination image");
202
        }
203
        imagecopy(
204
            $this->destination,
205
            $this->source,
206
            0,
207
            0,
208
            $x1,
209
            $y1,
210
            $width,
211
            $height
212
        );
213
214
        return $this->chain();
215
    }
216
217
    public function resizeCanvas(
218
        $width,
219
        $height,
220
        $position = null,
221
        $color = [0, 0, 0]
222
    ) {
223
        $this->destination = imagecreatetruecolor($width, $height);
224
        if ($this->destination === false) {
225
            throw new RuntimeException("Can't create destination image");
226
        }
227
        $colorRes = imagecolorallocate(
228
            $this->destination,
229
            $color[0],
230
            $color[1],
231
            $color[2]
232
        );
233
        $imageObj = new Image();
234
        $imageObj->width = $width;
235
        $imageObj->height = $height;
236
        imagefill($this->destination, 0, 0, $colorRes);
237
238
        if ($position !== null) {
239
            list($x, $y) = $imageObj->getCoordinatesFromString(
240
                $position,
241
                $this->width,
242
                $this->height
243
            );
244
        } else {
245
            $x = 0;
246
            $y = 0;
247
        }
248
        imagecopy(
249
            $this->destination,
250
            $this->source,
251
            $x,
252
            $y,
253
            0,
254
            0,
255
            $this->width,
256
            $this->height
257
        );
258
259
        return $this->chain();
260
    }
261
262
    public function rotate()
263
    {
264
    }
265
266
    public function mirror()
267
    {
268
    }
269
270
    public function flip()
271
    {
272
    }
273
274
    public function merge(
275
        $source,
276
        $position = null,
277
        $x = null,
278
        $y = null,
279
        $percent = 100
280
    ) {
281
        if ($source instanceof \Suricate\Image) {
282
        } else {
283
            $source = with(new Image())->load($source);
284
        }
285
286
        if ($position !== null) {
287
            list($x, $y) = $this->getCoordinatesFromString(
288
                $position,
289
                $source->width,
290
                $source->height
291
            );
292
        }
293
        $x = $x !== null ? $x : 0;
294
        $y = $y !== null ? $y : 0;
295
296
        // Handle transparent image
297
        // creating a cut resource
298
        $cut = imagecreatetruecolor($source->getWidth(), $source->getHeight());
299
        if ($cut === false) {
300
            throw new RuntimeException("Can't create destination image");
301
        }
302
        // copying relevant section from background to the cut resource
303
        imagecopy(
304
            $cut,
305
            $this->destination,
306
            0,
307
            0,
308
            $x,
309
            $y,
310
            $source->getWidth(),
311
            $source->getHeight()
312
        );
313
314
        // copying relevant section from watermark to the cut resource
315
        imagecopy(
316
            $cut,
317
            $source->source,
318
            0,
319
            0,
320
            0,
321
            0,
322
            $source->getWidth(),
323
            $source->getHeight()
324
        );
325
326
        imagecopymerge(
327
            $this->destination,
328
            $cut,
329
            $x,
330
            $y,
331
            0,
332
            0,
333
            $source->getWidth(),
334
            $source->getHeight(),
335
            $percent
336
        );
337
338
        return $this->chain();
339
    }
340
341
    public function writeText($text, $x = 0, $y = 0, \Closure $callback = null)
342
    {
343
        if ($x < 0) {
344
            $x = $this->width + $x;
345
        }
346
        if ($y < 0) {
347
            $y = $this->height + $y;
348
        }
349
        $imageFont = new ImageFont($text);
350
351
        if ($callback != null) {
352
            $callback($imageFont);
353
        }
354
355
        $imageFont->apply($this->source, $x, $y);
356
357
        return $this;
358
    }
359
360
    public function line($x1, $y1, $x2, $y2, \Closure $callback = null)
361
    {
362
        $imageShape = new ImageShape();
363
        $imageShape->setImage($this->source);
364
        if ($callback != null) {
365
            $callback($imageShape);
366
        }
367
368
        $imageShape->drawLine($x1, $y1, $x2, $y2);
369
370
        return $this;
371
    }
372
373
    /**
374
     * Export image
375
     *
376
     * @param string $outputType output format
377
     * @param integer $quality   Output quality, when available
378
     *
379
     * @return void
380
     */
381
    public function export($outputType, $quality = 70)
382
    {
383
        switch ($outputType) {
384
            case 'jpg':
385
            case 'jpeg':
386
                imagejpeg($this->source, null, $quality);
387
                break;
388
            case 'png':
389
                imagepng($this->source);
390
                break;
391
            case 'gif':
392
                imagegif($this->source);
393
                break;
394
            case 'webp':
395
                imagewebp($this->source, null, $quality);
396
                break;
397
            default:
398
                throw new InvalidArgumentException(
399
                    sprintf("Invalid output format %s", $outputType)
400
                );
401
        }
402
    }
403
404
    public function save($filename, $outputType = null, $quality = 70)
405
    {
406
        $result = false;
407
408
        $extension =
409
            $outputType === null
410
            ? pathinfo($filename, PATHINFO_EXTENSION)
411
            : $outputType;
412
413
        if ($extension !== false) {
414
            switch (strtolower($extension)) {
0 ignored issues
show
Bug introduced by
It seems like $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

414
            switch (strtolower(/** @scrutinizer ignore-type */ $extension)) {
Loading history...
415
                case 'jpg':
416
                case 'jpeg':
417
                    $result = imagejpeg($this->source, $filename, $quality);
418
                    break;
419
                case 'png':
420
                    $result = imagepng($this->source, $filename);
421
                    break;
422
                case 'gif':
423
                    $result = imagegif($this->source, $filename);
424
                    break;
425
                case 'webp':
426
                    $result = imagewebp($this->source, $filename, $quality);
427
                    break;
428
            }
429
        }
430
431
        return $result;
432
    }
433
434
    private function getCoordinatesFromString(
435
        $position,
436
        $offsetWidth = 0,
437
        $offsetHeight = 0
438
    ) {
439
        switch ($position) {
440
            case 'top':
441
                $x = floor($this->width / 2 - $offsetWidth / 2);
442
                $y = 0;
443
                break;
444
            case 'top-right':
445
                $x = $this->width - $offsetWidth;
446
                $y = 0;
447
                break;
448
            case 'left':
449
                $x = 0;
450
                $y = floor($this->height / 2 - $offsetHeight / 2);
451
                break;
452
            case 'center':
453
                $x = floor($this->width / 2 - $offsetWidth / 2);
454
                $y = floor($this->height / 2 - $offsetHeight / 2);
455
                break;
456
            case 'right':
457
                $x = $this->width - $offsetWidth;
458
                $y = floor($this->height / 2 - $offsetHeight / 2);
459
                break;
460
            case 'bottom-left':
461
                $x = 0;
462
                $y = $this->height - $offsetHeight;
463
                break;
464
            case 'bottom':
465
                $x = floor($this->width / 2 - $offsetWidth / 2);
466
                $y = $this->height - $offsetHeight;
467
                break;
468
            case 'bottom-right':
469
                $x = $this->width - $offsetWidth;
470
                $y = $this->height - $offsetHeight;
471
                break;
472
473
            case 'top-left':
474
            default:
475
                $x = 0;
476
                $y = 0;
477
                break;
478
        }
479
480
        return [intval($x), intval($y)];
481
    }
482
}
483