1<?php defined('SYSPATH') OR die('No direct access allowed.');
2/**
3 * ImageMagick Image Driver.
4 *
5 * $Id: ImageMagick.php 3769 2008-12-15 00:48:56Z zombor $
6 *
7 * @package    Image
8 * @author     Kohana Team
9 * @copyright  (c) 2007-2008 Kohana Team
10 * @license    http://kohanaphp.com/license.html
11 */
12class Image_ImageMagick_Driver extends Image_Driver {
13
14	// Directory that IM is installed in
15	protected $dir = '';
16
17	// Command extension (exe for windows)
18	protected $ext = '';
19
20	// Temporary image filename
21	protected $tmp_image;
22
23	/**
24	 * Attempts to detect the ImageMagick installation directory.
25	 *
26	 * @throws  Kohana_Exception
27	 * @param   array   configuration
28	 * @return  void
29	 */
30	public function __construct($config)
31	{
32		if (empty($config['directory']))
33		{
34			// Attempt to locate IM by using "which" (only works for *nix!)
35			if ( ! is_file($path = exec('which convert')))
36				throw new Kohana_Exception('image.imagemagick.not_found');
37
38			$config['directory'] = dirname($path);
39		}
40
41		// Set the command extension
42		$this->ext = (PHP_SHLIB_SUFFIX === 'dll') ? '.exe' : '';
43
44		// Check to make sure the provided path is correct
45		if ( ! is_file(realpath($config['directory']).'/convert'.$this->ext))
46			throw new Kohana_Exception('image.imagemagick.not_found', 'convert'.$this->ext);
47
48		// Set the installation directory
49		$this->dir = str_replace('\\', '/', realpath($config['directory'])).'/';
50	}
51
52	/**
53	 * Creates a temporary image and executes the given actions. By creating a
54	 * temporary copy of the image before manipulating it, this process is atomic.
55	 */
56	public function process($image, $actions, $dir, $file, $render = FALSE)
57	{
58		// We only need the filename
59		$image = $image['file'];
60
61		// Unique temporary filename
62		$this->tmp_image = $dir.'k2img--'.sha1(time().$dir.$file).substr($file, strrpos($file, '.'));
63
64		// Copy the image to the temporary file
65		copy($image, $this->tmp_image);
66
67		// Quality change is done last
68		$quality = (int) arr::remove('quality', $actions);
69
70		// Use 95 for the default quality
71		empty($quality) and $quality = 95;
72
73		// All calls to these will need to be escaped, so do it now
74		$this->cmd_image = escapeshellarg($this->tmp_image);
75		$this->new_image = ($render)? $this->cmd_image : escapeshellarg($dir.$file);
76
77		if ($status = $this->execute($actions))
78		{
79			// Use convert to change the image into its final version. This is
80			// done to allow the file type to change correctly, and to handle
81			// the quality conversion in the most effective way possible.
82			if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -quality '.$quality.'% '.$this->cmd_image.' '.$this->new_image))
83			{
84				$this->errors[] = $error;
85			}
86			else
87			{
88				// Output the image directly to the browser
89				if ($render !== FALSE)
90				{
91					$contents = file_get_contents($this->tmp_image);
92					switch (substr($file, strrpos($file, '.') + 1))
93					{
94						case 'jpg':
95						case 'jpeg':
96							header('Content-Type: image/jpeg');
97						break;
98						case 'gif':
99							header('Content-Type: image/gif');
100						break;
101						case 'png':
102							header('Content-Type: image/png');
103						break;
104 					}
105					echo $contents;
106				}
107			}
108		}
109
110		// Remove the temporary image
111		unlink($this->tmp_image);
112		$this->tmp_image = '';
113
114		return $status;
115	}
116
117	public function crop($prop)
118	{
119		// Sanitize and normalize the properties into geometry
120		$this->sanitize_geometry($prop);
121
122		// Set the IM geometry based on the properties
123		$geometry = escapeshellarg($prop['width'].'x'.$prop['height'].'+'.$prop['left'].'+'.$prop['top']);
124
125		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -crop '.$geometry.' '.$this->cmd_image.' '.$this->cmd_image))
126		{
127			$this->errors[] = $error;
128			return FALSE;
129		}
130
131		return TRUE;
132	}
133
134	public function flip($dir)
135	{
136		// Convert the direction into a IM command
137		$dir = ($dir === Image::HORIZONTAL) ? '-flop' : '-flip';
138
139		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' '.$dir.' '.$this->cmd_image.' '.$this->cmd_image))
140		{
141			$this->errors[] = $error;
142			return FALSE;
143		}
144
145		return TRUE;
146	}
147
148	public function resize($prop)
149	{
150		switch ($prop['master'])
151		{
152			case Image::WIDTH:  // Wx
153				$dim = escapeshellarg($prop['width'].'x');
154			break;
155			case Image::HEIGHT: // xH
156				$dim = escapeshellarg('x'.$prop['height']);
157			break;
158			case Image::AUTO:   // WxH
159				$dim = escapeshellarg($prop['width'].'x'.$prop['height']);
160			break;
161			case Image::NONE:   // WxH!
162				$dim = escapeshellarg($prop['width'].'x'.$prop['height'].'!');
163			break;
164		}
165
166		// Use "convert" to change the width and height
167		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -resize '.$dim.' '.$this->cmd_image.' '.$this->cmd_image))
168		{
169			$this->errors[] = $error;
170			return FALSE;
171		}
172
173		return TRUE;
174	}
175
176	public function rotate($amt)
177	{
178		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -rotate '.escapeshellarg($amt).' -background transparent '.$this->cmd_image.' '.$this->cmd_image))
179		{
180			$this->errors[] = $error;
181			return FALSE;
182		}
183
184		return TRUE;
185	}
186
187	public function sharpen($amount)
188	{
189		// Set the sigma, radius, and amount. The amount formula allows a nice
190		// spread between 1 and 100 without pixelizing the image badly.
191		$sigma  = 0.5;
192		$radius = $sigma * 2;
193		$amount = round(($amount / 80) * 3.14, 2);
194
195		// Convert the amount to an IM command
196		$sharpen = escapeshellarg($radius.'x'.$sigma.'+'.$amount.'+0');
197
198		if ($error = exec(escapeshellcmd($this->dir.'convert'.$this->ext).' -unsharp '.$sharpen.' '.$this->cmd_image.' '.$this->cmd_image))
199		{
200			$this->errors[] = $error;
201			return FALSE;
202		}
203
204		return TRUE;
205	}
206
207	protected function properties()
208	{
209		return array_slice(getimagesize($this->tmp_image), 0, 2, FALSE);
210	}
211
212} // End Image ImageMagick Driver