Completed
Push — master ( 798da7...ff5711 )
by Oscar
02:21
created

Image::getImage()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 2
Metric Value
c 5
b 1
f 2
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
namespace Imagecow;
4
5
use Imagecow\Utils\Dimmensions;
6
7
class Image
8
{
9
    const LIB_GD = 'Gd';
10
    const LIB_IMAGICK = 'Imagick';
11
12
    const CROP_ENTROPY = 'Entropy';
13
    const CROP_BALANCED = 'Balanced';
14
15
    protected $image;
16
    protected $filename;
17
    protected $clientHints = [
18
        'dpr' => null,
19
        'viewport-width' => null,
20
        'width' => null,
21
    ];
22
23
    /**
24
     * Static function to create a new Imagecow instance from an image file.
25
     *
26
     * @param string $filename The path of the file
27
     * @param string $library  The name of the image library to use (Gd or Imagick). If it's not defined, detects automatically the library to use.
28
     *
29
     * @return Image
30
     */
31
    public static function fromFile($filename, $library = null)
32
    {
33
        $class = self::getLibraryClass($library);
34
35
        $image = new static($class::createFromFile($filename), $filename);
36
37
        if ($image->getMimeType() !== 'image/gif') {
38
            return $image;
39
        }
40
41
        $stream = fopen($filename, 'rb');
42
43
        if (self::isAnimatedGif($stream)) {
44
            $image->image->setAnimated(true);
45
        }
46
47
        fclose($stream);
48
49
        return $image;
50
    }
51
52
    /**
53
     * Static function to create a new Imagecow instance from a binary string.
54
     *
55
     * @param string $string  The string of the image
56
     * @param string $library The name of the image library to use (Gd or Imagick). If it's not defined, detects automatically the library to use.
57
     *
58
     * @return Image
59
     */
60
    public static function fromString($string, $library = null)
61
    {
62
        $class = self::getLibraryClass($library);
63
64
        $image = new static($class::createFromString($string));
65
66
        if ($image->getMimeType() !== 'image/gif') {
67
            return $image;
68
        }
69
70
        $stream = fopen('php://temp', 'r+');
71
72
        fwrite($stream, $string);
73
        rewind($stream);
74
75
        if (self::isAnimatedGif($stream)) {
76
            $image->image->setAnimated(true);
77
        }
78
79
        fclose($stream);
80
81
        return $image;
82
    }
83
84
    /**
85
     * Constructor.
86
     *
87
     * @param Libs\LibInterface $image
88
     * @param string            $filename Original filename (used to overwrite)
89
     */
90
    public function __construct(Libs\LibInterface $image, $filename = null)
91
    {
92
        $this->image = $image;
93
        $this->filename = $filename;
94
    }
95
96
    /**
97
     * Set the available client hints.
98
     *
99
     * @param array $clientHints
100
     *
101
     * @return self
102
     */
103
    public function setClientHints(array $clientHints)
104
    {
105
        $normalize = [];
106
107
        foreach ($clientHints as $key => $value) {
108
            $normalize[strtolower($key)] = is_null($value) ? null : (float) $value;
109
        }
110
111
        if (array_diff_key($normalize, $this->clientHints)) {
112
            throw new \InvalidArgumentException('Invalid client hints');
113
        }
114
115
        $this->clientHints = array_replace($this->clientHints, $normalize);
116
117
        return $this;
118
    }
119
120
    /**
121
     * Set a default background color used to fill in some transformation functions.
122
     *
123
     * @param array $background The color in rgb, for example: array(0,127,34)
124
     *
125
     * @return self
126
     */
127
    public function setBackground(array $background)
128
    {
129
        $this->image->setBackground($background);
130
131
        return $this;
132
    }
133
134
    /**
135
     * Define the image compression quality for jpg images.
136
     *
137
     * @param int $quality The quality (from 0 to 100)
138
     *
139
     * @deprecated Use quality instead
140
     *
141
     * @return self
142
     */
143
    public function setCompressionQuality($quality)
144
    {
145
        error_log('The method `setCompressionQuality()` is deprecated. Use `quality()` instead.');
146
147
        return $this->quality($quality);
148
    }
149
150
    /**
151
     * Get the fixed size according with the client hints.
152
     *
153
     * @param int $width
154
     * @param int $height
155
     *
156
     * @return array
157
     */
158
    private function calculateClientSize($width, $height)
159
    {
160 View Code Duplication
        if ($this->clientHints['width'] !== null && $this->clientHints['width'] < $width) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
161
            return Dimmensions::getResizeDimmensions($width, $height, $this->clientHints['width'], null);
162
        }
163
164 View Code Duplication
        if ($this->clientHints['viewport-width'] !== null && $this->clientHints['viewport-width'] < $width) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
165
            return Dimmensions::getResizeDimmensions($width, $height, $this->clientHints['viewport-width'], null);
166
        }
167
168
        if ($this->clientHints['dpr'] !== null) {
169
            $width *= $this->clientHints['dpr'];
170
            $height *= $this->clientHints['dpr'];
171
        }
172
173
        return [$width, $height];
174
    }
175
176
    /**
177
     * Inverts the image vertically.
178
     *
179
     * @return self
180
     */
181
    public function flip()
182
    {
183
        $this->image->flip();
184
185
        return $this;
186
    }
187
188
    /**
189
     * Inverts the image horizontally.
190
     *
191
     * @return self
192
     */
193
    public function flop()
194
    {
195
        $this->image->flop();
196
197
        return $this;
198
    }
199
200
    /**
201
     * Saves the image in a file.
202
     *
203
     * @param string $filename Name of the file where the image will be saved. If it's not defined, The original file will be overwritten.
204
     *
205
     * @return self
206
     */
207
    public function save($filename = null)
208
    {
209
        $this->image->save($filename ?: $this->filename);
210
211
        return $this;
212
    }
213
214
    /**
215
     * Returns the image instance.
216
     *
217
     * @return Libs\LibInterface
218
     */
219
    public function getImage()
220
    {
221
        return $this->image;
222
    }
223
224
    /**
225
     * Gets the image data in a string.
226
     *
227
     * @return string The image data
228
     */
229
    public function getString()
230
    {
231
        return $this->image->getString();
232
    }
233
234
    /**
235
     * Gets the mime type of the image.
236
     *
237
     * @return string The mime type
238
     */
239
    public function getMimeType()
240
    {
241
        return $this->image->getMimeType();
242
    }
243
244
    /**
245
     * Gets the width of the image.
246
     *
247
     * @return int The width in pixels
248
     */
249
    public function getWidth()
250
    {
251
        return $this->image->getWidth();
252
    }
253
254
    /**
255
     * Gets the height of the image.
256
     *
257
     * @return int The height in pixels
258
     */
259
    public function getHeight()
260
    {
261
        return $this->image->getHeight();
262
    }
263
264
    /**
265
     * Converts the image to other format.
266
     *
267
     * @param string $format The new format: png, jpg, gif
268
     *
269
     * @return self
270
     */
271
    public function format($format)
272
    {
273
        $this->image->format($format);
274
275
        return $this;
276
    }
277
278
    /**
279
     * Resizes the image maintaining the proportion (A 800x600 image resized to 400x400 becomes to 400x300).
280
     *
281
     * @param int|string $width  The max width of the image. It can be a number (pixels) or percentaje
282
     * @param int|string $height The max height of the image. It can be a number (pixels) or percentaje
283
     * @param bool       $cover
284
     *
285
     * @return self
286
     */
287
    public function resize($width, $height = 0, $cover = false)
288
    {
289
        $imageWidth = $this->getWidth();
290
        $imageHeight = $this->getHeight();
291
292
        $width = Dimmensions::getIntegerValue('x', $width, $imageWidth);
293
        $height = Dimmensions::getIntegerValue('y', $height, $imageHeight);
294
295
        list($width, $height) = Dimmensions::getResizeDimmensions($imageWidth, $imageHeight, $width, $height, $cover);
296
        list($width, $height) = $this->calculateClientSize($width, $height);
297
298
        if ($width >= $imageWidth) {
299
            return $this;
300
        }
301
302
        $this->image->resize($width, $height);
303
304
        return $this;
305
    }
306
307
    /**
308
     * Crops the image.
309
     *
310
     * @param int|string $width  The new width of the image. It can be a number (pixels) or percentaje
311
     * @param int|string $height The new height of the image. It can be a number (pixels) or percentaje
312
     * @param int|string $x      The "x" position to crop. It can be number (pixels), percentaje, [left, center, right] or one of the Image::CROP_* constants
313
     * @param int|string $y      The "y" position to crop. It can be number (pixels), percentaje or [top, middle, bottom]
314
     *
315
     * @return self
316
     */
317
    public function crop($width, $height, $x = 'center', $y = 'middle')
318
    {
319
        $imageWidth = $this->getWidth();
320
        $imageHeight = $this->getHeight();
321
322
        $width = Dimmensions::getIntegerValue('x', $width, $imageWidth);
323
        $height = Dimmensions::getIntegerValue('y', $height, $imageHeight);
324
325
        list($width, $height) = $this->calculateClientSize($width, $height);
326
327
        if (($x === self::CROP_BALANCED) || ($x === self::CROP_ENTROPY)) {
328
            list($x, $y) = $this->image->getCropOffsets($width, $height, $x);
329
        }
330
331
        $x = Dimmensions::getPositionValue('x', $x, $width, $imageWidth);
332
        $y = Dimmensions::getPositionValue('y', $y, $height, $imageHeight);
333
334
        $this->image->crop($width, $height, $x, $y);
335
336
        return $this;
337
    }
338
339
    /**
340
     * Adjust the image to the given dimmensions. Resizes and crops the image maintaining the proportions.
341
     *
342
     * @param int|string $width  The new width in number (pixels) or percentaje
343
     * @param int|string $height The new height in number (pixels) or percentaje
344
     * @param int|string $x      The "x" position to crop. It can be number (pixels), percentaje, [left, center, right] or one of the Image::CROP_* constants
345
     * @param int|string $y      The "y" position to crop. It can be number (pixels), percentaje or [top, middle, bottom]
346
     *
347
     * @return self
348
     */
349
    public function resizeCrop($width, $height, $x = 'center', $y = 'middle')
350
    {
351
        $this->resize($width, $height, true);
352
        $this->crop($width, $height, $x, $y);
353
354
        return $this;
355
    }
356
357
    /**
358
     * Rotates the image.
359
     *
360
     * @param int $angle Rotation angle in degrees (anticlockwise)
361
     *
362
     * @return self
363
     */
364
    public function rotate($angle)
365
    {
366
        if (($angle = intval($angle)) !== 0) {
367
            $this->image->rotate($angle);
368
        }
369
370
        return $this;
371
    }
372
373
    /**
374
     * Define the image compression quality for jpg images.
375
     *
376
     * @param int $quality The quality (from 0 to 100)
377
     *
378
     * @return self
379
     */
380
    public function quality($quality)
381
    {
382
        $quality = intval($quality);
383
384
        if ($quality < 0) {
385
            $quality = 0;
386
        } elseif ($quality > 100) {
387
            $quality = 100;
388
        }
389
390
        $this->image->setCompressionQuality($quality);
391
392
        return $this;
393
    }
394
395
    /**
396
     * Add a watermark to current image.
397
     *
398
     * @param string $file Image to set as watermark
0 ignored issues
show
Bug introduced by
There is no parameter named $file. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
399
     * @param mixed  $x    Horizontal position
400
     * @param mixed  $y    Vertical position
401
     *
402
     * @return self
403
     */
404
    public function watermark(Image $image, $x = 'right', $y = 'bottom')
405
    {
406
        $imageWidth = $this->getWidth();
407
        $imageHeight = $this->getHeight();
408
409
        $width = $image->getWidth();
410
        $height = $image->getHeight();
411
412
        $x = Dimmensions::getPositionValue('x', $x, $width, $imageWidth);
413
        $y = Dimmensions::getPositionValue('y', $y, $height, $imageHeight);
414
415
        $this->image->watermark($image->getImage(), $x, $y);
416
417
        return $this;
418
    }
419
420
    /**
421
     * Add opacity to image from 0 (transparent) to 100 (opaque).
422
     *
423
     * @param int $opacity Opacity value
424
     * 
425
     * @return self
426
     */
427
    public function opacity($opacity)
428
    {
429
        $this->image->opacity($opacity);
430
431
        return $this;
432
    }
433
434
    /**
435
     * Reads the EXIF data from a JPEG and returns an associative array
436
     * (requires the exif PHP extension enabled).
437
     *
438
     * @param null|string $key
439
     *
440
     * @return null|array
441
     */
442
    public function getExifData($key = null)
443
    {
444
        if ($this->filename !== null && ($this->getMimeType() === 'image/jpeg')) {
445
            $exif = exif_read_data($this->filename);
446
447
            if ($key !== null) {
448
                return isset($exif[$key]) ? $exif[$key] : null;
449
            }
450
451
            return $exif;
452
        }
453
    }
454
455
    /**
456
     * Transform the image executing various operations of crop, resize, resizeCrop and format.
457
     *
458
     * @param string $operations The string with all operations separated by "|".
459
     *
460
     * @return self
461
     */
462
    public function transform($operations = null)
463
    {
464
        //No transform operations, resize to fix the client size
465
        if (empty($operations)) {
466
            return $this->resize($this->getWidth(), $this->getHeight());
467
        }
468
469
        $operations = self::parseOperations($operations);
470
471
        foreach ($operations as $operation) {
472
            switch ($operation['function']) {
473
                case 'crop':
474
                case 'resizecrop':
475
                    if (empty($operation['params'][2])) {
476
                        break;
477
                    }
478
479
                    switch ($operation['params'][2]) {
480
                        case 'CROP_ENTROPY':
481
                            $operation['params'][2] = self::CROP_ENTROPY;
482
                            break;
483
484
                        case 'CROP_BALANCED':
485
                            $operation['params'][2] = self::CROP_BALANCED;
486
                            break;
487
                    }
488
489
                    break;
490
            }
491
492
            call_user_func_array([$this, $operation['function']], $operation['params']);
493
        }
494
495
        return $this;
496
    }
497
498
    /**
499
     * Send the HTTP header with the content-type, output the image data and die.
500
     */
501 View Code Duplication
    public function show()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
502
    {
503
        if (($string = $this->getString()) && ($mimetype = $this->getMimeType())) {
504
            header('Content-Type: '.$mimetype);
505
            die($string);
506
        }
507
    }
508
509
    /**
510
     * Returns the image as base64 url.
511
     *
512
     * @return string|null
513
     */
514 View Code Duplication
    public function base64()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
515
    {
516
        if (($string = $this->getString()) && ($mimetype = $this->getMimeType())) {
517
            $string = base64_encode($string);
518
519
            return "data:{$mimetype};base64,{$string}";
520
        }
521
    }
522
523
    /**
524
     * Auto-rotate the image according with its exif data
525
     * Taken from: http://php.net/manual/en/function.exif-read-data.php#76964.
526
     *
527
     * @return self
528
     */
529
    public function autoRotate()
530
    {
531
        switch ($this->getExifData('Orientation')) {
532
            case 2:
533
                $this->flop();
534
                break;
535
536
            case 3:
537
                $this->rotate(180);
538
                break;
539
540
            case 4:
541
                $this->flip();
542
                break;
543
544
            case 5:
545
                $this->flip()->rotate(-90);
546
                break;
547
548
            case 6:
549
                $this->rotate(90);
550
                break;
551
552
            case 7:
553
                $this->flop()->rotate(-90);
554
                break;
555
556
            case 8:
557
                $this->rotate(90);
558
                break;
559
        }
560
561
        return $this;
562
    }
563
564
    /**
565
     * Check whether the image is an animated gif.
566
     * Copied from: https://github.com/Sybio/GifFrameExtractor/blob/master/src/GifFrameExtractor/GifFrameExtractor.php#L181.
567
     *
568
     * @param resource A stream pointer opened by fopen()
569
     *
570
     * @return bool
571
     */
572
    private static function isAnimatedGif($stream)
573
    {
574
        $count = 0;
575
576
        while (!feof($stream) && $count < 2) {
577
            $chunk = fread($stream, 1024 * 100); //read 100kb at a time
578
            $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches);
579
        }
580
581
        return $count > 1;
582
    }
583
584
    /**
585
     * Converts a string with operations in an array.
586
     *
587
     * @param string $operations The operations string
588
     *
589
     * @return array
590
     */
591
    private static function parseOperations($operations)
592
    {
593
        $valid_operations = ['resize', 'resizecrop', 'crop', 'format', 'quality'];
594
        $operations = explode('|', str_replace(' ', '', $operations));
595
        $return = [];
596
597
        foreach ($operations as $operations) {
598
            $params = explode(',', $operations);
599
            $function = strtolower(trim(array_shift($params)));
600
601
            if (!in_array($function, $valid_operations, true)) {
602
                throw new ImageException("The transform function '{$function}' is not valid");
603
            }
604
605
            $return[] = [
606
                'function' => $function,
607
                'params' => $params,
608
            ];
609
        }
610
611
        return $return;
612
    }
613
614
    /**
615
     * Checks the library to use and returns its class.
616
     *
617
     * @param string $library The library name (Gd, Imagick)
618
     *
619
     * @throws ImageException if the image library does not exists.
620
     *
621
     * @return string
622
     */
623
    private static function getLibraryClass($library)
624
    {
625
        if (!$library) {
626
            $library = Libs\Imagick::checkCompatibility() ? self::LIB_IMAGICK : self::LIB_GD;
627
        }
628
629
        $class = 'Imagecow\\Libs\\'.$library;
630
631
        if (!class_exists($class)) {
632
            throw new ImageException('The image library is not valid');
633
        }
634
635
        if (!$class::checkCompatibility()) {
636
            throw new ImageException("The image library '$library' is not installed in this computer");
637
        }
638
639
        return $class;
640
    }
641
}
642