Thomas Bruederli
2014-01-30 8c893b79d230cc844226bdf3b1de95cd5a99ecb2
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
105         if ($convert) {
106             $p['out']  = $filename;
107             $p['in']   = $this->image_file;
f5a7e1 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                 if ($scale >= 1) {
133                     $width  = $props['width'];
134                     $height = $props['height'];
135                 }
136                 else {
137                     $width  = intval($props['width']  * $scale);
138                     $height = intval($props['height'] * $scale);
139                 }
140
141                 $valid_types = "bmp,eps,gif,jp2,jpg,png,svg,tif";
142
143                 $p += array(
144                     'type'    => $type,
145                     'quality' => 75,
146                     'size'    => $width . 'x' . $height,
147                 );
148
149                 if (in_array($type, explode(',', $valid_types))) { // Valid type?
150                     $result = rcube::exec($convert . ' 2>&1 -flatten -auto-orient -colorspace sRGB -strip'
151                         . ' -quality {quality} -resize {size} {intype}:{in} {type}:{out}', $p);
152                 }
a71a97 153             }
A 154
19cc5b 155             if ($result === '') {
b413bb 156                 @chmod($filename, 0600);
031491 157                 return $type;
a71a97 158             }
A 159         }
160
161         // use GD extension
9ac960 162         if ($props['gd_type']) {
AM 163             if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
a71a97 164                 $image = imagecreatefromjpeg($this->image_file);
139635 165                 $type  = 'jpg';
a71a97 166             }
9ac960 167             else if($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
a71a97 168                 $image = imagecreatefromgif($this->image_file);
139635 169                 $type  = 'gid';
a71a97 170             }
9ac960 171             else if($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
a71a97 172                 $image = imagecreatefrompng($this->image_file);
139635 173                 $type  = 'png';
9ac960 174             }
AM 175             else {
176                 // @TODO: print error to the log?
177                 return false;
a71a97 178             }
A 179
1cdcaf 180             if ($image === false) {
AM 181                 return false;
182             }
183
139635 184             $scale = $size / max($props['width'], $props['height']);
AM 185
186             // Imagemagick resize is implemented in shrinking mode (see -resize argument above)
187             // we do the same here, if an image is smaller than specified size
188             // we do nothing but copy original file to destination file
328876 189             if ($scale >= 1) {
AM 190                 $result = $this->image_file == $filename || copy($this->image_file, $filename);
139635 191             }
328876 192             else {
AM 193                 $width     = intval($props['width']  * $scale);
194                 $height    = intval($props['height'] * $scale);
195                 $new_image = imagecreatetruecolor($width, $height);
139635 196
328876 197                 // Fix transparency of gif/png image
AM 198                 if ($props['gd_type'] != IMAGETYPE_JPEG) {
199                     imagealphablending($new_image, false);
200                     imagesavealpha($new_image, true);
201                     $transparent = imagecolorallocatealpha($new_image, 255, 255, 255, 127);
202                     imagefilledrectangle($new_image, 0, 0, $width, $height, $transparent);
203                 }
a71a97 204
328876 205                 imagecopyresampled($new_image, $image, 0, 0, 0, 0, $width, $height, $props['width'], $props['height']);
AM 206                 $image = $new_image;
a71a97 207
328876 208                 if ($props['gd_type'] == IMAGETYPE_JPEG) {
AM 209                     $result = imagejpeg($image, $filename, 75);
210                 }
211                 elseif($props['gd_type'] == IMAGETYPE_GIF) {
212                     $result = imagegif($image, $filename);
213                 }
214                 elseif($props['gd_type'] == IMAGETYPE_PNG) {
215                     $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS);
216                 }
a71a97 217             }
A 218
219             if ($result) {
b413bb 220                 @chmod($filename, 0600);
031491 221                 return $type;
a71a97 222             }
A 223         }
224
19cc5b 225         // @TODO: print error to the log?
AM 226         return false;
227     }
228
229     /**
230      * Convert image to a given type
231      *
232      * @param int    $type      Destination file type (see class constants)
233      * @param string $filename  Output filename (if empty, original file will be used
234      *                          and filename extension will be modified)
235      *
236      * @return bool True on success, False on failure
237      */
238     public function convert($type, $filename = null)
239     {
240         $rcube   = rcube::get_instance();
241         $convert = $rcube->config->get('im_convert_path', false);
242
243         if (!$filename) {
244             $filename = $this->image_file;
245
246             // modify extension
247             if ($extension = self::$extensions[$type]) {
248                 $filename = preg_replace('/\.[^.]+$/', '', $filename) . '.' . $extension;
249             }
250         }
251
252         // use ImageMagick
253         if ($convert) {
254             $p['in']   = $this->image_file;
255             $p['out']  = $filename;
256             $p['type'] = self::$extensions[$type];
257
7015dd 258             $result = rcube::exec($convert . ' 2>&1 -colorspace sRGB -strip -quality 75 {in} {type}:{out}', $p);
19cc5b 259
AM 260             if ($result === '') {
b413bb 261                 @chmod($filename, 0600);
19cc5b 262                 return true;
AM 263             }
264         }
265
266         // use GD extension (TIFF isn't supported)
9ac960 267         $props = $this->props();
19cc5b 268
9ac960 269         if ($props['gd_type']) {
AM 270             if ($props['gd_type'] == IMAGETYPE_JPEG && function_exists('imagecreatefromjpeg')) {
19cc5b 271                 $image = imagecreatefromjpeg($this->image_file);
AM 272             }
9ac960 273             else if ($props['gd_type'] == IMAGETYPE_GIF && function_exists('imagecreatefromgif')) {
19cc5b 274                 $image = imagecreatefromgif($this->image_file);
AM 275             }
9ac960 276             else if ($props['gd_type'] == IMAGETYPE_PNG && function_exists('imagecreatefrompng')) {
19cc5b 277                 $image = imagecreatefrompng($this->image_file);
AM 278             }
9ac960 279             else {
AM 280                 // @TODO: print error to the log?
281                 return false;
282             }
19cc5b 283
AM 284             if ($type == self::TYPE_JPG) {
285                 $result = imagejpeg($image, $filename, 75);
286             }
287             else if ($type == self::TYPE_GIF) {
288                 $result = imagegif($image, $filename);
289             }
290             else if ($type == self::TYPE_PNG) {
291                 $result = imagepng($image, $filename, 6, PNG_ALL_FILTERS);
292             }
139635 293
AM 294             if ($result) {
b413bb 295                 @chmod($filename, 0600);
139635 296                 return true;
AM 297             }
19cc5b 298         }
a71a97 299
A 300         // @TODO: print error to the log?
301         return false;
302     }
303
f5a7e1 304     /**
A 305      * Identify command handler.
306      */
307     private function identify()
308     {
be98df 309         $rcube = rcube::get_instance();
f5a7e1 310
be98df 311         if ($cmd = $rcube->config->get('im_identify_path')) {
f5a7e1 312             $args = array('in' => $this->image_file, 'format' => "%m %[fx:w] %[fx:h]");
be98df 313             $id   = rcube::exec($cmd. ' 2>/dev/null -format {format} {in}', $args);
f5a7e1 314
A 315             if ($id) {
316                 return explode(' ', strtolower($id));
317             }
318         }
319     }
19cc5b 320
a71a97 321 }