Passed
Push — master ( cff939...bc9af2 )
by Sebastian
02:31
created

ImageHelper::getIndexedColors()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 14
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 8
c 1
b 0
f 0
nc 2
nop 2
dl 0
loc 14
rs 10
1
<?php
2
/**
3
 * File containing the {@link ImageHelper} class.
4
 * 
5
 * @package Application Utils
6
 * @subpackage ImageHelper
7
 * @see ImageHelper
8
 */
9
10
namespace AppUtils;
11
12
/**
13
 * Image helper class that can be used to transform images,
14
 * and retrieve information about them.
15
 * 
16
 * @package Application Utils
17
 * @subpackage ImageHelper
18
 * @author Sebastian Mordziol <[email protected]>
19
 * @version 2.0
20
 */
21
class ImageHelper
22
{
23
    const ERROR_CANNOT_CREATE_IMAGE_CANVAS = 513001;
24
    
25
    const ERROR_IMAGE_FILE_DOES_NOT_EXIST = 513002;
26
    
27
    const ERROR_CANNOT_GET_IMAGE_SIZE = 513003;
28
    
29
    const ERROR_UNSUPPORTED_IMAGE_TYPE = 513004;
30
    
31
    const ERROR_FAILED_TO_CREATE_NEW_IMAGE = 513005;
32
33
    const ERROR_SAVE_NO_IMAGE_CREATED = 513006;
34
    
35
    const ERROR_CANNOT_WRITE_NEW_IMAGE_FILE = 513007;
36
    
37
    const ERROR_CREATED_AN_EMPTY_FILE = 513008;
38
    
39
    const ERROR_QUALITY_VALUE_BELOW_ZERO = 513009;
40
    
41
    const ERROR_QUALITY_ABOVE_ONE_HUNDRED = 513010;
42
    
43
    const ERROR_CANNOT_CREATE_IMAGE_OBJECT = 513011;
44
    
45
    const ERROR_CANNOT_COPY_RESAMPLED_IMAGE_DATA = 513012; 
46
    
47
    const ERROR_HEADERS_ALREADY_SENT = 513013;
48
    
49
    const ERROR_CANNOT_READ_SVG_IMAGE = 513014;
50
    
51
    const ERROR_SVG_SOURCE_VIEWBOX_MISSING = 513015;
52
    
53
    const ERROR_SVG_VIEWBOX_INVALID = 513016;
54
    
55
    const ERROR_NOT_A_RESOURCE = 513017;
56
57
    const ERROR_INVALID_STREAM_IMAGE_TYPE = 513018;
58
59
    const ERROR_NO_TRUE_TYPE_FONT_SET = 513019;
60
    
61
    const ERROR_POSITION_OUT_OF_BOUNDS = 513020;
62
63
    const ERROR_IMAGE_CREATION_FAILED = 513021;
64
65
    const ERROR_CANNOT_CREATE_IMAGE_CROP = 513023;
66
    
67
    const ERROR_GD_LIBRARY_NOT_INSTALLED = 513024;
68
69
   /**
70
    * @var string
71
    */
72
    protected $file;
73
74
   /**
75
    * @var ImageHelper_Size
76
    */
77
    protected $info;
78
79
   /**
80
    * @var string
81
    */
82
    protected $type;
83
84
   /**
85
    * @var resource|NULL
86
    */
87
    protected $newImage;
88
89
   /**
90
    * @var resource
91
    */
92
    protected $sourceImage;
93
94
   /**
95
    * @var int
96
    */
97
    protected $width;
98
99
   /**
100
    * @var int
101
    */
102
    protected $height;
103
104
   /**
105
    * @var int
106
    */
107
    protected $newWidth = 0;
108
109
   /**
110
    * @var int
111
    */
112
    protected $newHeight = 0;
113
114
   /**
115
    * @var int
116
    */
117
    protected $quality = 85;
118
    
119
    protected static $imageTypes = array(
120
        'png' => 'png',
121
        'jpg' => 'jpeg',
122
        'jpeg' => 'jpeg',
123
        'gif' => 'gif',
124
        'svg' => 'svg'
125
    );
126
    
127
    protected static $config = array(
128
        'auto-memory-adjustment' => true
129
    );
130
131
    protected $streamTypes = array(
132
        'jpeg',
133
        'png',
134
        'gif'
135
    );
136
    
137
    public function __construct($sourceFile=null, $resource=null, $type=null)
138
    {
139
        // ensure that the GD library is installed
140
        if(!function_exists('imagecreate')) 
141
        {
142
            throw new ImageHelper_Exception(
143
                'The PHP GD extension is not installed or not enabled.',
144
                null,
145
                self::ERROR_GD_LIBRARY_NOT_INSTALLED
146
            );
147
        }
148
        
149
        if(is_resource($resource)) 
150
        {
151
            $this->sourceImage = $resource;
152
            $this->type = $type;
153
            $this->info = self::getImageSize($resource);
154
        } 
155
        else 
156
        {
157
            $this->file = $sourceFile;
158
            if (!file_exists($this->file)) {
159
                throw new ImageHelper_Exception(
160
                    'Image file does not exist',
161
                    sprintf(
162
                        'Could not find the image file on disk at location [%s]',
163
                        $this->file
164
                    ),
165
                    self::ERROR_IMAGE_FILE_DOES_NOT_EXIST
166
                );
167
            }
168
    
169
            $this->type = self::getFileImageType($this->file);
170
            if (is_null($this->type)) {
171
                throw new ImageHelper_Exception(
172
                    'Error opening image',
173
                    'Not a valid supported image type for image ' . $this->file,
174
                    self::ERROR_UNSUPPORTED_IMAGE_TYPE
175
                );
176
            }
177
178
            $this->info = self::getImageSize($this->file);
179
180
            if(!$this->isVector()) 
181
            {
182
                $method = 'imagecreatefrom' . $this->type;
183
                $this->sourceImage = $method($this->file);
184
                if (!$this->sourceImage) {
185
                    throw new ImageHelper_Exception(
186
                        'Error creating new image',
187
                        $method . ' failed',
188
                        self::ERROR_FAILED_TO_CREATE_NEW_IMAGE
189
                    );
190
                }
191
                
192
                imagesavealpha($this->sourceImage, true);
193
            }
194
        }
195
196
        $this->width = $this->info->getWidth();
197
        $this->height = $this->info->getHeight();
198
199
        if(!$this->isVector()) {
200
            $this->setNewImage($this->duplicateImage($this->sourceImage));
201
        }
202
    }
203
204
   /**
205
    * Factory method: creates a new helper with a blank image.
206
    * 
207
    * @param integer $width
208
    * @param integer $height
209
    * @param string $type The target file type when saving
210
    * @return ImageHelper
211
    * @throws ImageHelper_Exception
212
    *
213
    * @see ImageHelper::ERROR_CANNOT_CREATE_IMAGE_OBJECT
214
    */
215
    public static function createNew($width, $height, $type='png')
216
    {
217
        $img = imagecreatetruecolor($width, $height);
218
        if($img !== false) {
219
            return self::createFromResource($img, 'png');
220
        }
221
        
222
        throw new ImageHelper_Exception(
223
            'Could not create new true color image.',
224
            null,
225
            self::ERROR_CANNOT_CREATE_IMAGE_OBJECT
226
        );
227
    }
228
    
229
   /**
230
    * Factory method: creates an image helper from an
231
    * existing image resource.
232
    *
233
    * Note: while the resource is type independent, the
234
    * type parameter is required for some methods, as well
235
    * as to be able to save the image.
236
    *
237
    * @param resource $resource
238
    * @param string $type The target image type, e.g. "jpeg", "png", etc.
239
    * @return ImageHelper
240
    */
241
    public static function createFromResource($resource, ?string $type=null)
242
    {
243
        self::requireResource($resource);
244
        
245
        return new ImageHelper(null, $resource, $type);
246
    }
247
    
248
   /**
249
    * Factory method: creates an image helper from an
250
    * image file on disk.
251
    *
252
    * @param string $path
253
    * @return ImageHelper
254
    */
255
    public static function createFromFile($file)
256
    {
257
        return new ImageHelper($file);
258
    }
259
    
260
   /**
261
    * Sets a global image helper configuration value. Available
262
    * configuration settings are:
263
    * 
264
    * <ul>
265
    * <li><code>auto-memory-adjustment</code> <i>boolean</i> Whether totry and adjust the memory limit automatically so there will be enough to load/process the target image.</li>
266
    * </ul>
267
    * 
268
    * @param string $name
269
    * @param mixed $value
270
    */
271
    public static function setConfig($name, $value)
272
    {
273
        if(isset(self::$config[$name])) {
274
            self::$config[$name] = $value;
275
        }
276
    }
277
    
278
   /**
279
    * Shorthand for setting the automatic memory adjustment
280
    * global configuration setting.
281
    * 
282
    * @param bool $enabled
283
    */
284
    public static function setAutoMemoryAdjustment($enabled=true)
285
    {
286
        self::setConfig('auto-memory-adjustment', $enabled);
287
    }
288
    
289
   /**
290
    * Duplicates an image resource.
291
    * @param resource $img
292
    * @return resource
293
    */
294
    protected function duplicateImage($img)
295
    {
296
        self::requireResource($img);
297
        
298
        $width = imagesx($img);
299
        $height = imagesy($img);
300
        $duplicate = $this->createNewImage($width, $height);
301
        imagecopy($duplicate, $img, 0, 0, 0, 0, $width, $height);
302
        return $duplicate;
303
    }
304
    
305
   /**
306
    * Duplicates the current state of the image into a new
307
    * image helper instance.
308
    * 
309
    * @return ImageHelper
310
    */
311
    public function duplicate()
312
    {
313
        return ImageHelper::createFromResource($this->duplicateImage($this->newImage));
314
    }
315
316
    public function enableAlpha()
317
    {
318
        if(!$this->alpha) 
319
        {
320
            self::addAlphaSupport($this->newImage, false);
321
            $this->alpha = true;
322
        }
323
        
324
        return $this;
325
    }
326
    
327
    public function resize($width, $height)
328
    {
329
        $new = $this->createNewImage($width, $height);
330
        
331
        imagecopy($new, $this->newImage, 0, 0, 0, 0, $width, $height);
332
        
333
        $this->setNewImage($new);
334
        
335
        return $this;
336
    }
337
    
338
    public function getNewSize()
339
    {
340
        return array($this->newWidth, $this->newHeight);
341
    }
342
    
343
    /**
344
     * Sharpens the image by the specified percentage.
345
     *
346
     * @param number $percent
347
     * @return ImageHelper
348
     */
349
    public function sharpen($percent=0)
350
    {
351
        if($percent <= 0) {
352
            return $this;
353
        }
354
        
355
        // the factor goes from 0 to 64 for sharpening.
356
        $factor = $percent * 64 / 100;
357
        return $this->convolute($factor);
358
    }
359
    
360
    public function blur($percent=0)
361
    {
362
        if($percent <= 0) {
363
            return $this;
364
        }
365
        
366
        // the factor goes from -64 to 0 for blurring.
367
        $factor = ($percent * 64 / 100) * -1;
368
        return $this->convolute($factor);
369
    }
370
    
371
    protected function convolute($factor)
372
    {
373
        // get a value thats equal to 64 - abs( factor )
374
        // ( using min/max to limited the factor to 0 - 64 to not get out of range values )
375
        $val1Adjustment = 64 - min( 64, max( 0, abs( $factor ) ) );
376
        
377
        // the base factor for the "current" pixel depends on if we are blurring or sharpening.
378
        // If we are blurring use 1, if sharpening use 9.
379
        $val1Base = 9;
380
        if( abs( $factor ) != $factor ) {
381
            $val1Base = 1;
382
        }
383
        
384
        // value for the center/currrent pixel is:
385
        //  1 + 0 - max blurring
386
        //  1 + 64- minimal blurring
387
        //  9 + 64- minimal sharpening
388
        //  9 + 0 - maximum sharpening
389
        $val1 = $val1Base + $val1Adjustment;
390
        
391
        // the value for the surrounding pixels is either positive or negative depending on if we are blurring or sharpening.
392
        $val2 = -1;
393
        if( abs( $factor ) != $factor ) {
394
            $val2 = 1;
395
        }
396
        
397
        // setup matrix ..
398
        $matrix = array(
399
            array( $val2, $val2, $val2 ),
400
            array( $val2, $val1, $val2 ),
401
            array( $val2, $val2, $val2 )
402
        );
403
        
404
        // calculate the correct divisor
405
        // actual divisor is equal to "$divisor = $val1 + $val2 * 8;"
406
        // but the following line is more generic
407
        $divisor = array_sum( array_map( 'array_sum', $matrix ) );
408
        
409
        // apply the matrix
410
        imageconvolution( $this->newImage, $matrix, $divisor, 0 );
411
        
412
        return $this;
413
    }
414
    
415
    /**
416
     * Whether the image is an SVG image.
417
     * @return boolean
418
     */
419
    public function isTypeSVG()
420
    {
421
        return $this->type === 'svg';
422
    }
423
    
424
    /**
425
     * Whether the image is a PNG image.
426
     * @return boolean
427
     */
428
    public function isTypePNG()
429
    {
430
        return $this->type === 'png';
431
    }
432
    
433
    /**
434
     * Whether the image is a JPEG image.
435
     * @return boolean
436
     */
437
    public function isTypeJPEG()
438
    {
439
        return $this->type === 'jpeg';
440
    }
441
    
442
    /**
443
     * Whether the image is a vector image.
444
     * @return boolean
445
     */
446
    public function isVector()
447
    {
448
        return $this->isTypeSVG();
449
    }
450
    
451
    /**
452
     * Retrieves the type of the image.
453
     * @return string e.g. "jpeg", "png"
454
     */
455
    public function getType() : string
456
    {
457
        return $this->type;
458
    }
459
    
460
    /**
461
     * Calculates the size of the image by the specified width,
462
     * and returns an indexed array with the width and height size.
463
     *
464
     * @param integer $height
465
     * @return ImageHelper_Size
466
     */
467
    public function getSizeByWidth(int $width) : ImageHelper_Size
468
    {
469
        $height = floor(($width * $this->height) / $this->width);
470
        
471
        return new ImageHelper_Size(array(
472
            $width,
473
            $height,
474
            $this->info['bits'],
475
            $this->info['channels']
476
        ));
477
    }
478
    
479
    /**
480
     * Calculates the size of the image by the specified height,
481
     * and returns an indexed array with the width and height size.
482
     *
483
     * @param integer $height
484
     * @return ImageHelper_Size
485
     */
486
    public function getSizeByHeight($height) : ImageHelper_Size
487
    {
488
        $width = floor(($height * $this->width) / $this->height);
489
        
490
        return new ImageHelper_Size(array(
491
            $width,
492
            $height,
493
            $this->info['bits'],
494
            $this->info['channels']
495
        ));
496
    }
497
    
498
   /**
499
    * Resamples the image to a new width, maintaining
500
    * aspect ratio.
501
    * 
502
    * @param int $width
503
    * @return ImageHelper
504
    */
505
    public function resampleByWidth(int $width) : ImageHelper
506
    {
507
        $size = $this->getSizeByWidth($width);
508
509
        $this->resampleImage($size->getWidth(), $size->getHeight());
510
        
511
        return $this;
512
    }
513
514
   /**
515
    * Resamples the image by height, and creates a new image file on disk.
516
    * 
517
    * @param int $height
518
    * @return ImageHelper
519
    */
520
    public function resampleByHeight($height) : ImageHelper
521
    {
522
        $size = $this->getSizeByHeight($height);
523
524
        return $this->resampleImage($size->getWidth(), $size->getHeight());
525
    }
526
527
   /**
528
    * Resamples the image without keeping the aspect ratio.
529
    * 
530
    * @param int $width
531
    * @param int $height
532
    * @return ImageHelper
533
    */
534
    public function resample(?int $width = null, ?int $height = null) : ImageHelper
535
    {
536
        if($this->isVector()) {
537
            return $this;
538
        }
539
        
540
        if ($width === null && $height === null) {
541
            return $this->resampleByWidth($this->width);
542
        }
543
544
        if (empty($width)) {
545
            return $this->resampleByHeight($height);
546
        }
547
548
        if (empty($height)) {
549
            return $this->resampleByWidth($width);
550
        }
551
552
        return $this->resampleAndCrop($width, $height);
553
    }
554
555
    public function resampleAndCrop($width, $height) : ImageHelper
556
    {
557
        if($this->isVector()) {
558
            return $this;
559
        }
560
561
        if ($this->width <= $this->height) 
562
        {
563
            $this->resampleByWidth($width);
564
        } 
565
        else 
566
        {
567
            $this->resampleByHeight($height);
568
        }
569
        
570
        $newCanvas = $this->createNewImage($width, $height);
571
        
572
        // and now we can add the crop
573
        if (!imagecopy(
574
            $newCanvas,
575
            $this->newImage,
576
            0, // destination X
577
            0, // destination Y
578
            0, // source X
579
            0, // source Y
580
            $width,
581
            $height
582
        )
583
        ) {
584
            throw new ImageHelper_Exception(
585
                'Error creating new image',
586
                'Cannot create crop of the image',
587
                self::ERROR_CANNOT_CREATE_IMAGE_CROP
588
            );
589
        }
590
591
        $this->setNewImage($newCanvas);
592
593
        return $this;
594
    }
595
    
596
    protected $alpha = false;
597
598
   /**
599
    * Configures the specified image resource to make it alpha compatible.
600
    * 
601
    * @param resource $canvas
602
    * @param bool $fill Whether to fill the whole canvas with the transparency
603
    */
604
    public static function addAlphaSupport($canvas, $fill=true)
605
    {
606
        self::requireResource($canvas);
607
        
608
        imagealphablending($canvas,true);
609
        imagesavealpha($canvas, true);
610
611
        if($fill) {
612
            self::fillImageTransparent($canvas);
613
        }
614
    }
615
    
616
    public function isAlpha()
617
    {
618
        return $this->alpha;
619
    }
620
621
    public function save(string $targetFile, $dispose=true)
622
    {
623
        if($this->isVector()) {
624
            return true;
625
        }
626
        
627
        if(!is_resource($this->newImage)) {
628
            throw new ImageHelper_Exception(
629
                'Error creating new image',
630
                'Cannot save an image, no valid image resource was created. You have to call one of the resample methods to create a new image.',
631
                self::ERROR_SAVE_NO_IMAGE_CREATED
632
            );
633
        }
634
635
        if (file_exists($targetFile)) {
636
            unlink($targetFile);
637
        }
638
        
639
        $method = 'image' . $this->type;
640
        if (!$method($this->newImage, $targetFile, $this->resolveQuality())) {
641
            throw new ImageHelper_Exception(
642
                'Error creating new image',
643
                sprintf(
644
                    'The %s method could not write the new image to %s',
645
                    $method,
646
                    $targetFile
647
                ),
648
                self::ERROR_CANNOT_WRITE_NEW_IMAGE_FILE
649
            );
650
        }
651
652
        if (filesize($targetFile) < 1) {
653
            throw new ImageHelper_Exception(
654
                'Error creating new image',
655
                'Resampling completed sucessfully, but the generated file is 0 bytes big.',
656
                self::ERROR_CREATED_AN_EMPTY_FILE
657
            );
658
        }
659
660
        if($dispose) {
661
            $this->dispose();
662
        }
663
        
664
        return true;
665
    }
666
    
667
    public function dispose()
668
    {
669
        if(is_resource($this->sourceImage)) {
670
            imagedestroy($this->sourceImage);
671
        }
672
        
673
        if(is_resource($this->newImage)) {
674
            imagedestroy($this->newImage);
675
        }
676
    }
677
678
    protected function resolveQuality()
679
    {
680
        switch ($this->type) {
681
            case 'png':
682
                return 0;
683
684
            case 'jpeg':
685
                return $this->quality;
686
687
            default:
688
                return 0;
689
        }
690
    }
691
692
    /**
693
     * Sets the quality for image types like jpg that use compression.
694
     * @param int $quality
695
     */
696
    public function setQuality($quality)
697
    {
698
        $quality = $quality * 1;
699
        if ($quality < 0) {
700
            throw new ImageHelper_Exception(
701
                'Invalid configuration',
702
                'Cannot set a quality less than 0.',
703
                self::ERROR_QUALITY_VALUE_BELOW_ZERO
704
            );
705
        }
706
707
        if ($quality > 100) {
708
            throw new ImageHelper_Exception(
709
                'Invalid configuration',
710
                'Cannot set a quality higher than 100.',
711
                self::ERROR_QUALITY_ABOVE_ONE_HUNDRED
712
            );
713
        }
714
715
        $this->quality = $quality * 1;
716
    }
717
718
   /**
719
    * Attempts to adjust the memory to the required size
720
    * to work with the current image.
721
    * 
722
    * @return boolean
723
    */
724
    protected function adjustMemory() : bool
725
    {
726
        if(!self::$config['auto-memory-adjustment']) {
727
            return true;
728
        }
729
        
730
        $MB = 1048576; // number of bytes in 1M
731
        $K64 = 65536; // number of bytes in 64K
732
        $tweakFactor = 25; // magic adjustment value as safety threshold
733
        $memoryNeeded = ceil(
734
            (
735
                $this->info->getWidth() 
736
                * 
737
                $this->info->getHeight() 
738
                * 
739
                $this->info->getBits() 
740
                * 
741
                ($this->info->getChannels() / 8) 
742
                + 
743
                $K64
744
            )
745
            * $tweakFactor
746
        );
747
748
        //ini_get('memory_limit') only works if compiled with "--enable-memory-limit" also
749
        //default memory limit is 8MB so we will stick with that.
750
        $memoryLimit = 8 * $MB;
751
            
752
        if (function_exists('memory_get_usage') && memory_get_usage() + $memoryNeeded > $memoryLimit) {
753
            $newLimit = ($memoryLimit + (memory_get_usage() + $memoryNeeded)) / $MB;
754
            $newLimit = ceil($newLimit);
755
            ini_set('memory_limit', $newLimit . 'M');
756
757
            return true;
758
        }
759
760
        return false;
761
    }
762
763
   /**
764
    * Stretches the image to the specified dimensions.
765
    * 
766
    * @param int $width
767
    * @param int $height
768
    * @return ImageHelper
769
    */
770
    public function stretch(int $width, int $height) : ImageHelper
771
    {
772
        return $this->resampleImage($width, $height);
773
    }
774
775
   /**
776
    * Creates a new image from the current image,
777
    * resampling it to the new size.
778
    * 
779
    * @param int $newWidth
780
    * @param int $newHeight   
781
    * @throws ImageHelper_Exception
782
    * @return ImageHelper
783
    */
784
    protected function resampleImage(int $newWidth, int $newHeight) : ImageHelper
785
    {
786
        if($this->isVector()) {
787
            return $this;
788
        }
789
790
        if($this->newWidth==$newWidth && $this->newHeight==$newHeight) {
791
            return $this;
792
        }
793
        
794
        if($newWidth < 1) { $newWidth = 1; }
795
        if($newHeight < 1) { $newHeight = 1; }
796
        
797
        $this->adjustMemory();
798
799
        $new = $this->createNewImage($newWidth, $newHeight);
800
       
801
        if (!imagecopyresampled($new, $this->newImage, 0, 0, 0, 0, $newWidth, $newHeight, $this->newWidth, $this->newHeight)) 
802
        {
803
            throw new ImageHelper_Exception(
804
                'Error creating new image',
805
                'Cannot copy resampled image data',
806
                self::ERROR_CANNOT_COPY_RESAMPLED_IMAGE_DATA
807
            );
808
        }
809
810
        $this->setNewImage($new);
811
812
        return $this;
813
    }
814
815
    /**
816
     * Gets the image type for the specified file name.
817
     * Like {@link getImageType()}, except that it automatically
818
     * extracts the file extension from the file name.
819
     *
820
     * @param string $fileName
821
     * @return string|NULL
822
     * @see getImageType()
823
     */
824
    public static function getFileImageType($fileName)
825
    {
826
        return self::getImageType(strtolower(pathinfo($fileName, PATHINFO_EXTENSION)));
827
    }
828
829
    /**
830
     * Gets the image type for the specified file extension,
831
     * or NULL if the extension is not among the supported
832
     * file types.
833
     *
834
     * @param string $extension
835
     * @return string|NULL
836
     */
837
    public static function getImageType($extension)
838
    {
839
        if (isset(self::$imageTypes[$extension])) {
840
            return self::$imageTypes[$extension];
841
        }
842
843
        return null;
844
    }
845
846
    public static function getImageTypes()
847
    {
848
        $types = array_values(self::$imageTypes);
849
        return array_unique($types);
850
    }
851
    
852
    /**
853
     * Displays an existing image resource.
854
     *
855
     * @param resource $resource
856
     * @param string $imageType The image format to send, i.e. "jpeg", "png"
857
     * @param int $quality The quality to use for the image. This is 0-9 (0=no compression, 9=max) for PNG, and 0-100 (0=lowest, 100=highest quality) for JPG 
858
     */
859
    public static function displayImageStream($resource, $imageType, $quality=-1)
860
    {
861
        $imageType = strtolower($imageType);
862
        
863
        if(!in_array($imageType, self::$streamTypes)) {
864
            throw new ImageHelper_Exception(
865
                'Invalid image stream type',
866
                sprintf(
867
                    'The image type [%s] cannot be used for a stream.',
868
                    $imageType
869
                ),
870
                self::ERROR_INVALID_STREAM_IMAGE_TYPE
871
            );
872
        }
873
        
874
        header('Content-type:image/' . $imageType);
875
876
        $function = 'image' . $imageType;
877
        
878
        $function($resource, null, $quality);
879
    }
880
881
    /**
882
     * Displays an image from an existing image file.
883
     * @param string $imageFile
884
     */
885
    public static function displayImage($imageFile)
886
    {
887
        $file = null;
888
        $line = null;
889
        if (headers_sent($file, $line)) {
890
            throw new ImageHelper_Exception(
891
                'Error displaying image',
892
                'Headers have already been sent: in file ' . $file . ':' . $line,
893
                self::ERROR_HEADERS_ALREADY_SENT
894
            );
895
        }
896
897
        if (!file_exists($imageFile)) {
898
            throw new ImageHelper_Exception(
899
                'Image file does not exist',
900
                sprintf(
901
                    'Cannot display image, the file does not exist on disk: [%s].',
902
                    $imageFile
903
                ),
904
                self::ERROR_IMAGE_FILE_DOES_NOT_EXIST
905
            );
906
        }
907
908
        $format = self::getFileImageType($imageFile);
909
        if($format == 'svg') {
910
            $format = 'svg+xml';
911
        }
912
913
        $contentType = 'image/' . $format;
914
        
915
        header('Content-Type: '.$contentType);
916
        header("Last-Modified: " . gmdate("D, d M Y H:i:s", filemtime($imageFile)) . " GMT");
917
        header('Cache-Control: public');
918
        header('Content-Length: ' . filesize($imageFile));
919
920
        readfile($imageFile);
921
    }
922
    
923
   /**
924
    * Displays the current image.
925
    */
926
    public function display()
927
    {
928
        $this->displayImageStream($this->newImage, $this->getType(), $this->resolveQuality());
929
    }
930
    
931
   /**
932
    * Trims the current loaded image.
933
    * 
934
    * @param array $color A color definition, as an associative array with red, green, and blue keys. If not specified, the color at pixel position 0,0 will be used.
935
    */
936
    public function trim($color=null)
937
    {
938
        return $this->trimImage($this->newImage, $color);
939
    }
940
    
941
   /**
942
    * Retrieves a color definition by its index.
943
    * 
944
    * @param resource $img A valid image resource.
945
    * @param int $colorIndex The color index, as returned by imagecolorat for example.
946
    * @return array An array with red, green, blue and alpha keys.
947
    */
948
    public function getIndexedColors($img, int $colorIndex) : array
949
    {
950
        $color = imagecolorsforindex($img, $colorIndex);
951
        
952
        // it seems imagecolorsforindex may return false (undocumented, unproven)
953
        if(is_array($color)) {
954
            return $color;
955
        }
956
        
957
        return array(
958
            'red' => 0,
959
            'green' => 0,
960
            'blue' => 0,
961
            'alpha' => 1
962
        );
963
    }
964
        
965
   /**
966
    * Trims the specified image resource by removing the specified color.
967
    * Also works with transparency.
968
    * 
969
    * @param resource $img
970
    * @param array $color A color definition, as an associative array with red, green, blue and alpha keys. If not specified, the color at pixel position 0,0 will be used.
971
    * @return ImageHelper
972
    */
973
    protected function trimImage($img, ?array $color=null) : ImageHelper
974
    {
975
        if($this->isVector()) {
976
            return $this;
977
        }
978
979
        self::requireResource($img);
980
        
981
        if(empty($color)) 
982
        {
983
            $color = imagecolorat($img, 0, 0);
984
            $color = $this->getIndexedColors($img, $color);
985
        }
986
        
987
        // Get the image width and height.
988
        $imw = imagesx($img);
989
        $imh = imagesy($img);
990
991
        // Set the X variables.
992
        $xmin = $imw;
993
        $xmax = 0;
994
        $ymin = null;
995
        $ymax = null;
996
         
997
        // Start scanning for the edges.
998
        for ($iy=0; $iy<$imh; $iy++)
999
        {
1000
            $first = true;
1001
            
1002
            for ($ix=0; $ix<$imw; $ix++)
1003
            {
1004
                $ndx = imagecolorat($img, $ix, $iy);
1005
                $colors = $this->getIndexedColors($img, $ndx);
1006
                
1007
                if(!$this->colorsMatch($colors, $color)) 
1008
                {
1009
                    if ($xmin > $ix) { $xmin = $ix; }
1010
                    if ($xmax < $ix) { $xmax = $ix; }
1011
                    if (!isset($ymin)) { $ymin = $iy; }
1012
                    
1013
                    $ymax = $iy;
1014
                    
1015
                    if($first)
1016
                    { 
1017
                        $ix = $xmax; 
1018
                        $first = false; 
1019
                    }
1020
                }
1021
            }
1022
        }
1023
        
1024
        // no trimming border found
1025
        if($ymax === null && $ymax === null) {
1026
            return $this;
1027
        }
1028
        
1029
        // The new width and height of the image. 
1030
        $imw = 1+$xmax-$xmin; // Image width in pixels
1031
        $imh = 1+$ymax-$ymin; // Image height in pixels
1032
1033
        // Make another image to place the trimmed version in.
1034
        $im2 = $this->createNewImage($imw, $imh);
1035
        
1036
        if($color['alpha'] > 0) 
1037
        {
1038
            $bg2 = imagecolorallocatealpha($im2, $color['red'], $color['green'], $color['blue'], $color['alpha']);
1039
            imagecolortransparent($im2, $bg2);
1040
        }
1041
        else
1042
        {
1043
            $bg2 = imagecolorallocate($im2, $color['red'], $color['green'], $color['blue']);
1044
        }
1045
        
1046
        // Make the background of the new image the same as the background of the old one.
1047
        imagefill($im2, 0, 0, $bg2);
1048
1049
        // Copy it over to the new image.
1050
        imagecopy($im2, $img, 0, 0, $xmin, $ymin, $imw, $imh);
1051
        
1052
        // To finish up, we replace the old image which is referenced.
1053
        imagedestroy($img);
1054
        
1055
        $this->setNewImage($im2);
1056
1057
        return $this;
1058
    }
1059
    
1060
   /**
1061
    * Sets the new image after a transformation operation:
1062
    * automatically adjusts the new size information.
1063
    * 
1064
    * @param resource $image
1065
    */
1066
    protected function setNewImage($image)
1067
    {
1068
        self::requireResource($image);
1069
        
1070
        $this->newImage = $image;
1071
        $this->newWidth = imagesx($image);
1072
        $this->newHeight= imagesy($image);
1073
    }
1074
    
1075
   /**
1076
    * Requires the subject to be a resource.
1077
    * 
1078
    * @param resource $subject
1079
    * @throws ImageHelper_Exception
1080
    */
1081
    protected static function requireResource($subject)
1082
    {
1083
        if(is_resource($subject)) {
1084
            return;
1085
        }
1086
        
1087
        throw new ImageHelper_Exception(
1088
            'Not an image resource',
1089
            sprintf(
1090
                'Specified image should be a resource, [%s] given.',
1091
                gettype($subject)
1092
            ),
1093
            self::ERROR_NOT_A_RESOURCE
1094
        );
1095
    }
1096
    
1097
   /**
1098
    * Creates a new image resource, with transparent background.
1099
    * 
1100
    * @param int $width
1101
    * @param int $height
1102
    * @throws ImageHelper_Exception
1103
    * @return resource
1104
    */
1105
    protected function createNewImage(int $width, int $height)
1106
    {
1107
        $img = imagecreatetruecolor($width, $height);
1108
        
1109
        if($img === false) 
1110
        {
1111
            throw new ImageHelper_Exception(
1112
                'Error creating new image',
1113
                'Cannot create new image canvas',
1114
                self::ERROR_CANNOT_CREATE_IMAGE_CANVAS
1115
            );
1116
        }
1117
1118
        self::addAlphaSupport($img, true);
1119
        
1120
        return $img;
1121
    }
1122
    
1123
   /**
1124
    * Whether the two specified colors are the same.
1125
    * 
1126
    * @param array $a
1127
    * @param array $b
1128
    * @return boolean
1129
    */
1130
	protected function colorsMatch($a, $b) : bool
1131
	{
1132
		$parts = array('red', 'green', 'blue');
1133
		foreach($parts as $part) {
1134
			if($a[$part] != $b[$part]) {
1135
				return false;
1136
			}
1137
		} 
1138
		
1139
		return true;
1140
	}
1141
	
1142
	public function fillWhite($x=0, $y=0)
1143
	{
1144
	    $this->addRGBColor('white', 255, 255, 255);
1145
        return $this->fill('white', $x, $y);
1146
	}
1147
	
1148
	public function fillTransparent() : ImageHelper
1149
	{
1150
        $this->enableAlpha();
1151
	    
1152
	    self::fillImageTransparent($this->newImage);
1153
	    
1154
	    return $this;
1155
	}
1156
	
1157
	public static function fillImageTransparent($resource)
1158
	{
1159
	    self::requireResource($resource);
1160
	    
1161
	    $transparent = imagecolorallocatealpha($resource, 89, 14, 207, 127);
1162
	    imagecolortransparent ($resource, $transparent);
1163
	    imagefill($resource, 0, 0, $transparent);
1164
	}
1165
	
1166
	public function fill($colorName, $x=0, $y=0)
1167
	{
1168
	    imagefill($this->newImage, $x, $y, $this->colors[$colorName]);
1169
	    return $this;
1170
	}
1171
	
1172
    protected $colors = array();
1173
1174
    public function addRGBColor($name, $red, $green, $blue)
1175
    {
1176
        $this->colors[$name] = imagecolorallocate($this->newImage, $red, $green, $blue);
1177
        return $this;
1178
    }
1179
    
1180
    public function textTTF($text, $size, $colorName, $x=0, $y=0, $angle=0)
1181
    {
1182
        imagealphablending($this->newImage, true);
1183
        
1184
        imagettftext($this->newImage, $size, $angle, $x, $y, $this->colors[$colorName], $this->TTFFile, $text);
1185
        
1186
        imagealphablending($this->newImage, false);
1187
        
1188
        return $this;
1189
    }
1190
    
1191
   /**
1192
    * @return resource
1193
    */
1194
    public function getImage()
1195
    {
1196
        return $this->newImage;
1197
    }
1198
    
1199
    public function paste(ImageHelper $target, $xpos=0, $ypos=0, $sourceX=0, $sourceY=0)
1200
    {
1201
        $img = $target->getImage();
1202
        
1203
        if($target->isAlpha()) {
1204
            $this->enableAlpha();
1205
        }
1206
        
1207
        imagecopy($this->newImage, $img, $xpos, $ypos, $sourceX, $sourceY, imagesx($img), imagesy($img));
1208
        return $this;
1209
    }
1210
    
1211
   /**
1212
    * Retrieves the size of the image.
1213
    * 
1214
    * @param bool $exception Whether to trigger an exception when the image does not exist
1215
    * @return ImageHelper_Size
1216
    * @throws ImageHelper_Exception
1217
    * @see ImageHelper::ERROR_CANNOT_GET_IMAGE_SIZE
1218
    */
1219
	public function getSize() : ImageHelper_Size
1220
    {
1221
	    return self::getImageSize($this->newImage);
1222
    }
1223
    
1224
    protected $TTFFile;
1225
    
1226
   /**
1227
    * Sets the TTF font file to use for text operations.
1228
    * 
1229
    * @param string $filePath
1230
    * @return ImageHelper
1231
    */
1232
    public function setFontTTF($filePath)
1233
    {
1234
        $this->TTFFile = $filePath;
1235
        return $this;
1236
    }
1237
    
1238
    /**
1239
     * Goes through a series of text sizes to find the closest match to
1240
     * fit the text into the target width.
1241
     *
1242
     * @param string $text
1243
     * @param integer $matchWidth
1244
     * @return array
1245
     */
1246
    public function fitText($text, $matchWidth)
1247
    {
1248
        $sizes = array();
1249
        for($i=1; $i<=1000; $i=$i+0.1) {
1250
            $size = $this->calcTextSize($text, $i);
1251
            $sizes[] = $size;
1252
            if($size['width'] >= $matchWidth) {
1253
                break;
1254
            }
1255
        }
1256
    
1257
        $last = array_pop($sizes);
1258
        $prev = array_pop($sizes);
1259
    
1260
        // determine which is the closest match, and use that
1261
        $diffLast = $last['width'] - $matchWidth;
1262
        $diffPrev = $matchWidth - $prev['width'];
1263
    
1264
        if($diffLast <= $diffPrev) {
1265
            return $last;
1266
        }
1267
    
1268
        return $prev;
1269
    }
1270
    
1271
    public function calcTextSize($text, $size)
1272
    {
1273
        $this->requireTTFFont();
1274
        
1275
        $box = imagettfbbox($size, 0, $this->TTFFile, $text);
1276
    
1277
        $left = $box[0];
1278
        $right = $box[4];
1279
        $bottom = $box[1];
1280
        $top = $box[7];
1281
    
1282
        return array(
1283
            'size' => $size,
1284
            'top_left_x' => $box[6],
1285
            'top_left_y' => $box[7],
1286
            'top_right_x' => $box[4],
1287
            'top_right_y' => $box[5],
1288
            'bottom_left_x' => $box[0],
1289
            'bottom_left_y' => $box[1],
1290
            'bottom_right_x' => $box[2],
1291
            'bottom_right_y' => $box[3],
1292
            'width' => $right-$left,
1293
            'height' => $bottom-$top
1294
        );
1295
    }
1296
    
1297
    protected function requireTTFFont()
1298
    {
1299
        if(isset($this->TTFFile)) {
1300
            return;
1301
        }
1302
        
1303
	    throw new ImageHelper_Exception(
1304
            'No true type font specified',
1305
            'This functionality requires a TTF font file to be specified with the [setFontTTF] method.',
1306
            self::ERROR_NO_TRUE_TYPE_FONT_SET    
1307
        );
1308
    }
1309
    
1310
   /**
1311
	 * Retrieves the size of an image file on disk, or
1312
	 * an existing image resource.
1313
	 *
1314
	 * <pre>
1315
	 * array(
1316
	 *     0: (width),
1317
	 *     1: (height),
1318
	 *     "channels": the amount of channels
1319
	 *     "bits": bits per channel
1320
     * )     
1321
	 * </pre>
1322
	 *
1323
	 * @param string|resource $pathOrResource
1324
	 * @return ImageHelper_Size Size object, can also be accessed like the traditional array from getimagesize
1325
	 * @see ImageHelper_Size
1326
	 * @throws ImageHelper_Exception
1327
	 * @see ImageHelper::ERROR_CANNOT_GET_IMAGE_SIZE
1328
	 * @see ImageHelper::ERROR_CANNOT_READ_SVG_IMAGE
1329
	 * @see ImageHelper::ERROR_SVG_SOURCE_VIEWBOX_MISSING
1330
	 * @see ImageHelper::ERROR_SVG_VIEWBOX_INVALID
1331
	 */
1332
	public static function getImageSize($pathOrResource) : ImageHelper_Size
1333
	{
1334
	    if(is_resource($pathOrResource)) 
1335
	    {
1336
	        return new ImageHelper_Size(array(
1337
	            'width' => imagesx($pathOrResource),
1338
	            'height' => imagesy($pathOrResource),
1339
	            'channels' => 1,
1340
	            'bits' => 8
1341
	        ));
1342
	    }
1343
	    
1344
	    $type = self::getFileImageType($pathOrResource);
1345
	    
1346
	    $info = false;
1347
	    $method = 'getImageSize_'.$type;
1348
	    if(method_exists(__CLASS__, $method)) 
1349
	    {
1350
	        $info = call_user_func(array(__CLASS__, $method), $pathOrResource);
1351
	    } 
1352
	    else 
1353
	    {
1354
	        $info = getimagesize($pathOrResource);
1355
	    }
1356
	    
1357
	    if($info !== false) {
1358
	        return new ImageHelper_Size($info);
1359
	    }
1360
	    
1361
        throw new ImageHelper_Exception(
1362
            'Error opening image file',
1363
            sprintf(
1364
                'Could not get image size for image [%s]',
1365
                $pathOrResource
1366
            ),
1367
            self::ERROR_CANNOT_GET_IMAGE_SIZE
1368
        );
1369
	}
1370
	
1371
   /**
1372
    * @param string $imagePath
1373
    * @throws ImageHelper_Exception
1374
    * @return array
1375
    * 
1376
    * @todo This should return a ImageHelper_Size instance.
1377
    */
1378
	protected static function getImageSize_svg(string $imagePath) : array
1379
	{
1380
	    $xml = XMLHelper::createSimplexml();
1381
	    $xml->loadFile($imagePath);
1382
	    
1383
	    if($xml->hasErrors()) {
1384
	        throw new ImageHelper_Exception(
1385
	            'Error opening SVG image',
1386
	            sprintf(
1387
	                'The XML content of the image [%s] could not be parsed.',
1388
	                $imagePath
1389
                ),
1390
	            self::ERROR_CANNOT_READ_SVG_IMAGE
1391
            );
1392
	    }
1393
	    
1394
	    $data = $xml->toArray();
1395
	    $xml->dispose();
1396
	    unset($xml);
1397
	    
1398
	    if(!isset($data['@attributes']) || !isset($data['@attributes']['viewBox'])) {
1399
	        throw new ImageHelper_Exception(
1400
	            'SVG Image is corrupted',
1401
	            sprintf(
1402
	                'The [viewBox] attribute is missing in the XML of the image at path [%s].',
1403
	                $imagePath
1404
                ),
1405
	            self::ERROR_SVG_SOURCE_VIEWBOX_MISSING
1406
            );
1407
	    }
1408
	    
1409
	    $svgWidth = parseNumber($data['@attributes']['width'])->getNumber();
1410
	    $svgHeight = parseNumber($data['@attributes']['height'])->getNumber();
1411
	    
1412
	    $viewBox = str_replace(' ', ',', $data['@attributes']['viewBox']);
1413
	    $viewBox = explode(',', $viewBox);
1414
	    if(count($viewBox) != 4) {
1415
	        throw new ImageHelper_Exception(
1416
	            'SVG image has an invalid viewBox attribute',
1417
	            sprintf(
1418
	               'The [viewBox] attribute does not have an expected value: [%s] in path [%s].',
1419
	                $viewBox,
0 ignored issues
show
Bug introduced by
$viewBox of type string[] is incompatible with the type string expected by parameter $args of sprintf(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1419
	                /** @scrutinizer ignore-type */ $viewBox,
Loading history...
1420
	                $imagePath
1421
                ),
1422
	            self::ERROR_SVG_VIEWBOX_INVALID
1423
            );
1424
	    }
1425
	    
1426
	    $boxWidth = $viewBox[2];
1427
	    $boxHeight = $viewBox[3];
1428
	    
1429
	    // calculate the x and y units of the document: 
1430
	    // @see http://tutorials.jenkov.com/svg/svg-viewport-view-box.html#viewbox
1431
	    //
1432
	    // The viewbox combined with the width and heigt of the svg
1433
	    // allow calculating how many pixels are in one unit of the 
1434
	    // width and height of the document.
1435
        //
1436
	    $xUnits = $svgWidth / $boxWidth;
1437
	    $yUnits = $svgHeight / $boxHeight;
1438
	    
1439
	    $pxWidth = $xUnits * $svgWidth;
1440
	    $pxHeight = $yUnits * $svgHeight;
1441
	    
1442
	    return array(
1443
	        $pxWidth,
1444
	        $pxHeight,
1445
	        'bits' => 8
1446
	    );
1447
	}
1448
	
1449
	/**
1450
    * Crops the image to the specified width and height, optionally
1451
    * specifying the origin position to crop from.
1452
    * 
1453
    * @param integer $width
1454
    * @param integer $height
1455
    * @param integer $x
1456
    * @param integer $y
1457
    * @return ImageHelper
1458
    */
1459
    public function crop(int $width, int $height, int $x=0, int $y=0) : ImageHelper
1460
    {
1461
        $new = $this->createNewImage($width, $height);
1462
        
1463
        imagecopy($new, $this->newImage, 0, 0, $x, $y, $width, $height);
1464
        
1465
        $this->setNewImage($new);
1466
        
1467
        return $this;
1468
    }
1469
    
1470
    public function getWidth() : int
1471
    {
1472
        return $this->newWidth;
1473
    }
1474
    
1475
    public function getHeight() : int
1476
    {
1477
        return $this->newHeight;
1478
    }
1479
1480
   /**
1481
    * Calculates the average color value used in 
1482
    * the image. Returns an associative array
1483
    * with the red, green, blue and alpha components.
1484
    * 
1485
    * @param int $format The format in which to return the color value.
1486
    * @return array|string
1487
    */
1488
    public function calcAverageColor(int $format=self::COLORFORMAT_RGB)
1489
    {
1490
        $image = $this->duplicate();
1491
        $image->resample(1, 1);
1492
        
1493
        return $image->getColorAt(0, 0, $format);
1494
    }
1495
    
1496
    public static function rgb2hex(array $rgb) : string
1497
    {
1498
        return sprintf(
1499
            "%02x%02x%02x",
1500
            $rgb['red'],
1501
            $rgb['green'],
1502
            $rgb['blue']
1503
        );
1504
    }
1505
    
1506
    const COLORFORMAT_RGB = 1;
1507
    
1508
    const COLORFORMAT_HEX = 2;
1509
    
1510
   /**
1511
    * Retrieves the color value at the specified pixel
1512
    * coordinates in the image.
1513
    * 
1514
    * @param int $x
1515
    * @param int $y
1516
    * @param int $format The format in which to return the color value.
1517
    * @return array|string
1518
    * 
1519
    * @see ImageHelper::COLORFORMAT_RGB
1520
    * @see ImageHelper::COLORFORMAT_HEX
1521
    */
1522
    public function getColorAt(int $x, int $y, int $format=self::COLORFORMAT_RGB)
1523
    {
1524
        if($x > $this->getWidth() || $y > $this->getHeight()) 
1525
        {
1526
            throw new ImageHelper_Exception(
1527
                'Position out of bounds',
1528
                sprintf(
1529
                    'The position [%sx%s] does not exist in the image, it is [%sx%s] pixels in size.',
1530
                    $x,
1531
                    $y,
1532
                    $this->getWidth(),
1533
                    $this->getHeight()
1534
                ),
1535
                self::ERROR_POSITION_OUT_OF_BOUNDS
1536
            );
1537
        }
1538
        
1539
        $idx = imagecolorat($this->newImage, $x, $y);
1540
        $rgb = $this->getIndexedColors($this->newImage, $idx);
1541
        
1542
        if($format == self::COLORFORMAT_HEX) {
1543
            return self::rgb2hex($rgb);
1544
        }
1545
1546
        return $rgb;
1547
    }
1548
    
1549
   /**
1550
    * Converts an RGB value to its luminance equivalent.
1551
    * 
1552
    * @param array $rgb
1553
    * @return integer Integer, from 0 to 255 (0=black, 255=white)
1554
    */
1555
    public static function rgb2luma($rgb)
1556
    {
1557
        return (($rgb['red']*2)+$rgb['blue']+($rgb['green']*3))/6;
1558
    }
1559
    
1560
   /**
1561
    * Retrieves the brightness of the image, in percent.
1562
    * @return number
1563
    */
1564
    public function getBrightness()
1565
    {
1566
        $luma = self::rgb2luma($this->calcAverageColor());
0 ignored issues
show
Bug introduced by
It seems like $this->calcAverageColor() can also be of type string; however, parameter $rgb of AppUtils\ImageHelper::rgb2luma() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1566
        $luma = self::rgb2luma(/** @scrutinizer ignore-type */ $this->calcAverageColor());
Loading history...
1567
        $percent = $luma * 100 / 255;
1568
        return $percent;
1569
    }
1570
    
1571
   /**
1572
    * Retrieves an md5 hash of the source image file.
1573
    * 
1574
    * NOTE: Only works when the helper has been created
1575
    * from a file. Otherwise an exception is thrown.
1576
    * 
1577
    * @return string
1578
    * @throws ImageHelper_Exception
1579
    */
1580
    public function getHash()
1581
    {
1582
        ob_start();
1583
        imagepng($this->newImage);
1584
        $md5 = md5(ob_get_clean());
1585
        
1586
        return $md5;
1587
    }
1588
}
1589