Completed
Push — master ( ef23cd...06cbc3 )
by
unknown
02:02
created

Image::crop()   B

Complexity

Conditions 4
Paths 4

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 14
Bugs 4 Features 5
Metric Value
c 14
b 4
f 5
dl 0
loc 24
rs 8.6845
cc 4
eloc 15
nc 4
nop 4
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
    const CROP_FACE = 'Face';
15
16
    protected $image;
17
    protected $filename;
18
    protected $clientHints = [
19
        'dpr' => null,
20
        'viewport-width' => null,
21
        'width' => null,
22
    ];
23
24
    /**
25
     * Static function to create a new Imagecow instance from an image file.
26
     *
27
     * @param string $filename The path of the file
28
     * @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.
29
     *
30
     * @return Image
31
     */
32
    public static function fromFile($filename, $library = null)
33
    {
34
        $class = self::getLibraryClass($library);
35
36
        $image = new static($class::createFromFile($filename), $filename);
37
38
        if ($image->getMimeType() !== 'image/gif') {
39
            return $image;
40
        }
41
42
        $stream = fopen($filename, 'rb');
43
44
        if (self::isAnimatedGif($stream)) {
45
            $image->image->setAnimated(true);
46
        }
47
48
        fclose($stream);
49
50
        return $image;
51
    }
52
53
    /**
54
     * Static function to create a new Imagecow instance from a binary string.
55
     *
56
     * @param string $string  The string of the image
57
     * @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.
58
     *
59
     * @return Image
60
     */
61
    public static function fromString($string, $library = null)
62
    {
63
        $class = self::getLibraryClass($library);
64
65
        $image = new static($class::createFromString($string));
66
67
        if ($image->getMimeType() !== 'image/gif') {
68
            return $image;
69
        }
70
71
        $stream = fopen('php://temp', 'r+');
72
73
        fwrite($stream, $string);
74
        rewind($stream);
75
76
        if (self::isAnimatedGif($stream)) {
77
            $image->image->setAnimated(true);
78
        }
79
80
        fclose($stream);
81
82
        return $image;
83
    }
84
85
    /**
86
     * Constructor.
87
     *
88
     * @param Libs\LibInterface $image
89
     * @param string            $filename Original filename (used to overwrite)
90
     */
91
    public function __construct(Libs\LibInterface $image, $filename = null)
92
    {
93
        $this->image = $image;
94
        $this->filename = $filename;
95
    }
96
97
    /**
98
     * Set the available client hints.
99
     *
100
     * @param array $clientHints
101
     *
102
     * @return self
103
     */
104
    public function setClientHints(array $clientHints)
105
    {
106
        $normalize = [];
107
108
        foreach ($clientHints as $key => $value) {
109
            $normalize[strtolower($key)] = is_null($value) ? null : (float) $value;
110
        }
111
112
        if (array_diff_key($normalize, $this->clientHints)) {
113
            throw new \InvalidArgumentException('Invalid client hints');
114
        }
115
116
        $this->clientHints = array_replace($this->clientHints, $normalize);
117
118
        return $this;
119
    }
120
121
    /**
122
     * Set a default background color used to fill in some transformation functions.
123
     *
124
     * @param array $background The color in rgb, for example: array(0,127,34)
125
     *
126
     * @return self
127
     */
128
    public function setBackground(array $background)
129
    {
130
        $this->image->setBackground($background);
131
132
        return $this;
133
    }
134
135
    /**
136
     * Define the image compression quality for jpg images.
137
     *
138
     * @param int $quality The quality (from 0 to 100)
139
     *
140
     * @deprecated Use quality instead
141
     *
142
     * @return self
143
     */
144
    public function setCompressionQuality($quality)
145
    {
146
        error_log('The method `setCompressionQuality()` is deprecated. Use `quality()` instead.');
147
148
        return $this->quality($quality);
149
    }
150
151
    /**
152
     * Get the fixed size according with the client hints.
153
     *
154
     * @param int $width
155
     * @param int $height
156
     *
157
     * @return array
158
     */
159
    private function calculateClientSize($width, $height)
160
    {
161 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...
162
            return Dimmensions::getResizeDimmensions($width, $height, $this->clientHints['width'], null);
163
        }
164
165 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...
166
            return Dimmensions::getResizeDimmensions($width, $height, $this->clientHints['viewport-width'], null);
167
        }
168
169
        if ($this->clientHints['dpr'] !== null) {
170
            $width *= $this->clientHints['dpr'];
171
            $height *= $this->clientHints['dpr'];
172
        }
173
174
        return [$width, $height];
175
    }
176
177
    /**
178
     * Inverts the image vertically.
179
     *
180
     * @return self
181
     */
182
    public function flip()
183
    {
184
        $this->image->flip();
185
186
        return $this;
187
    }
188
189
    /**
190
     * Inverts the image horizontally.
191
     *
192
     * @return self
193
     */
194
    public function flop()
195
    {
196
        $this->image->flop();
197
198
        return $this;
199
    }
200
201
    /**
202
     * Saves the image in a file.
203
     *
204
     * @param string $filename Name of the file where the image will be saved. If it's not defined, The original file will be overwritten.
205
     *
206
     * @return self
207
     */
208
    public function save($filename = null)
209
    {
210
        $this->image->save($filename ?: $this->filename);
211
212
        return $this;
213
    }
214
215
    /**
216
     * Returns the image instance.
217
     *
218
     * @return Libs\LibInterface
219
     */
220
    public function getImage()
221
    {
222
        return $this->image;
223
    }
224
225
    /**
226
     * Gets the image data in a string.
227
     *
228
     * @return string The image data
229
     */
230
    public function getString()
231
    {
232
        return $this->image->getString();
233
    }
234
235
    /**
236
     * Gets the mime type of the image.
237
     *
238
     * @return string The mime type
239
     */
240
    public function getMimeType()
241
    {
242
        return $this->image->getMimeType();
243
    }
244
245
    /**
246
     * Gets the width of the image.
247
     *
248
     * @return int The width in pixels
249
     */
250
    public function getWidth()
251
    {
252
        return $this->image->getWidth();
253
    }
254
255
    /**
256
     * Gets the height of the image.
257
     *
258
     * @return int The height in pixels
259
     */
260
    public function getHeight()
261
    {
262
        return $this->image->getHeight();
263
    }
264
265
    /**
266
     * Converts the image to other format.
267
     *
268
     * @param string $format The new format: png, jpg, gif
269
     *
270
     * @return self
271
     */
272
    public function format($format)
273
    {
274
        $this->image->format($format);
275
276
        return $this;
277
    }
278
279
    /**
280
     * Resizes the image maintaining the proportion (A 800x600 image resized to 400x400 becomes to 400x300).
281
     *
282
     * @param int|string $width  The max width of the image. It can be a number (pixels) or percentaje
283
     * @param int|string $height The max height of the image. It can be a number (pixels) or percentaje
284
     * @param bool       $cover
285
     *
286
     * @return self
287
     */
288
    public function resize($width, $height = 0, $cover = false)
289
    {
290
        $imageWidth = $this->getWidth();
291
        $imageHeight = $this->getHeight();
292
293
        $width = Dimmensions::getIntegerValue('x', $width, $imageWidth);
294
        $height = Dimmensions::getIntegerValue('y', $height, $imageHeight);
295
296
        list($width, $height) = Dimmensions::getResizeDimmensions($imageWidth, $imageHeight, $width, $height, $cover);
297
        list($width, $height) = $this->calculateClientSize($width, $height);
298
299
        if ($width >= $imageWidth) {
300
            return $this;
301
        }
302
303
        $this->image->resize($width, $height);
304
305
        return $this;
306
    }
307
308
    /**
309
     * Crops the image.
310
     *
311
     * @param int|string $width  The new width of the image. It can be a number (pixels) or percentaje
312
     * @param int|string $height The new height of the image. It can be a number (pixels) or percentaje
313
     * @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
314
     * @param int|string $y      The "y" position to crop. It can be number (pixels), percentaje or [top, middle, bottom]
315
     *
316
     * @return self
317
     */
318
    public function crop($width, $height, $x = 'center', $y = 'middle')
319
    {
320
        $imageWidth = $this->getWidth();
321
        $imageHeight = $this->getHeight();
322
323
        $width = Dimmensions::getIntegerValue('x', $width, $imageWidth);
324
        $height = Dimmensions::getIntegerValue('y', $height, $imageHeight);
325
326
        list($width, $height) = $this->calculateClientSize($width, $height);
327
328
        switch ($x) {
329
            case self::CROP_BALANCED:
330
            case self::CROP_ENTROPY:
331
            case self::CROP_FACE:
332
                list($x, $y) = $this->image->getCropOffsets($width, $height, $x);
333
        }
334
335
        $x = Dimmensions::getPositionValue('x', $x, $width, $imageWidth);
336
        $y = Dimmensions::getPositionValue('y', $y, $height, $imageHeight);
337
338
        $this->image->crop($width, $height, $x, $y);
339
340
        return $this;
341
    }
342
343
    /**
344
     * Adjust the image to the given dimmensions. Resizes and crops the image maintaining the proportions.
345
     *
346
     * @param int|string $width  The new width in number (pixels) or percentaje
347
     * @param int|string $height The new height in number (pixels) or percentaje
348
     * @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
349
     * @param int|string $y      The "y" position to crop. It can be number (pixels), percentaje or [top, middle, bottom]
350
     *
351
     * @return self
352
     */
353
    public function resizeCrop($width, $height, $x = 'center', $y = 'middle')
354
    {
355
        $this->resize($width, $height, true);
356
        $this->crop($width, $height, $x, $y);
357
358
        return $this;
359
    }
360
361
    /**
362
     * Rotates the image.
363
     *
364
     * @param int $angle Rotation angle in degrees (anticlockwise)
365
     *
366
     * @return self
367
     */
368
    public function rotate($angle)
369
    {
370
        if (($angle = intval($angle)) !== 0) {
371
            $this->image->rotate($angle);
372
        }
373
374
        return $this;
375
    }
376
377
    /**
378
     * Define the image compression quality for jpg images.
379
     *
380
     * @param int $quality The quality (from 0 to 100)
381
     *
382
     * @return self
383
     */
384
    public function quality($quality)
385
    {
386
        $quality = intval($quality);
387
388
        if ($quality < 0) {
389
            $quality = 0;
390
        } elseif ($quality > 100) {
391
            $quality = 100;
392
        }
393
394
        $this->image->setCompressionQuality($quality);
395
396
        return $this;
397
    }
398
399
    /**
400
     * Add a watermark to current image.
401
     *
402
     * @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...
403
     * @param mixed  $x    Horizontal position
404
     * @param mixed  $y    Vertical position
405
     *
406
     * @return self
407
     */
408
    public function watermark(Image $image, $x = 'right', $y = 'bottom')
409
    {
410
        $imageWidth = $this->getWidth();
411
        $imageHeight = $this->getHeight();
412
413
        $width = $image->getWidth();
414
        $height = $image->getHeight();
415
416
        $x = Dimmensions::getPositionValue('x', $x, $width, $imageWidth);
417
        $y = Dimmensions::getPositionValue('y', $y, $height, $imageHeight);
418
419
        $this->image->watermark($image->getImage(), $x, $y);
420
421
        return $this;
422
    }
423
424
    /**
425
     * Add opacity to image from 0 (transparent) to 100 (opaque).
426
     *
427
     * @param int $opacity Opacity value
428
     *
429
     * @return self
430
     */
431
    public function opacity($opacity)
432
    {
433
        $this->image->opacity($opacity);
434
435
        return $this;
436
    }
437
438
    /**
439
     * Reads the EXIF data from a JPEG and returns an associative array
440
     * (requires the exif PHP extension enabled).
441
     *
442
     * @param null|string $key
443
     *
444
     * @return null|array
445
     */
446
    public function getExifData($key = null)
447
    {
448
        if ($this->filename !== null && ($this->getMimeType() === 'image/jpeg')) {
449
            $exif = exif_read_data($this->filename);
450
451
            if ($key !== null) {
452
                return isset($exif[$key]) ? $exif[$key] : null;
453
            }
454
455
            return $exif;
456
        }
457
    }
458
459
    /**
460
     * Transform the image executing various operations of crop, resize, resizeCrop and format.
461
     *
462
     * @param string $operations The string with all operations separated by "|".
463
     *
464
     * @return self
465
     */
466
    public function transform($operations = null)
467
    {
468
        //No transform operations, resize to fix the client size
469
        if (empty($operations)) {
470
            return $this->resize($this->getWidth(), $this->getHeight());
471
        }
472
473
        $operations = self::parseOperations($operations);
474
475
        foreach ($operations as $operation) {
476
            switch ($operation['function']) {
477
                case 'crop':
478
                case 'resizecrop':
479
                    if (empty($operation['params'][2])) {
480
                        break;
481
                    }
482
483
                    switch ($operation['params'][2]) {
484
                        case 'CROP_ENTROPY':
485
                            $operation['params'][2] = self::CROP_ENTROPY;
486
                            break;
487
488
                        case 'CROP_BALANCED':
489
                            $operation['params'][2] = self::CROP_BALANCED;
490
                            break;
491
492
                        case 'CROP_FACE':
493
                            $operation['params'][2] = self::CROP_FACE;
494
                            break;
495
                    }
496
497
                    break;
498
            }
499
500
            call_user_func_array([$this, $operation['function']], $operation['params']);
501
        }
502
503
        return $this;
504
    }
505
506
    /**
507
     * Send the HTTP header with the content-type, output the image data and die.
508
     */
509 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...
510
    {
511
        if (($string = $this->getString()) && ($mimetype = $this->getMimeType())) {
512
            header('Content-Type: '.$mimetype);
513
            die($string);
514
        }
515
    }
516
517
    /**
518
     * Returns the image as base64 url.
519
     *
520
     * @return string|null
521
     */
522 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...
523
    {
524
        if (($string = $this->getString()) && ($mimetype = $this->getMimeType())) {
525
            $string = base64_encode($string);
526
527
            return "data:{$mimetype};base64,{$string}";
528
        }
529
    }
530
531
    /**
532
     * Auto-rotate the image according with its exif data
533
     * Taken from: http://php.net/manual/en/function.exif-read-data.php#76964.
534
     *
535
     * @return self
536
     */
537
    public function autoRotate()
538
    {
539
        switch ($this->getExifData('Orientation')) {
540
            case 2:
541
                $this->flop();
542
                break;
543
544
            case 3:
545
                $this->rotate(180);
546
                break;
547
548
            case 4:
549
                $this->flip();
550
                break;
551
552
            case 5:
553
                $this->flip()->rotate(-90);
554
                break;
555
556
            case 6:
557
                $this->rotate(90);
558
                break;
559
560
            case 7:
561
                $this->flop()->rotate(-90);
562
                break;
563
564
            case 8:
565
                $this->rotate(90);
566
                break;
567
        }
568
569
        return $this;
570
    }
571
572
    /**
573
     * Check whether the image is an animated gif.
574
     * Copied from: https://github.com/Sybio/GifFrameExtractor/blob/master/src/GifFrameExtractor/GifFrameExtractor.php#L181.
575
     *
576
     * @param resource A stream pointer opened by fopen()
577
     *
578
     * @return bool
579
     */
580
    private static function isAnimatedGif($stream)
581
    {
582
        $count = 0;
583
584
        while (!feof($stream) && $count < 2) {
585
            $chunk = fread($stream, 1024 * 100); //read 100kb at a time
586
            $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches);
587
        }
588
589
        return $count > 1;
590
    }
591
592
    /**
593
     * Converts a string with operations in an array.
594
     *
595
     * @param string $operations The operations string
596
     *
597
     * @return array
598
     */
599
    private static function parseOperations($operations)
600
    {
601
        $valid_operations = ['resize', 'resizecrop', 'crop', 'format', 'quality'];
602
        $operations = explode('|', str_replace(' ', '', $operations));
603
        $return = [];
604
605
        foreach ($operations as $operations) {
606
            $params = explode(',', $operations);
607
            $function = strtolower(trim(array_shift($params)));
608
609
            if (!in_array($function, $valid_operations, true)) {
610
                throw new ImageException("The transform function '{$function}' is not valid");
611
            }
612
613
            $return[] = [
614
                'function' => $function,
615
                'params' => $params,
616
            ];
617
        }
618
619
        return $return;
620
    }
621
622
    /**
623
     * Checks the library to use and returns its class.
624
     *
625
     * @param string $library The library name (Gd, Imagick)
626
     *
627
     * @throws ImageException if the image library does not exists.
628
     *
629
     * @return string
630
     */
631
    private static function getLibraryClass($library)
632
    {
633
        if (!$library) {
634
            $library = Libs\Imagick::checkCompatibility() ? self::LIB_IMAGICK : self::LIB_GD;
635
        }
636
637
        $class = 'Imagecow\\Libs\\'.$library;
638
639
        if (!class_exists($class)) {
640
            throw new ImageException('The image library is not valid');
641
        }
642
643
        if (!$class::checkCompatibility()) {
644
            throw new ImageException("The image library '$library' is not installed in this computer");
645
        }
646
647
        return $class;
648
    }
649
}
650