Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
91.32% covered (success)
91.32%
242 / 265
76.74% covered (success)
76.74%
33 / 43
CRAP
0.00% covered (danger)
0.00%
0 / 1
Imagick
91.32% covered (success)
91.32%
242 / 265
76.74% covered (success)
76.74%
33 / 43
152.81
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
81.48% covered (success)
81.48%
22 / 27
0.00% covered (danger)
0.00%
0 / 1
17.63
 loadFromString
65.00% covered (warning)
65.00%
13 / 20
0.00% covered (danger)
0.00%
0 / 1
16.19
 create
92.86% covered (success)
92.86%
13 / 14
0.00% covered (danger)
0.00%
0 / 1
7.02
 createIndex
93.75% covered (success)
93.75%
15 / 16
0.00% covered (danger)
0.00%
0 / 1
7.01
 addImage
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 hasImages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getImages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 rebuildImages
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setResolution
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 setImageColorspace
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setCompression
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setImageFilter
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setImageBlur
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getNumberOfImages
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCompression
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getImageFilter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getImageBlur
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 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%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 scale
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 resizeImage
72.73% covered (success)
72.73%
8 / 11
0.00% covered (danger)
0.00%
0 / 1
5.51
 crop
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 cropThumb
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
6
 cropImage
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
3.01
 cropThumbnailImage
90.00% covered (success)
90.00%
9 / 10
0.00% covered (danger)
0.00%
0 / 1
3.01
 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%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 flop
100.00% covered (success)
100.00%
2 / 2
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
89.47% covered (success)
89.47%
17 / 19
0.00% covered (danger)
0.00%
0 / 1
9.09
 writeToFile
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
10.05
 outputToRawString
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 outputToHttp
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
12
 destroy
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 createColor
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
4
 __toString
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
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;
23use ImagickPixel;
24use ImagickPixelException;
25use ImagickException;
26
27/**
28 * Imagick adapter class
29 *
30 * @category   Pop
31 * @package    Pop\Image
32 * @author     Nick Sagona, III <dev@nolainteractive.com>
33 * @copyright  Copyright (c) 2009-2024 NOLA Interactive, LLC. (http://www.nolainteractive.com)
34 * @license    http://www.popphp.org/license     New BSD License
35 * @version    4.0.0
36 */
37class Imagick extends AbstractAdapter
38{
39
40    /**
41     * Image compression
42     * @var ?int
43     */
44    protected ?int $compression = null;
45
46    /**
47     * Image filter
48     * @var int
49     */
50    protected int $imageFilter = \Imagick::FILTER_LANCZOS;
51
52    /**
53     * Image blur
54     * @var float
55     */
56    protected float $imageBlur = 1;
57
58    /**
59     * Create the image resource
60     *
61     * @return void
62     */
63    public function createResource(): void
64    {
65        $this->resource = new \Imagick();
66    }
67
68    /**
69     * Load the image resource from the existing image file
70     *
71     * @param  ?string $name
72     * @throws Exception|ImagickException
73     * @return Imagick
74     */
75    public function load(?string $name = null): Imagick
76    {
77        $filename = null;
78        if ($name !== null) {
79            $filename = ((str_contains($name, '[')) && (str_contains($name, ']'))) ?
80                substr($name, 0, strpos($name, '[')) : $name;
81            $this->name = $name;
82        }
83
84        if (($this->name === null) || (($filename !== null) && !file_exists($filename))) {
85            throw new Exception('Error: The image file has not been passed to the image adapter');
86        }
87
88        if ($this->resource !== null) {
89            $this->resource->readImage($this->name);
90        } else {
91            $this->resource = new \Imagick($this->name);
92        }
93
94        $this->width  = $this->resource->getImageWidth();
95        $this->height = $this->resource->getImageHeight();
96
97        switch ($this->resource->getImageColorspace()) {
98            case \Imagick::COLORSPACE_GRAY:
99                $this->colorspace = self::IMAGE_GRAY;
100                break;
101            case \Imagick::COLORSPACE_RGB:
102            case \Imagick::COLORSPACE_SRGB:
103                $this->colorspace = self::IMAGE_RGB;
104                break;
105            case \Imagick::COLORSPACE_CMYK:
106                $this->colorspace = self::IMAGE_CMYK;
107                break;
108        }
109
110        $this->format = strtolower($this->resource->getImageFormat());
111        if ($this->resource->getImageColors() < 256) {
112            $this->indexed = true;
113        }
114
115        if ((str_contains($this->format, 'jp')) && function_exists('exif_read_data')) {
116            $exif = @exif_read_data($this->name);
117            if ($exif !== false) {
118                $this->exif = $exif;
119            }
120        }
121
122        return $this;
123    }
124
125    /**
126     * Load the image resource from data
127     *
128     * @param  string  $data
129     * @param  ?string $name
130     * @throws ImagickException
131     * @return Imagick
132     */
133    public function loadFromString(string $data, ?string $name = null): Imagick
134    {
135        if ($this->resource === null) {
136            $this->resource = new \Imagick();
137        }
138        $this->resource->readImageBlob($data);
139
140        if ($name !== null) {
141            $this->name = $name;
142        }
143
144        switch ($this->resource->getImageColorspace()) {
145            case \Imagick::COLORSPACE_GRAY:
146                $this->colorspace = self::IMAGE_GRAY;
147                break;
148            case \Imagick::COLORSPACE_RGB:
149            case \Imagick::COLORSPACE_SRGB:
150                $this->colorspace = self::IMAGE_RGB;
151                break;
152            case \Imagick::COLORSPACE_CMYK:
153                $this->colorspace = self::IMAGE_CMYK;
154                break;
155        }
156
157        $this->format = strtolower($this->resource->getImageFormat());
158        if ($this->resource->getImageColors() < 256) {
159            $this->indexed = true;
160        }
161
162        if ((str_contains($this->format, 'jp')) && function_exists('exif_read_data')) {
163            $exif = @exif_read_data('data://image/jpeg;base64,' . base64_encode($data));
164            if ($exif !== false) {
165                $this->exif = $exif;
166            }
167        }
168
169        return $this;
170    }
171
172    /**
173     * Create a new image resource
174     *
175     * @param  ?int    $width
176     * @param  ?int    $height
177     * @param  ?string $name
178     * @return Imagick
179     */
180    public function create(?int $width = null, ?int $height = null, ?string $name = null): Imagick
181    {
182        if (($width !== null) && ($height !== null)) {
183            $this->width  = $width;
184            $this->height = $height;
185        }
186
187        if ($name !== null) {
188            $this->name = $name;
189        }
190
191        if ($this->resource === null) {
192            $this->resource = new \Imagick();
193        }
194
195        $this->resource->newImage($this->width, $this->height, new ImagickPixel('white'));
196
197        if ($this->name !== null) {
198            $extension = strtolower(substr($this->name, (strrpos($this->name, '.') + 1)));
199            if (!empty($extension)) {
200                $this->resource->setImageFormat($extension);
201                $this->format = $extension;
202            }
203        }
204
205        return $this;
206    }
207
208    /**
209     * Create a new image resource
210     *
211     * @param  ?int    $width
212     * @param  ?int    $height
213     * @param  ?string $name
214     * @throws ImagickException
215     * @return Imagick
216     */
217    public function createIndex(?int $width = null, ?int $height = null, ?string $name = null): Imagick
218    {
219        if (($width !== null) && ($height !== null)) {
220            $this->width  = $width;
221            $this->height = $height;
222        }
223
224        if ($name !== null) {
225            $this->name = $name;
226        }
227
228        if ($this->resource === null) {
229            $this->resource = new \Imagick();
230        }
231
232        $this->resource->newImage($this->width, $this->height, new ImagickPixel('white'));
233
234        if ($this->name !== null) {
235            $extension = strtolower(substr($this->name, (strrpos($this->name, '.') + 1)));
236            if (!empty($extension)) {
237                $this->resource->setImageFormat($extension);
238                $this->format = $extension;
239            }
240        }
241
242        $this->resource->setImageType(\Imagick::IMGTYPE_PALETTE);
243        $this->indexed = true;
244
245        return $this;
246    }
247
248    /**
249     * Add image to the image resource
250     *
251     * @param  mixed $image
252     * @param  ?int  $delay
253     * @throws ImagickException
254     * @return Imagick
255     */
256    public function addImage(mixed $image, ?int $delay = null): Imagick
257    {
258        if (!($image instanceof \Imagick)) {
259            $image = new \Imagick($image);
260        }
261        if ($delay !== null) {
262            $image->setImageDelay($delay);
263        }
264        $this->resource->addImage($image);
265        return $this;
266    }
267
268    /**
269     * Does image have images
270     *
271     * @return bool
272     */
273    public function hasImages(): bool
274    {
275        return ($this->resource->getNumberImages() > 0);
276    }
277
278    /**
279     * Get images
280     *
281     * @return \Imagick
282     */
283    public function getImages(): \Imagick
284    {
285        return $this->resource->coalesceImages();
286    }
287
288    /**
289     * Get images
290     *
291     * @param  \Imagick $images
292     * @throws ImagickException
293     * @return Imagick
294     */
295    public function rebuildImages(\Imagick $images): Imagick
296    {
297        $this->resource = $images->deconstructImages();
298        return $this;
299    }
300
301    /**
302     * Set the image resolution
303     *
304     * @param  int  $x
305     * @param  ?int $y
306     * @return Imagick
307     */
308    public function setResolution(int $x, ?int $y = null): Imagick
309    {
310        if ($y === null) {
311            $y = $x;
312        }
313        $this->resource->setResolution($x, $y);
314        return $this;
315    }
316
317    /**
318     * Set the image colorspace
319     *
320     * @param  int $colorspace
321     * @return Imagick
322     */
323    public function setImageColorspace(int $colorspace): Imagick
324    {
325        $this->resource->setImageColorspace($colorspace);
326        return $this;
327    }
328
329    /**
330     * Set the image compression
331     *
332     * @param  int $compression
333     * @return Imagick
334     */
335    public function setCompression(int $compression): Imagick
336    {
337        $this->compression = $compression;
338        return $this;
339    }
340
341    /**
342     * Set the image filter
343     *
344     * @param  int $filter
345     * @return Imagick
346     */
347    public function setImageFilter(int $filter): Imagick
348    {
349        $this->imageFilter = $filter;
350        return $this;
351    }
352
353    /**
354     * Set the image blur
355     *
356     * @param  float $blur
357     * @return Imagick
358     */
359    public function setImageBlur(float $blur): Imagick
360    {
361        $this->imageBlur = $blur;
362        return $this;
363    }
364
365    /**
366     * Get number of images
367     *
368     * @return int
369     */
370    public function getNumberOfImages(): int
371    {
372        return $this->resource->getNumberImages();
373    }
374
375    /**
376     * Get the image compression
377     *
378     * @return int
379     */
380    public function getCompression(): int
381    {
382        return $this->compression;
383    }
384
385    /**
386     * Get the image filter
387     *
388     * @return int
389     */
390    public function getImageFilter(): int
391    {
392        return $this->imageFilter;
393    }
394
395    /**
396     * Get the image blur
397     *
398     * @return float
399     */
400    public function getImageBlur(): float
401    {
402        return $this->imageBlur;
403    }
404
405    /**
406     * Resize the image object to the width parameter passed
407     *
408     * @param  int $w
409     * @return Imagick
410     */
411    public function resizeToWidth(int $w): Imagick
412    {
413        $scale        = $w / $this->width;
414        $this->width  = $w;
415        $this->height = round($this->height * $scale);
416
417        return $this->resizeImage($this->width, $this->height, $this->imageFilter, $this->imageBlur);
418    }
419
420    /**
421     * Resize the image object to the height parameter passed
422     *
423     * @param  int $h
424     * @return Imagick
425     */
426    public function resizeToHeight(int $h): Imagick
427    {
428        $scale        = $h / $this->height;
429        $this->height = $h;
430        $this->width  = round($this->width * $scale);
431
432        return $this->resizeImage($this->width, $this->height, $this->imageFilter, $this->imageBlur);
433    }
434
435    /**
436     * Resize the image object, allowing for the largest dimension
437     * to be scaled to the value of the $px argument.
438     *
439     * @param  int $px
440     * @return Imagick
441     */
442    public function resize(int $px): Imagick
443    {
444        $scale        = ($this->width > $this->height) ? ($px / $this->width) : ($px / $this->height);
445        $this->width  = round($this->width * $scale);
446        $this->height = round($this->height * $scale);
447
448        return $this->resizeImage($this->width, $this->height, $this->imageFilter, $this->imageBlur);
449    }
450
451    /**
452     * Scale the image object, allowing for the dimensions to be scaled
453     * proportionally to the value of the $scl argument.
454     *
455     * @param  float $scale
456     * @return Imagick
457     */
458    public function scale(float $scale): Imagick
459    {
460        $this->width  = round($this->width * $scale);
461        $this->height = round($this->height * $scale);
462
463        return $this->resizeImage($this->width, $this->height, $this->imageFilter, $this->imageBlur);
464    }
465
466    /**
467     * Resize image, checking for multiple frames
468     *
469     * @param  int  $width
470     * @param  int  $height
471     * @param  ?int $filter
472     * @param  ?int $blur
473     * @return Imagick
474     */
475    public function resizeImage(int $width, int $height, ?int $filter = null, ?int $blur = null): Imagick
476    {
477        if ($filter === null) {
478            $filter = $this->imageFilter;
479        }
480        if ($blur === null) {
481            $blur = $this->imageBlur;
482        }
483        if ($this->resource->getNumberImages() > 0) {
484            $frames = $this->resource->coalesceImages();
485            foreach ($frames as $frame) {
486                $frame->resizeImage($width, $height, $filter, $blur);
487            }
488            $this->resource = $frames->deconstructImages();
489        } else {
490            $this->resource->resizeImage($width, $height, $filter, $blur);
491        }
492
493        return $this;
494    }
495
496    /**
497     * Crop the image object to a image whose dimensions are based on the
498     * value of the $wid and $hgt argument. The optional $x and $y arguments
499     * allow for the adjustment of the crop to select a certain area of the
500     * image to be cropped.
501     *
502     * @param  int $w
503     * @param  int $h
504     * @param  int $x
505     * @param  int $y
506     * @return Imagick
507     */
508    public function crop(int $w, int $h, int $x = 0, int $y = 0): Imagick
509    {
510        $this->width  = $w;
511        $this->height = $h;
512        return $this->cropImage($this->width, $this->height, $x, $y);
513    }
514
515    /**
516     * Crop the image object to a square image whose dimensions are based on the
517     * value of the $px argument. The optional $offset argument allows for the
518     * adjustment of the crop to select a certain area of the image to be cropped.
519     *
520     * @param  int  $px
521     * @param  ?int $offset
522     * @return Imagick
523     */
524    public function cropThumb(int $px, ?int $offset = null): Imagick
525    {
526        $xOffset = 0;
527        $yOffset = 0;
528
529        if ($offset !== null) {
530            if ($this->width > $this->height) {
531                $xOffset = $offset;
532                $yOffset = 0;
533            } else if ($this->width < $this->height) {
534                $xOffset = 0;
535                $yOffset = $offset;
536            }
537        }
538
539        $scale = ($this->width > $this->height) ? ($px / $this->height) : ($px / $this->width);
540
541        $wid = round($this->width * $scale);
542        $hgt = round($this->height * $scale);
543
544        // Create a new image output resource.
545        if ($offset !== null) {
546            $this->resizeImage($wid, $hgt, $this->imageFilter, $this->imageBlur);
547            $this->cropImage($px, $px, $xOffset, $yOffset);
548        } else {
549            $this->cropThumbnailImage($px, $px);
550        }
551
552        $this->width  = $px;
553        $this->height = $px;
554        return $this;
555    }
556
557    /**
558     * Crop image, checking for multiple frames
559     *
560     * @param  int $width
561     * @param  int $height
562     * @param  int $x
563     * @param  int $y
564     * @return Imagick
565     */
566    public function cropImage(int $width, int $height, int $x, int $y): Imagick
567    {
568        if ($this->resource->getNumberImages() > 0) {
569            $frames = $this->resource->coalesceImages();
570            foreach ($frames as $frame) {
571                $frame->setImageBackgroundColor('none');
572                $frame->cropImage($width, $height, $x, $y);
573                $frame->thumbnailImage($width, $height);
574                $frame->setImagePage($width, $height, 0, 0);
575            }
576            $this->resource = $frames->deconstructImages();
577            $this->resource->resizeImage($width, $height, $this->imageFilter, $this->imageBlur);
578        } else {
579            $this->resource->cropImage($width, $height, $x, $y);
580        }
581
582        return $this;
583    }
584
585    /**
586     * Crop image, checking for multiple frames
587     *
588     * @param  int $width
589     * @param  int $height
590     * @return Imagick
591     */
592    public function cropThumbnailImage(int $width, int $height): Imagick
593    {
594        if ($this->resource->getNumberImages() > 0) {
595            $frames = $this->resource->coalesceImages();
596            foreach ($frames as $frame) {
597                $frame->setImageBackgroundColor('none');
598                $frame->cropThumbnailImage($width, $height);
599                $frame->thumbnailImage($width, $height);
600                $frame->setImagePage($width, $height, 0, 0);
601            }
602            $this->resource = $frames->deconstructImages();
603        } else {
604            $this->resource->cropThumbnailImage($width, $height);
605        }
606
607        return $this;
608    }
609
610    /**
611     * Rotate the image object
612     *
613     * @param  int                   $degrees
614     * @param  ?Color\ColorInterface $bgColor
615     * @return Imagick
616     */
617    public function rotate(int $degrees, ?Color\ColorInterface $bgColor = null): Imagick
618    {
619        $this->resource->rotateImage($this->createColor($bgColor), $degrees);
620        $this->width  = $this->resource->getImageWidth();
621        $this->height = $this->resource->getImageHeight();
622        return $this;
623    }
624
625    /**
626     * Method to flip the image over the x-axis
627     *
628     * @return Imagick
629     */
630    public function flip(): Imagick
631    {
632        $this->resource->flipImage();
633        return $this;
634    }
635
636    /**
637     * Method to flip the image over the y-axis
638     *
639     * @return Imagick
640     */
641    public function flop(): Imagick
642    {
643        $this->resource->flopImage();
644        return $this;
645    }
646
647    /**
648     * Get the image adjust object
649     *
650     * @return Adjust\AdjustInterface
651     */
652    public function adjust(): Adjust\AdjustInterface
653    {
654        if ($this->adjust === null) {
655            $this->adjust = new Adjust\Imagick($this);
656        }
657        return $this->adjust;
658    }
659
660    /**
661     * Get the image draw object
662     *
663     * @return Draw\DrawInterface
664     */
665    public function draw(): Draw\DrawInterface
666    {
667        if ($this->draw === null) {
668            $this->draw = new Draw\Imagick($this);
669        }
670        return $this->draw;
671    }
672
673    /**
674     * Get the image effect object
675     *
676     * @return Effect\EffectInterface
677     */
678    public function effect(): Effect\EffectInterface
679    {
680        if ($this->effect === null) {
681            $this->effect = new Effect\Imagick($this);
682        }
683        return $this->effect;
684    }
685
686    /**
687     * Get the image filter object
688     *
689     * @return Filter\FilterInterface
690     */
691    public function filter(): Filter\FilterInterface
692    {
693        if ($this->filter === null) {
694            $this->filter = new Filter\Imagick($this);
695        }
696        return $this->filter;
697    }
698
699    /**
700     * Get the image layer object
701     *
702     * @return Layer\LayerInterface
703     */
704    public function layer(): Layer\LayerInterface
705    {
706        if ($this->layer === null) {
707            $this->layer = new Layer\Imagick($this);
708        }
709        return $this->layer;
710    }
711
712    /**
713     * Get the image type object
714     *
715     * @return Type\TypeInterface
716     */
717    public function type(): Type\TypeInterface
718    {
719        if ($this->type === null) {
720            $this->type = new Type\Imagick($this);
721        }
722        return $this->type;
723    }
724
725    /**
726     * Convert the image object to another format
727     *
728     * @param  string $type
729     * @return Imagick
730     */
731    public function convert(string $type): Imagick
732    {
733        $type = strtolower($type);
734        $old  = strtolower($this->format);
735
736        if (($old == 'psd') || ($old == 'tif') || ($old == 'tiff')) {
737            $this->resource->mergeImageLayers(\Imagick::LAYERMETHOD_FLATTEN);
738        }
739
740        $this->resource->setImageFormat($type);
741
742        switch ($type) {
743            case 'jpg':
744            case 'jpeg':
745                $this->format  = 'jpg';
746                $this->indexed = false;
747                break;
748            case 'png':
749                $this->format = 'png';
750                break;
751            case 'gif':
752                $this->format  = 'gif';
753                $this->indexed = true;
754                break;
755            default:
756                $this->format = $type;
757        }
758
759        return $this;
760    }
761
762    /**
763     * Write the image object to a file on disk
764     *
765     * @param  ?string $to
766     * @param  ?int    $quality
767     * @throws Exception
768     * @return void
769     */
770    public function writeToFile(?string $to = null, ?int $quality = null): void
771    {
772        if (($this->resource === null) || (($this->resource !== null) && ($this->resource->count() == 0))) {
773            throw new Exception('Error: An image resource has not been created or loaded');
774        }
775
776        if ($quality !== null) {
777            $this->setQuality($quality);
778        }
779
780        if ($this->compression !== null) {
781            $this->resource->setImageCompression($this->compression);
782        }
783
784        if (((int)$this->quality < 0) || ((int)$this->quality > 100)) {
785            throw new \OutOfRangeException('Error: The quality parameter must be between 0 and 100');
786        }
787
788        $this->resource->setImageCompressionQuality($this->quality);
789
790        if ($to === null) {
791            $to = ($this->name !== null) ? basename($this->name) : 'pop-image.' . $this->format;
792        } else {
793            $this->name = $to;
794        }
795
796        $this->resource->writeImage($to);
797    }
798
799    /**
800     * Output the image object to a raw string
801     *
802     * @param  int $quality
803     * @throws Exception
804     * @return string|false
805     */
806    public function outputToRawString(int $quality = 100): string|false
807    {
808        if ($this->resource === null) {
809            throw new Exception('Error: An image resource has not been created or loaded');
810        }
811
812        return (string)(($this->resource->getNumberImages() > 0) ? $this->resource->getImagesBlob() : $this->resource);
813    }
814
815    /**
816     * Output the image object directly to HTTP
817     *
818     * @param  ?int    $quality
819     * @param  ?string $to
820     * @param  bool    $download
821     * @param  bool    $sendHeaders
822     * @param  array   $headers
823     * @throws Exception
824     * @return void
825     */
826    public function outputToHttp(
827        ?int $quality = null, ?string $to = null, bool $download = false, bool $sendHeaders = true, array $headers = []
828    ): void
829    {
830        if (($this->resource === null) || (($this->resource !== null) && ($this->resource->count() == 0))) {
831            throw new Exception('Error: An image resource has not been created or loaded');
832        }
833
834        if ($quality !== null) {
835            $this->setQuality($quality);
836        }
837
838        if (((int)$this->quality < 0) || ((int)$this->quality > 100)) {
839            throw new \OutOfRangeException('Error: The quality parameter must be between 0 and 100');
840        }
841
842        if ($this->compression !== null) {
843            $this->resource->setImageCompression($this->compression);
844        }
845
846        $this->resource->setImageCompressionQuality($this->quality);
847
848        if ($to === null) {
849            $to = ($this->name !== null) ? basename($this->name) : 'pop-image.' . strtolower($this->format);
850        }
851
852        if ($sendHeaders) {
853            $this->sendHeaders($to, $download, $headers);
854        }
855        echo ($this->resource->getNumberImages() > 0) ? $this->resource->getImagesBlob() : $this->resource;
856    }
857
858    /**
859     * Destroy the image object and the related image file directly
860     *
861     * @param  bool $delete
862     * @return void
863     */
864    public function destroy(bool $delete = false): void
865    {
866        if ($this->resource !== null) {
867            $this->resource->clear();
868            $this->resource->destroy();
869        }
870
871        $this->resource = null;
872        clearstatcache();
873
874        // If the $delete flag is passed, delete the image file.
875        if (($delete) && file_exists($this->name)) {
876            unlink($this->name);
877        }
878    }
879
880    /**
881     * Create and return a color.
882     *
883     * @param  ?Color\ColorInterface $color
884     * @param  int                   $alpha
885     * @throws ImagickPixelException
886     * @return ImagickPixel
887     */
888    public function createColor(?Color\ColorInterface $color = null, int $alpha = 100): ImagickPixel
889    {
890        if ($color === null) {
891            $color = new Color\Rgb(0, 0, 0);
892        }
893
894        if (!($color instanceof Color\Rgb)) {
895            $color = $color->toRgb();
896        }
897
898        if ($alpha < 100) {
899            $color->setA($alpha / 100);
900        }
901
902        return new ImagickPixel($color->render(Color\Rgb::CSS));
903    }
904
905    /**
906     * Output the image
907     *
908     * @return string
909     */
910    public function __toString(): string
911    {
912        $this->sendHeaders();
913        echo ($this->resource->getNumberImages() > 0) ? $this->resource->getImagesBlob() : $this->resource;
914        return '';
915    }
916
917}