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