Completed
Push — master ( b168cf...8792d0 )
by Oscar
01:54
created

Image::setClientHints()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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