Mercurial > hg > rc2
comparison program/lib/Roundcube/rcube_image.php @ 0:4681f974d28b
vanilla 1.3.3 distro, I hope
| author | Charlie Root |
|---|---|
| date | Thu, 04 Jan 2018 15:52:31 -0500 |
| parents | |
| children |
comparison
equal
deleted
inserted
replaced
| -1:000000000000 | 0:4681f974d28b |
|---|---|
| 1 <?php | |
| 2 | |
| 3 /** | |
| 4 +-----------------------------------------------------------------------+ | |
| 5 | This file is part of the Roundcube Webmail client | | |
| 6 | Copyright (C) 2005-2012, The Roundcube Dev Team | | |
| 7 | Copyright (C) 2011-2012, Kolab Systems AG | | |
| 8 | | | |
| 9 | Licensed under the GNU General Public License version 3 or | | |
| 10 | any later version with exceptions for skins & plugins. | | |
| 11 | See the README file for a full license statement. | | |
| 12 | | | |
| 13 | PURPOSE: | | |
| 14 | Image resizer and converter | | |
| 15 +-----------------------------------------------------------------------+ | |
| 16 | Author: Thomas Bruederli <roundcube@gmail.com> | | |
| 17 | Author: Aleksander Machniak <alec@alec.pl> | | |
| 18 +-----------------------------------------------------------------------+ | |
| 19 */ | |
| 20 | |
| 21 /** | |
| 22 * Image resizer and converter | |
| 23 * | |
| 24 * @package Framework | |
| 25 * @subpackage Utils | |
| 26 */ | |
| 27 class rcube_image | |
| 28 { | |
| 29 private $image_file; | |
| 30 | |
| 31 const TYPE_GIF = 1; | |
| 32 const TYPE_JPG = 2; | |
| 33 const TYPE_PNG = 3; | |
| 34 const TYPE_TIF = 4; | |
| 35 | |
| 36 public static $extensions = array( | |
| 37 self::TYPE_GIF => 'gif', | |
| 38 self::TYPE_JPG => 'jpg', | |
| 39 self::TYPE_PNG => 'png', | |
| 40 self::TYPE_TIF => 'tif', | |
| 41 ); | |
| 42 | |
| 43 | |
| 44 /** | |
| 45 * Class constructor | |
| 46 * | |
| 47 * @param string $filename Image file name/path | |
| 48 */ | |
| 49 function __construct($filename) | |
| 50 { | |
| 51 $this->image_file = $filename; | |
| 52 } | |
| 53 | |
| 54 /** | |
| 55 * Get image properties. | |
| 56 * | |
| 57 * @return mixed Hash array with image props like type, width, height | |
| 58 */ | |
| 59 public function props() | |
| 60 { | |
| 61 // use GD extension | |
| 62 if (function_exists('getimagesize') && ($imsize = @getimagesize($this->image_file))) { | |
| 63 $width = $imsize[0]; | |
| 64 $height = $imsize[1]; | |
| 65 $gd_type = $imsize[2]; | |
| 66 $type = image_type_to_extension($gd_type, false); | |
| 67 $channels = $imsize['channels']; | |
| 68 } | |
| 69 | |
| 70 // use ImageMagick | |
| 71 if (!$type && ($data = $this->identify())) { | |
| 72 list($type, $width, $height) = $data; | |
| 73 $channels = null; | |
| 74 } | |
| 75 | |
| 76 if ($type) { | |
| 77 return array( | |
| 78 'type' => $type, | |
| 79 'gd_type' => $gd_type, | |
| 80 'width' => $width, | |
| 81 'height' => $height, | |
| 82 'channels' => $channels, | |
| 83 ); | |
| 84 } | |
| 85 | |
| 86 return null; | |
| 87 } | |
| 88 | |
| 89 /** | |
| 90 * Resize image to a given size. Use only to shrink an image. | |
| 91 * If an image is smaller than specified size it will be not resized. | |
| 92 * | |
| 93 * @param int $size Max width/height size | |
| 94 * @param string $filename Output filename | |
| 95 * @param boolean $browser_compat Convert to image type displayable by any browser | |
| 96 * | |
| 97 * @return mixed Output type on success, False on failure | |
| 98 */ | |
| 99 public function resize($size, $filename = null, $browser_compat = false) | |
| 100 { | |
| 101 $result = false; | |
| 102 $rcube = rcube::get_instance(); | |
| 103 $convert = $rcube->config->get('im_convert_path', false); | |
| 104 $props = $this->props(); | |
| 105 | |
| 106 if (empty($props)) { | |
| 107 return false; | |
| 108 } | |
| 109 | |
| 110 if (!$filename) { | |
| 111 $filename = $this->image_file; | |
| 112 } | |
| 113 | |
| 114 // use Imagemagick | |
| 115 if ($convert || class_exists('Imagick', false)) { | |
| 116 $p['out'] = $filename; | |
| 117 $p['in'] = $this->image_file; | |
| 118 $type = $props['type']; | |
| 119 | |
| 120 if (!$type && ($data = $this->identify())) { | |
| 121 $type = $data[0]; | |
| 122 } | |
| 123 | |
| 124 $type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps")); | |
| 125 $p['intype'] = $type; | |
| 126 | |
| 127 // convert to an image format every browser can display | |
| 128 if ($browser_compat && !in_array($type, array('jpg','gif','png'))) { | |
| 129 $type = 'jpg'; | |
| 130 } | |
| 131 | |
| 132 // If only one dimension is greater than the limit convert doesn't | |
| 133 // work as expected, we need to calculate new dimensions | |
| 134 $scale = $size / max($props['width'], $props['height']); | |
| 135 | |
| 136 // if file is smaller than the limit, we do nothing | |
| 137 // but copy original file to destination file | |
| 138 if ($scale >= 1 && $p['intype'] == $type) { | |
| 139 $result = ($this->image_file == $filename || copy($this->image_file, $filename)) ? '' : false; | |
| 140 } | |
| 141 else { | |
| 142 $valid_types = "bmp,eps,gif,jp2,jpg,png,svg,tif"; | |
| 143 | |
| 144 if (in_array($type, explode(',', $valid_types))) { // Valid type? | |
| 145 if ($scale >= 1) { | |
| 146 $width = $props['width']; | |
| 147 $height = $props['height']; | |
| 148 } | |
| 149 else { | |
| 150 $width = intval($props['width'] * $scale); | |
| 151 $height = intval($props['height'] * $scale); | |
| 152 } | |
| 153 | |
| 154 // use ImageMagick in command line | |
| 155 if ($convert) { | |
| 156 $p += array( | |
| 157 'type' => $type, | |
| 158 'quality' => 75, | |
| 159 'size' => $width . 'x' . $height, | |
| 160 ); | |
| 161 | |
| 162 $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace sRGB -strip' | |
| 163 . ' -quality {quality} -resize {size} {intype}:{in} {type}:{out}', $p); | |
| 164 } | |
| 165 // use PHP's Imagick class | |
| 166 else { | |
| 167 try { | |
| 168 $image = new Imagick($this->image_file); | |
| 169 | |
| 170 try { | |
| 171 // it throws exception on formats not supporting these features | |
| 172 $image->setImageBackgroundColor('white'); | |
| 173 $image->setImageAlphaChannel(11); | |
| 174 $image->mergeImageLayers(Imagick::LAYERMETHOD_FLATTEN); | |
| 175 } | |
| 176 catch (Exception $e) { | |
| 177 // ignore errors | |
| 178 } | |
| 179 | |
| 180 $image->setImageColorspace(Imagick::COLORSPACE_SRGB); | |
| 181 $image->setImageCompressionQuality(75); | |
| 182 $image->setImageFormat($type); | |
| 183 $image->stripImage(); | |
| 184 $image->scaleImage($width, $height); | |
| 185 | |
| 186 if ($image->writeImage($filename)) { | |
| 187 $result = ''; | |
| 188 } | |
| 189 } | |
| 190 catch (Exception $e) { | |
| 191 rcube::raise_error($e, true, false); | |
| 192 } | |
| 193 } | |
| 194 } | |
| 195 } | |
| 196 | |
| 197 if ($result === '') { | |
| 198 @chmod($filename, 0600); | |
| 199 return $type; | |
| 200 } | |
| 201 } | |
| 202 | |
| 203 // do we have enough memory? (#1489937) | |
| 204 if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && !$this->mem_check($props)) { | |
| 205 return false; | |
| 206 } | |
| 207 | |
| 208 // use GD extension | |
| 209 if ($props['gd_type']) { | |
| 210 if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) { | |
| 211 $image = imagecreatefromjpeg($this->image_file); | |
| 212 $type = 'jpg'; | |
| 213 } | |
| 214 else if ($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) { | |
| 215 $image = imagecreatefromgif($this->image_file); | |
| 216 $type = 'gif'; | |
| 217 } | |
| 218 else if ($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) { | |
| 219 $image = imagecreatefrompng($this->image_file); | |
| 220 $type = 'png'; | |
| 221 } | |
| 222 else { | |
| 223 // @TODO: print error to the log? | |
| 224 return false; | |
| 225 } | |
| 226 | |
| 227 if ($image === false) { | |
| 228 return false; | |
| 229 } | |
| 230 | |
| 231 $scale = $size / max($props['width'], $props['height']); | |
| 232 | |
| 233 // Imagemagick resize is implemented in shrinking mode (see -resize argument above) | |
| 234 // we do the same here, if an image is smaller than specified size | |
| 235 // we do nothing but copy original file to destination file | |
| 236 if ($scale >= 1) { | |
| 237 $result = $this->image_file == $filename || copy($this->image_file, $filename); | |
| 238 } | |
| 239 else { | |
| 240 $width = intval($props['width'] * $scale); | |
| 241 $height = intval($props['height'] * $scale); | |
| 242 $new_image = imagecreatetruecolor($width, $height); | |
| 243 | |
| 244 if ($new_image === false) { | |
| 245 return false; | |
| 246 } | |
| 247 | |
| 248 // Fix transparency of gif/png image | |
| 249 if ($props['gd_type'] != IMAGETYPE_JPEG) { | |
| 250 imagealphablending($new_image, false); | |
| 251 imagesavealpha($new_image, true); | |
| 252 $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127); | |
| 253 imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent); | |
| 254 } | |
| 255 | |
| 256 imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $props['width'], $props['height']); | |
| 257 $image = $new_image; | |
| 258 | |
| 259 // fix rotation of image if EXIF data exists and specifies rotation (GD strips the EXIF data) | |
| 260 if ($this->image_file && $type == 'jpg' && function_exists('exif_read_data')) { | |
| 261 $exif = exif_read_data($this->image_file); | |
| 262 if ($exif && $exif['Orientation']) { | |
| 263 switch ($exif['Orientation']) { | |
| 264 case 3: | |
| 265 $image = imagerotate($image, 180, 0); | |
| 266 break; | |
| 267 case 6: | |
| 268 $image = imagerotate($image, -90, 0); | |
| 269 break; | |
| 270 case 8: | |
| 271 $image = imagerotate($image, 90, 0); | |
| 272 break; | |
| 273 } | |
| 274 } | |
| 275 } | |
| 276 | |
| 277 if ($props['gd_type'] == IMAGETYPE_JPEG) { | |
| 278 $result = imagejpeg($image, $filename, 75); | |
| 279 } | |
| 280 elseif($props['gd_type'] == IMAGETYPE_GIF) { | |
| 281 $result = imagegif($image, $filename); | |
| 282 } | |
| 283 elseif($props['gd_type'] == IMAGETYPE_PNG) { | |
| 284 $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS); | |
| 285 } | |
| 286 } | |
| 287 | |
| 288 if ($result) { | |
| 289 @chmod($filename, 0600); | |
| 290 return $type; | |
| 291 } | |
| 292 } | |
| 293 | |
| 294 // @TODO: print error to the log? | |
| 295 return false; | |
| 296 } | |
| 297 | |
| 298 /** | |
| 299 * Convert image to a given type | |
| 300 * | |
| 301 * @param int $type Destination file type (see class constants) | |
| 302 * @param string $filename Output filename (if empty, original file will be used | |
| 303 * and filename extension will be modified) | |
| 304 * | |
| 305 * @return bool True on success, False on failure | |
| 306 */ | |
| 307 public function convert($type, $filename = null) | |
| 308 { | |
| 309 $rcube = rcube::get_instance(); | |
| 310 $convert = $rcube->config->get('im_convert_path', false); | |
| 311 | |
| 312 if (!$filename) { | |
| 313 $filename = $this->image_file; | |
| 314 | |
| 315 // modify extension | |
| 316 if ($extension = self::$extensions[$type]) { | |
| 317 $filename = preg_replace('/\.[^.]+$/', '', $filename) . '.' . $extension; | |
| 318 } | |
| 319 } | |
| 320 | |
| 321 // use ImageMagick in command line | |
| 322 if ($convert) { | |
| 323 $p['in'] = $this->image_file; | |
| 324 $p['out'] = $filename; | |
| 325 $p['type'] = self::$extensions[$type]; | |
| 326 | |
| 327 $result = rcube::exec($convert . ' 2>&1 -colorspace sRGB -strip -quality 75 {in} {type}:{out}', $p); | |
| 328 | |
| 329 if ($result === '') { | |
| 330 chmod($filename, 0600); | |
| 331 return true; | |
| 332 } | |
| 333 } | |
| 334 | |
| 335 // use PHP's Imagick class | |
| 336 if (class_exists('Imagick', false)) { | |
| 337 try { | |
| 338 $image = new Imagick($this->image_file); | |
| 339 | |
| 340 $image->setImageColorspace(Imagick::COLORSPACE_SRGB); | |
| 341 $image->setImageCompressionQuality(75); | |
| 342 $image->setImageFormat(self::$extensions[$type]); | |
| 343 $image->stripImage(); | |
| 344 | |
| 345 if ($image->writeImage($filename)) { | |
| 346 @chmod($filename, 0600); | |
| 347 return true; | |
| 348 } | |
| 349 } | |
| 350 catch (Exception $e) { | |
| 351 rcube::raise_error($e, true, false); | |
| 352 } | |
| 353 } | |
| 354 | |
| 355 // use GD extension (TIFF isn't supported) | |
| 356 $props = $this->props(); | |
| 357 | |
| 358 // do we have enough memory? (#1489937) | |
| 359 if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && !$this->mem_check($props)) { | |
| 360 return false; | |
| 361 } | |
| 362 | |
| 363 if ($props['gd_type']) { | |
| 364 if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) { | |
| 365 $image = imagecreatefromjpeg($this->image_file); | |
| 366 } | |
| 367 else if ($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) { | |
| 368 $image = imagecreatefromgif($this->image_file); | |
| 369 } | |
| 370 else if ($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) { | |
| 371 $image = imagecreatefrompng($this->image_file); | |
| 372 } | |
| 373 else { | |
| 374 // @TODO: print error to the log? | |
| 375 return false; | |
| 376 } | |
| 377 | |
| 378 if ($type == self::TYPE_JPG) { | |
| 379 $result = imagejpeg($image, $filename, 75); | |
| 380 } | |
| 381 else if ($type == self::TYPE_GIF) { | |
| 382 $result = imagegif($image, $filename); | |
| 383 } | |
| 384 else if ($type == self::TYPE_PNG) { | |
| 385 $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS); | |
| 386 } | |
| 387 | |
| 388 if ($result) { | |
| 389 @chmod($filename, 0600); | |
| 390 return true; | |
| 391 } | |
| 392 } | |
| 393 | |
| 394 // @TODO: print error to the log? | |
| 395 return false; | |
| 396 } | |
| 397 | |
| 398 /** | |
| 399 * Checks if image format conversion is supported | |
| 400 * | |
| 401 * @return boolean True if specified format can be converted to another format | |
| 402 */ | |
| 403 public static function is_convertable($mimetype = null) | |
| 404 { | |
| 405 $rcube = rcube::get_instance(); | |
| 406 | |
| 407 // @TODO: check if specified mimetype is really supported | |
| 408 return class_exists('Imagick', false) || $rcube->config->get('im_convert_path'); | |
| 409 } | |
| 410 | |
| 411 /** | |
| 412 * ImageMagick based image properties read. | |
| 413 */ | |
| 414 private function identify() | |
| 415 { | |
| 416 $rcube = rcube::get_instance(); | |
| 417 | |
| 418 // use ImageMagick in command line | |
| 419 if ($cmd = $rcube->config->get('im_identify_path')) { | |
| 420 $args = array('in' => $this->image_file, 'format' => "%m %[fx:w] %[fx:h]"); | |
| 421 $id = rcube::exec($cmd. ' 2>/dev/null -format {format} {in}', $args); | |
| 422 | |
| 423 if ($id) { | |
| 424 return explode(' ', strtolower($id)); | |
| 425 } | |
| 426 } | |
| 427 | |
| 428 // use PHP's Imagick class | |
| 429 if (class_exists('Imagick', false)) { | |
| 430 try { | |
| 431 $image = new Imagick($this->image_file); | |
| 432 | |
| 433 return array( | |
| 434 strtolower($image->getImageFormat()), | |
| 435 $image->getImageWidth(), | |
| 436 $image->getImageHeight(), | |
| 437 ); | |
| 438 } | |
| 439 catch (Exception $e) {} | |
| 440 } | |
| 441 } | |
| 442 | |
| 443 /** | |
| 444 * Check if we have enough memory to load specified image | |
| 445 */ | |
| 446 private function mem_check($props) | |
| 447 { | |
| 448 // image size is unknown, we can't calculate required memory | |
| 449 if (!$props['width']) { | |
| 450 return true; | |
| 451 } | |
| 452 | |
| 453 // channels: CMYK - 4, RGB - 3 | |
| 454 $multip = ($props['channels'] ?: 3) + 1; | |
| 455 | |
| 456 // calculate image size in memory (in bytes) | |
| 457 $size = $props['width'] * $props['height'] * $multip; | |
| 458 return rcube_utils::mem_check($size); | |
| 459 } | |
| 460 } |
