1
2/*
3 +------------------------------------------------------------------------+
4 | Phalcon Framework                                                      |
5 +------------------------------------------------------------------------+
6 | Copyright (c) 2011-2017 Phalcon Team (https://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;
21
22use Phalcon\Image;
23
24/**
25 * Phalcon\Image\Adapter
26 *
27 * All image adapters must use this class
28 */
29abstract class Adapter implements AdapterInterface
30{
31
32	protected _image { get };
33
34	protected _file;
35
36	protected _realpath { get };
37
38	/**
39	 * Image width
40	 *
41	 * @var int
42	 */
43	protected _width { get };
44
45	/**
46	 * Image height
47	 *
48	 * @var int
49	 */
50	protected _height { get };
51
52	/**
53	 * Image type
54	 *
55	 * Driver dependent
56	 *
57	 * @var int
58	 */
59	protected _type { get };
60
61	/**
62	 * Image mime type
63	 *
64	 * @var string
65	 */
66	protected _mime { get };
67
68	protected static _checked = false;
69
70	/**
71 	 * Resize the image to the given size
72 	 */
73	public function resize(int width = null, int height = null, int master = Image::AUTO) -> <Adapter>
74	{
75		var ratio;
76
77		if master == Image::TENSILE {
78
79			if !width || !height {
80				throw new Exception("width and height must be specified");
81			}
82
83		} else {
84
85			if master == Image::AUTO {
86
87				if !width || !height {
88					throw new Exception("width and height must be specified");
89				}
90
91				let master = (this->_width / width) > (this->_height / height) ? Image::WIDTH : Image::HEIGHT;
92			}
93
94			if master == Image::INVERSE {
95
96				if !width || !height {
97					throw new Exception("width and height must be specified");
98				}
99
100				let master = (this->_width / width) > (this->_height / height) ? Image::HEIGHT : Image::WIDTH;
101			}
102
103			switch master {
104
105				case Image::WIDTH:
106					if !width {
107						throw new Exception("width must be specified");
108					}
109					let height = this->_height * width / this->_width;
110					break;
111
112				case Image::HEIGHT:
113					if !height {
114						throw new Exception("height must be specified");
115					}
116					let width = this->_width * height / this->_height;
117					break;
118
119				case Image::PRECISE:
120					if !width || !height {
121						throw new Exception("width and height must be specified");
122					}
123					let ratio = this->_width / this->_height;
124
125					if (width / height) > ratio {
126						let height = this->_height * width / this->_width;
127					} else {
128						let width = this->_width * height / this->_height;
129					}
130					break;
131
132				case Image::NONE:
133					if !width {
134						let width = (int) this->_width;
135					}
136
137					if !height {
138						let width = (int) this->_height;
139					}
140					break;
141			}
142		}
143
144		let width  = (int) max(round(width), 1);
145		let height = (int) max(round(height), 1);
146
147		this->{"_resize"}(width, height);
148
149		return this;
150	}
151
152	/**
153	 * This method scales the images using liquid rescaling method. Only support Imagick
154	 *
155	 * @param int $width   new width
156	 * @param int $height  new height
157	 * @param int $deltaX How much the seam can traverse on x-axis. Passing 0 causes the seams to be straight.
158	 * @param int $rigidity Introduces a bias for non-straight seams. This parameter is typically 0.
159	 */
160	public function liquidRescale(int width, int height, int deltaX = 0, int rigidity = 0) -> <Adapter>
161	{
162		this->{"_liquidRescale"}(width, height, deltaX, rigidity);
163		return this;
164	}
165
166	/**
167 	 * Crop an image to the given size
168 	 */
169	public function crop(int width, int height, int offsetX = null, int offsetY = null) -> <Adapter>
170	{
171		if is_null(offsetX) {
172			let offsetX = ((this->_width - width) / 2);
173		} else {
174			if offsetX < 0 {
175				let offsetX = this->_width - width + offsetX;
176			}
177
178			if offsetX > this->_width {
179				let offsetX = (int) this->_width;
180			}
181		}
182
183		if is_null(offsetY) {
184			let offsetY = ((this->_height - height) / 2);
185		} else {
186			if offsetY < 0 {
187				let offsetY = this->_height - height + offsetY;
188			}
189
190			if offsetY > this->_height {
191				let offsetY = (int) this->_height;
192			}
193		}
194
195		if width > (this->_width - offsetX) {
196			let width = this->_width - offsetX;
197		}
198
199		if height > (this->_height - offsetY) {
200			let height = this->_height - offsetY;
201		}
202
203		this->{"_crop"}(width, height, offsetX, offsetY);
204
205		return this;
206	}
207
208	/**
209 	 * Rotate the image by a given amount
210 	 */
211	public function rotate(int degrees) -> <Adapter>
212	{
213			if degrees > 180 {
214			// FIXME: Fix Zephir Parser to allow use  let degrees %= 360
215			let degrees = degrees % 360;
216			if degrees > 180 {
217				let degrees -= 360;
218			}
219		} else {
220			while degrees < -180 {
221				let degrees += 360;
222			}
223		}
224
225		this->{"_rotate"}(degrees);
226		return this;
227	}
228
229	/**
230 	 * Flip the image along the horizontal or vertical axis
231 	 */
232	public function flip(int direction) -> <Adapter>
233	{
234		if direction != Image::HORIZONTAL && direction != Image::VERTICAL {
235			let direction = Image::HORIZONTAL;
236		}
237
238		this->{"_flip"}(direction);
239		return this;
240	}
241
242	/**
243 	 * Sharpen the image by a given amount
244 	 */
245	public function sharpen(int amount) -> <Adapter>
246	{
247		if amount > 100 {
248			let amount = 100;
249		} elseif amount < 1 {
250			let amount = 1;
251		}
252
253		this->{"_sharpen"}(amount);
254		return this;
255	}
256
257	/**
258 	 * Add a reflection to an image
259 	 */
260	public function reflection(int height, int opacity = 100, boolean fadeIn = false) -> <Adapter>
261	{
262		if height <= 0 || height > this->_height {
263			let height = (int) this->_height;
264		}
265
266		if opacity < 0 {
267			let opacity = 0;
268		} elseif opacity > 100 {
269			let opacity = 100;
270		}
271
272		this->{"_reflection"}(height, opacity, fadeIn);
273
274		return this;
275	}
276
277	/**
278 	 * Add a watermark to an image with the specified opacity
279 	 */
280	public function watermark(<Adapter> watermark, int offsetX = 0, int offsetY = 0, int opacity = 100) -> <Adapter>
281	{
282		int tmp;
283
284		let tmp = this->_width - watermark->getWidth();
285
286		if offsetX < 0 {
287			let offsetX = 0;
288		} elseif offsetX > tmp {
289			let offsetX = tmp;
290		}
291
292		let tmp = this->_height - watermark->getHeight();
293
294		if offsetY < 0 {
295			let offsetY = 0;
296		} elseif offsetY > tmp {
297			let offsetY = tmp;
298		}
299
300		if opacity < 0 {
301			let opacity = 0;
302		} elseif opacity > 100 {
303			let opacity = 100;
304		}
305
306		this->{"_watermark"}(watermark, offsetX, offsetY, opacity);
307
308		return this;
309	}
310
311	/**
312 	 * Add a text to an image with a specified opacity
313 	 */
314	public function text(string text, var offsetX = false, var offsetY = false, int opacity = 100, string color = "000000", int size = 12, string fontfile = null) -> <Adapter>
315	{
316		var colors;
317
318		if opacity < 0 {
319			let opacity = 0;
320		} else {
321			if opacity > 100 {
322				let opacity = 100;
323			}
324		}
325
326		if strlen(color) > 1 && substr(color, 0, 1) === "#" {
327			let color = substr(color, 1);
328		}
329
330		if strlen(color) === 3 {
331			let color = preg_replace("/./", "$0$0", color);
332		}
333
334		let colors = array_map("hexdec", str_split(color, 2));
335
336		this->{"_text"}(text, offsetX, offsetY, opacity, colors[0], colors[1], colors[2], size, fontfile);
337
338		return this;
339	}
340
341	/**
342 	 * Composite one image onto another
343 	 */
344	public function mask(<Adapter> watermark) -> <Adapter>
345	{
346		this->{"_mask"}(watermark);
347		return this;
348	}
349
350	/**
351 	 * Set the background color of an image
352 	 */
353	public function background(string color, int opacity = 100) -> <Adapter>
354	{
355		var colors;
356
357		if strlen(color) > 1 && substr(color, 0, 1) === "#" {
358			let color = substr(color, 1);
359		}
360
361		if strlen(color) === 3 {
362			let color = preg_replace("/./", "$0$0", color);
363		}
364
365		let colors = array_map("hexdec", str_split(color, 2));
366
367		this->{"_background"}(colors[0], colors[1], colors[2], opacity);
368		return this;
369	}
370
371	/**
372 	 * Blur image
373 	 */
374	public function blur(int radius) -> <Adapter>
375	{
376		if radius < 1 {
377			let radius = 1;
378		} elseif radius > 100 {
379			let radius = 100;
380		}
381
382		this->{"_blur"}(radius);
383		return this;
384	}
385
386	/**
387 	 * Pixelate image
388 	 */
389	public function pixelate(int amount) -> <Adapter>
390	{
391		if amount < 2 {
392			let amount = 2;
393		}
394
395		this->{"_pixelate"}(amount);
396		return this;
397	}
398
399	/**
400 	 * Save the image
401 	 */
402	public function save(string file = null, int quality = -1) -> <Adapter>
403	{
404		if !file {
405			let file = (string) this->_realpath;
406		}
407
408		this->{"_save"}(file, quality);
409		return this;
410	}
411
412	/**
413 	 * Render the image and return the binary string
414 	 */
415	public function render(string ext = null, int quality = 100) -> string
416	{
417		if !ext {
418			let ext = (string) pathinfo(this->_file, PATHINFO_EXTENSION);
419		}
420
421		if empty ext {
422			let ext = "png";
423		}
424
425		if quality < 1 {
426			let quality = 1;
427		} elseif quality > 100 {
428			let quality = 100;
429		}
430
431		return this->{"_render"}(ext, quality);
432	}
433}
434