Aleksander Machniak
2014-09-15 83a64265a7d8865f0381e53a0cc47d6ef53217b6
commit | author | age
a71a97 1 <?php
A 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:                                                              |
19cc5b 14  |   Image resizer and converter                                         |
a71a97 15  +-----------------------------------------------------------------------+
A 16  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
17  | Author: Aleksander Machniak <alec@alec.pl>                            |
18  +-----------------------------------------------------------------------+
19 */
20
9ab346 21 /**
AM 22  * Image resizer and converter
23  *
24  * @package    Framework
25  * @subpackage Utils
26  */
a71a97 27 class rcube_image
A 28 {
29     private $image_file;
19cc5b 30
AM 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
a71a97 43
A 44     function __construct($filename)
45     {
46         $this->image_file = $filename;
47     }
48
49     /**
50      * Get image properties.
51      *
52      * @return mixed Hash array with image props like type, width, height
53      */
54     public function props()
55     {
f5a7e1 56         // use GD extension
a71a97 57         if (function_exists('getimagesize') && ($imsize = @getimagesize($this->image_file))) {
A 58             $width   = $imsize[0];
59             $height  = $imsize[1];
60             $gd_type = $imsize['2'];
61             $type    = image_type_to_extension($imsize['2'], false);
62         }
63
f5a7e1 64         // use ImageMagick
A 65         if (!$type && ($data = $this->identify())) {
66             list($type, $width, $height) = $data;
a71a97 67         }
A 68
69         if ($type) {
70             return array(
71                 'type'    => $type,
72                 'gd_type' => $gd_type,
73                 'width'   => $width,
74                 'height'  => $height,
75             );
76         }
77     }
78
79     /**
139635 80      * Resize image to a given size. Use only to shrink an image.
AM 81      * If an image is smaller than specified size it will be not resized.
a71a97 82      *
A 83      * @param int    $size      Max width/height size
84      * @param string $filename  Output filename
031491 85      * @param boolean $browser_compat  Convert to image type displayable by any browser
a71a97 86      *
031491 87      * @return mixed Output type on success, False on failure
a71a97 88      */
031491 89     public function resize($size, $filename = null, $browser_compat = false)
a71a97 90     {
be98df 91         $result  = false;
A 92         $rcube   = rcube::get_instance();
b1b808 93         $convert = $rcube->config->get('im_convert_path', false);
be98df 94         $props   = $this->props();
a71a97 95
1cdcaf 96         if (empty($props)) {
AM 97             return false;
98         }
99
a71a97 100         if (!$filename) {
A 101             $filename = $this->image_file;
102         }
103
104         // use Imagemagick
8968f9 105         if ($convert || class_exists('Imagick', false)) {
AM 106             $p['out'] = $filename;
107             $p['in']  = $this->image_file;
108             $type     = $props['type'];
a71a97 109
f5a7e1 110             if (!$type && ($data = $this->identify())) {
A 111                 $type = $data[0];
a71a97 112             }
A 113
114             $type = strtr($type, array("jpeg" => "jpg", "tiff" => "tif", "ps" => "eps", "ept" => "eps"));
031491 115             $p['intype'] = $type;
TB 116
117             // convert to an image format every browser can display
118             if ($browser_compat && !in_array($type, array('jpg','gif','png'))) {
119                 $type = 'jpg';
120             }
121
328876 122             // If only one dimension is greater than the limit convert doesn't
AM 123             // work as expected, we need to calculate new dimensions
124             $scale = $size / max($props['width'], $props['height']);
a71a97 125
328876 126             // if file is smaller than the limit, we do nothing
AM 127             // but copy original file to destination file
128             if ($scale >= 1 && $p['intype'] == $type) {
129                 $result = ($this->image_file == $filename || copy($this->image_file, $filename)) ? '' : false;
130             }
131             else {
132                 $valid_types = "bmp,eps,gif,jp2,jpg,png,svg,tif";
133
134                 if (in_array($type, explode(',', $valid_types))) { // Valid type?
8968f9 135                     if ($scale >= 1) {
AM 136                         $width  = $props['width'];
137                         $height = $props['height'];
138                     }
139                     else {
140                         $width  = intval($props['width']  * $scale);
141                         $height = intval($props['height'] * $scale);
142                     }
143
144                     // use ImageMagick in command line
145                     if ($convert) {
146                         $p += array(
147                             'type'    => $type,
148                             'quality' => 75,
149                             'size'    => $width . 'x' . $height,
150                         );
151
152                         $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace sRGB -strip'
153                             . ' -quality {quality} -resize {size} {intype}:{in} {type}:{out}', $p);
154                     }
155                     // use PHP's Imagick class
156                     else {
157                         try {
158                             $image = new Imagick($this->image_file);
159                             $image = $image->flattenImages();
160
161                             $image->setImageColorspace(Imagick::COLORSPACE_SRGB);
162                             $image->setImageCompressionQuality(75);
163                             $image->setImageFormat($type);
164                             $image->stripImage();
165                             $image->scaleImage($width, $height);
166
167                             if ($image->writeImage($filename)) {
168                                 $result = '';
169                             }
170                         }
171                         catch (Exception $e) {
172                             rcube::raise_error($e, true, false);
173                         }
174                     }
328876 175                 }
a71a97 176             }
A 177
19cc5b 178             if ($result === '') {
b413bb 179                 @chmod($filename, 0600);
031491 180                 return $type;
a71a97 181             }
A 182         }
183
184         // use GD extension
9ac960 185         if ($props['gd_type']) {
AM 186             if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
a71a97 187                 $image = imagecreatefromjpeg($this->image_file);
139635 188                 $type  = 'jpg';
a71a97 189             }
9ac960 190             else if($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
a71a97 191                 $image = imagecreatefromgif($this->image_file);
c6183b 192                 $type  = 'gif';
a71a97 193             }
9ac960 194             else if($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
a71a97 195                 $image = imagecreatefrompng($this->image_file);
139635 196                 $type  = 'png';
9ac960 197             }
AM 198             else {
199                 // @TODO: print error to the log?
200                 return false;
a71a97 201             }
A 202
1cdcaf 203             if ($image === false) {
AM 204                 return false;
205             }
206
139635 207             $scale = $size / max($props['width'], $props['height']);
AM 208
209             // Imagemagick resize is implemented in shrinking mode (see -resize argument above)
210             // we do the same here, if an image is smaller than specified size
211             // we do nothing but copy original file to destination file
328876 212             if ($scale >= 1) {
AM 213                 $result = $this->image_file == $filename || copy($this->image_file, $filename);
139635 214             }
328876 215             else {
AM 216                 $width     = intval($props['width']  * $scale);
217                 $height    = intval($props['height'] * $scale);
218                 $new_image = imagecreatetruecolor($width, $height);
139635 219
328876 220                 // Fix transparency of gif/png image
AM 221                 if ($props['gd_type'] != IMAGETYPE_JPEG) {
222                     imagealphablending($new_image, false);
223                     imagesavealpha($new_image, true);
224                     $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127);
225                     imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent);
226                 }
a71a97 227
328876 228                 imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $props['width'], $props['height']);
AM 229                 $image = $new_image;
a71a97 230
e2dd31 231                 // fix rotation of image if EXIF data exists and specifies rotation (GD strips the EXIF data)
4045cd 232                 if ($this->image_file && function_exists('exif_read_data')) {
e2dd31 233                     $exif = exif_read_data($this->image_file);
BB 234                     if ($exif && $exif['Orientation']) {
4045cd 235                         switch ($exif['Orientation']) {
e2dd31 236                             case 3:
BB 237                                 $image = imagerotate($image, 180, 0);
238                                 break;
239                             case 6:
240                                 $image = imagerotate($image, -90, 0);
241                                 break;
242                             case 8:
243                                 $image = imagerotate($image, 90, 0);
244                                 break;
245                         }
246                     }
247                 }
248
328876 249                 if ($props['gd_type'] == IMAGETYPE_JPEG) {
AM 250                     $result = imagejpeg($image, $filename, 75);
251                 }
252                 elseif($props['gd_type'] == IMAGETYPE_GIF) {
253                     $result = imagegif($image, $filename);
254                 }
255                 elseif($props['gd_type'] == IMAGETYPE_PNG) {
256                     $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS);
257                 }
a71a97 258             }
A 259
260             if ($result) {
b413bb 261                 @chmod($filename, 0600);
031491 262                 return $type;
a71a97 263             }
A 264         }
265
19cc5b 266         // @TODO: print error to the log?
AM 267         return false;
268     }
269
270     /**
271      * Convert image to a given type
272      *
273      * @param int    $type      Destination file type (see class constants)
274      * @param string $filename  Output filename (if empty, original file will be used
275      *                          and filename extension will be modified)
276      *
277      * @return bool True on success, False on failure
278      */
279     public function convert($type, $filename = null)
280     {
281         $rcube   = rcube::get_instance();
282         $convert = $rcube->config->get('im_convert_path', false);
283
284         if (!$filename) {
285             $filename = $this->image_file;
286
287             // modify extension
288             if ($extension = self::$extensions[$type]) {
289                 $filename = preg_replace('/\.[^.]+$/', '', $filename) . '.' . $extension;
290             }
291         }
292
8968f9 293         // use ImageMagick in command line
19cc5b 294         if ($convert) {
AM 295             $p['in']   = $this->image_file;
296             $p['out']  = $filename;
297             $p['type'] = self::$extensions[$type];
298
7015dd 299             $result = rcube::exec($convert . ' 2>&1 -colorspace sRGB -strip -quality 75 {in} {type}:{out}', $p);
19cc5b 300
AM 301             if ($result === '') {
8968f9 302                 chmod($filename, 0600);
19cc5b 303                 return true;
8968f9 304             }
AM 305         }
306
307         // use PHP's Imagick class
308         if (class_exists('Imagick', false)) {
309             try {
310                 $image = new Imagick($this->image_file);
311
312                 $image->setImageColorspace(Imagick::COLORSPACE_SRGB);
313                 $image->setImageCompressionQuality(75);
314                 $image->setImageFormat(self::$extensions[$type]);
315                 $image->stripImage();
316
317                 if ($image->writeImage($filename)) {
318                     @chmod($filename, 0600);
319                     return true;
320                 }
321             }
322             catch (Exception $e) {
323                 rcube::raise_error($e, true, false);
19cc5b 324             }
AM 325         }
326
327         // use GD extension (TIFF isn't supported)
9ac960 328         $props = $this->props();
19cc5b 329
9ac960 330         if ($props['gd_type']) {
AM 331             if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
19cc5b 332                 $image = imagecreatefromjpeg($this->image_file);
AM 333             }
9ac960 334             else if ($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
19cc5b 335                 $image = imagecreatefromgif($this->image_file);
AM 336             }
9ac960 337             else if ($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
19cc5b 338                 $image = imagecreatefrompng($this->image_file);
AM 339             }
9ac960 340             else {
AM 341                 // @TODO: print error to the log?
342                 return false;
343             }
19cc5b 344
AM 345             if ($type == self::TYPE_JPG) {
346                 $result = imagejpeg($image, $filename, 75);
347             }
348             else if ($type == self::TYPE_GIF) {
349                 $result = imagegif($image, $filename);
350             }
351             else if ($type == self::TYPE_PNG) {
352                 $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS);
353             }
139635 354
AM 355             if ($result) {
b413bb 356                 @chmod($filename, 0600);
139635 357                 return true;
AM 358             }
19cc5b 359         }
a71a97 360
A 361         // @TODO: print error to the log?
362         return false;
363     }
364
f5a7e1 365     /**
8968f9 366      * Checks if image format conversion is supported
AM 367      *
368      * @return boolean True if specified format can be converted to another format
369      */
370     public static function is_convertable($mimetype = null)
371     {
372         $rcube = rcube::get_instance();
373
374         // @TODO: check if specified mimetype is really supported
375         return class_exists('Imagick', false) || $rcube->config->get('im_convert_path');
376     }
377
378     /**
379      * ImageMagick based image properties read.
f5a7e1 380      */
A 381     private function identify()
382     {
be98df 383         $rcube = rcube::get_instance();
f5a7e1 384
8968f9 385         // use ImageMagick in command line
be98df 386         if ($cmd = $rcube->config->get('im_identify_path')) {
f5a7e1 387             $args = array('in' => $this->image_file, 'format' => "%m %[fx:w] %[fx:h]");
be98df 388             $id   = rcube::exec($cmd. ' 2>/dev/null -format {format} {in}', $args);
f5a7e1 389
A 390             if ($id) {
391                 return explode(' ', strtolower($id));
392             }
393         }
19cc5b 394
8968f9 395         // use PHP's Imagick class
AM 396         if (class_exists('Imagick', false)) {
397             try {
398                 $image = new Imagick($this->image_file);
399
400                 return array(
401                     strtolower($image->getImageFormat()),
402                     $image->getImageWidth(),
403                     $image->getImageHeight(),
404                 );
405             }
406             catch (Exception $e) {}
407         }
408     }
a71a97 409 }