Completed
Push — 2.x-dev ( 170855...7eb1ef )
by Oscar
05:09 queued 47s
created

Image::setClientHints()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 13
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

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