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