Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
92.01% covered (success)
92.01%
265 / 288
73.33% covered (success)
73.33%
22 / 30
CRAP
0.00% covered (danger)
0.00%
0 / 1
Gd
92.01% covered (success)
92.01%
265 / 288
73.33% covered (success)
73.33%
22 / 30
139.74
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%
10 / 10
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%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 draw
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 effect
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 filter
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 layer
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 type
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 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%
11 / 11
100.00% covered (success)
100.00%
1 / 1
7
 outputToRawString
80.00% covered (success)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 outputToHttp
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
8
 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%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 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-2024 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\Color\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-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
31 * @license    http://www.popphp.org/license     New BSD License
32 * @version    4.0.0
33 */
34class Gd extends AbstractAdapter
35{
36
37    /**
38     * Create the image resource
39     *
40     * @return void
41     */
42    public function createResource(): void
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(?string $name = null): Gd
55    {
56        if ($name !== null) {
57            $this->name = $name;
58        }
59
60        if (($this->name === null) || !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(string $data, ?string $name = null): Gd
97    {
98        if ($name !== null) {
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 ((str_contains($this->format, 'jp')) && 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(?int $width = null, ?int $height = null, ?string $name = null): Gd
130    {
131        if (($width !== null) && ($height !== null)) {
132            $this->width  = $width;
133            $this->height = $height;
134        }
135
136        if ($name !== null) {
137            $this->name = $name;
138        }
139
140        if (($this->width === null) && ($this->height === null)) {
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(?int $width = null, ?int $height = null, ?string $name = null): Gd
174    {
175        if (($width !== null) && ($height !== null)) {
176            $this->width  = $width;
177            $this->height = $height;
178        }
179
180        if ($name !== null) {
181            $this->name = $name;
182        }
183
184        if (($this->width === null) && ($this->height === null)) {
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(int $w): Gd
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(int $h): Gd
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(int $px): Gd
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(float $scale): Gd
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(int $w, int $h, int $x = 0, int $y = 0): Gd
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(int $px, ?int $offset = null): Gd
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 ($offset !== null) {
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(int $degrees, ?Color\ColorInterface $bgColor = null, int $alpha = null): Gd
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(): Gd
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(): Gd
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(): Adjust\AdjustInterface
406    {
407        if ($this->adjust === null) {
408            $this->adjust = new Adjust\Gd($this);
409        }
410        return $this->adjust;
411    }
412
413    /**
414     * Get the image draw object
415     *
416     * @return Draw\DrawInterface
417     */
418    public function draw(): Draw\DrawInterface
419    {
420        if ($this->draw === null) {
421            $this->draw = new Draw\Gd($this);
422        }
423        return $this->draw;
424    }
425
426    /**
427     * Get the image effect object
428     *
429     * @return Effect\EffectInterface
430     */
431    public function effect(): Effect\EffectInterface
432    {
433        if ($this->effect === null) {
434            $this->effect = new Effect\Gd($this);
435        }
436        return $this->effect;
437    }
438
439    /**
440     * Get the image filter object
441     *
442     * @return Filter\FilterInterface
443     */
444    public function filter(): Filter\FilterInterface
445    {
446        if ($this->filter === null) {
447            $this->filter = new Filter\Gd($this);
448        }
449        return $this->filter;
450    }
451
452    /**
453     * Get the image layer object
454     *
455     * @return Layer\LayerInterface
456     */
457    public function layer(): Layer\LayerInterface
458    {
459        if ($this->layer === null) {
460            $this->layer = new Layer\Gd($this);
461        }
462        return $this->layer;
463    }
464
465    /**
466     * Get the image type object
467     *
468     * @return Type\TypeInterface
469     */
470    public function type(): Type\TypeInterface
471    {
472        if ($this->type === null) {
473            $this->type = new Type\Gd($this);
474        }
475        return $this->type;
476    }
477
478    /**
479     * Convert the image object to another format
480     *
481     * @param  string $type
482     * @throws Exception
483     * @return Gd
484     */
485    public function convert(string $type): Gd
486    {
487        $type = strtolower($type);
488
489        if (($type != 'jpg') && ($type != 'jpeg') && ($type != 'gif') && ($type != 'png')) {
490            throw new Exception('Error: The image type must be a GIF, PNG or JPG');
491        }
492
493        if ($this->resource === null) {
494            throw new Exception('Error: An image resource has not been created or loaded');
495        }
496
497        switch ($type) {
498            case 'jpg':
499            case 'jpeg':
500                $this->format  = 'jpg';
501                $this->indexed = false;
502                break;
503            case 'png':
504                $this->format = 'png';
505                break;
506            case 'gif':
507                $this->format  = 'gif';
508                $this->indexed = true;
509                break;
510        }
511
512        if (($this->name !== null) && (strpos($this->name, '.') !== false)) {
513            $this->name = substr($this->name, 0, (strrpos($this->name, '.') + 1)) . $this->format;
514        }
515
516        $result = imagecreatetruecolor($this->width, $this->height);
517        imagecopyresampled(
518            $result, $this->resource, 0, 0, 0, 0, $this->width, $this->height, $this->width, $this->height
519        );
520
521        if ($this->indexed) {
522            imagetruecolortopalette($result, false, 255);
523        }
524
525        $this->resource = $result;
526        $this->width    = imagesx($this->resource);
527        $this->height   = imagesy($this->resource);
528
529        return $this;
530    }
531
532    /**
533     * Write the image object to a file on disk
534     *
535     * @param  ?string $to
536     * @param  ?int    $quality
537     * @throws Exception
538     * @return void
539     */
540    public function writeToFile(?string $to = null, ?int $quality = null): void
541    {
542        if ($this->resource === null) {
543            throw new Exception('Error: An image resource has not been created or loaded');
544        }
545
546        if ($quality !== null) {
547            $this->setQuality($quality);
548        }
549
550        if (((int)$this->quality < 0) || ((int)$this->quality > 100)) {
551            throw new \OutOfRangeException('Error: The quality parameter must be between 0 and 100');
552        }
553
554        $this->format = strtolower($this->format);
555
556        if ($to === null) {
557            $to = ($this->name !== null) ? basename($this->name) : 'pop-image.' . $this->format;
558        } else {
559            $this->name = $to;
560        }
561
562        $this->generateImage((int)$this->quality, $to);
563    }
564
565    /**
566     * Output the image object to a raw string
567     *
568     * @param  int $quality
569     * @throws Exception
570     * @return string|false
571     */
572    public function outputToRawString(int $quality = 100): string|false
573    {
574        if ($this->resource === null) {
575            throw new Exception('Error: An image resource has not been created or loaded');
576        }
577
578        ob_start();
579        $this->generateImage($quality);
580        return ob_get_clean();
581    }
582
583    /**
584     * Output the image object directly to HTTP
585     *
586     * @param  ?int    $quality
587     * @param  ?string $to
588     * @param  bool    $download
589     * @param  bool    $sendHeaders
590     * @param  array   $headers
591     * @throws Exception
592     * @return void
593     */
594    public function outputToHttp(
595        ?int $quality = null, ?string $to = null, bool $download = false, bool $sendHeaders = true, array $headers = []
596    ): void
597    {
598        if ($this->resource === null) {
599            throw new Exception('Error: An image resource has not been created or loaded');
600        }
601
602        if ($quality !== null) {
603            $this->setQuality($quality);
604        }
605
606        if (((int)$this->quality < 0) || ((int)$this->quality > 100)) {
607            throw new \OutOfRangeException('Error: The quality parameter must be between 0 and 100');
608        }
609
610        $this->format = strtolower($this->format);
611
612        if ($to === null) {
613            $to = ($this->name !== null) ? basename($this->name) : 'pop-image.' . $this->format;
614        }
615
616        if ($sendHeaders) {
617            $this->sendHeaders($to, $download, $headers);
618        }
619        $this->generateImage((int)$this->quality);
620    }
621
622    /**
623     * Destroy the image object and the related image file directly
624     *
625     * @param  bool $delete
626     * @return void
627     */
628    public function destroy(bool $delete = false): void
629    {
630        // Destroy the image resource.
631        if ($this->resource !== null) {
632            if (!is_string($this->resource) && is_resource($this->resource)) {
633                imagedestroy($this->resource);
634            }
635        }
636
637        $this->resource = null;
638        clearstatcache();
639
640        // If the $delete flag is passed, delete the image file.
641        if (($delete) && file_exists($this->name)) {
642            unlink($this->name);
643        }
644    }
645
646    /**
647     * Create and return a color
648     *
649     * @param  ?Color\ColorInterface $color
650     * @param  ?int                  $alpha
651     * @throws Exception
652     * @return mixed
653     */
654    public function createColor(?Color\ColorInterface $color = null, ?int $alpha = null): mixed
655    {
656        if ($color === null) {
657            $color = new Color\Rgb(0, 0, 0);
658        }
659
660        if (!($color instanceof Color\Rgb)) {
661            $color = $color->toRgb();
662        }
663
664        $r = $color->getR();
665        $g = $color->getG();
666        $b = $color->getB();
667
668        if ($alpha !== null) {
669            if (((int)$alpha < 0) || ((int)$alpha > 127)) {
670                throw new \OutOfRangeException('Error: The alpha parameter must be between 0 and 127');
671            }
672            return imagecolorallocatealpha($this->resource, (int)$r, (int)$g, (int)$b, (int)$alpha);
673        } else {
674            return imagecolorallocate($this->resource, (int)$r, (int)$g, (int)$b);
675        }
676    }
677
678    /**
679     * Output the image
680     *
681     * @return string
682     */
683    public function __toString(): string
684    {
685        $quality = ($this->quality !== null) ? $this->quality : 100;
686        $this->sendHeaders();
687        $this->generateImage($quality);
688        return '';
689    }
690
691    /**
692     * Copy the image resource to the image output resource with the set parameters
693     *
694     * @param  int $w
695     * @param  int $h
696     * @param  int $x
697     * @param  int $y
698     * @return void
699     */
700    protected function copyImage(int $w, int $h, int $x = 0, int $y = 0): void
701    {
702        $result = imagecreatetruecolor($w, $h);
703        imagecopyresampled($result, $this->resource, 0, 0, $x, $y, $w, $h, $this->width, $this->height);
704
705        if ($this->indexed) {
706            imagetruecolortopalette($result, false, 255);
707        }
708
709        $this->resource = $result;
710        $this->width    = imagesx($this->resource);
711        $this->height   = imagesy($this->resource);
712    }
713
714    /**
715     * Parse the image data
716     *
717     * @param  array  $size
718     * @param  string $data
719     * @return void
720     */
721    protected function parseImage(array $size, string $data): void
722    {
723        $this->width  = $size[0];
724        $this->height = $size[1];
725
726        if ((($size[2] == IMAGETYPE_JPEG) || ($size[2] == IMAGETYPE_JPEG2000)) && isset($size['channels'])) {
727            switch ($size['channels']) {
728                case 1:
729                    $this->colorspace = self::IMAGE_GRAY;
730                    break;
731                case 3:
732                    $this->colorspace = self::IMAGE_RGB;
733                    break;
734                case 4:
735                    $this->colorspace = self::IMAGE_CMYK;
736                    break;
737            }
738            $this->format = 'jpg';
739        } else if ($size[2] == IMAGETYPE_PNG) {
740            switch (ord($data[25])) {
741                case 0:
742                case 4:
743                    $this->colorspace = self::IMAGE_GRAY;
744                    break;
745                case 2:
746                case 6:
747                    $this->colorspace = self::IMAGE_RGB;
748                    break;
749                case 3:
750                    $this->colorspace = self::IMAGE_RGB;
751                    $this->indexed    = true;
752                    break;
753            }
754            $this->format = 'png';
755        } else if ($size[2] == IMAGETYPE_GIF) {
756            $this->colorspace = self::IMAGE_RGB;
757            $this->indexed    = true;
758            $this->format     = 'gif';
759        }
760    }
761
762    /**
763     * Parse the image data
764     *
765     * @param  int     $quality
766     * @param  ?string $to
767     * @return void
768     */
769    protected function generateImage(int $quality, ?string $to = null): void
770    {
771        switch ($this->format) {
772            case 'jpg':
773            case 'jpeg':
774                imagejpeg($this->resource, $to, (int)$quality);
775                break;
776            case 'png':
777                $quality = ($quality < 10) ? 9 : 10 - round(($quality / 10), PHP_ROUND_HALF_DOWN);
778                imagepng($this->resource, $to, (int)$quality);
779                break;
780            case 'gif':
781                imagegif($this->resource, $to);
782                break;
783        }
784    }
785
786}