1
2/**
3 * This file is part of the Phalcon Framework.
4 *
5 * (c) Phalcon Team <team@phalcon.io>
6 *
7 * For the full copyright and license information, please view the LICENSE.txt
8 * file that was distributed with this source code.
9 */
10
11namespace Phalcon\Image\Adapter;
12
13use Phalcon\Image\Enum;
14use Phalcon\Image\Exception;
15
16/**
17 * Phalcon\Image\Adapter\Imagick
18 *
19 * Image manipulation support. Allows images to be resized, cropped, etc.
20 *
21 *```php
22 * $image = new \Phalcon\Image\Adapter\Imagick("upload/test.jpg");
23 *
24 * $image->resize(200, 200)->rotate(90)->crop(100, 100);
25 *
26 * if ($image->save()) {
27 *     echo "success";
28
29 * }
30 *```
31 */
32class Imagick extends AbstractAdapter
33{
34    protected static checked = false;
35    protected static version = 0;
36
37    /**
38     * \Phalcon\Image\Adapter\Imagick constructor
39     */
40    public function __construct(string! file, int width = null, int height = null)
41    {
42        var image;
43
44        if !self::checked {
45            self::check();
46        }
47
48        let this->file = file;
49
50        let this->image = new \Imagick();
51
52        if file_exists(this->file) {
53            let this->realpath = realpath(this->file);
54
55            if unlikely !this->image->readImage(this->realpath) {
56                 throw new Exception(
57                     "Imagick::readImage " . this->file . " failed"
58                 );
59            }
60
61            if !this->image->getImageAlphaChannel() {
62                this->image->setImageAlphaChannel(
63                    constant("Imagick::ALPHACHANNEL_SET")
64                );
65            }
66
67            if this->type == 1 {
68                let image = this->image->coalesceImages();
69
70                this->image->clear();
71                this->image->destroy();
72
73                let this->image = image;
74            }
75        } else {
76            if unlikely (!width || !height) {
77                throw new Exception(
78                    "Failed to create image from file " . this->file
79                );
80            }
81
82            this->image->newImage(
83                width,
84                height,
85                new \ImagickPixel("transparent")
86            );
87
88            this->image->setFormat("png");
89            this->image->setImageFormat("png");
90
91            let this->realpath = this->file;
92        }
93
94        let this->width  = this->image->getImageWidth();
95        let this->height = this->image->getImageHeight();
96        let this->type   = this->image->getImageType();
97        let this->mime   = "image/" . this->image->getImageFormat();
98    }
99
100    /**
101     * Destroys the loaded image to free up resources.
102     */
103    public function __destruct()
104    {
105        if this->image instanceof \Imagick {
106            this->image->clear();
107            this->image->destroy();
108        }
109    }
110
111    /**
112     * Checks if Imagick is enabled
113     */
114    public static function check() -> bool
115    {
116        if self::checked {
117            return true;
118        }
119
120        if unlikely !class_exists("imagick") {
121            throw new Exception(
122                "Imagick is not installed, or the extension is not loaded"
123            );
124        }
125
126        if defined("Imagick::IMAGICK_EXTNUM") {
127            let self::version = constant("Imagick::IMAGICK_EXTNUM");
128        }
129
130        let self::checked = true;
131
132        return self::checked;
133    }
134
135    /**
136     * Get instance
137     */
138    public function getInternalImInstance() -> <\Imagick>
139    {
140        return this->image;
141    }
142
143    /**
144     * Sets the limit for a particular resource in megabytes
145     *
146     * @link http://php.net/manual/ru/imagick.constants.php#imagick.constants.resourcetypes
147     */
148    public function setResourceLimit(int type, int limit)
149    {
150        this->image->setResourceLimit(type, limit);
151    }
152
153    /**
154     * Execute a background.
155     */
156    protected function processBackground(int r, int g, int b, int opacity)
157    {
158        var background, color, pixel1, pixel2, ret;
159
160        let color = sprintf("rgb(%d, %d, %d)", r, g, b);
161        let pixel1 = new \ImagickPixel(color);
162        let opacity = opacity / 100;
163
164        let pixel2 = new \ImagickPixel("transparent");
165
166        let background = new \Imagick();
167
168        this->image->setIteratorIndex(0);
169
170        loop {
171            background->newImage(this->width, this->height, pixel1);
172
173            if !background->getImageAlphaChannel() {
174                background->setImageAlphaChannel(
175                    constant("Imagick::ALPHACHANNEL_SET")
176                );
177            }
178
179            background->setImageBackgroundColor(pixel2);
180
181            background->evaluateImage(
182                constant("Imagick::EVALUATE_MULTIPLY"),
183                opacity,
184                constant("Imagick::CHANNEL_ALPHA")
185            );
186
187            background->setColorspace(
188                this->image->getColorspace()
189            );
190
191            let ret = background->compositeImage(
192                this->image,
193                constant("Imagick::COMPOSITE_DISSOLVE"),
194                0,
195                0
196            );
197
198            if unlikely ret !== true {
199                throw new Exception("Imagick::compositeImage failed");
200            }
201
202            if this->image->nextImage() === false {
203                break;
204            }
205        }
206
207        this->image->clear();
208        this->image->destroy();
209
210        let this->image = background;
211    }
212
213    /**
214     * Blur image
215     *
216     * @param int $radius Blur radius
217     */
218    protected function processBlur(int radius)
219    {
220        this->image->setIteratorIndex(0);
221
222        loop {
223            this->image->blurImage(radius, 100);
224
225            if this->image->nextImage() === false {
226                break;
227            }
228        }
229    }
230
231    /**
232     * Execute a crop.
233     */
234    protected function processCrop(int width, int height, int offsetX, int offsetY)
235    {
236        var image;
237
238        let image = this->image;
239
240        image->setIteratorIndex(0);
241
242        loop {
243            image->cropImage(width, height, offsetX, offsetY);
244            image->setImagePage(width, height, 0, 0);
245
246            if !image->nextImage() {
247                break;
248            }
249        }
250
251        let this->width  = image->getImageWidth();
252        let this->height = image->getImageHeight();
253    }
254
255    /**
256     * Execute a flip.
257     */
258    protected function processFlip(int direction)
259    {
260        var func;
261
262        let func = "flipImage";
263
264        if direction == Enum::HORIZONTAL {
265           let func = "flopImage";
266        }
267
268        this->image->setIteratorIndex(0);
269
270        loop {
271            this->image->{func}();
272
273            if this->image->nextImage() === false {
274                break;
275            }
276        }
277    }
278
279    /**
280     * This method scales the images using liquid rescaling method. Only support
281     * Imagick
282     *
283     * @param int $width   new width
284     * @param int $height  new height
285     * @param int $deltaX How much the seam can traverse on x-axis. Passing 0 causes the seams to be straight.
286     * @param int $rigidity Introduces a bias for non-straight seams. This parameter is typically 0.
287     */
288    protected function processLiquidRescale(int width, int height, int deltaX, int rigidity)
289    {
290        var ret, image;
291
292        let image = this->image;
293
294        image->setIteratorIndex(0);
295
296        loop {
297            let ret = image->liquidRescaleImage(
298                width,
299                height,
300                deltaX,
301                rigidity
302            );
303
304            if unlikely ret !== true {
305                throw new Exception("Imagick::liquidRescale failed");
306            }
307
308            if image->nextImage() === false {
309                break;
310            }
311        }
312
313        let this->width = image->getImageWidth();
314        let this->height = image->getImageHeight();
315    }
316
317    /**
318     * Composite one image onto another
319     */
320    protected function processMask(<AdapterInterface> image)
321    {
322        var mask, ret;
323
324        let mask = new \Imagick();
325
326        mask->readImageBlob(
327            image->render()
328        );
329
330        this->image->setIteratorIndex(0);
331
332        loop {
333            this->image->setImageMatte(1);
334
335            let ret = this->image->compositeImage(
336                mask,
337                constant("Imagick::COMPOSITE_DSTIN"),
338                0,
339                0
340            );
341
342            if unlikely ret !== true {
343                throw new Exception("Imagick::compositeImage failed");
344            }
345
346            if this->image->nextImage() === false {
347                break;
348            }
349        }
350
351        mask->clear();
352        mask->destroy();
353    }
354
355    /**
356     * Pixelate image
357     *
358     * @param int $amount amount to pixelate
359     */
360    protected function processPixelate(int amount)
361    {
362        int width, height;
363
364        let width = this->width / amount;
365        let height = this->height / amount;
366
367        this->image->setIteratorIndex(0);
368
369        loop {
370            this->image->scaleImage(width, height);
371            this->image->scaleImage(this->width, this->height);
372
373            if this->image->nextImage() === false{
374                break;
375            }
376        }
377    }
378
379    /**
380     * Execute a reflection.
381     */
382    protected function processReflection(int height, int opacity, bool fadeIn)
383    {
384        var reflection, fade, pseudo, image, pixel, ret;
385
386        if self::version >= 30100 {
387            let reflection = clone this->image;
388        } else {
389            let reflection = clone this->image->$clone();
390        }
391
392        reflection->setIteratorIndex(0);
393
394        loop {
395            reflection->flipImage();
396
397            reflection->cropImage(
398                reflection->getImageWidth(),
399                height,
400                0,
401                0
402            );
403
404            reflection->setImagePage(
405                reflection->getImageWidth(),
406                height,
407                0,
408                0
409            );
410
411            if reflection->nextImage() === false {
412                break;
413            }
414        }
415
416        let pseudo = fadeIn ? "gradient:black-transparent" : "gradient:transparent-black",
417            fade = new \Imagick();
418
419        fade->newPseudoImage(
420            reflection->getImageWidth(),
421            reflection->getImageHeight(),
422            pseudo
423        );
424
425        let opacity /= 100;
426
427        reflection->setIteratorIndex(0);
428
429        loop {
430            let ret = reflection->compositeImage(
431                fade,
432                constant("Imagick::COMPOSITE_DSTOUT"),
433                0,
434                0
435            );
436
437            if unlikely ret !== true {
438                throw new Exception("Imagick::compositeImage failed");
439            }
440
441            reflection->evaluateImage(
442                constant("Imagick::EVALUATE_MULTIPLY"),
443                opacity,
444                constant("Imagick::CHANNEL_ALPHA")
445            );
446
447            if reflection->nextImage() === false {
448                break;
449            }
450        }
451
452        fade->destroy();
453
454        let image = new \Imagick(),
455            pixel = new \ImagickPixel(),
456            height = this->image->getImageHeight() + height;
457
458        this->image->setIteratorIndex(0);
459
460        loop {
461            image->newImage(this->width, height, pixel);
462
463            image->setImageAlphaChannel(
464                constant("Imagick::ALPHACHANNEL_SET")
465            );
466
467            image->setColorspace(
468                this->image->getColorspace()
469            );
470
471            image->setImageDelay(
472                this->image->getImageDelay()
473            );
474
475            let ret = image->compositeImage(
476                this->image,
477                constant("Imagick::COMPOSITE_SRC"),
478                0,
479                0
480            );
481
482            if unlikely ret !== true {
483                throw new Exception("Imagick::compositeImage failed");
484            }
485
486            if this->image->nextImage() === false {
487                break;
488            }
489        }
490
491        image->setIteratorIndex(0);
492        reflection->setIteratorIndex(0);
493
494        loop {
495            let ret = image->compositeImage(
496                reflection,
497                constant("Imagick::COMPOSITE_OVER"),
498                0,
499                this->height
500            );
501
502            if unlikely ret !== true {
503                throw new Exception("Imagick::compositeImage failed");
504            }
505
506            if image->nextImage() === false || reflection->nextImage() === false {
507                break;
508            }
509        }
510
511        reflection->destroy();
512
513        this->image->clear();
514        this->image->destroy();
515
516        let this->image = image;
517        let this->width = this->image->getImageWidth();
518        let this->height = this->image->getImageHeight();
519    }
520
521    /**
522     * Execute a render.
523     */
524    protected function processRender(string extension, int quality) -> string
525    {
526        var image;
527
528        let image = this->image;
529
530        image->setFormat(extension);
531        image->setImageFormat(extension);
532        image->stripImage();
533
534        let this->type = image->getImageType(),
535            this->mime = "image/" . image->getImageFormat();
536
537        if strcasecmp(extension, "gif") === 0 {
538            image->optimizeImageLayers();
539        } else {
540            if strcasecmp(extension, "jpg") === 0 || strcasecmp(extension, "jpeg") === 0 {
541                image->setImageCompression(
542                    constant("Imagick::COMPRESSION_JPEG")
543                );
544            }
545
546            image->setImageCompressionQuality(quality);
547        }
548
549        return image->getImageBlob();
550    }
551
552    /**
553     * Execute a resize.
554     */
555    protected function processResize(int width, int height)
556    {
557        var image;
558
559        let image = this->image;
560
561        image->setIteratorIndex(0);
562
563        loop {
564            image->scaleImage(width, height);
565
566            if image->nextImage() === false {
567                break;
568            }
569        }
570
571        let this->width = image->getImageWidth();
572        let this->height = image->getImageHeight();
573    }
574
575    /**
576     * Execute a rotation.
577     */
578    protected function processRotate(int degrees)
579    {
580        var pixel;
581
582        this->image->setIteratorIndex(0);
583
584        let pixel = new \ImagickPixel();
585
586        loop {
587            this->image->rotateImage(pixel, degrees);
588
589            this->image->setImagePage(
590                this->width,
591                this->height,
592                0,
593                0
594            );
595
596            if this->image->nextImage() === false {
597                break;
598            }
599        }
600
601        let this->width = this->image->getImageWidth();
602        let this->height = this->image->getImageHeight();
603    }
604
605    /**
606     * Execute a save.
607     */
608    protected function processSave(string file, int quality)
609    {
610        var ext, fp;
611
612        let ext = pathinfo(file, PATHINFO_EXTENSION);
613
614        this->image->setFormat(ext);
615        this->image->setImageFormat(ext);
616
617        let this->type = this->image->getImageType();
618        let this->mime = "image/" . this->image->getImageFormat();
619
620        if strcasecmp(ext, "gif") == 0 {
621            this->image->optimizeImageLayers();
622
623            let fp = fopen(file, "w");
624
625            this->image->writeImagesFile(fp);
626
627            fclose(fp);
628
629            return;
630        } else {
631            if strcasecmp(ext, "jpg") == 0 || strcasecmp(ext, "jpeg") == 0 {
632                this->image->setImageCompression(
633                    constant("Imagick::COMPRESSION_JPEG")
634                );
635            }
636
637            if quality >= 0 {
638                if quality < 1 {
639                    let quality = 1;
640                } elseif quality > 100 {
641                    let quality = 100;
642                }
643
644                this->image->setImageCompressionQuality(quality);
645            }
646
647            this->image->writeImage(file);
648        }
649    }
650
651    /**
652     * Execute a sharpen.
653     */
654    protected function processSharpen(int amount)
655    {
656        let amount = (amount < 5) ? 5 : amount;
657        let amount = (amount * 3.0) / 100;
658
659        this->image->setIteratorIndex(0);
660
661        loop {
662            this->image->sharpenImage(0, amount);
663
664            if this->image->nextImage() === false {
665                break;
666            }
667        }
668    }
669
670    /**
671     * Execute a text
672     */
673    protected function processText(string text, var offsetX, var offsetY, int opacity, int r, int g, int b, int size, string fontfile)
674    {
675        var x, y, draw, color, gravity;
676
677        let opacity = opacity / 100,
678            draw = new \ImagickDraw(),
679            color = sprintf("rgb(%d, %d, %d)", r, g, b);
680
681        draw->setFillColor(
682            new \ImagickPixel(color)
683        );
684
685        if fontfile {
686            draw->setFont(fontfile);
687        }
688
689        if size {
690            draw->setFontSize(size);
691        }
692
693        if opacity {
694            draw->setfillopacity(opacity);
695        }
696
697        let gravity = null;
698
699        if typeof offsetX == "boolean" {
700            if typeof offsetY == "boolean" {
701                let offsetX = 0,
702                    offsetY = 0;
703
704                let gravity = constant("Imagick::GRAVITY_CENTER");
705            } else {
706                if typeof offsetY == "int" {
707                    let y = (int) offsetY;
708
709                    if offsetX {
710                        if y < 0 {
711                            let offsetX = 0,
712                                offsetY = y * -1,
713                                gravity = constant("Imagick::GRAVITY_SOUTHEAST");
714                        } else {
715                            let offsetX = 0,
716                                gravity = constant("Imagick::GRAVITY_NORTHEAST");
717                        }
718                    } else {
719                        if y < 0 {
720                            let offsetX = 0,
721                                offsetY = y * -1,
722                                gravity = constant("Imagick::GRAVITY_SOUTH");
723                        } else {
724                            let offsetX = 0,
725                                gravity = constant("Imagick::GRAVITY_NORTH");
726                        }
727                    }
728                }
729            }
730        } else {
731            if typeof offsetX == "int" {
732                let x = (int) offsetX;
733
734                if offsetX {
735                    if typeof offsetY == "boolean" {
736                        if offsetY {
737                            if x < 0 {
738                                let offsetX = x * -1,
739                                    offsetY = 0,
740                                    gravity = constant("Imagick::GRAVITY_SOUTHEAST");
741                            } else {
742                                let offsetY = 0,
743                                    gravity = constant("Imagick::GRAVITY_SOUTH");
744                            }
745                        } else {
746                            if x < 0 {
747                                let offsetX = x * -1,
748                                    offsetY = 0,
749                                    gravity = constant("Imagick::GRAVITY_EAST");
750                            } else {
751                                let offsetY = 0,
752                                    gravity = constant("Imagick::GRAVITY_WEST");
753                            }
754                        }
755                    } else {
756                        if typeof offsetY == "long" {
757                            let x = (int) offsetX,
758                                y = (int) offsetY;
759
760                            if x < 0 {
761                                if y < 0 {
762                                    let offsetX = x * -1,
763                                        offsetY = y * -1,
764                                        gravity = constant("Imagick::GRAVITY_SOUTHEAST");
765                                } else {
766                                    let offsetX    = x * -1,
767                                        gravity = constant("Imagick::GRAVITY_NORTHEAST");
768                                }
769                            } else {
770                                if y < 0 {
771                                    let offsetX = 0,
772                                        offsetY = y * -1,
773                                        gravity = constant("Imagick::GRAVITY_SOUTHWEST");
774                                } else {
775                                    let offsetX = 0,
776                                        gravity = constant("Imagick::GRAVITY_NORTHWEST");
777                                }
778                            }
779                        }
780                    }
781                }
782            }
783        }
784
785        draw->setGravity(gravity);
786
787        this->image->setIteratorIndex(0);
788
789        loop {
790            this->image->annotateImage(draw, offsetX, offsetY, 0, text);
791
792            if this->image->nextImage() === false {
793                break;
794            }
795        }
796
797        draw->destroy();
798    }
799
800    /**
801     * Execute a watermarking.
802     */
803    protected function processWatermark(<AdapterInterface> image, int offsetX, int offsetY, int opacity)
804    {
805        var watermark, ret;
806
807        let opacity = opacity / 100,
808            watermark = new \Imagick();
809
810        watermark->readImageBlob(
811            image->render()
812        );
813
814        watermark->evaluateImage(
815            constant("Imagick::EVALUATE_MULTIPLY"),
816            opacity,
817            constant("Imagick::CHANNEL_ALPHA")
818        );
819
820        this->image->setIteratorIndex(0);
821
822        loop {
823            let ret = this->image->compositeImage(
824                watermark,
825                constant("Imagick::COMPOSITE_OVER"),
826                offsetX,
827                offsetY
828            );
829
830            if unlikely ret !== true {
831                throw new Exception("Imagick::compositeImage failed");
832            }
833
834            if this->image->nextImage() === false {
835                break;
836            }
837        }
838
839        watermark->clear();
840        watermark->destroy();
841    }
842}
843