Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.36% covered (success)
92.36%
266 / 288
75.86% covered (success)
75.86%
22 / 29
CRAP
0.00% covered (danger)
0.00%
0 / 1
Gd
92.36% covered (success)
92.36%
266 / 288
75.86% covered (success)
75.86%
22 / 29
138.65
0.00% covered (danger)
0.00%
0 / 1
 createResource
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 load
89.47% covered (success)
89.47%
17 / 19
0.00% covered (danger)
0.00%
0 / 1
10.12
 loadFromString
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
6.03
 create
89.47% covered (success)
89.47%
17 / 19
0.00% covered (danger)
0.00%
0 / 1
10.12
 createIndex
80.00% covered (success)
80.00%
12 / 15
0.00% covered (danger)
0.00%
0 / 1
8.51
 resizeToWidth
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 resizeToHeight
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 resize
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 scale
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 crop
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
2
 cropThumb
100.00% covered (success)
100.00%
26 / 26
100.00% covered (success)
100.00%
1 / 1
8
 rotate
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 flip
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 flop
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
1
 adjust
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 draw
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 effect
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 filter
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 layer
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 type
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 convert
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
13
 writeToFile
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 outputToHttp
100.00% covered (success)
100.00%
9 / 9
100.00% covered (success)
100.00%
1 / 1
6
 destroy
85.71% covered (success)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
6.10
 createColor
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
 __toString
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 copyImage
85.71% covered (success)
85.71%
6 / 7
0.00% covered (danger)
0.00%
0 / 1
2.01
 parseImage
63.64% covered (warning)
63.64%
21 / 33
0.00% covered (danger)
0.00%
0 / 1
23.42
 generateImage
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
6
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Image\Adapter;
15
16use Pop\Image\Adjust;
17use Pop\Image\Color;
18use Pop\Image\Draw;
19use Pop\Image\Effect;
20use Pop\Image\Filter;
21use Pop\Image\Layer;
22use Pop\Image\Type;
23
24/**
25 * Gd adapter class
26 *
27 * @category   Pop
28 * @package    Pop\Image
29 * @author     Nick Sagona, III <dev@nolainteractive.com>
30 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
31 * @license    http://www.popphp.org/license     New BSD License
32 * @version    3.4.0
33 */
34class Gd extends AbstractAdapter
35{
36
37    /**
38     * Create the image resource
39     *
40     * @return void
41     */
42    public function createResource()
43    {
44        $this->resource = null;
45    }
46
47    /**
48     * Load the image resource from the existing image file
49     *
50     * @param  string $name
51     * @throws Exception
52     * @return Gd
53     */
54    public function load($name = null)
55    {
56        if (null !== $name) {
57            $this->name = $name;
58        }
59
60        if ((null === $this->name) || !file_exists($this->name)) {
61            throw new Exception('Error: The image file has not been passed to the image adapter');
62        }
63
64        if (stripos($this->name, '.gif') !== false) {
65            $this->resource = imagecreatefromgif($this->name);
66        } else if (stripos($this->name, '.jp') !== false) {
67            $this->resource = imagecreatefromjpeg($this->name);
68            if (function_exists('exif_read_data')) {
69                $exif = @exif_read_data($this->name);
70                if ($exif !== false) {
71                    $this->exif = $exif;
72                }
73            }
74        } else if (stripos($this->name, '.png') !== false) {
75            $this->resource = imagecreatefrompng($this->name);
76        } else {
77            throw new Exception('Error: The image file must be a GIF, PNG or JPG');
78        }
79
80        if ($this->resource === false) {
81            throw new Exception('Error: Unable to load image resource');
82        }
83
84        $this->parseImage(getimagesize($this->name), file_get_contents($this->name));
85        return $this;
86    }
87
88    /**
89     * Load the image resource from data
90     *
91     * @param  string $data
92     * @param  string $name
93     * @throws Exception
94     * @return Gd
95     */
96    public function loadFromString($data, $name = null)
97    {
98        if (null !== $name) {
99            $this->name = $name;
100        }
101
102        $this->resource = @imagecreatefromstring($data);
103
104        if ($this->resource === false) {
105            throw new Exception('Error: Unable to load image resource');
106        }
107
108        $this->parseImage(getimagesizefromstring($data), $data);
109
110        if ((strpos($this->format, 'jp') !== false) && function_exists('exif_read_data')) {
111            $exif = @exif_read_data('data://image/jpeg;base64,' . base64_encode($data));
112            if ($exif !== false) {
113                $this->exif = $exif;
114            }
115        }
116
117        return $this;
118    }
119
120    /**
121     * Create a new image resource
122     *
123     * @param  int    $width
124     * @param  int    $height
125     * @param  string $name
126     * @throws Exception
127     * @return Gd
128     */
129    public function create($width = null, $height = null, $name = null)
130    {
131        if ((null !== $width) && (null !== $height)) {
132            $this->width  = $width;
133            $this->height = $height;
134        }
135
136        if (null !== $name) {
137            $this->name = $name;
138        }
139
140        if ((null === $this->width) && (null === $this->height)) {
141            throw new Exception('Error: You must pass a width and a height');
142        }
143
144        if (stripos($this->name, '.gif') !== false) {
145            $this->resource = imagecreate($this->width, $this->height);
146            $this->indexed  = true;
147            $this->format   = 'gif';
148        } else {
149            $this->resource = imagecreatetruecolor($this->width, $this->height);
150            if (stripos($this->name, '.png') !== false) {
151                $this->format = 'png';
152            } else if (stripos($this->name, '.jp') !== false) {
153                $this->format = 'jpg';
154            }
155        }
156
157        if ($this->resource === false) {
158            throw new Exception('Error: Unable to create image resource');
159        }
160
161        return $this;
162    }
163
164    /**
165     * Create a new image resource
166     *
167     * @param  int    $width
168     * @param  int    $height
169     * @param  string $name
170     * @throws Exception
171     * @return Gd
172     */
173    public function createIndex($width = null, $height = null, $name = null)
174    {
175        if ((null !== $width) && (null !== $height)) {
176            $this->width  = $width;
177            $this->height = $height;
178        }
179
180        if (null !== $name) {
181            $this->name = $name;
182        }
183
184        if ((null === $this->width) && (null === $this->height)) {
185            throw new Exception('Error: You must pass a width and a height');
186        }
187
188        $this->resource = imagecreate($this->width, $this->height);
189        $this->indexed  = true;
190
191        if (stripos($this->name, '.png') !== false) {
192            $this->format = 'png';
193        } else {
194            $this->format = 'gif';
195        }
196
197        if ($this->resource === false) {
198            throw new Exception('Error: Unable to create image resource');
199        }
200
201        return $this;
202    }
203
204    /**
205     * Resize the image object to the width parameter passed
206     *
207     * @param  int $w
208     * @return Gd
209     */
210    public function resizeToWidth($w)
211    {
212        $scale = $w / $this->width;
213        $h     = round($this->height * $scale);
214        $this->copyImage($w, $h);
215
216        return $this;
217    }
218
219    /**
220     * Resize the image object to the height parameter passed
221     *
222     * @param  int $h
223     * @return Gd
224     */
225    public function resizeToHeight($h)
226    {
227        $scale = $h / $this->height;
228        $w     = round($this->width * $scale);
229        $this->copyImage($w, $h);
230
231        return $this;
232    }
233
234    /**
235     * Resize the image object, allowing for the largest dimension
236     * to be scaled to the value of the $px argument.
237     *
238     * @param  int $px
239     * @return Gd
240     */
241    public function resize($px)
242    {
243        $scale = ($this->width > $this->height) ? ($px / $this->width) : ($px / $this->height);
244        $w     = round($this->width * $scale);
245        $h     = round($this->height * $scale);
246        $this->copyImage($w, $h);
247
248        return $this;
249    }
250
251    /**
252     * Scale the image object, allowing for the dimensions to be scaled
253     * proportionally to the value of the $scale argument.
254     *
255     * @param  float $scale
256     * @return Gd
257     */
258    public function scale($scale)
259    {
260        $w = round($this->width * $scale);
261        $h = round($this->height * $scale);
262        $this->copyImage($w, $h);
263
264        return $this;
265    }
266
267    /**
268     * Crop the image object to a image whose dimensions are based on the
269     * value of the $wid and $hgt argument. The optional $x and $y arguments
270     * allow for the adjustment of the crop to select a certain area of the
271     * image to be cropped.
272     *
273     * @param  int $w
274     * @param  int $h
275     * @param  int $x
276     * @param  int $y
277     * @return Gd
278     */
279    public function crop($w, $h, $x = 0, $y = 0)
280    {
281        $result = imagecreatetruecolor($w, $h);
282        imagecopyresampled(
283            $result, $this->resource, 0, 0, $x, $y, $this->width, $this->height, $this->width, $this->height
284        );
285
286        if ($this->indexed) {
287            imagetruecolortopalette($result, false, 255);
288        }
289
290        $this->resource = $result;
291        $this->width    = imagesx($this->resource);
292        $this->height   = imagesy($this->resource);
293
294        return $this;
295    }
296
297    /**
298     * Crop the image object to a square image whose dimensions are based on the
299     * value of the $px argument. The optional $offset argument allows for the
300     * adjustment of the crop to select a certain area of the image to be cropped.
301     *
302     * @param  int $px
303     * @param  int $offset
304     * @return Gd
305     */
306    public function cropThumb($px, $offset = null)
307    {
308        $xOffset = 0;
309        $yOffset = 0;
310        $scale   = ($this->width > $this->height) ? ($px / $this->height) : ($px / $this->width);
311        $w       = round($this->width * $scale);
312        $h       = round($this->height * $scale);
313
314        if (null !== $offset) {
315            if ($this->width > $this->height) {
316                $xOffset = $offset;
317                $yOffset = 0;
318            } else if ($this->width < $this->height) {
319                $xOffset = 0;
320                $yOffset = $offset;
321            }
322        } else {
323            if ($this->width > $this->height) {
324                $xOffset = round(($this->width - $this->height) / 2);
325                $yOffset = 0;
326            } else if ($this->width < $this->height) {
327                $xOffset = 0;
328                $yOffset = round(($this->height - $this->width) / 2);
329            }
330        }
331
332        $result = imagecreatetruecolor($px, $px);
333        imagecopyresampled($result, $this->resource, 0, 0, $xOffset, $yOffset, $w, $h, $this->width, $this->height);
334
335        if ($this->indexed) {
336            imagetruecolortopalette($result, false, 255);
337        }
338
339        $this->resource = $result;
340        $this->width    = imagesx($this->resource);
341        $this->height   = imagesy($this->resource);
342
343        return $this;
344    }
345
346    /**
347     * Rotate the image object
348     *
349     * @param  int                  $degrees
350     * @param  Color\ColorInterface $bgColor
351     * @param  int                  $alpha
352     * @return Gd
353     */
354    public function rotate($degrees, Color\ColorInterface $bgColor = null, $alpha = null)
355    {
356        $this->resource = imagerotate($this->resource, $degrees, $this->createColor($bgColor, $alpha));
357        $this->width    = imagesx($this->resource);
358        $this->height   = imagesy($this->resource);
359        return $this;
360    }
361
362    /**
363     * Method to flip the image over the x-axis
364     *
365     * @return Gd
366     */
367    public function flip()
368    {
369        $curWidth     = $this->width;
370        $curHeight    = $this->height;
371        $srcX         = 0;
372        $srcY         = $this->height - 1; // Compensate for a 1-pixel space on the flipped image
373        $this->height = 0 - $this->height;
374
375        $this->copyImage($curWidth, $curHeight, $srcX , $srcY);
376        $this->height = abs($this->height);
377
378        return $this;
379    }
380
381    /**
382     * Method to flip the image over the y-axis
383     *
384     * @return Gd
385     */
386    public function flop()
387    {
388        $curWidth    = $this->width;
389        $curHeight   = $this->height;
390        $srcX        = $this->width - 1; // Compensate for a 1-pixel space on the flipped image
391        $srcY        = 0;
392        $this->width = 0 - $this->width;
393
394        $this->copyImage($curWidth, $curHeight, $srcX , $srcY);
395        $this->width = abs($this->width);
396
397        return $this;
398    }
399
400    /**
401     * Get the image adjust object
402     *
403     * @return Adjust\AdjustInterface
404     */
405    public function adjust()
406    {
407        if (null === $this->adjust) {
408            $this->adjust = new Adjust\Gd($this);
409        }
410        if (null === $this->adjust->getImage()) {
411            $this->adjust->setImage($this);
412        }
413
414        return $this->adjust;
415    }
416
417    /**
418     * Get the image draw object
419     *
420     * @return Draw\DrawInterface
421     */
422    public function draw()
423    {
424        if (null === $this->draw) {
425            $this->draw = new Draw\Gd($this);
426        }
427        if (null === $this->draw->getImage()) {
428            $this->draw->setImage($this);
429        }
430        return $this->draw;
431    }
432
433    /**
434     * Get the image effect object
435     *
436     * @return Effect\EffectInterface
437     */
438    public function effect()
439    {
440        if (null === $this->effect) {
441            $this->effect = new Effect\Gd($this);
442        }
443        if (null === $this->effect->getImage()) {
444            $this->effect->setImage($this);
445        }
446        return $this->effect;
447    }
448
449    /**
450     * Get the image filter object
451     *
452     * @return Filter\FilterInterface
453     */
454    public function filter()
455    {
456        if (null === $this->filter) {
457            $this->filter = new Filter\Gd($this);
458        }
459        if (null === $this->filter->getImage()) {
460            $this->filter->setImage($this);
461        }
462        return $this->filter;
463    }
464
465    /**
466     * Get the image layer object
467     *
468     * @return Layer\LayerInterface
469     */
470    public function layer()
471    {
472        if (null === $this->layer) {
473            $this->layer = new Layer\Gd($this);
474        }
475        if (null === $this->layer->getImage()) {
476            $this->layer->setImage($this);
477        }
478        return $this->layer;
479    }
480
481    /**
482     * Get the image type object
483     *
484     * @return Type\TypeInterface
485     */
486    public function type()
487    {
488        if (null === $this->type) {
489            $this->type = new Type\Gd($this);
490        }
491        if (null === $this->type->getImage()) {
492            $this->type->setImage($this);
493        }
494        return $this->type;
495    }
496
497    /**
498     * Convert the image object to another format
499     *
500     * @param  string $to
501     * @throws Exception
502     * @return Gd
503     */
504    public function convert($to)
505    {
506        $to = strtolower($to);
507
508        if (($to != 'jpg') && ($to != 'jpeg') && ($to != 'gif') && ($to != 'png')) {
509            throw new Exception('Error: The image type must be a GIF, PNG or JPG');
510        }
511
512        if (null === $this->resource) {
513            throw new Exception('Error: An image resource has not been created or loaded');
514        }
515
516        switch ($to) {
517            case 'jpg':
518            case 'jpeg':
519                $this->format  = 'jpg';
520                $this->indexed = false;
521                break;
522            case 'png':
523                $this->format = 'png';
524                break;
525            case 'gif':
526                $this->format  = 'gif';
527                $this->indexed = true;
528                break;
529        }
530
531        if ((null !== $this->name) && (strpos($this->name, '.') !== false)) {
532            $this->name = substr($this->name, 0, (strrpos($this->name, '.') + 1)) . $this->format;
533        }
534
535        $result = imagecreatetruecolor($this->width, $this->height);
536        imagecopyresampled(
537            $result, $this->resource, 0, 0, 0, 0, $this->width, $this->height, $this->width, $this->height
538        );
539
540        if ($this->indexed) {
541            imagetruecolortopalette($result, false, 255);
542        }
543
544        $this->resource = $result;
545        $this->width    = imagesx($this->resource);
546        $this->height   = imagesy($this->resource);
547
548        return $this;
549    }
550
551    /**
552     * Write the image object to a file on disk
553     *
554     * @param  string $to
555     * @param  int    $quality
556     * @throws Exception
557     * @return void
558     */
559    public function writeToFile($to = null, $quality = 100)
560    {
561        if (null === $this->resource) {
562            throw new Exception('Error: An image resource has not been created or loaded');
563        }
564
565        if (((int)$quality < 0) || ((int)$quality > 100)) {
566            throw new \OutOfRangeException('Error: The quality parameter must be between 0 and 100');
567        }
568
569        $this->format = strtolower($this->format);
570
571        if (null === $to) {
572            $to = (null !== $this->name) ? basename($this->name) : 'pop-image.' . $this->format;
573        } else {
574            $this->name = $to;
575        }
576
577        $this->generateImage((int)$quality, $to);
578    }
579
580    /**
581     * Output the image object directly to HTTP
582     *
583     * @param  int     $quality
584     * @param  string  $to
585     * @param  boolean $download
586     * @param  boolean $sendHeaders
587     * @param  array   $headers
588     * @throws Exception
589     * @return void
590     */
591    public function outputToHttp($quality = 100, $to = null, $download = false, $sendHeaders = true, array $headers = [])
592    {
593        if (null === $this->resource) {
594            throw new Exception('Error: An image resource has not been created or loaded');
595        }
596
597        if (((int)$quality < 0) || ((int)$quality > 100)) {
598            throw new \OutOfRangeException('Error: The quality parameter must be between 0 and 100');
599        }
600
601        $this->format = strtolower($this->format);
602
603        if (null === $to) {
604            $to = (null !== $this->name) ? basename($this->name) : 'pop-image.' . $this->format;
605        }
606
607        $this->sendHeaders($to, $download, $headers);
608        $this->generateImage((int)$quality);
609    }
610
611    /**
612     * Destroy the image object and the related image file directly
613     *
614     * @param  boolean $delete
615     * @return void
616     */
617    public function destroy($delete = false)
618    {
619        // Destroy the image resource.
620        if (null !== $this->resource) {
621            if (!is_string($this->resource) && is_resource($this->resource)) {
622                imagedestroy($this->resource);
623            }
624        }
625
626        $this->resource = null;
627        clearstatcache();
628
629        // If the $delete flag is passed, delete the image file.
630        if (($delete) && file_exists($this->name)) {
631            unlink($this->name);
632        }
633    }
634
635    /**
636     * Create and return a color
637     *
638     * @param  Color\ColorInterface $color
639     * @param  int                  $alpha
640     * @throws Exception
641     * @return mixed
642     */
643    public function createColor(Color\ColorInterface $color = null, $alpha = null)
644    {
645        if (null === $color) {
646            $color = new Color\Rgb(0, 0, 0);
647        }
648
649        if (!($color instanceof Color\Rgb)) {
650            $color = $color->toRgb();
651        }
652
653        $r = $color->getR();
654        $g = $color->getG();
655        $b = $color->getB();
656
657        if (null !== $alpha) {
658            if (((int)$alpha < 0) || ((int)$alpha > 127)) {
659                throw new \OutOfRangeException('Error: The alpha parameter must be between 0 and 127');
660            }
661            return imagecolorallocatealpha($this->resource, (int)$r, (int)$g, (int)$b, (int)$alpha);
662        } else {
663            return imagecolorallocate($this->resource, (int)$r, (int)$g, (int)$b);
664        }
665    }
666
667    /**
668     * Output the image
669     *
670     * @return string
671     */
672    public function __toString()
673    {
674        $this->sendHeaders();
675        $this->generateImage(100);
676        return '';
677    }
678
679    /**
680     * Copy the image resource to the image output resource with the set parameters
681     *
682     * @param  int $w
683     * @param  int $h
684     * @param  int $x
685     * @param  int $y
686     * @return void
687     */
688    protected function copyImage($w, $h, $x = 0, $y = 0)
689    {
690        $result = imagecreatetruecolor($w, $h);
691        imagecopyresampled($result, $this->resource, 0, 0, $x, $y, $w, $h, $this->width, $this->height);
692
693        if ($this->indexed) {
694            imagetruecolortopalette($result, false, 255);
695        }
696
697        $this->resource = $result;
698        $this->width    = imagesx($this->resource);
699        $this->height   = imagesy($this->resource);
700    }
701
702    /**
703     * Parse the image data
704     *
705     * @param  array  $size
706     * @param  string $data
707     * @return void
708     */
709    protected function parseImage(array $size, $data)
710    {
711        $this->width  = $size[0];
712        $this->height = $size[1];
713
714        if ((($size[2] == IMAGETYPE_JPEG) || ($size[2] == IMAGETYPE_JPEG2000)) && isset($size['channels'])) {
715            switch ($size['channels']) {
716                case 1:
717                    $this->colorspace = self::IMAGE_GRAY;
718                    break;
719                case 3:
720                    $this->colorspace = self::IMAGE_RGB;
721                    break;
722                case 4:
723                    $this->colorspace = self::IMAGE_CMYK;
724                    break;
725            }
726            $this->format = 'jpg';
727        } else if ($size[2] == IMAGETYPE_PNG) {
728            switch (ord($data[25])) {
729                case 0:
730                case 4:
731                    $this->colorspace = self::IMAGE_GRAY;
732                    break;
733                case 2:
734                case 6:
735                    $this->colorspace = self::IMAGE_RGB;
736                    break;
737                case 3:
738                    $this->colorspace = self::IMAGE_RGB;
739                    $this->indexed    = true;
740                    break;
741            }
742            $this->format = 'png';
743        } else if ($size[2] == IMAGETYPE_GIF) {
744            $this->colorspace = self::IMAGE_RGB;
745            $this->indexed    = true;
746            $this->format     = 'gif';
747        }
748    }
749
750    /**
751     * Parse the image data
752     *
753     * @param  int    $quality
754     * @param  string $to
755     * @return void
756     */
757    protected function generateImage($quality, $to = null)
758    {
759        switch ($this->format) {
760            case 'jpg':
761            case 'jpeg':
762                imagejpeg($this->resource, $to, (int)$quality);
763                break;
764            case 'png':
765                $quality = ($quality < 10) ? 9 : 10 - round(($quality / 10), PHP_ROUND_HALF_DOWN);
766                imagepng($this->resource, $to, (int)$quality);
767                break;
768            case 'gif':
769                imagegif($this->resource, $to);
770                break;
771        }
772    }
773
774}