Image::calculateClientSize()   A
last analyzed

Complexity

Conditions 6
Paths 4

Size

Total Lines 17

Duplication

Lines 6
Ratio 35.29 %

Importance

Changes 0
Metric Value
dl 6
loc 17
rs 9.0777
c 0
b 0
f 0
cc 6
nc 4
nop 2
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 && !$cover) {
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
        if (in_array($x, [self::CROP_BALANCED, self::CROP_ENTROPY, self::CROP_FACE], true)) {
329
            list($x, $y) = $this->image->getCropOffsets($width, $height, $x);
330
        }
331
332
        $x = Dimmensions::getPositionValue('x', $x, $width, $imageWidth);
333
        $y = Dimmensions::getPositionValue('y', $y, $height, $imageHeight);
334
335
        $this->image->crop($width, $height, $x, $y);
336
337
        return $this;
338
    }
339
340
    /**
341
     * Adjust the image to the given dimmensions. Resizes and crops the image maintaining the proportions.
342
     *
343
     * @param int|string $width  The new width in number (pixels) or percentaje
344
     * @param int|string $height The new height in number (pixels) or percentaje
345
     * @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
346
     * @param int|string $y      The "y" position to crop. It can be number (pixels), percentaje or [top, middle, bottom]
347
     *
348
     * @return self
349
     */
350
    public function resizeCrop($width, $height, $x = 'center', $y = 'middle')
351
    {
352
        $this->resize($width, $height, true);
353
        $this->crop($width, $height, $x, $y);
354
355
        return $this;
356
    }
357
358
    /**
359
     * Rotates the image.
360
     *
361
     * @param int $angle Rotation angle in degrees (anticlockwise)
362
     *
363
     * @return self
364
     */
365
    public function rotate($angle)
366
    {
367
        if (($angle = intval($angle)) !== 0) {
368
            $this->image->rotate($angle);
369
        }
370
371
        return $this;
372
    }
373
374
    /**
375
     * Apply blur to image
376
     *
377
     * @param int $loops Quantity of blur effect loop
378
     *
379
     * @return self
380
     */
381
    public function blur($loops = 4)
382
    {
383
        $this->image->blur($loops);
384
385
        return $this;
386
    }
387
388
    /**
389
     * Define the image compression quality for jpg images.
390
     *
391
     * @param int $quality The quality (from 0 to 100)
392
     *
393
     * @return self
394
     */
395
    public function quality($quality)
396
    {
397
        $quality = intval($quality);
398
399
        if ($quality < 0) {
400
            $quality = 0;
401
        } elseif ($quality > 100) {
402
            $quality = 100;
403
        }
404
405
        $this->image->setCompressionQuality($quality);
406
407
        return $this;
408
    }
409
410
    /**
411
     * Add a watermark to current image.
412
     *
413
     * @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...
414
     * @param mixed  $x    Horizontal position
415
     * @param mixed  $y    Vertical position
416
     *
417
     * @return self
418
     */
419
    public function watermark(Image $image, $x = 'right', $y = 'bottom')
420
    {
421
        $imageWidth = $this->getWidth();
422
        $imageHeight = $this->getHeight();
423
424
        $width = $image->getWidth();
425
        $height = $image->getHeight();
426
427
        $x = Dimmensions::getPositionValue('x', $x, $width, $imageWidth);
428
        $y = Dimmensions::getPositionValue('y', $y, $height, $imageHeight);
429
430
        $this->image->watermark($image->getImage(), $x, $y);
431
432
        return $this;
433
    }
434
435
    /**
436
     * Add opacity to image from 0 (transparent) to 100 (opaque).
437
     *
438
     * @param int $opacity Opacity value
439
     *
440
     * @return self
441
     */
442
    public function opacity($opacity)
443
    {
444
        $this->image->opacity($opacity);
445
446
        return $this;
447
    }
448
449
    /**
450
     * Set the image progressive or not
451
     *
452
     * @param bool $progressive
453
     *
454
     * @return self
455
     */
456
    public function progressive($progressive = true)
457
    {
458
        $this->image->setProgressive((bool) $progressive);
459
460
        return $this;
461
    }
462
463
    /**
464
     * Reads the EXIF data from a JPEG and returns an associative array
465
     * (requires the exif PHP extension enabled).
466
     *
467
     * @param null|string $key
468
     *
469
     * @return null|array
470
     */
471
    public function getExifData($key = null)
472
    {
473
        if ($this->filename !== null && ($this->getMimeType() === 'image/jpeg')) {
474
            $exif = exif_read_data($this->filename);
475
476
            if ($key !== null) {
477
                return isset($exif[$key]) ? $exif[$key] : null;
478
            }
479
480
            return $exif;
481
        }
482
    }
483
484
    /**
485
     * Transform the image executing various operations of crop, resize, resizeCrop and format.
486
     *
487
     * @param string $operations The string with all operations separated by "|".
488
     *
489
     * @return self
490
     */
491
    public function transform($operations = null)
492
    {
493
        //No transform operations, resize to fix the client size
494
        if (empty($operations)) {
495
            return $this->resize($this->getWidth(), $this->getHeight());
496
        }
497
498
        $operations = self::parseOperations($operations);
499
500
        foreach ($operations as $operation) {
501
            switch ($operation['function']) {
502
                case 'crop':
503
                case 'resizecrop':
504
                    if (empty($operation['params'][2])) {
505
                        break;
506
                    }
507
508
                    switch ($operation['params'][2]) {
509
                        case 'CROP_ENTROPY':
510
                            $operation['params'][2] = self::CROP_ENTROPY;
511
                            break;
512
513
                        case 'CROP_BALANCED':
514
                            $operation['params'][2] = self::CROP_BALANCED;
515
                            break;
516
517
                        case 'CROP_FACE':
518
                            $operation['params'][2] = self::CROP_FACE;
519
                            break;
520
                    }
521
522
                    break;
523
            }
524
525
            call_user_func_array([$this, $operation['function']], $operation['params']);
526
        }
527
528
        return $this;
529
    }
530
531
    /**
532
     * Send the HTTP header with the content-type, output the image data and die.
533
     */
534 View Code Duplication
    public function show()
535
    {
536
        if (($string = $this->getString()) && ($mimetype = $this->getMimeType())) {
537
            header('Content-Type: '.$mimetype);
538
            die($string);
539
        }
540
    }
541
542
    /**
543
     * Returns the image as base64 url.
544
     *
545
     * @return string|null
546
     */
547 View Code Duplication
    public function base64()
548
    {
549
        if (($string = $this->getString()) && ($mimetype = $this->getMimeType())) {
550
            $string = base64_encode($string);
551
552
            return "data:{$mimetype};base64,{$string}";
553
        }
554
    }
555
556
    /**
557
     * Auto-rotate the image according with its exif data
558
     * Taken from: http://php.net/manual/en/function.exif-read-data.php#76964.
559
     *
560
     * @return self
561
     */
562
    public function autoRotate()
563
    {
564
        switch ($this->getExifData('Orientation')) {
565
            case 2:
566
                $this->flop();
567
                break;
568
569
            case 3:
570
                $this->rotate(180);
571
                break;
572
573
            case 4:
574
                $this->flip();
575
                break;
576
577
            case 5:
578
                $this->flip()->rotate(90);
579
                break;
580
581
            case 6:
582
                $this->rotate(90);
583
                break;
584
585
            case 7:
586
                $this->flop()->rotate(90);
587
                break;
588
589
            case 8:
590
                $this->rotate(-90);
591
                break;
592
        }
593
594
        return $this;
595
    }
596
597
    /**
598
     * Check whether the image is an animated gif.
599
     * Copied from: https://github.com/Sybio/GifFrameExtractor/blob/master/src/GifFrameExtractor/GifFrameExtractor.php#L181.
600
     *
601
     * @param resource A stream pointer opened by fopen()
602
     *
603
     * @return bool
604
     */
605
    private static function isAnimatedGif($stream)
606
    {
607
        $count = 0;
608
609
        while (!feof($stream) && $count < 2) {
610
            $chunk = fread($stream, 1024 * 100); //read 100kb at a time
611
            $count += preg_match_all('#\x00\x21\xF9\x04.{4}\x00(\x2C|\x21)#s', $chunk, $matches);
612
        }
613
614
        return $count > 1;
615
    }
616
617
    /**
618
     * Converts a string with operations in an array.
619
     *
620
     * @param string $operations The operations string
621
     *
622
     * @return array
623
     */
624
    private static function parseOperations($operations)
625
    {
626
        $valid_operations = ['resize', 'resizecrop', 'crop', 'format', 'quality'];
627
        $operations = explode('|', str_replace(' ', '', $operations));
628
        $return = [];
629
630
        foreach ($operations as $operations) {
631
            $params = explode(',', $operations);
632
            $function = strtolower(trim(array_shift($params)));
633
634
            if (!in_array($function, $valid_operations, true)) {
635
                throw new ImageException("The transform function '{$function}' is not valid");
636
            }
637
638
            $return[] = [
639
                'function' => $function,
640
                'params' => $params,
641
            ];
642
        }
643
644
        return $return;
645
    }
646
647
    /**
648
     * Checks the library to use and returns its class.
649
     *
650
     * @param string $library The library name (Gd, Imagick)
651
     *
652
     * @throws ImageException if the image library does not exists.
653
     *
654
     * @return string
655
     */
656
    private static function getLibraryClass($library)
657
    {
658
        if (!$library) {
659
            $library = Libs\Imagick::checkCompatibility() ? self::LIB_IMAGICK : self::LIB_GD;
660
        }
661
662
        $class = 'Imagecow\\Libs\\'.$library;
663
664
        if (!class_exists($class)) {
665
            throw new ImageException('The image library is not valid');
666
        }
667
668
        if (!$class::checkCompatibility()) {
669
            throw new ImageException("The image library '$library' is not installed in this computer");
670
        }
671
672
        return $class;
673
    }
674
}
675