Completed
Push — resize ( f2153e...0f3c1b )
by Mikael
04:20 queued 15s
created

CImage::checkFileExtension()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6
Metric Value
dl 0
loc 9
rs 9.6667
ccs 0
cts 5
cp 0
cc 2
eloc 5
nc 2
nop 1
crap 6
1
<?php
2
/**
3
 * Resize and crop images on the fly, store generated images in a cache.
4
 *
5
 * @author  Mikael Roos [email protected]
6
 * @example http://dbwebb.se/opensource/cimage
7
 * @link    https://github.com/mosbth/cimage
8
 */
9
class CImage
10
{
11
12
    /**
13
     * Constants type of PNG image
14
     */
15
    const PNG_GREYSCALE         = 0;
16
    const PNG_RGB               = 2;
17
    const PNG_RGB_PALETTE       = 3;
18
    const PNG_GREYSCALE_ALPHA   = 4;
19
    const PNG_RGB_ALPHA         = 6;
20
21
22
23
    /**
24
     * Constant for default image quality when not set
25
     */
26
    const JPEG_QUALITY_DEFAULT = 60;
27
28
29
30
    /**
31
     * Quality level for JPEG images.
32
     */
33
    private $quality;
34
35
36
37
    /**
38
     * Is the quality level set from external use (true) or is it default (false)?
39
     */
40
    private $useQuality = false;
41
42
43
44
    /**
45
     * Constant for default image quality when not set
46
     */
47
    const PNG_COMPRESSION_DEFAULT = -1;
48
49
50
51
    /**
52
     * Compression level for PNG images.
53
     */
54
    private $compress;
55
56
57
58
    /**
59
     * Is the compress level set from external use (true) or is it default (false)?
60
     */
61
    private $useCompress = false;
62
63
64
65
66
    /**
67
     * Add HTTP headers for outputing image.
68
     */
69
    private $HTTPHeader = array();
70
71
72
73
    /**
74
     * Default background color, red, green, blue, alpha.
75
     *
76
     * @todo remake when upgrading to PHP 5.5
77
     */
78
    /*
79
    const BACKGROUND_COLOR = array(
80
        'red'   => 0,
81
        'green' => 0,
82
        'blue'  => 0,
83
        'alpha' => null,
84
    );*/
85
86
87
88
    /**
89
     * Default background color to use.
90
     *
91
     * @todo remake when upgrading to PHP 5.5
92
     */
93
    //private $bgColorDefault = self::BACKGROUND_COLOR;
94
    private $bgColorDefault = array(
95
        'red'   => 0,
96
        'green' => 0,
97
        'blue'  => 0,
98
        'alpha' => null,
99
    );
100
101
102
    /**
103
     * Background color to use, specified as part of options.
104
     */
105
    private $bgColor;
106
107
108
109
    /**
110
     * Where to save the target file.
111
     */
112
    private $saveFolder;
113
114
115
116
    /**
117
     * The working image object.
118
     */
119
    private $image;
120
121
122
123
    /**
124
     * Image filename, may include subdirectory, relative from $imageFolder
125
     */
126
    private $imageSrc;
127
128
129
130
    /**
131
     * Actual path to the image, $imageFolder . '/' . $imageSrc
132
     */
133
    private $pathToImage;
134
135
136
137
    /**
138
     * File type for source image, as provided by getimagesize()
139
     */
140
    private $fileType;
141
142
143
144
    /**
145
     * File extension to use when saving image.
146
     */
147
    private $extension;
148
149
150
151
    /**
152
     * Output format, supports null (image) or json.
153
     */
154
    private $outputFormat = null;
155
156
157
158
    /**
159
     * Verbose mode to print out a trace and display the created image
160
     */
161
    private $verbose = false;
162
163
164
165
    /**
166
     * Keep a log/trace on what happens
167
     */
168
    private $log = array();
169
170
171
172
    /**
173
     * Handle image as palette image
174
     */
175
    private $palette;
176
177
178
179
    /**
180
     * Target filename, with path, to save resulting image in.
181
     */
182
    private $cacheFileName;
183
184
185
186
    /**
187
     * Set a format to save image as, or null to use original format.
188
     */
189
    private $saveAs;
190
191
192
    /**
193
     * Path to command for filter optimize, for example optipng or null.
194
     */
195
    private $pngFilter;
196
    private $pngFilterCmd;
197
198
199
200
    /**
201
     * Path to command for deflate optimize, for example pngout or null.
202
     */
203
    private $pngDeflate;
204
    private $pngDeflateCmd;
205
206
207
208
    /**
209
     * Path to command to optimize jpeg images, for example jpegtran or null.
210
     */
211
    private $jpegOptimize;
212
    private $jpegOptimizeCmd;
213
214
215
216
    /**
217
     * Image dimensions, calculated from loaded image.
218
     */
219
    private $width;  // Calculated from source image
220
    private $height; // Calculated from source image
221
222
223
    /**
224
     * New image dimensions, incoming as argument or calculated.
225
     */
226
    private $newWidth;
227
    private $newWidthOrig;  // Save original value
228
    private $newHeight;
229
    private $newHeightOrig; // Save original value
230
231
232
    /**
233
     * Change target height & width when different dpr, dpr 2 means double image dimensions.
234
     */
235
    private $dpr = 1;
236
237
238
    /**
239
     * Always upscale images, even if they are smaller than target image.
240
     */
241
    const UPSCALE_DEFAULT = true;
242
    private $upscale = self::UPSCALE_DEFAULT;
243
244
245
246
    /**
247
     * Array with details on how to crop, incoming as argument and calculated.
248
     */
249
    public $crop;
250
    public $cropOrig; // Save original value
251
252
253
    /**
254
     * String with details on how to do image convolution. String
255
     * should map a key in the $convolvs array or be a string of
256
     * 11 float values separated by comma. The first nine builds
257
     * up the matrix, then divisor and last offset.
258
     */
259
    private $convolve;
260
261
262
    /**
263
     * Custom convolution expressions, matrix 3x3, divisor and offset.
264
     */
265
    private $convolves = array(
266
        'lighten'       => '0,0,0, 0,12,0, 0,0,0, 9, 0',
267
        'darken'        => '0,0,0, 0,6,0, 0,0,0, 9, 0',
268
        'sharpen'       => '-1,-1,-1, -1,16,-1, -1,-1,-1, 8, 0',
269
        'sharpen-alt'   => '0,-1,0, -1,5,-1, 0,-1,0, 1, 0',
270
        'emboss'        => '1,1,-1, 1,3,-1, 1,-1,-1, 3, 0',
271
        'emboss-alt'    => '-2,-1,0, -1,1,1, 0,1,2, 1, 0',
272
        'blur'          => '1,1,1, 1,15,1, 1,1,1, 23, 0',
273
        'gblur'         => '1,2,1, 2,4,2, 1,2,1, 16, 0',
274
        'edge'          => '-1,-1,-1, -1,8,-1, -1,-1,-1, 9, 0',
275
        'edge-alt'      => '0,1,0, 1,-4,1, 0,1,0, 1, 0',
276
        'draw'          => '0,-1,0, -1,5,-1, 0,-1,0, 0, 0',
277
        'mean'          => '1,1,1, 1,1,1, 1,1,1, 9, 0',
278
        'motion'        => '1,0,0, 0,1,0, 0,0,1, 3, 0',
279
    );
280
281
282
    /**
283
     * Resize strategy to fill extra area with background color.
284
     * True or false.
285
     */
286
    private $fillToFit;
287
288
289
290
    /**
291
     * To store value for option scale.
292
     */
293
    private $scale;
294
295
296
297
    /**
298
     * To store value for option.
299
     */
300
    private $rotateBefore;
301
302
303
304
    /**
305
     * To store value for option.
306
     */
307
    private $rotateAfter;
308
309
310
311
    /**
312
     * To store value for option.
313
     */
314
    private $autoRotate;
315
316
317
318
    /**
319
     * To store value for option.
320
     */
321
    private $sharpen;
322
323
324
325
    /**
326
     * To store value for option.
327
     */
328
    private $emboss;
329
330
331
332
    /**
333
     * To store value for option.
334
     */
335
    private $blur;
336
337
338
339
    /**
340
     * Used with option area to set which parts of the image to use.
341
     */
342
    private $area;
343
    private $offset;
344
345
346
347
    /**
348
     * Calculate target dimension for image when using fill-to-fit resize strategy.
349
     */
350
    private $fillWidth;
351
    private $fillHeight;
352
353
354
355
    /**
356
     * Allow remote file download, default is to disallow remote file download.
357
     */
358
    private $allowRemote = false;
359
360
361
362
    /**
363
     * Path to cache for remote download.
364
     */
365
    private $remoteCache;
366
367
368
369
    /**
370
     * Pattern to recognize a remote file.
371
     */
372
    //private $remotePattern = '#^[http|https]://#';
373
    private $remotePattern = '#^https?://#';
374
375
376
377
    /**
378
     * Use the cache if true, set to false to ignore the cached file.
379
     */
380
    private $useCache = true;
381
382
383
384
    /*
385
     * Set whitelist for valid hostnames from where remote source can be
386
     * downloaded.
387
     */
388
    private $remoteHostWhitelist = null;
389
390
391
392
    /*
393
     * Do verbose logging to file by setting this to a filename.
394
     */
395
    private $verboseFileName = null;
396
397
398
399
    /*
400
     * Output to ascii can take som options as an array.
401
     */
402
    private $asciiOptions = array();
403
404
405
406
    /*
407
     * Image copy strategy, defaults to RESAMPLE.
408
     */
409
     const RESIZE = 1;
410
     const RESAMPLE = 2;
411
    private $copyStrategy = null;
412
413
414
415
     /*
416
      * Class for image resizer.
417
      */
418
    private $imageResizer = null;
419
420
    /**
421
     * Properties, the class is mutable and the method setOptions()
422
     * decides (partly) what properties are created.
423
     *
424
     * @todo Clean up these and check if and how they are used
425
     */
426
427
    public $keepRatio;
428
    public $cropToFit;
429
    private $cropWidth;
430
    private $cropHeight;
431
    public $crop_x;
432
    public $crop_y;
433
    public $filters;
434
    private $attr; // Calculated from source image
435
436
437
438
439
    /**
440
     * Constructor, can take arguments to init the object.
441
     *
442
     * @param string $imageSrc    filename which may contain subdirectory.
443
     * @param string $imageFolder path to root folder for images.
444
     * @param string $saveFolder  path to folder where to save the new file or null to skip saving.
445
     * @param string $saveName    name of target file when saveing.
446
     */
447 9
    public function __construct($imageSrc = null, $imageFolder = null, $saveFolder = null, $saveName = null)
448
    {
449 9
        $this->setSource($imageSrc, $imageFolder);
450 9
        $this->setTarget($saveFolder, $saveName);
451 9
        $this->imageResizer = new CImageResizer(array($this, 'log'));
452 9
    }
453
454
455
456
457
    /**
458
     * Set verbose mode.
459
     *
460
     * @param boolean $mode true or false to enable and disable verbose mode,
461
     *                      default is true.
462
     *
463
     * @return $this
464
     */
465
    public function setVerbose($mode = true)
466
    {
467
        $this->verbose = $mode;
468
        return $this;
469
    }
470
471
472
473
    /**
474
     * Set save folder, base folder for saving cache files.
475
     *
476
     * @todo clean up how $this->saveFolder is used in other methods.
477
     *
478
     * @param string $path where to store cached files.
479
     *
480
     * @return $this
481
     */
482 2
    public function setSaveFolder($path)
483
    {
484 2
        $this->saveFolder = $path;
485 2
        return $this;
486
    }
487
488
489
490
    /**
491
     * Use cache or not.
492
     *
493
     * @param boolean $use true or false to use cache.
494
     *
495
     * @return $this
496
     */
497
    public function useCache($use = true)
498
    {
499
        $this->useCache = $use;
500
        return $this;
501
    }
502
503
504
505
    /**
506
     * Create and save a dummy image. Use dimensions as stated in
507
     * $this->newWidth, or $width or default to 100 (same for height.
508
     *
509
     * @param integer $width  use specified width for image dimension.
510
     * @param integer $height use specified width for image dimension.
511
     *
512
     * @return $this
513
     */
514 2
    public function createDummyImage($width = null, $height = null)
515
    {
516 2
        $this->newWidth  = $this->newWidth  ?: $width  ?: 100;
517 2
        $this->newHeight = $this->newHeight ?: $height ?: 100;
518
519 2
        $this->image = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
520
521 2
        return $this;
522
    }
523
524
525
526
    /**
527
     * Allow or disallow remote image download.
528
     *
529
     * @param boolean $allow   true or false to enable and disable.
530
     * @param string  $cache   path to cache dir.
531
     * @param string  $pattern to use to detect if its a remote file.
532
     *
533
     * @return $this
534
     */
535 2
    public function setRemoteDownload($allow, $cache, $pattern = null)
536
    {
537 2
        $this->allowRemote = $allow;
538 2
        $this->remoteCache = $cache;
539 2
        $this->remotePattern = is_null($pattern) ? $this->remotePattern : $pattern;
540
541 2
        $this->log(
542
            "Set remote download to: "
543 2
            . ($this->allowRemote ? "true" : "false")
544 2
            . " using pattern "
545 2
            . $this->remotePattern
546 2
        );
547
548 2
        return $this;
549
    }
550
551
552
553
    /**
554
     * Check if the image resource is a remote file or not.
555
     *
556
     * @param string $src check if src is remote.
557
     *
558
     * @return boolean true if $src is a remote file, else false.
559
     */
560 2
    public function isRemoteSource($src)
561
    {
562 2
        $remote = preg_match($this->remotePattern, $src);
563 2
        $this->log("Detected remote image: " . ($remote ? "true" : "false"));
564 2
        return !!$remote;
565
    }
566
567
568
569
    /**
570
     * Set whitelist for valid hostnames from where remote source can be
571
     * downloaded.
572
     *
573
     * @param array $whitelist with regexp hostnames to allow download from.
574
     *
575
     * @return $this
576
     */
577 2
    public function setRemoteHostWhitelist($whitelist = null)
578
    {
579 2
        $this->remoteHostWhitelist = $whitelist;
580 2
        $this->log(
581
            "Setting remote host whitelist to: "
582 2
            . (is_null($whitelist) ? "null" : print_r($whitelist, 1))
583 2
        );
584 2
        return $this;
585
    }
586
587
588
589
    /**
590
     * Check if the hostname for the remote image, is on a whitelist,
591
     * if the whitelist is defined.
592
     *
593
     * @param string $src the remote source.
594
     *
595
     * @return boolean true if hostname on $src is in the whitelist, else false.
596
     */
597 3
    public function isRemoteSourceOnWhitelist($src)
598
    {
599 3
        if (is_null($this->remoteHostWhitelist)) {
600 1
            $this->log("Remote host on whitelist not configured - allowing.");
601 1
            return true;
602
        }
603
604 2
        $whitelist = new CWhitelist();
605 2
        $hostname = parse_url($src, PHP_URL_HOST);
606 2
        $allow = $whitelist->check($hostname, $this->remoteHostWhitelist);
0 ignored issues
show
Security Bug introduced by
It seems like $hostname defined by parse_url($src, PHP_URL_HOST) on line 605 can also be of type false; however, CWhitelist::check() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
607
608 2
        $this->log(
609
            "Remote host is on whitelist: "
610 2
            . ($allow ? "true" : "false")
611 2
        );
612 2
        return $allow;
613
    }
614
615
616
617
    /**
618
     * Check if file extension is valid as a file extension.
619
     *
620
     * @param string $extension of image file.
621
     *
622
     * @return $this
623
     */
624
    private function checkFileExtension($extension)
625
    {
626
        $valid = array('jpg', 'jpeg', 'png', 'gif');
627
628
        in_array(strtolower($extension), $valid)
629
            or $this->raiseError('Not a valid file extension.');
630
631
        return $this;
632
    }
633
634
635
636
    /**
637
     * Normalize the file extension.
638
     *
639
     * @param string $extension of image file or skip to use internal.
640
     *
641
     * @return string $extension as a normalized file extension.
642
     */
643 2
    private function normalizeFileExtension($extension = null)
644
    {
645 2
        $extension = strtolower($extension ? $extension : $this->extension);
646
647 2
        if ($extension == 'jpeg') {
648
                $extension = 'jpg';
649
        }
650
651 2
        return $extension;
652
    }
653
654
655
656
    /**
657
     * Download a remote image and return path to its local copy.
658
     *
659
     * @param string $src remote path to image.
660
     *
661
     * @return string as path to downloaded remote source.
662
     */
663
    public function downloadRemoteSource($src)
664
    {
665
        if (!$this->isRemoteSourceOnWhitelist($src)) {
666
            throw new Exception("Hostname is not on whitelist for remote sources.");
667
        }
668
669
        $remote = new CRemoteImage();
670
671
        if (!is_writable($this->remoteCache)) {
672
            $this->log("The remote cache is not writable.");
673
        }
674
675
        $remote->setCache($this->remoteCache);
676
        $remote->useCache($this->useCache);
677
        $src = $remote->download($src);
678
679
        $this->log("Remote HTTP status: " . $remote->getStatus());
680
        $this->log("Remote item is in local cache: $src");
681
        $this->log("Remote details on cache:" . print_r($remote->getDetails(), true));
682
683
        return $src;
684
    }
685
686
687
688
    /**
689
     * Set source file to use as image source.
690
     *
691
     * @param string $src of image.
692
     * @param string $dir as optional base directory where images are.
693
     *
694
     * @return $this
695
     */
696 9
    public function setSource($src, $dir = null)
697
    {
698 9
        if (!isset($src)) {
699 9
            $this->imageSrc = null;
700 9
            $this->pathToImage = null;
701 9
            return $this;
702
        }
703
704 2
        if ($this->allowRemote && $this->isRemoteSource($src)) {
705
            $src = $this->downloadRemoteSource($src);
706
            $dir = null;
707
        }
708
709 2
        if (!isset($dir)) {
710
            $dir = dirname($src);
711
            $src = basename($src);
712
        }
713
714 2
        $this->imageSrc     = ltrim($src, '/');
715 2
        $imageFolder        = rtrim($dir, '/');
716 2
        $this->pathToImage  = $imageFolder . '/' . $this->imageSrc;
717
718 2
        return $this;
719
    }
720
721
722
723
    /**
724
     * Set target file.
725
     *
726
     * @param string $src of target image.
727
     * @param string $dir as optional base directory where images are stored.
728
     *                    Uses $this->saveFolder if null.
729
     *
730
     * @return $this
731
     */
732 9
    public function setTarget($src = null, $dir = null)
733
    {
734 9
        if (!isset($src)) {
735 9
            $this->cacheFileName = null;
736 9
            return $this;
737
        }
738
739 2
        if (isset($dir)) {
740
            $this->saveFolder = rtrim($dir, '/');
741
        }
742
743 2
        $this->cacheFileName  = $this->saveFolder . '/' . $src;
744
745
        // Sanitize filename
746 2
        $this->cacheFileName = preg_replace('/^a-zA-Z0-9\.-_/', '', $this->cacheFileName);
747 2
        $this->log("The cache file name is: " . $this->cacheFileName);
748
749 2
        return $this;
750
    }
751
752
753
754
    /**
755
     * Get filename of target file.
756
     *
757
     * @return Boolean|String as filename of target or false if not set.
758
     */
759 2
    public function getTarget()
760
    {
761 2
        return $this->cacheFileName;
762
    }
763
764
765
766
    /**
767
     * Set options to use when processing image.
768
     *
769
     * @param array $args used when processing image.
770
     *
771
     * @return $this
772
     */
773
    public function setOptions($args)
774
    {
775
        $this->log("Set new options for processing image.");
776
777
        $defaults = array(
778
            // Options for calculate dimensions
779
            'newWidth'    => null,
780
            'newHeight'   => null,
781
            'aspectRatio' => null,
782
            'keepRatio'   => true,
783
            'cropToFit'   => false,
784
            'fillToFit'   => null,
785
            'crop'        => null, //array('width'=>null, 'height'=>null, 'start_x'=>0, 'start_y'=>0),
786
            'area'        => null, //'0,0,0,0',
787
            'upscale'     => self::UPSCALE_DEFAULT,
788
789
            // Options for caching or using original
790
            'useCache'    => true,
791
            'useOriginal' => true,
792
793
            // Pre-processing, before resizing is done
794
            'scale'        => null,
795
            'rotateBefore' => null,
796
            'autoRotate'  => false,
797
798
            // General options
799
            'bgColor'     => null,
800
801
            // Post-processing, after resizing is done
802
            'palette'     => null,
803
            'filters'     => null,
804
            'sharpen'     => null,
805
            'emboss'      => null,
806
            'blur'        => null,
807
            'convolve'       => null,
808
            'rotateAfter' => null,
809
810
            // Output format
811
            'outputFormat' => null,
812
            'dpr'          => 1,
813
        );
814
815
        // Convert crop settings from string to array
816 View Code Duplication
        if (isset($args['crop']) && !is_array($args['crop'])) {
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...
817
            $pices = explode(',', $args['crop']);
818
            $args['crop'] = array(
819
                'width'   => $pices[0],
820
                'height'  => $pices[1],
821
                'start_x' => $pices[2],
822
                'start_y' => $pices[3],
823
            );
824
        }
825
826
        // Convert area settings from string to array
827 View Code Duplication
        if (isset($args['area']) && !is_array($args['area'])) {
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...
828
                $pices = explode(',', $args['area']);
829
                $args['area'] = array(
830
                    'top'    => $pices[0],
831
                    'right'  => $pices[1],
832
                    'bottom' => $pices[2],
833
                    'left'   => $pices[3],
834
                );
835
        }
836
837
        // Convert filter settings from array of string to array of array
838
        if (isset($args['filters']) && is_array($args['filters'])) {
839
            foreach ($args['filters'] as $key => $filterStr) {
840
                $parts = explode(',', $filterStr);
841
                $filter = $this->mapFilter($parts[0]);
842
                $filter['str'] = $filterStr;
843
                for ($i=1; $i<=$filter['argc']; $i++) {
844
                    if (isset($parts[$i])) {
845
                        $filter["arg{$i}"] = $parts[$i];
846
                    } else {
847
                        throw new Exception(
848
                            'Missing arg to filter, review how many arguments are needed at
849
                            http://php.net/manual/en/function.imagefilter.php'
850
                        );
851
                    }
852
                }
853
                $args['filters'][$key] = $filter;
854
            }
855
        }
856
857
        // Merge default arguments with incoming and set properties.
858
        //$args = array_merge_recursive($defaults, $args);
859
        $args = array_merge($defaults, $args);
860
        foreach ($defaults as $key => $val) {
861
            $this->{$key} = $args[$key];
862
        }
863
864
        if ($this->bgColor) {
865
            $this->setDefaultBackgroundColor($this->bgColor);
866
        }
867
868
        // Save original values to enable re-calculating
869
        $this->newWidthOrig  = $this->newWidth;
870
        $this->newHeightOrig = $this->newHeight;
871
        $this->cropOrig      = $this->crop;
872
873
        return $this;
874
    }
875
876
877
878
    /**
879
     * Map filter name to PHP filter and id.
880
     *
881
     * @param string $name the name of the filter.
882
     *
883
     * @return array with filter settings
884
     * @throws Exception
885
     */
886
    private function mapFilter($name)
887
    {
888
        $map = array(
889
            'negate'          => array('id'=>0,  'argc'=>0, 'type'=>IMG_FILTER_NEGATE),
890
            'grayscale'       => array('id'=>1,  'argc'=>0, 'type'=>IMG_FILTER_GRAYSCALE),
891
            'brightness'      => array('id'=>2,  'argc'=>1, 'type'=>IMG_FILTER_BRIGHTNESS),
892
            'contrast'        => array('id'=>3,  'argc'=>1, 'type'=>IMG_FILTER_CONTRAST),
893
            'colorize'        => array('id'=>4,  'argc'=>4, 'type'=>IMG_FILTER_COLORIZE),
894
            'edgedetect'      => array('id'=>5,  'argc'=>0, 'type'=>IMG_FILTER_EDGEDETECT),
895
            'emboss'          => array('id'=>6,  'argc'=>0, 'type'=>IMG_FILTER_EMBOSS),
896
            'gaussian_blur'   => array('id'=>7,  'argc'=>0, 'type'=>IMG_FILTER_GAUSSIAN_BLUR),
897
            'selective_blur'  => array('id'=>8,  'argc'=>0, 'type'=>IMG_FILTER_SELECTIVE_BLUR),
898
            'mean_removal'    => array('id'=>9,  'argc'=>0, 'type'=>IMG_FILTER_MEAN_REMOVAL),
899
            'smooth'          => array('id'=>10, 'argc'=>1, 'type'=>IMG_FILTER_SMOOTH),
900
            'pixelate'        => array('id'=>11, 'argc'=>2, 'type'=>IMG_FILTER_PIXELATE),
901
        );
902
903
        if (isset($map[$name])) {
904
            return $map[$name];
905
        } else {
906
            throw new Exception('No such filter.');
907
        }
908
    }
909
910
911
912
    /**
913
     * Load image details from original image file.
914
     *
915
     * @param string $file the file to load or null to use $this->pathToImage.
916
     *
917
     * @return $this
918
     * @throws Exception
919
     */
920
    public function loadImageDetails($file = null)
921
    {
922
        $file = $file ? $file : $this->pathToImage;
923
924
        is_readable($file)
925
            or $this->raiseError('Image file does not exist.');
926
927
        // Get details on image
928
        $info = list($this->width, $this->height, $this->fileType, $this->attr) = getimagesize($file);
929
        if (empty($info)) {
930
            throw new Exception("The file doesn't seem to be a valid image.");
931
        }
932
        
933
        $this->imageResizer->setSource($this->width, $this->height);
934
935
        if ($this->verbose) {
936
            $this->log("#Loading image details for: {$file}");
937
            $this->log(" Image width x height (type): {$this->width} x {$this->height} ({$this->fileType}).");
938
            $this->log(" Image filesize: " . filesize($file) . " bytes.");
939
            $this->log(" Image mimetype: " . image_type_to_mime_type($this->fileType));
940
        }
941
942
        return $this;
943
    }
944
945
946
947
    /**
948
     * Init new width and height and do some sanity checks on constraints,
949
     * before any processing can be done.
950
     *
951
     * @return $this
952
     * @throws Exception
953
     */
954
    public function initDimensions()
955
    {
956
        $this->imageResizer->setBaseWidthHeight($this->newWidth, $this->newHeight)
0 ignored issues
show
Documentation introduced by
$this->newWidth is of type integer|double, but the function expects a object<numeric>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
$this->newHeight is of type integer|double, but the function expects a object<numeric>|null.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
957
                           ->setBaseAspecRatio($this->aspectRatio)
0 ignored issues
show
Bug introduced by
The property aspectRatio does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
958
                           ->setBaseDevicePixelRate($this->dpr)
959
                           ->prepareTargetDimensions();
960
961
        return $this;
962
    }
963
964
965
966
    /**
967
     * Calculate new width and height of image, based on settings.
968
     *
969
     * @return $this
970
     */
971
    public function calculateNewWidthAndHeight()
972
    {
973
        $imres = $this->imageResizer;
974
        $strategy = null;
0 ignored issues
show
Unused Code introduced by
$strategy is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
975
        
976
        $strategy = $imres::KEEP_RATIO;
977
978
        if ($this->keepRatio == false) {
979
            $strategy = $imres::STRETCH;
980
        }
981
982
        if ($this->cropToFit == true) {
983
            $strategy = $imres::CROP_TO_FIT;
984
        }
985
986
        if ($this->fillToFit == true) {
987
            $strategy = $imres::FILL_TO_FIT;
988
        }
989
        
990
        $imres->setResizeStrategy($strategy)
991
              ->allowUpscale($this->upscale)
992
              ->calculateTargetWidthAndHeight();
993
        
994
        //$this->newWidth  = $imres->getTargetWidth();
995
        //$this->newHeight = $imres->getTargetHeight();
996
997
        /*
998
        // Crop, use cropped width and height as base for calulations
999
        $this->log("Calculate new width and height.");
1000
        $this->log("Original width x height is {$this->width} x {$this->height}.");
1001
        $this->log("Target dimension (before calculating) newWidth x newHeight is {$this->newWidth} x {$this->newHeight}.");
1002
1003
        // Check if there is an area to crop off
1004
        if (isset($this->area)) {
1005
            $this->offset['top']    = round($this->area['top'] / 100 * $this->height);
1006
            $this->offset['right']  = round($this->area['right'] / 100 * $this->width);
1007
            $this->offset['bottom'] = round($this->area['bottom'] / 100 * $this->height);
1008
            $this->offset['left']   = round($this->area['left'] / 100 * $this->width);
1009
            $this->offset['width']  = $this->width - $this->offset['left'] - $this->offset['right'];
1010
            $this->offset['height'] = $this->height - $this->offset['top'] - $this->offset['bottom'];
1011
            $this->width  = $this->offset['width'];
1012
            $this->height = $this->offset['height'];
1013
            $this->log("The offset for the area to use is top {$this->area['top']}%, right {$this->area['right']}%, bottom {$this->area['bottom']}%, left {$this->area['left']}%.");
1014
            $this->log("The offset for the area to use is top {$this->offset['top']}px, right {$this->offset['right']}px, bottom {$this->offset['bottom']}px, left {$this->offset['left']}px, width {$this->offset['width']}px, height {$this->offset['height']}px.");
1015
        }
1016
1017
        $width  = $this->width;
1018
        $height = $this->height;
1019
1020
        // Check if crop is set
1021
        if ($this->crop) {
1022
            $width  = $this->crop['width']  = $this->crop['width'] <= 0 ? $this->width + $this->crop['width'] : $this->crop['width'];
1023
            $height = $this->crop['height'] = $this->crop['height'] <= 0 ? $this->height + $this->crop['height'] : $this->crop['height'];
1024
1025
            if ($this->crop['start_x'] == 'left') {
1026
                $this->crop['start_x'] = 0;
1027
            } elseif ($this->crop['start_x'] == 'right') {
1028
                $this->crop['start_x'] = $this->width - $width;
1029
            } elseif ($this->crop['start_x'] == 'center') {
1030
                $this->crop['start_x'] = round($this->width / 2) - round($width / 2);
1031
            }
1032
1033
            if ($this->crop['start_y'] == 'top') {
1034
                $this->crop['start_y'] = 0;
1035
            } elseif ($this->crop['start_y'] == 'bottom') {
1036
                $this->crop['start_y'] = $this->height - $height;
1037
            } elseif ($this->crop['start_y'] == 'center') {
1038
                $this->crop['start_y'] = round($this->height / 2) - round($height / 2);
1039
            }
1040
1041
            $this->log("Crop area is width {$width}px, height {$height}px, start_x {$this->crop['start_x']}px, start_y {$this->crop['start_y']}px.");
1042
        }
1043
1044
        // Calculate new width and height if keeping aspect-ratio.
1045
        if ($this->keepRatio) {
1046
1047
            $this->log("Keep aspect ratio.");
1048
1049
            // Crop-to-fit and both new width and height are set.
1050
            if (($this->cropToFit || $this->fillToFit) && isset($this->newWidth) && isset($this->newHeight)) {
1051
1052
                // Use newWidth and newHeigh as width/height, image should fit in box.
1053
                $this->log("Use newWidth and newHeigh as width/height, image should fit in box.");
1054
1055
            } elseif (isset($this->newWidth) && isset($this->newHeight)) {
1056
1057
                // Both new width and height are set.
1058
                // Use newWidth and newHeigh as max width/height, image should not be larger.
1059
                $ratioWidth  = $width  / $this->newWidth;
1060
                $ratioHeight = $height / $this->newHeight;
1061
                $ratio = ($ratioWidth > $ratioHeight) ? $ratioWidth : $ratioHeight;
1062
                $this->newWidth  = round($width  / $ratio);
1063
                $this->newHeight = round($height / $ratio);
1064
                $this->log("New width and height was set.");
1065
1066
            } elseif (isset($this->newWidth)) {
1067
1068
                // Use new width as max-width
1069
                $factor = (float)$this->newWidth / (float)$width;
1070
                $this->newHeight = round($factor * $height);
1071
                $this->log("New width was set.");
1072
1073
            } elseif (isset($this->newHeight)) {
1074
1075
                // Use new height as max-hight
1076
                $factor = (float)$this->newHeight / (float)$height;
1077
                $this->newWidth = round($factor * $width);
1078
                $this->log("New height was set.");
1079
1080
            }
1081
1082
            // Get image dimensions for pre-resize image.
1083
            if ($this->cropToFit || $this->fillToFit) {
1084
1085
                // Get relations of original & target image
1086
                $ratioWidth  = $width  / $this->newWidth;
1087
                $ratioHeight = $height / $this->newHeight;
1088
1089
                if ($this->cropToFit) {
1090
1091
                    // Use newWidth and newHeigh as defined width/height,
1092
                    // image should fit the area.
1093
                    $this->log("Crop to fit.");
1094
                    $ratio = ($ratioWidth < $ratioHeight) ? $ratioWidth : $ratioHeight;
1095
                    $this->cropWidth  = round($width  / $ratio);
1096
                    $this->cropHeight = round($height / $ratio);
1097
                    $this->log("Crop width, height, ratio: $this->cropWidth x $this->cropHeight ($ratio).");
1098
1099
                } elseif ($this->fillToFit) {
1100
1101
                    // Use newWidth and newHeigh as defined width/height,
1102
                    // image should fit the area.
1103
                    $this->log("Fill to fit.");
1104
                    $ratio = ($ratioWidth < $ratioHeight) ? $ratioHeight : $ratioWidth;
1105
                    $this->fillWidth  = round($width  / $ratio);
1106
                    $this->fillHeight = round($height / $ratio);
1107
                    $this->log("Fill width, height, ratio: $this->fillWidth x $this->fillHeight ($ratio).");
1108
                }
1109
            }
1110
        }
1111
1112
        // Crop, ensure to set new width and height
1113
        if ($this->crop) {
1114
            $this->log("Crop.");
1115
            $this->newWidth = round(isset($this->newWidth) ? $this->newWidth : $this->crop['width']);
1116
            $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->crop['height']);
1117
        }
1118
1119
        // No new height or width is set, use existing measures.
1120
        $this->newWidth  = round(isset($this->newWidth) ? $this->newWidth : $this->width);
1121
        $this->newHeight = round(isset($this->newHeight) ? $this->newHeight : $this->height);
1122
        $this->log("Calculated new width x height as {$this->newWidth} x {$this->newHeight}.");
1123
1124
        */
1125
        return $this;
1126
    }
1127
1128
1129
1130
    /**
1131
     * Re-calculate image dimensions when original image dimension has changed.
1132
     *
1133
     * @return $this
1134
     */
1135
    public function reCalculateDimensions()
1136
    {
1137
        $this->log("Re-calculate image dimensions, newWidth x newHeigh was: " . $this->newWidth . " x " . $this->newHeight);
1138
1139
        $this->newWidth  = $this->newWidthOrig;
1140
        $this->newHeight = $this->newHeightOrig;
1141
        $this->crop      = $this->cropOrig;
1142
1143
        $this->initDimensions()
1144
             ->calculateNewWidthAndHeight();
1145
1146
        return $this;
1147
    }
1148
1149
1150
1151
    /**
1152
     * Set extension for filename to save as.
1153
     *
1154
     * @param string $saveas extension to save image as
0 ignored issues
show
Documentation introduced by
There is no parameter named $saveas. Did you maybe mean $saveAs?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

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

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

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

Loading history...
1155
     *
1156
     * @return $this
1157
     */
1158
    public function setSaveAsExtension($saveAs = null)
1159
    {
1160
        if (isset($saveAs)) {
1161
            $saveAs = strtolower($saveAs);
1162
            $this->checkFileExtension($saveAs);
1163
            $this->saveAs = $saveAs;
1164
            $this->extension = $saveAs;
1165
        }
1166
1167
        $this->log("Prepare to save image as: " . $this->extension);
1168
1169
        return $this;
1170
    }
1171
1172
1173
1174
    /**
1175
     * Set JPEG quality to use when saving image
1176
     *
1177
     * @param int $quality as the quality to set.
1178
     *
1179
     * @return $this
1180
     */
1181 View Code Duplication
    public function setJpegQuality($quality = null)
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...
1182
    {
1183
        if ($quality) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $quality of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1184
            $this->useQuality = true;
1185
        }
1186
1187
        $this->quality = isset($quality)
1188
            ? $quality
1189
            : self::JPEG_QUALITY_DEFAULT;
1190
1191
        (is_numeric($this->quality) and $this->quality > 0 and $this->quality <= 100)
1192
            or $this->raiseError('Quality not in range.');
1193
1194
        $this->log("Setting JPEG quality to {$this->quality}.");
1195
1196
        return $this;
1197
    }
1198
1199
1200
1201
    /**
1202
     * Set PNG compressen algorithm to use when saving image
1203
     *
1204
     * @param int $compress as the algorithm to use.
1205
     *
1206
     * @return $this
1207
     */
1208 View Code Duplication
    public function setPngCompression($compress = null)
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...
1209
    {
1210
        if ($compress) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $compress of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1211
            $this->useCompress = true;
1212
        }
1213
1214
        $this->compress = isset($compress)
1215
            ? $compress
1216
            : self::PNG_COMPRESSION_DEFAULT;
1217
1218
        (is_numeric($this->compress) and $this->compress >= -1 and $this->compress <= 9)
1219
            or $this->raiseError('Quality not in range.');
1220
1221
        $this->log("Setting PNG compression level to {$this->compress}.");
1222
1223
        return $this;
1224
    }
1225
1226
1227
1228
    /**
1229
     * Use original image if possible, check options which affects image processing.
1230
     *
1231
     * @param boolean $useOrig default is to use original if possible, else set to false.
1232
     *
1233
     * @return $this
1234
     */
1235
    public function useOriginalIfPossible($useOrig = true)
1236
    {
1237
        if ($useOrig
1238
            && ($this->newWidth == $this->width)
1239
            && ($this->newHeight == $this->height)
1240
            && !$this->area
1241
            && !$this->crop
1242
            && !$this->cropToFit
1243
            && !$this->fillToFit
1244
            && !$this->filters
1245
            && !$this->sharpen
1246
            && !$this->emboss
1247
            && !$this->blur
1248
            && !$this->convolve
1249
            && !$this->palette
1250
            && !$this->useQuality
1251
            && !$this->useCompress
1252
            && !$this->saveAs
1253
            && !$this->rotateBefore
1254
            && !$this->rotateAfter
1255
            && !$this->autoRotate
1256
            && !$this->bgColor
1257
            && ($this->upscale === self::UPSCALE_DEFAULT)
1258
        ) {
1259
            $this->log("Using original image.");
1260
            $this->output($this->pathToImage);
1261
        }
1262
1263
        return $this;
1264
    }
1265
1266
1267
1268
    /**
1269
     * Generate filename to save file in cache.
1270
     *
1271
     * @param string  $base      as optional basepath for storing file.
1272
     * @param boolean $useSubdir use or skip the subdir part when creating the
1273
     *                           filename.
1274
     * @param string  $prefix    to add as part of filename
1275
     *
1276
     * @return $this
1277
     */
1278 2
    public function generateFilename($base = null, $useSubdir = true, $prefix = null)
1279
    {
1280 2
        $filename     = basename($this->pathToImage);
1281 2
        $cropToFit    = $this->cropToFit    ? '_cf'                      : null;
1282 2
        $fillToFit    = $this->fillToFit    ? '_ff'                      : null;
1283 2
        $stretch      = $this->keepRatio === false ? '_st'               : null;
1284 2
        $crop_x       = $this->crop_x       ? "_x{$this->crop_x}"        : null;
1285 2
        $crop_y       = $this->crop_y       ? "_y{$this->crop_y}"        : null;
1286 2
        $scale        = $this->scale        ? "_s{$this->scale}"         : null;
1287 2
        $bgColor      = $this->bgColor      ? "_bgc{$this->bgColor}"     : null;
1288 2
        $quality      = $this->quality      ? "_q{$this->quality}"       : null;
1289 2
        $compress     = $this->compress     ? "_co{$this->compress}"     : null;
1290 2
        $rotateBefore = $this->rotateBefore ? "_rb{$this->rotateBefore}" : null;
1291 2
        $rotateAfter  = $this->rotateAfter  ? "_ra{$this->rotateAfter}"  : null;
1292
1293 2
        $saveAs = $this->normalizeFileExtension();
1294 2
        $saveAs = $saveAs ? "_$saveAs" : null;
1295
1296 2
        $copyStrat = null;
1297 2
        if ($this->copyStrategy === self::RESIZE) {
1298
            $copyStrat = "_rs";
1299
        }
1300
1301 2
        $width  = $this->newWidth  ? '_' . $this->newWidth  : null;
1302 2
        $height = $this->newHeight ? '_' . $this->newHeight : null;
1303
1304 2
        $offset = isset($this->offset)
1305 2
            ? '_o' . $this->offset['top'] . '-' . $this->offset['right'] . '-' . $this->offset['bottom'] . '-' . $this->offset['left']
1306 2
            : null;
1307
1308 2
        $crop = $this->crop
1309 2
            ? '_c' . $this->crop['width'] . '-' . $this->crop['height'] . '-' . $this->crop['start_x'] . '-' . $this->crop['start_y']
1310 2
            : null;
1311
1312 2
        $filters = null;
1313 2
        if (isset($this->filters)) {
1314
            foreach ($this->filters as $filter) {
1315
                if (is_array($filter)) {
1316
                    $filters .= "_f{$filter['id']}";
1317
                    for ($i=1; $i<=$filter['argc']; $i++) {
1318
                        $filters .= "-".$filter["arg{$i}"];
1319
                    }
1320
                }
1321
            }
1322
        }
1323
1324 2
        $sharpen = $this->sharpen ? 's' : null;
1325 2
        $emboss  = $this->emboss  ? 'e' : null;
1326 2
        $blur    = $this->blur    ? 'b' : null;
1327 2
        $palette = $this->palette ? 'p' : null;
1328
1329 2
        $autoRotate = $this->autoRotate ? 'ar' : null;
1330
1331 2
        $optimize  = $this->jpegOptimize ? 'o' : null;
1332 2
        $optimize .= $this->pngFilter    ? 'f' : null;
1333 2
        $optimize .= $this->pngDeflate   ? 'd' : null;
1334
1335 2
        $convolve = null;
1336 2
        if ($this->convolve) {
1337
            $convolve = '_conv' . preg_replace('/[^a-zA-Z0-9]/', '', $this->convolve);
1338
        }
1339
1340 2
        $upscale = null;
1341 2
        if ($this->upscale !== self::UPSCALE_DEFAULT) {
1342
            $upscale = '_nu';
1343
        }
1344
1345 2
        $subdir = null;
1346 2
        if ($useSubdir === true) {
1347
            $subdir = str_replace('/', '-', dirname($this->imageSrc));
1348
            $subdir = ($subdir == '.') ? '_.' : $subdir;
1349
            $subdir .= '_';
1350
        }
1351
        
1352 2
        $file = $prefix . $subdir . $filename . $width . $height
1353 2
            . $offset . $crop . $cropToFit . $fillToFit . $stretch
1354 2
            . $crop_x . $crop_y . $upscale
1355 2
            . $quality . $filters . $sharpen . $emboss . $blur . $palette
1356 2
            . $optimize . $compress
1357 2
            . $scale . $rotateBefore . $rotateAfter . $autoRotate . $bgColor
1358 2
            . $convolve . $copyStrat . $saveAs;
1359
1360 2
        return $this->setTarget($file, $base);
1361
    }
1362
1363
1364
1365
    /**
1366
     * Use cached version of image, if possible.
1367
     *
1368
     * @param boolean $useCache is default true, set to false to avoid using cached object.
1369
     *
1370
     * @return $this
1371
     */
1372
    public function useCacheIfPossible($useCache = true)
1373
    {
1374
        if ($useCache && is_readable($this->cacheFileName)) {
1375
            $fileTime   = filemtime($this->pathToImage);
1376
            $cacheTime  = filemtime($this->cacheFileName);
1377
1378
            if ($fileTime <= $cacheTime) {
1379
                if ($this->useCache) {
1380
                    if ($this->verbose) {
1381
                        $this->log("Use cached file.");
1382
                        $this->log("Cached image filesize: " . filesize($this->cacheFileName) . " bytes.");
1383
                    }
1384
                    $this->output($this->cacheFileName, $this->outputFormat);
1385
                } else {
1386
                    $this->log("Cache is valid but ignoring it by intention.");
1387
                }
1388
            } else {
1389
                $this->log("Original file is modified, ignoring cache.");
1390
            }
1391
        } else {
1392
            $this->log("Cachefile does not exists or ignoring it.");
1393
        }
1394
1395
        return $this;
1396
    }
1397
1398
1399
1400
    /**
1401
     * Load image from disk.
1402
     *
1403
     * @param string $src of image.
1404
     * @param string $dir as base directory where images are.
1405
     *
1406
     * @return $this
1407
     *
1408
     */
1409
    public function load($src = null, $dir = null)
1410
    {
1411
        if (isset($src)) {
1412
            $this->setSource($src, $dir);
1413
        }
1414
1415
        $this->loadImageDetails($this->pathToImage);
1416
1417
        $this->image = imagecreatefromstring(file_get_contents($this->pathToImage));
1418
        if ($this->image === false) {
1419
            throw new Exception("Could not load image.");
1420
        }
1421
1422
        /* Removed v0.7.7
1423
        if (image_type_to_mime_type($this->fileType) == 'image/png') {
1424
            $type = $this->getPngType();
1425
            $hasFewColors = imagecolorstotal($this->image);
1426
1427
            if ($type == self::PNG_RGB_PALETTE || ($hasFewColors > 0 && $hasFewColors <= 256)) {
1428
                if ($this->verbose) {
1429
                    $this->log("Handle this image as a palette image.");
1430
                }
1431
                $this->palette = true;
1432
            }
1433
        }
1434
        */
1435
1436
        if ($this->verbose) {
1437
            $this->log("### Image successfully loaded from file.");
1438
            $this->log(" imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false'));
1439
            $this->log(" imagecolorstotal() : " . imagecolorstotal($this->image));
1440
            $this->log(" Number of colors in image = " . $this->colorsTotal($this->image));
1441
            $index = imagecolortransparent($this->image);
1442
            $this->log(" Detected transparent color = " . ($index >= 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index");
1443
        }
1444
1445
        return $this;
1446
    }
1447
1448
1449
1450
    /**
1451
     * Get the type of PNG image.
1452
     *
1453
     * @param string $filename to use instead of default.
1454
     *
1455
     * @return int as the type of the png-image
1456
     *
1457
     */
1458
    public function getPngType($filename = null)
1459
    {
1460
        $filename = $filename ? $filename : $this->pathToImage;
1461
1462
        $pngType = ord(file_get_contents($filename, false, null, 25, 1));
1463
1464
        if ($this->verbose) {
1465
            $this->log("Checking png type of: " . $filename);
1466
            $this->log($this->getPngTypeAsString($pngType));
1467
        }
1468
1469
        return $pngType;
1470
    }
1471
1472
1473
1474
    /**
1475
     * Get the type of PNG image as a verbose string.
1476
     *
1477
     * @param integer $type     to use, default is to check the type.
0 ignored issues
show
Bug introduced by
There is no parameter named $type. 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...
1478
     * @param string  $filename to use instead of default.
1479
     *
1480
     * @return int as the type of the png-image
1481
     *
1482
     */
1483
    private function getPngTypeAsString($pngType = null, $filename = null)
1484
    {
1485
        if ($filename || !$pngType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $filename of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1486
            $pngType = $this->getPngType($filename);
1487
        }
1488
1489
        $index = imagecolortransparent($this->image);
1490
        $transparent = null;
1491
        if ($index != -1) {
1492
            $transparent = " (transparent)";
1493
        }
1494
1495
        switch ($pngType) {
1496
1497
            case self::PNG_GREYSCALE:
1498
                $text = "PNG is type 0, Greyscale$transparent";
1499
                break;
1500
1501
            case self::PNG_RGB:
1502
                $text = "PNG is type 2, RGB$transparent";
1503
                break;
1504
1505
            case self::PNG_RGB_PALETTE:
1506
                $text = "PNG is type 3, RGB with palette$transparent";
1507
                break;
1508
1509
            case self::PNG_GREYSCALE_ALPHA:
1510
                $text = "PNG is type 4, Greyscale with alpha channel";
1511
                break;
1512
1513
            case self::PNG_RGB_ALPHA:
1514
                $text = "PNG is type 6, RGB with alpha channel (PNG 32-bit)";
1515
                break;
1516
1517
            default:
1518
                $text = "PNG is UNKNOWN type, is it really a PNG image?";
1519
        }
1520
1521
        return $text;
1522
    }
1523
1524
1525
1526
1527
    /**
1528
     * Calculate number of colors in an image.
1529
     *
1530
     * @param resource $im the image.
1531
     *
1532
     * @return int
1533
     */
1534
    private function colorsTotal($im)
1535
    {
1536
        if (imageistruecolor($im)) {
1537
            $this->log("Colors as true color.");
1538
            $h = imagesy($im);
1539
            $w = imagesx($im);
1540
            $c = array();
1541
            for ($x=0; $x < $w; $x++) {
1542
                for ($y=0; $y < $h; $y++) {
1543
                    @$c['c'.imagecolorat($im, $x, $y)]++;
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1544
                }
1545
            }
1546
            return count($c);
1547
        } else {
1548
            $this->log("Colors as palette.");
1549
            return imagecolorstotal($im);
1550
        }
1551
    }
1552
1553
1554
1555
    /**
1556
     * Preprocess image before rezising it.
1557
     *
1558
     * @return $this
1559
     */
1560
    public function preResize()
1561
    {
1562
        $this->log("### Pre-process before resizing");
1563
1564
        // Rotate image
1565
        if ($this->rotateBefore) {
1566
            $this->log("Rotating image.");
1567
            $this->rotate($this->rotateBefore, $this->bgColor)
1568
                 ->reCalculateDimensions();
1569
        }
1570
1571
        // Auto-rotate image
1572
        if ($this->autoRotate) {
1573
            $this->log("Auto rotating image.");
1574
            $this->rotateExif()
1575
                 ->reCalculateDimensions();
1576
        }
1577
1578
        // Scale the original image before starting
1579
        if (isset($this->scale)) {
1580
            $this->log("Scale by {$this->scale}%");
1581
            $newWidth  = $this->width * $this->scale / 100;
1582
            $newHeight = $this->height * $this->scale / 100;
1583
            $img = $this->CreateImageKeepTransparency($newWidth, $newHeight);
1584
            imagecopyresampled($img, $this->image, 0, 0, 0, 0, $newWidth, $newHeight, $this->width, $this->height);
1585
            $this->image = $img;
1586
            $this->width = $newWidth;
1587
            $this->height = $newHeight;
1588
        }
1589
1590
        return $this;
1591
    }
1592
1593
1594
1595
    /**
1596
     * Resize or resample the image while resizing.
1597
     *
1598
     * @param int $strategy as CImage::RESIZE or CImage::RESAMPLE
1599
     *
1600
     * @return $this
1601
     */
1602
    public function setCopyResizeStrategy($strategy)
1603
    {
1604
        $this->copyStrategy = $strategy;
1605
        return $this;
1606
    }
1607
1608
1609
1610
    /**
1611
     * Resize and or crop the image.
1612
     *
1613
     * @return void
1614
     */
1615
    public function imageCopyResampled($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h)
1616
    {
1617
        if ($this->copyStrategy == self::RESIZE) {
1618
            $this->log("Copy by resize");
1619
            imagecopyresized($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
1620
        } else {
1621
            $this->log("Copy by resample");
1622
            imagecopyresampled($dst_image, $src_image, $dst_x, $dst_y, $src_x, $src_y, $dst_w, $dst_h, $src_w, $src_h);
1623
        }
1624
    }
1625
1626
1627
1628
    /**
1629
     * Resize and or crop the image.
1630
     *
1631
     * @return $this
1632
     */
1633
    public function resize()
1634
    {
1635
        $res = $this->imageResizer;
1636
1637
        $this->log("### Starting to Resize()");
1638
        $this->log(" Upscale = '$this->upscale'");
1639
1640
        $sw = $res->getSourceWidth();
0 ignored issues
show
Unused Code introduced by
$sw is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1641
        $sh = $res->getSourceHeight();
0 ignored issues
show
Unused Code introduced by
$sh is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
1642
        $tw = $res->getTargetWidth();
1643
        $th = $res->getTargetHeight();
1644
        $cx = $res->getCropX();
1645
        $cy = $res->getCropY();
1646
        $cw = $res->getCropWidth();
1647
        $ch = $res->getCropHeight();
1648
        $dx = $res->getDestinationX();
1649
        $dy = $res->getDestinationY();
1650
        $dw = $res->getDestinationWidth();
1651
        $dh = $res->getDestinationHeight();
1652
1653
        $img = $this->CreateImageKeepTransparency($tw, $th);
0 ignored issues
show
Bug introduced by
It seems like $tw defined by $res->getTargetWidth() on line 1642 can also be of type double or null; however, CImage::createImageKeepTransparency() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
Bug introduced by
It seems like $th defined by $res->getTargetHeight() on line 1643 can also be of type double or null; however, CImage::createImageKeepTransparency() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1654
        $this->imageCopyResampled($img, $this->image, $dx, $dy, $cx, $cy, $dw, $dh, $cw, $ch);
1655
        $this->image = $img;
1656
        $this->width = $tw;
1657
        $this->height = $th;
1658
1659
        return $this;
1660
1661
1662
        // Only use a specified area of the image, $this->offset is defining the area to use
1663 View Code Duplication
        if (isset($this->offset)) {
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...
Unused Code introduced by
// Only use a specified ...is->offset['height']; } does not seem to be reachable.

This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.

Unreachable code is most often the result of return, die or exit statements that have been added for debug purposes.

function fx() {
    try {
        doSomething();
        return true;
    }
    catch (\Exception $e) {
        return false;
    }

    return false;
}

In the above example, the last return false will never be executed, because a return statement has already been met in every possible execution path.

Loading history...
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1664
1665
            $this->log("Offset for area to use, cropping it width={$this->offset['width']}, height={$this->offset['height']}, start_x={$this->offset['left']}, start_y={$this->offset['top']}");
1666
            $img = $this->CreateImageKeepTransparency($this->offset['width'], $this->offset['height']);
1667
            imagecopy($img, $this->image, 0, 0, $this->offset['left'], $this->offset['top'], $this->offset['width'], $this->offset['height']);
1668
            $this->image = $img;
1669
            $this->width = $this->offset['width'];
1670
            $this->height = $this->offset['height'];
1671
        }
1672
1673 View Code Duplication
        if ($this->crop) {
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...
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1674
1675
            // Do as crop, take only part of image
1676
            $this->log("Cropping area width={$this->crop['width']}, height={$this->crop['height']}, start_x={$this->crop['start_x']}, start_y={$this->crop['start_y']}");
1677
            $img = $this->CreateImageKeepTransparency($this->crop['width'], $this->crop['height']);
1678
            imagecopy($img, $this->image, 0, 0, $this->crop['start_x'], $this->crop['start_y'], $this->crop['width'], $this->crop['height']);
1679
            $this->image = $img;
1680
            $this->width = $this->crop['width'];
1681
            $this->height = $this->crop['height'];
1682
        }
1683
1684
        if (!$this->upscale) {
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1685
            // Consider rewriting the no-upscale code to fit within this if-statement,
1686
            // likely to be more readable code.
1687
            // The code is more or leass equal in below crop-to-fit, fill-to-fit and stretch
1688
        }
1689
1690
        if ($this->cropToFit) {
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1691
1692
            // Resize by crop to fit
1693
            $this->log("Resizing using strategy - Crop to fit");
1694
1695
            if (!$this->upscale && ($this->width < $this->newWidth || $this->height < $this->newHeight)) {
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1696
                $this->log("Resizing - smaller image, do not upscale.");
1697
1698
                $cropX = round(($this->cropWidth/2) - ($this->newWidth/2));
1699
                $cropY = round(($this->cropHeight/2) - ($this->newHeight/2));
1700
1701
                $posX = 0;
1702
                $posY = 0;
1703
1704
                if ($this->newWidth > $this->width) {
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1705
                    $posX = round(($this->newWidth - $this->width) / 2);
1706
                }
1707
1708
                if ($this->newHeight > $this->height) {
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1709
                    $posY = round(($this->newHeight - $this->height) / 2);
1710
                }
1711
1712
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1713
                imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight);
1714
            } else {
1715
                $cropX      = $imgres->getCropX();
1716
                $cropY      = $imgres->getCropY();
1717
                $cropWidth  = $imgres->getCropWidth();
1718
                $cropHeight = $imgres->getCropHeight();
1719
                $this->log(" Crop from $cropX x $cropY by $cropWidth x $cropHeight.");
1720
1721
                
1722
                //$cropX = round(($this->cropWidth/2) - ($this->newWidth/2));
1723
                //$cropY = round(($this->cropHeight/2) - ($this->newHeight/2));
1724
                
1725
                //$imgPreCrop   = $this->CreateImageKeepTransparency($this->cropWidth, $this->cropHeight);
1726
                $imgPreCrop   = $this->CreateImageKeepTransparency($cropWidth, $cropHeight);
1727
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1728
                // $this->imageCopyResampled($imgPreCrop, $this->image, 0, 0, 0, 0, $this->cropWidth, $this->cropHeight, $this->width, $this->height);
1729
                $this->imageCopyResampled($imgPreCrop, $this->image, 0, 0, 0, 0, $cropWidth, $cropHeight, $this->width, $this->height);
1730
                imagecopy($imageResized, $imgPreCrop, 0, 0, $cropX, $cropY, $this->newWidth, $this->newHeight);
1731
            }
1732
1733
            $this->image = $imageResized;
1734
            $this->width = $this->newWidth;
1735
            $this->height = $this->newHeight;
1736
1737
        } elseif ($this->fillToFit) {
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1738
1739
            // Resize by fill to fit
1740
            $this->log("Resizing using strategy - Fill to fit");
1741
1742
            $posX = 0;
1743
            $posY = 0;
1744
1745
            $ratioOrig = $this->width / $this->height;
1746
            $ratioNew  = $this->newWidth / $this->newHeight;
1747
1748
            // Check ratio for landscape or portrait
1749
            if ($ratioOrig < $ratioNew) {
0 ignored issues
show
Bug introduced by
The variable $ratioOrig seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
Bug introduced by
The variable $ratioNew seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1750
                $posX = round(($this->newWidth - $this->fillWidth) / 2);
1751
            } else {
1752
                $posY = round(($this->newHeight - $this->fillHeight) / 2);
1753
            }
1754
1755
            if (!$this->upscale
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1756
                && ($this->width < $this->newWidth || $this->height < $this->newHeight)
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1757
            ) {
1758
1759
                $this->log("Resizing - smaller image, do not upscale.");
1760
                $posX = round(($this->fillWidth - $this->width) / 2);
1761
                $posY = round(($this->fillHeight - $this->height) / 2);
1762
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1763
                imagecopy($imageResized, $this->image, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight);
1764
1765
            } else {
1766
                $imgPreFill   = $this->CreateImageKeepTransparency($this->fillWidth, $this->fillHeight);
1767
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1768
                $this->imageCopyResampled($imgPreFill, $this->image, 0, 0, 0, 0, $this->fillWidth, $this->fillHeight, $this->width, $this->height);
1769
                imagecopy($imageResized, $imgPreFill, $posX, $posY, 0, 0, $this->fillWidth, $this->fillHeight);
1770
            }
1771
1772
            $this->image = $imageResized;
1773
            $this->width = $this->newWidth;
1774
            $this->height = $this->newHeight;
1775
1776
        } elseif (!($this->newWidth == $this->width && $this->newHeight == $this->height)) {
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1777
1778
            // Resize it
1779
            $this->log("Resizing, new height and/or width");
1780
1781
            if (!$this->upscale
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1782
                && ($this->width < $this->newWidth || $this->height < $this->newHeight)
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1783
            ) {
1784
                $this->log("Resizing - smaller image, do not upscale.");
1785
1786
                if (!$this->keepRatio) {
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1787
                    $this->log("Resizing - stretch to fit selected.");
1788
1789
                    $posX = 0;
1790
                    $posY = 0;
1791
                    $cropX = 0;
1792
                    $cropY = 0;
1793
1794
                    if ($this->newWidth > $this->width && $this->newHeight > $this->height) {
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1795
                        $posX = round(($this->newWidth - $this->width) / 2);
1796
                        $posY = round(($this->newHeight - $this->height) / 2);
1797
                    } elseif ($this->newWidth > $this->width) {
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1798
                        $posX = round(($this->newWidth - $this->width) / 2);
1799
                        $cropY = round(($this->height - $this->newHeight) / 2);
1800
                    } elseif ($this->newHeight > $this->height) {
0 ignored issues
show
Bug introduced by
The variable $this seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
1801
                        $posY = round(($this->newHeight - $this->height) / 2);
1802
                        $cropX = round(($this->width - $this->newWidth) / 2);
1803
                    }
1804
1805
                    //$this->log("posX=$posX, posY=$posY, cropX=$cropX, cropY=$cropY.");
1806
                    $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1807
                    imagecopy($imageResized, $this->image, $posX, $posY, $cropX, $cropY, $this->newWidth, $this->newHeight);
1808
                    $this->image = $imageResized;
1809
                    $this->width = $this->newWidth;
1810
                    $this->height = $this->newHeight;
1811
                }
1812
            } else {
1813
                $imageResized = $this->CreateImageKeepTransparency($this->newWidth, $this->newHeight);
1814
                $this->imageCopyResampled($imageResized, $this->image, 0, 0, 0, 0, $this->newWidth, $this->newHeight, $this->width, $this->height);
1815
                $this->image = $imageResized;
1816
                $this->width = $this->newWidth;
1817
                $this->height = $this->newHeight;
1818
            }
1819
        }
1820
1821
        return $this;
1822
    }
1823
1824
1825
1826
    /**
1827
     * Postprocess image after rezising image.
1828
     *
1829
     * @return $this
1830
     */
1831
    public function postResize()
1832
    {
1833
        $this->log("### Post-process after resizing");
1834
1835
        // Rotate image
1836
        if ($this->rotateAfter) {
1837
            $this->log("Rotating image.");
1838
            $this->rotate($this->rotateAfter, $this->bgColor);
1839
        }
1840
1841
        // Apply filters
1842
        if (isset($this->filters) && is_array($this->filters)) {
1843
1844
            foreach ($this->filters as $filter) {
1845
                $this->log("Applying filter {$filter['type']}.");
1846
1847
                switch ($filter['argc']) {
1848
1849
                    case 0:
1850
                        imagefilter($this->image, $filter['type']);
1851
                        break;
1852
1853
                    case 1:
1854
                        imagefilter($this->image, $filter['type'], $filter['arg1']);
1855
                        break;
1856
1857
                    case 2:
1858
                        imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2']);
1859
                        break;
1860
1861 View Code Duplication
                    case 3:
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...
1862
                        imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3']);
1863
                        break;
1864
1865 View Code Duplication
                    case 4:
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...
1866
                        imagefilter($this->image, $filter['type'], $filter['arg1'], $filter['arg2'], $filter['arg3'], $filter['arg4']);
1867
                        break;
1868
                }
1869
            }
1870
        }
1871
1872
        // Convert to palette image
1873
        if ($this->palette) {
1874
            $this->log("Converting to palette image.");
1875
            $this->trueColorToPalette();
1876
        }
1877
1878
        // Blur the image
1879
        if ($this->blur) {
1880
            $this->log("Blur.");
1881
            $this->blurImage();
1882
        }
1883
1884
        // Emboss the image
1885
        if ($this->emboss) {
1886
            $this->log("Emboss.");
1887
            $this->embossImage();
1888
        }
1889
1890
        // Sharpen the image
1891
        if ($this->sharpen) {
1892
            $this->log("Sharpen.");
1893
            $this->sharpenImage();
1894
        }
1895
1896
        // Custom convolution
1897
        if ($this->convolve) {
1898
            //$this->log("Convolve: " . $this->convolve);
1899
            $this->imageConvolution();
1900
        }
1901
1902
        return $this;
1903
    }
1904
1905
1906
1907
    /**
1908
     * Rotate image using angle.
1909
     *
1910
     * @param float $angle        to rotate image.
1911
     * @param int   $anglebgColor to fill image with if needed.
0 ignored issues
show
Documentation introduced by
There is no parameter named $anglebgColor. Did you maybe mean $bgColor?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function. It has, however, found a similar but not annotated parameter which might be a good fit.

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

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

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

Loading history...
1912
     *
1913
     * @return $this
1914
     */
1915
    public function rotate($angle, $bgColor)
0 ignored issues
show
Unused Code introduced by
The parameter $bgColor is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
1916
    {
1917
        $this->log("Rotate image " . $angle . " degrees with filler color.");
1918
1919
        $color = $this->getBackgroundColor();
1920
        $this->image = imagerotate($this->image, $angle, $color);
1921
1922
        $this->width  = imagesx($this->image);
1923
        $this->height = imagesy($this->image);
1924
1925
        $this->log("New image dimension width x height: " . $this->width . " x " . $this->height);
1926
1927
        return $this;
1928
    }
1929
1930
1931
1932
    /**
1933
     * Rotate image using information in EXIF.
1934
     *
1935
     * @return $this
1936
     */
1937
    public function rotateExif()
1938
    {
1939
        if (!in_array($this->fileType, array(IMAGETYPE_JPEG, IMAGETYPE_TIFF_II, IMAGETYPE_TIFF_MM))) {
1940
            $this->log("Autorotate ignored, EXIF not supported by this filetype.");
1941
            return $this;
1942
        }
1943
1944
        $exif = exif_read_data($this->pathToImage);
1945
1946
        if (!empty($exif['Orientation'])) {
1947
            switch ($exif['Orientation']) {
1948
                case 3:
1949
                    $this->log("Autorotate 180.");
1950
                    $this->rotate(180, $this->bgColor);
1951
                    break;
1952
1953
                case 6:
1954
                    $this->log("Autorotate -90.");
1955
                    $this->rotate(-90, $this->bgColor);
1956
                    break;
1957
1958
                case 8:
1959
                    $this->log("Autorotate 90.");
1960
                    $this->rotate(90, $this->bgColor);
1961
                    break;
1962
1963
                default:
1964
                    $this->log("Autorotate ignored, unknown value as orientation.");
1965
            }
1966
        } else {
1967
            $this->log("Autorotate ignored, no orientation in EXIF.");
1968
        }
1969
1970
        return $this;
1971
    }
1972
1973
1974
1975
    /**
1976
     * Convert true color image to palette image, keeping alpha.
1977
     * http://stackoverflow.com/questions/5752514/how-to-convert-png-to-8-bit-png-using-php-gd-library
1978
     *
1979
     * @return void
1980
     */
1981
    public function trueColorToPalette()
1982
    {
1983
        $img = imagecreatetruecolor($this->width, $this->height);
1984
        $bga = imagecolorallocatealpha($img, 0, 0, 0, 127);
1985
        imagecolortransparent($img, $bga);
1986
        imagefill($img, 0, 0, $bga);
1987
        imagecopy($img, $this->image, 0, 0, 0, 0, $this->width, $this->height);
1988
        imagetruecolortopalette($img, false, 255);
1989
        imagesavealpha($img, true);
1990
1991
        if (imageistruecolor($this->image)) {
1992
            $this->log("Matching colors with true color image.");
1993
            imagecolormatch($this->image, $img);
1994
        }
1995
1996
        $this->image = $img;
1997
    }
1998
1999
2000
2001
    /**
2002
     * Sharpen image using image convolution.
2003
     *
2004
     * @return $this
2005
     */
2006
    public function sharpenImage()
2007
    {
2008
        $this->imageConvolution('sharpen');
2009
        return $this;
2010
    }
2011
2012
2013
2014
    /**
2015
     * Emboss image using image convolution.
2016
     *
2017
     * @return $this
2018
     */
2019
    public function embossImage()
2020
    {
2021
        $this->imageConvolution('emboss');
2022
        return $this;
2023
    }
2024
2025
2026
2027
    /**
2028
     * Blur image using image convolution.
2029
     *
2030
     * @return $this
2031
     */
2032
    public function blurImage()
2033
    {
2034
        $this->imageConvolution('blur');
2035
        return $this;
2036
    }
2037
2038
2039
2040
    /**
2041
     * Create convolve expression and return arguments for image convolution.
2042
     *
2043
     * @param string $expression constant string which evaluates to a list of
2044
     *                           11 numbers separated by komma or such a list.
2045
     *
2046
     * @return array as $matrix (3x3), $divisor and $offset
2047
     */
2048
    public function createConvolveArguments($expression)
2049
    {
2050
        // Check of matching constant
2051
        if (isset($this->convolves[$expression])) {
2052
            $expression = $this->convolves[$expression];
2053
        }
2054
2055
        $part = explode(',', $expression);
2056
        $this->log("Creating convolution expressen: $expression");
2057
2058
        // Expect list of 11 numbers, split by , and build up arguments
2059
        if (count($part) != 11) {
2060
            throw new Exception(
2061
                "Missmatch in argument convolve. Expected comma-separated string with
2062
                11 float values. Got $expression."
2063
            );
2064
        }
2065
2066
        array_walk($part, function ($item, $key) {
0 ignored issues
show
Unused Code introduced by
The parameter $key is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
2067
            if (!is_numeric($item)) {
2068
                throw new Exception("Argument to convolve expression should be float but is not.");
2069
            }
2070
        });
2071
2072
        return array(
2073
            array(
2074
                array($part[0], $part[1], $part[2]),
2075
                array($part[3], $part[4], $part[5]),
2076
                array($part[6], $part[7], $part[8]),
2077
            ),
2078
            $part[9],
2079
            $part[10],
2080
        );
2081
    }
2082
2083
2084
2085
    /**
2086
     * Add custom expressions (or overwrite existing) for image convolution.
2087
     *
2088
     * @param array $options Key value array with strings to be converted
2089
     *                       to convolution expressions.
2090
     *
2091
     * @return $this
2092
     */
2093
    public function addConvolveExpressions($options)
2094
    {
2095
        $this->convolves = array_merge($this->convolves, $options);
2096
        return $this;
2097
    }
2098
2099
2100
2101
    /**
2102
     * Image convolution.
2103
     *
2104
     * @param string $options A string with 11 float separated by comma.
2105
     *
2106
     * @return $this
2107
     */
2108
    public function imageConvolution($options = null)
2109
    {
2110
        // Use incoming options or use $this.
2111
        $options = $options ? $options : $this->convolve;
2112
2113
        // Treat incoming as string, split by +
2114
        $this->log("Convolution with '$options'");
2115
        $options = explode(":", $options);
2116
2117
        // Check each option if it matches constant value
2118
        foreach ($options as $option) {
2119
            list($matrix, $divisor, $offset) = $this->createConvolveArguments($option);
2120
            imageconvolution($this->image, $matrix, $divisor, $offset);
2121
        }
2122
2123
        return $this;
2124
    }
2125
2126
2127
2128
    /**
2129
     * Set default background color between 000000-FFFFFF or if using
2130
     * alpha 00000000-FFFFFF7F.
2131
     *
2132
     * @param string $color as hex value.
2133
     *
2134
     * @return $this
2135
    */
2136
    public function setDefaultBackgroundColor($color)
2137
    {
2138
        $this->log("Setting default background color to '$color'.");
2139
2140
        if (!(strlen($color) == 6 || strlen($color) == 8)) {
2141
            throw new Exception(
2142
                "Background color needs a hex value of 6 or 8
2143
                digits. 000000-FFFFFF or 00000000-FFFFFF7F.
2144
                Current value was: '$color'."
2145
            );
2146
        }
2147
2148
        $red    = hexdec(substr($color, 0, 2));
2149
        $green  = hexdec(substr($color, 2, 2));
2150
        $blue   = hexdec(substr($color, 4, 2));
2151
2152
        $alpha = (strlen($color) == 8)
2153
            ? hexdec(substr($color, 6, 2))
2154
            : null;
2155
2156
        if (($red < 0 || $red > 255)
2157
            || ($green < 0 || $green > 255)
2158
            || ($blue < 0 || $blue > 255)
2159
            || ($alpha < 0 || $alpha > 127)
2160
        ) {
2161
            throw new Exception(
2162
                "Background color out of range. Red, green blue
2163
                should be 00-FF and alpha should be 00-7F.
2164
                Current value was: '$color'."
2165
            );
2166
        }
2167
2168
        $this->bgColor = strtolower($color);
2169
        $this->bgColorDefault = array(
2170
            'red'   => $red,
2171
            'green' => $green,
2172
            'blue'  => $blue,
2173
            'alpha' => $alpha
2174
        );
2175
2176
        return $this;
2177
    }
2178
2179
2180
2181
    /**
2182
     * Get the background color.
2183
     *
2184
     * @param resource $img the image to work with or null if using $this->image.
2185
     *
2186
     * @return color value or null if no background color is set.
2187
    */
2188 2
    private function getBackgroundColor($img = null)
2189
    {
2190 2
        $img = isset($img) ? $img : $this->image;
2191
2192 2
        if ($this->bgColorDefault) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->bgColorDefault of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2193
2194 2
            $red   = $this->bgColorDefault['red'];
2195 2
            $green = $this->bgColorDefault['green'];
2196 2
            $blue  = $this->bgColorDefault['blue'];
2197 2
            $alpha = $this->bgColorDefault['alpha'];
2198
2199 2
            if ($alpha) {
2200
                $color = imagecolorallocatealpha($img, $red, $green, $blue, $alpha);
2201
            } else {
2202 2
                $color = imagecolorallocate($img, $red, $green, $blue);
2203
            }
2204
2205 2
            return $color;
2206
2207
        } else {
2208
            return 0;
2209
        }
2210
    }
2211
2212
2213
2214
    /**
2215
     * Create a image and keep transparency for png and gifs.
2216
     *
2217
     * @param int $width of the new image.
2218
     * @param int $height of the new image.
2219
     *
2220
     * @return image resource.
2221
    */
2222 2
    private function createImageKeepTransparency($width, $height)
2223
    {
2224 2
        $this->log("Creating a new working image width={$width}px, height={$height}px.");
2225 2
        $img = imagecreatetruecolor($width, $height);
2226 2
        imagealphablending($img, false);
2227 2
        imagesavealpha($img, true);
2228
2229 2
        $index = $this->image
2230 2
            ? imagecolortransparent($this->image)
2231 2
            : -1;
2232
2233 2
        if ($index != -1) {
2234
2235
            imagealphablending($img, true);
2236
            $transparent = imagecolorsforindex($this->image, $index);
2237
            $color = imagecolorallocatealpha($img, $transparent['red'], $transparent['green'], $transparent['blue'], $transparent['alpha']);
2238
            imagefill($img, 0, 0, $color);
2239
            $index = imagecolortransparent($img, $color);
2240
            $this->Log("Detected transparent color = " . implode(", ", $transparent) . " at index = $index");
2241
2242 2
        } elseif ($this->bgColorDefault) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->bgColorDefault of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
2243
2244 2
            $color = $this->getBackgroundColor($img);
2245 2
            imagefill($img, 0, 0, $color);
2246 2
            $this->Log("Filling image with background color.");
2247 2
        }
2248
2249 2
        return $img;
2250
    }
2251
2252
2253
2254
    /**
2255
     * Set optimizing  and post-processing options.
2256
     *
2257
     * @param array $options with config for postprocessing with external tools.
2258
     *
2259
     * @return $this
2260
     */
2261
    public function setPostProcessingOptions($options)
2262
    {
2263
        if (isset($options['jpeg_optimize']) && $options['jpeg_optimize']) {
2264
            $this->jpegOptimizeCmd = $options['jpeg_optimize_cmd'];
2265
        } else {
2266
            $this->jpegOptimizeCmd = null;
2267
        }
2268
2269
        if (isset($options['png_filter']) && $options['png_filter']) {
2270
            $this->pngFilterCmd = $options['png_filter_cmd'];
2271
        } else {
2272
            $this->pngFilterCmd = null;
2273
        }
2274
2275
        if (isset($options['png_deflate']) && $options['png_deflate']) {
2276
            $this->pngDeflateCmd = $options['png_deflate_cmd'];
2277
        } else {
2278
            $this->pngDeflateCmd = null;
2279
        }
2280
2281
        return $this;
2282
    }
2283
2284
2285
2286
    /**
2287
     * Find out the type (file extension) for the image to be saved.
2288
     *
2289
     * @return string as image extension.
2290
     */
2291 2
    protected function getTargetImageExtension()
2292
    {
2293
        // switch on mimetype
2294 2
        if (isset($this->extension)) {
2295
            return strtolower($this->extension);
2296
        } else {
2297 2
            return substr(image_type_to_extension($this->fileType), 1);
2298
        }
2299
    }
2300
2301
2302
2303
    /**
2304
     * Save image.
2305
     *
2306
     * @param string  $src       as target filename.
2307
     * @param string  $base      as base directory where to store images.
2308
     * @param boolean $overwrite or not, default to always overwrite file.
2309
     *
2310
     * @return $this or false if no folder is set.
2311
     */
2312 2
    public function save($src = null, $base = null, $overwrite = true)
2313
    {
2314 2
        if (isset($src)) {
2315
            $this->setTarget($src, $base);
2316
        }
2317
2318 2
        if ($overwrite === false && is_file($this->cacheFileName)) {
2319
            $this->Log("Not overwriting file since its already exists and \$overwrite if false.");
2320
            return;
2321
        }
2322
2323 2
        is_writable($this->saveFolder)
2324
            or $this->raiseError('Target directory is not writable.');
2325
2326 2
        $type = $this->getTargetImageExtension();
2327 2
        $this->Log("Saving image as " . $type);
2328
        switch ($type) {
2329
2330 2
            case 'jpeg':
2331 2
            case 'jpg':
2332
                $this->Log("Saving image as JPEG to cache using quality = {$this->quality}.");
2333
                imagejpeg($this->image, $this->cacheFileName, $this->quality);
2334
2335
                // Use JPEG optimize if defined
2336 View Code Duplication
                if ($this->jpegOptimizeCmd) {
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...
2337
                    if ($this->verbose) {
2338
                        clearstatcache();
2339
                        $this->log("Filesize before optimize: " . filesize($this->cacheFileName) . " bytes.");
2340
                    }
2341
                    $res = array();
2342
                    $cmd = $this->jpegOptimizeCmd . " -outfile $this->cacheFileName $this->cacheFileName";
2343
                    exec($cmd, $res);
2344
                    $this->log($cmd);
2345
                    $this->log($res);
0 ignored issues
show
Documentation introduced by
$res is of type null|array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2346
                }
2347
                break;
2348
2349 2
            case 'gif':
2350
                $this->Log("Saving image as GIF to cache.");
2351
                imagegif($this->image, $this->cacheFileName);
2352
                break;
2353
2354 2
            case 'png':
2355 2
            default:
2356 2
                $this->Log("Saving image as PNG to cache using compression = {$this->compress}.");
2357
2358
                // Turn off alpha blending and set alpha flag
2359 2
                imagealphablending($this->image, false);
2360 2
                imagesavealpha($this->image, true);
2361 2
                imagepng($this->image, $this->cacheFileName, $this->compress);
2362
2363
                // Use external program to filter PNG, if defined
2364 2 View Code Duplication
                if ($this->pngFilterCmd) {
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...
2365
                    if ($this->verbose) {
2366
                        clearstatcache();
2367
                        $this->Log("Filesize before filter optimize: " . filesize($this->cacheFileName) . " bytes.");
2368
                    }
2369
                    $res = array();
2370
                    $cmd = $this->pngFilterCmd . " $this->cacheFileName";
2371
                    exec($cmd, $res);
2372
                    $this->Log($cmd);
2373
                    $this->Log($res);
0 ignored issues
show
Documentation introduced by
$res is of type null|array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2374
                }
2375
2376
                // Use external program to deflate PNG, if defined
2377 2 View Code Duplication
                if ($this->pngDeflateCmd) {
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...
2378
                    if ($this->verbose) {
2379
                        clearstatcache();
2380
                        $this->Log("Filesize before deflate optimize: " . filesize($this->cacheFileName) . " bytes.");
2381
                    }
2382
                    $res = array();
2383
                    $cmd = $this->pngDeflateCmd . " $this->cacheFileName";
2384
                    exec($cmd, $res);
2385
                    $this->Log($cmd);
2386
                    $this->Log($res);
0 ignored issues
show
Documentation introduced by
$res is of type null|array, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2387
                }
2388 2
                break;
2389 2
        }
2390
2391 2
        if ($this->verbose) {
2392
            clearstatcache();
2393
            $this->log("Saved image to cache.");
2394
            $this->log(" Cached image filesize: " . filesize($this->cacheFileName) . " bytes.");
2395
            $this->log(" imageistruecolor() : " . (imageistruecolor($this->image) ? 'true' : 'false'));
2396
            $this->log(" imagecolorstotal() : " . imagecolorstotal($this->image));
2397
            $this->log(" Number of colors in image = " . $this->ColorsTotal($this->image));
2398
            $index = imagecolortransparent($this->image);
2399
            $this->log(" Detected transparent color = " . ($index > 0 ? implode(", ", imagecolorsforindex($this->image, $index)) : "NONE") . " at index = $index");
2400
        }
2401
2402 2
        return $this;
2403
    }
2404
2405
2406
2407
    /**
2408
     * Convert image from one colorpsace/color profile to sRGB without
2409
     * color profile.
2410
     *
2411
     * @param string  $src      of image.
2412
     * @param string  $dir      as base directory where images are.
2413
     * @param string  $cache    as base directory where to store images.
2414
     * @param string  $iccFile  filename of colorprofile.
2415
     * @param boolean $useCache or not, default to always use cache.
2416
     *
2417
     * @return string | boolean false if no conversion else the converted
2418
     *                          filename.
2419
     */
2420 2
    public function convert2sRGBColorSpace($src, $dir, $cache, $iccFile, $useCache = true)
2421
    {
2422 2
        if ($this->verbose) {
2423
            $this->log("# Converting image to sRGB colorspace.");
2424
        }
2425
2426 2
        if (!class_exists("Imagick")) {
2427 2
            $this->log(" Ignoring since Imagemagick is not installed.");
2428 2
            return false;
2429
        }
2430
2431
        // Prepare
2432
        $this->setSaveFolder($cache)
2433
             ->setSource($src, $dir)
2434
             ->generateFilename(null, false, 'srgb_');
2435
2436
        // Check if the cached version is accurate.
2437
        if ($useCache && is_readable($this->cacheFileName)) {
2438
            $fileTime  = filemtime($this->pathToImage);
2439
            $cacheTime = filemtime($this->cacheFileName);
2440
2441
            if ($fileTime <= $cacheTime) {
2442
                $this->log(" Using cached version: " . $this->cacheFileName);
2443
                return $this->cacheFileName;
2444
            }
2445
        }
2446
2447
        // Only convert if cachedir is writable
2448
        if (is_writable($this->saveFolder)) {
2449
            // Load file and check if conversion is needed
2450
            $image      = new Imagick($this->pathToImage);
2451
            $colorspace = $image->getImageColorspace();
2452
            $this->log(" Current colorspace: " . $colorspace);
2453
2454
            $profiles      = $image->getImageProfiles('*', false);
2455
            $hasICCProfile = (array_search('icc', $profiles) !== false);
2456
            $this->log(" Has ICC color profile: " . ($hasICCProfile ? "YES" : "NO"));
2457
2458
            if ($colorspace != Imagick::COLORSPACE_SRGB || $hasICCProfile) {
2459
                $this->log(" Converting to sRGB.");
2460
2461
                $sRGBicc = file_get_contents($iccFile);
2462
                $image->profileImage('icc', $sRGBicc);
2463
2464
                $image->transformImageColorspace(Imagick::COLORSPACE_SRGB);
2465
                $image->writeImage($this->cacheFileName);
2466
                return $this->cacheFileName;
2467
            }
2468
        }
2469
2470
        return false;
2471
    }
2472
2473
2474
2475
    /**
2476
     * Create a hard link, as an alias, to the cached file.
2477
     *
2478
     * @param string $alias where to store the link,
2479
     *                      filename without extension.
2480
     *
2481
     * @return $this
2482
     */
2483
    public function linkToCacheFile($alias)
2484
    {
2485
        if ($alias === null) {
2486
            $this->log("Ignore creating alias.");
2487
            return $this;
2488
        }
2489
2490
        if (is_readable($alias)) {
2491
            unlink($alias);
2492
        }
2493
2494
        $res = link($this->cacheFileName, $alias);
2495
2496
        if ($res) {
2497
            $this->log("Created an alias as: $alias");
2498
        } else {
2499
            $this->log("Failed to create the alias: $alias");
2500
        }
2501
2502
        return $this;
2503
    }
2504
2505
2506
2507
    /**
2508
     * Add HTTP header for outputting together with image.
2509
     *
2510
     * @param string $type  the header type such as "Cache-Control"
2511
     * @param string $value the value to use
2512
     *
2513
     * @return void
2514
     */
2515
    public function addHTTPHeader($type, $value)
2516
    {
2517
        $this->HTTPHeader[$type] = $value;
2518
    }
2519
2520
2521
2522
    /**
2523
     * Output image to browser using caching.
2524
     *
2525
     * @param string $file   to read and output, default is to
2526
     *                       use $this->cacheFileName
2527
     * @param string $format set to json to output file as json
2528
     *                       object with details
2529
     *
2530
     * @return void
2531
     */
2532
    public function output($file = null, $format = null)
2533
    {
2534
        if (is_null($file)) {
2535
            $file = $this->cacheFileName;
2536
        }
2537
2538
        if (is_null($format)) {
2539
            $format = $this->outputFormat;
2540
        }
2541
2542
        $this->log("Output format is: $format");
2543
2544
        if (!$this->verbose && $format == 'json') {
2545
            header('Content-type: application/json');
2546
            echo $this->json($file);
2547
            exit;
2548
        } elseif ($format == 'ascii') {
2549
            header('Content-type: text/plain');
2550
            echo $this->ascii($file);
2551
            exit;
2552
        }
2553
2554
        $this->log("Outputting image: $file");
2555
2556
        // Get image modification time
2557
        clearstatcache();
2558
        $lastModified = filemtime($file);
2559
        $gmdate = gmdate("D, d M Y H:i:s", $lastModified);
2560
2561
        if (!$this->verbose) {
2562
            header('Last-Modified: ' . $gmdate . " GMT");
2563
        }
2564
2565
        foreach ($this->HTTPHeader as $key => $val) {
2566
            header("$key: $val");
2567
        }
2568
2569
        if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE']) && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) == $lastModified) {
2570
2571
            if ($this->verbose) {
2572
                $this->log("304 not modified");
2573
                $this->verboseOutput();
2574
                exit;
2575
            }
2576
2577
            header("HTTP/1.0 304 Not Modified");
2578
2579
        } else {
2580
2581
            // Get details on image
2582
            $info = getimagesize($file);
2583
            !empty($info) or $this->raiseError("The file doesn't seem to be an image.");
2584
            $mime = $info['mime'];
2585
            $size = filesize($file);
2586
2587
            if ($this->verbose) {
2588
                $this->log("Last-Modified: " . $gmdate . " GMT");
2589
                $this->log("Content-type: " . $mime);
2590
                $this->log("Content-length: " . $size);
2591
                $this->verboseOutput();
2592
2593
                if (is_null($this->verboseFileName)) {
2594
                    exit;
2595
                }
2596
            }
2597
2598
            header("Content-type: $mime");
2599
            header("Content-length: $size");
2600
            readfile($file);
2601
        }
2602
2603
        exit;
2604
    }
2605
2606
2607
2608
    /**
2609
     * Create a JSON object from the image details.
2610
     *
2611
     * @param string $file the file to output.
2612
     *
2613
     * @return string json-encoded representation of the image.
2614
     */
2615
    public function json($file = null)
2616
    {
2617
        $file = $file ? $file : $this->cacheFileName;
2618
2619
        $details = array();
2620
2621
        clearstatcache();
2622
2623
        $details['src']       = $this->imageSrc;
2624
        $lastModified         = filemtime($this->pathToImage);
2625
        $details['srcGmdate'] = gmdate("D, d M Y H:i:s", $lastModified);
2626
2627
        $details['cache']       = basename($this->cacheFileName);
2628
        $lastModified           = filemtime($this->cacheFileName);
2629
        $details['cacheGmdate'] = gmdate("D, d M Y H:i:s", $lastModified);
2630
2631
        $this->load($file);
2632
2633
        $details['filename']    = basename($file);
2634
        $details['mimeType']    = image_type_to_mime_type($this->fileType);
2635
        $details['width']       = $this->width;
2636
        $details['height']      = $this->height;
2637
        $details['aspectRatio'] = round($this->width / $this->height, 3);
2638
        $details['size']        = filesize($file);
2639
        $details['colors'] = $this->colorsTotal($this->image);
2640
        $details['includedFiles'] = count(get_included_files());
2641
        $details['memoryPeek'] = round(memory_get_peak_usage()/1024/1024, 3) . " MB" ;
2642
        $details['memoryCurrent'] = round(memory_get_usage()/1024/1024, 3) . " MB";
2643
        $details['memoryLimit'] = ini_get('memory_limit');
2644
2645
        if (isset($_SERVER['REQUEST_TIME_FLOAT'])) {
2646
            $details['loadTime'] = (string) round((microtime(true) - $_SERVER['REQUEST_TIME_FLOAT']), 3) . "s";
2647
        }
2648
2649
        if ($details['mimeType'] == 'image/png') {
2650
            $details['pngType'] = $this->getPngTypeAsString(null, $file);
2651
        }
2652
2653
        $options = null;
2654
        if (defined("JSON_PRETTY_PRINT") && defined("JSON_UNESCAPED_SLASHES")) {
2655
            $options = JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES;
2656
        }
2657
2658
        return json_encode($details, $options);
2659
    }
2660
2661
2662
2663
    /**
2664
     * Set options for creating ascii version of image.
2665
     *
2666
     * @param array $options empty to use default or set options to change.
2667
     *
2668
     * @return void.
0 ignored issues
show
Documentation introduced by
The doc-type void. could not be parsed: Unknown type name "void." at position 0. (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
2669
     */
2670
    public function setAsciiOptions($options = array())
2671
    {
2672
        $this->asciiOptions = $options;
2673
    }
2674
2675
2676
2677
    /**
2678
     * Create an ASCII version from the image details.
2679
     *
2680
     * @param string $file the file to output.
2681
     *
2682
     * @return string ASCII representation of the image.
2683
     */
2684
    public function ascii($file = null)
2685
    {
2686
        $file = $file ? $file : $this->cacheFileName;
2687
2688
        $asciiArt = new CAsciiArt();
2689
        $asciiArt->setOptions($this->asciiOptions);
2690
        return $asciiArt->createFromFile($file);
2691
    }
2692
2693
2694
2695
    /**
2696
     * Log an event if verbose mode.
2697
     *
2698
     * @param string $message to log.
2699
     *
2700
     * @return this
2701
     */
2702 9
    public function log($message)
2703
    {
2704 9
        if ($this->verbose) {
2705
            $this->log[] = $message;
2706
        }
2707
2708 9
        return $this;
2709
    }
2710
2711
2712
2713
    /**
2714
     * Do verbose output to a file.
2715
     *
2716
     * @param string $fileName where to write the verbose output.
2717
     *
2718
     * @return void
2719
     */
2720
    public function setVerboseToFile($fileName)
2721
    {
2722
        $this->log("Setting verbose output to file.");
2723
        $this->verboseFileName = $fileName;
2724
    }
2725
2726
2727
2728
    /**
2729
     * Do verbose output and print out the log and the actual images.
2730
     *
2731
     * @return void
2732
     */
2733
    private function verboseOutput()
2734
    {
2735
        $log = null;
2736
        $this->log("As JSON: \n" . $this->json());
2737
        $this->log("Memory peak: " . round(memory_get_peak_usage() /1024/1024) . "M");
2738
        $this->log("Memory limit: " . ini_get('memory_limit'));
2739
2740
        $included = get_included_files();
2741
        $this->log("Included files: " . count($included));
2742
2743
        foreach ($this->log as $val) {
2744
            if (is_array($val)) {
2745
                foreach ($val as $val1) {
2746
                    $log .= htmlentities($val1) . '<br/>';
2747
                }
2748
            } else {
2749
                $log .= htmlentities($val) . '<br/>';
2750
            }
2751
        }
2752
2753
        if (!is_null($this->verboseFileName)) {
2754
            file_put_contents(
2755
                $this->verboseFileName,
2756
                str_replace("<br/>", "\n", $log)
2757
            );
2758
        } else {
2759
            echo <<<EOD
2760
<h1>CImage Verbose Output</h1>
2761
<pre>{$log}</pre>
2762
EOD;
2763
        }
2764
    }
2765
2766
2767
2768
    /**
2769
     * Raise error, enables to implement a selection of error methods.
2770
     *
2771
     * @param string $message the error message to display.
2772
     *
2773
     * @return void
2774
     * @throws Exception
2775
     */
2776
    private function raiseError($message)
2777
    {
2778
        throw new Exception($message);
2779
    }
2780
}
2781