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