Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F4682992
rcube_image.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
17 KB
Referenced Files
None
Subscribers
None
rcube_image.php
View Options
<?php
/**
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
| Copyright (C) The Roundcube Dev Team |
| Copyright (C) Kolab Systems AG |
| |
| Licensed under the GNU General Public License version 3 or |
| any later version with exceptions for skins & plugins. |
| See the README file for a full license statement. |
| |
| PURPOSE: |
| Image resizer and converter |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
*/
/**
* Image resizer and converter
*
* @package Framework
* @subpackage Utils
*/
class
rcube_image
{
private
$image_file
;
const
TYPE_GIF
=
1
;
const
TYPE_JPG
=
2
;
const
TYPE_PNG
=
3
;
const
TYPE_TIF
=
4
;
public
static
$extensions
=
array
(
self
::
TYPE_GIF
=>
'gif'
,
self
::
TYPE_JPG
=>
'jpg'
,
self
::
TYPE_PNG
=>
'png'
,
self
::
TYPE_TIF
=>
'tif'
,
);
/**
* Class constructor
*
* @param string $filename Image file name/path
*/
function
__construct
(
$filename
)
{
$this
->
image_file
=
$filename
;
}
/**
* Get image properties.
*
* @return mixed Hash array with image props like type, width, height
*/
public
function
props
()
{
// use GD extension
if
(
function_exists
(
'getimagesize'
)
&&
(
$imsize
=
@
getimagesize
(
$this
->
image_file
)))
{
$width
=
$imsize
[
0
];
$height
=
$imsize
[
1
];
$gd_type
=
$imsize
[
2
];
$type
=
image_type_to_extension
(
$gd_type
,
false
);
$channels
=
$imsize
[
'channels'
];
}
// use ImageMagick
if
(!
$type
&&
(
$data
=
$this
->
identify
()))
{
list
(
$type
,
$width
,
$height
)
=
$data
;
$channels
=
null
;
}
if
(
$type
)
{
return
array
(
'type'
=>
$type
,
'gd_type'
=>
$gd_type
,
'width'
=>
$width
,
'height'
=>
$height
,
'channels'
=>
$channels
,
);
}
}
/**
* Resize image to a given size. Use only to shrink an image.
* If an image is smaller than specified size it will be not resized.
*
* @param int $size Max width/height size
* @param string $filename Output filename
* @param boolean $browser_compat Convert to image type displayable by any browser
*
* @return mixed Output type on success, False on failure
*/
public
function
resize
(
$size
,
$filename
=
null
,
$browser_compat
=
false
)
{
$result
=
false
;
$rcube
=
rcube
::
get_instance
();
$convert
=
self
::
getCommand
(
'im_convert_path'
);
$props
=
$this
->
props
();
if
(
empty
(
$props
))
{
return
false
;
}
if
(!
$filename
)
{
$filename
=
$this
->
image_file
;
}
// use Imagemagick
if
(
$convert
||
class_exists
(
'Imagick'
,
false
))
{
$p
[
'out'
]
=
$filename
;
$p
[
'in'
]
=
$this
->
image_file
;
$type
=
$props
[
'type'
];
if
(!
$type
&&
(
$data
=
$this
->
identify
()))
{
$type
=
$data
[
0
];
}
$type
=
strtr
(
$type
,
array
(
"jpeg"
=>
"jpg"
,
"tiff"
=>
"tif"
,
"ps"
=>
"eps"
,
"ept"
=>
"eps"
));
$p
[
'intype'
]
=
$type
;
// convert to an image format every browser can display
if
(
$browser_compat
&&
!
in_array
(
$type
,
array
(
'jpg'
,
'gif'
,
'png'
)))
{
$type
=
'jpg'
;
}
// If only one dimension is greater than the limit convert doesn't
// work as expected, we need to calculate new dimensions
$scale
=
$size
/
max
(
$props
[
'width'
],
$props
[
'height'
]);
// if file is smaller than the limit, we do nothing
// but copy original file to destination file
if
(
$scale
>=
1
&&
$p
[
'intype'
]
==
$type
)
{
$result
=
(
$this
->
image_file
==
$filename
||
copy
(
$this
->
image_file
,
$filename
))
?
''
:
false
;
}
else
{
$valid_types
=
"bmp,eps,gif,jp2,jpg,png,svg,tif"
;
if
(
in_array
(
$type
,
explode
(
','
,
$valid_types
)))
{
// Valid type?
if
(
$scale
>=
1
)
{
$width
=
$props
[
'width'
];
$height
=
$props
[
'height'
];
}
else
{
$width
=
intval
(
$props
[
'width'
]
*
$scale
);
$height
=
intval
(
$props
[
'height'
]
*
$scale
);
}
// use ImageMagick in command line
if
(
$convert
)
{
$p
+=
array
(
'type'
=>
$type
,
'quality'
=>
75
,
'size'
=>
$width
.
'x'
.
$height
,
);
$result
=
rcube
::
exec
(
$convert
.
' 2>&1 -flatten -auto-orient -colorspace sRGB -strip'
.
' -quality {quality} -resize {size} {intype}:{in} {type}:{out}'
,
$p
);
}
// use PHP's Imagick class
else
{
try
{
$image
=
new
Imagick
(
$this
->
image_file
);
try
{
// it throws exception on formats not supporting these features
$image
->
setImageBackgroundColor
(
'white'
);
$image
->
setImageAlphaChannel
(
11
);
$image
->
mergeImageLayers
(
Imagick
::
LAYERMETHOD_FLATTEN
);
}
catch
(
Exception
$e
)
{
// ignore errors
}
$image
->
setImageColorspace
(
Imagick
::
COLORSPACE_SRGB
);
$image
->
setImageCompressionQuality
(
75
);
$image
->
setImageFormat
(
$type
);
$image
->
stripImage
();
$image
->
scaleImage
(
$width
,
$height
);
if
(
$image
->
writeImage
(
$filename
))
{
$result
=
''
;
}
}
catch
(
Exception
$e
)
{
rcube
::
raise_error
(
$e
,
true
,
false
);
}
}
}
}
if
(
$result
===
''
)
{
@
chmod
(
$filename
,
0600
);
return
$type
;
}
}
// do we have enough memory? (#1489937)
if
(
strtoupper
(
substr
(
PHP_OS
,
0
,
3
))
==
'WIN'
&&
!
$this
->
mem_check
(
$props
))
{
return
false
;
}
// use GD extension
if
(
$props
[
'gd_type'
])
{
if
(
$props
[
'gd_type'
]
==
IMAGETYPE_JPEG
&&
function_exists
(
'imagecreatefromjpeg'
))
{
$image
=
imagecreatefromjpeg
(
$this
->
image_file
);
$type
=
'jpg'
;
}
else
if
(
$props
[
'gd_type'
]
==
IMAGETYPE_GIF
&&
function_exists
(
'imagecreatefromgif'
))
{
$image
=
imagecreatefromgif
(
$this
->
image_file
);
$type
=
'gif'
;
}
else
if
(
$props
[
'gd_type'
]
==
IMAGETYPE_PNG
&&
function_exists
(
'imagecreatefrompng'
))
{
$image
=
imagecreatefrompng
(
$this
->
image_file
);
$type
=
'png'
;
}
else
{
// @TODO: print error to the log?
return
false
;
}
if
(
$image
===
false
)
{
return
false
;
}
$scale
=
$size
/
max
(
$props
[
'width'
],
$props
[
'height'
]);
// Imagemagick resize is implemented in shrinking mode (see -resize argument above)
// we do the same here, if an image is smaller than specified size
// we do nothing but copy original file to destination file
if
(
$scale
>=
1
)
{
$result
=
$this
->
image_file
==
$filename
||
copy
(
$this
->
image_file
,
$filename
);
}
else
{
$width
=
intval
(
$props
[
'width'
]
*
$scale
);
$height
=
intval
(
$props
[
'height'
]
*
$scale
);
$new_image
=
imagecreatetruecolor
(
$width
,
$height
);
if
(
$new_image
===
false
)
{
return
false
;
}
// Fix transparency of gif/png image
if
(
$props
[
'gd_type'
]
!=
IMAGETYPE_JPEG
)
{
imagealphablending
(
$new_image
,
false
);
imagesavealpha
(
$new_image
,
true
);
$transparent
=
imagecolorallocatealpha
(
$new_image
,
255
,
255
,
255
,
127
);
imagefilledrectangle
(
$new_image
,
0
,
0
,
$width
,
$height
,
$transparent
);
}
imagecopyresampled
(
$new_image
,
$image
,
0
,
0
,
0
,
0
,
$width
,
$height
,
$props
[
'width'
],
$props
[
'height'
]);
$image
=
$new_image
;
// fix orientation of image if EXIF data exists and specifies orientation (GD strips the EXIF data)
if
(
$this
->
image_file
&&
$type
==
'jpg'
&&
function_exists
(
'exif_read_data'
))
{
$exif
=
@
exif_read_data
(
$this
->
image_file
);
if
(
$exif
&&
$exif
[
'Orientation'
])
{
switch
(
$exif
[
'Orientation'
])
{
case
3
:
$image
=
imagerotate
(
$image
,
180
,
0
);
break
;
case
6
:
$image
=
imagerotate
(
$image
,
-
90
,
0
);
break
;
case
8
:
$image
=
imagerotate
(
$image
,
90
,
0
);
break
;
}
}
}
if
(
$props
[
'gd_type'
]
==
IMAGETYPE_JPEG
)
{
$result
=
imagejpeg
(
$image
,
$filename
,
75
);
}
elseif
(
$props
[
'gd_type'
]
==
IMAGETYPE_GIF
)
{
$result
=
imagegif
(
$image
,
$filename
);
}
elseif
(
$props
[
'gd_type'
]
==
IMAGETYPE_PNG
)
{
$result
=
imagepng
(
$image
,
$filename
,
6
,
PNG_ALL_FILTERS
);
}
}
if
(
$result
)
{
@
chmod
(
$filename
,
0600
);
return
$type
;
}
}
// @TODO: print error to the log?
return
false
;
}
/**
* Convert image to a given type
*
* @param int $type Destination file type (see class constants)
* @param string $filename Output filename (if empty, original file will be used
* and filename extension will be modified)
*
* @return bool True on success, False on failure
*/
public
function
convert
(
$type
,
$filename
=
null
)
{
$rcube
=
rcube
::
get_instance
();
$convert
=
self
::
getCommand
(
'im_convert_path'
);
if
(!
$filename
)
{
$filename
=
$this
->
image_file
;
// modify extension
if
(
$extension
=
self
::
$extensions
[
$type
])
{
$filename
=
preg_replace
(
'/
\.
[^.]+$/'
,
''
,
$filename
)
.
'.'
.
$extension
;
}
}
// use ImageMagick in command line
if
(
$convert
)
{
$p
[
'in'
]
=
$this
->
image_file
;
$p
[
'out'
]
=
$filename
;
$p
[
'type'
]
=
self
::
$extensions
[
$type
];
$result
=
rcube
::
exec
(
$convert
.
' 2>&1 -colorspace sRGB -strip -flatten -quality 75 {in} {type}:{out}'
,
$p
);
if
(
$result
===
''
)
{
chmod
(
$filename
,
0600
);
return
true
;
}
}
// use PHP's Imagick class
if
(
class_exists
(
'Imagick'
,
false
))
{
try
{
$image
=
new
Imagick
(
$this
->
image_file
);
$image
->
setImageColorspace
(
Imagick
::
COLORSPACE_SRGB
);
$image
->
setImageCompressionQuality
(
75
);
$image
->
setImageFormat
(
self
::
$extensions
[
$type
]);
$image
->
stripImage
();
if
(
$image
->
writeImage
(
$filename
))
{
@
chmod
(
$filename
,
0600
);
return
true
;
}
}
catch
(
Exception
$e
)
{
rcube
::
raise_error
(
$e
,
true
,
false
);
}
}
// use GD extension (TIFF isn't supported)
$props
=
$this
->
props
();
// do we have enough memory? (#1489937)
if
(
strtoupper
(
substr
(
PHP_OS
,
0
,
3
))
==
'WIN'
&&
!
$this
->
mem_check
(
$props
))
{
return
false
;
}
if
(
$props
[
'gd_type'
])
{
if
(
$props
[
'gd_type'
]
==
IMAGETYPE_JPEG
&&
function_exists
(
'imagecreatefromjpeg'
))
{
$image
=
imagecreatefromjpeg
(
$this
->
image_file
);
}
else
if
(
$props
[
'gd_type'
]
==
IMAGETYPE_GIF
&&
function_exists
(
'imagecreatefromgif'
))
{
$image
=
imagecreatefromgif
(
$this
->
image_file
);
}
else
if
(
$props
[
'gd_type'
]
==
IMAGETYPE_PNG
&&
function_exists
(
'imagecreatefrompng'
))
{
$image
=
imagecreatefrompng
(
$this
->
image_file
);
}
else
{
// @TODO: print error to the log?
return
false
;
}
if
(
$type
==
self
::
TYPE_JPG
)
{
$result
=
imagejpeg
(
$image
,
$filename
,
75
);
}
else
if
(
$type
==
self
::
TYPE_GIF
)
{
$result
=
imagegif
(
$image
,
$filename
);
}
else
if
(
$type
==
self
::
TYPE_PNG
)
{
$result
=
imagepng
(
$image
,
$filename
,
6
,
PNG_ALL_FILTERS
);
}
if
(
$result
)
{
@
chmod
(
$filename
,
0600
);
return
true
;
}
}
// @TODO: print error to the log?
return
false
;
}
/**
* Checks if image format conversion is supported (for specified mimetype).
*
* @param string $mimetype Mimetype name
*
* @return boolean True if specified format can be converted to another format
*/
public
static
function
is_convertable
(
$mimetype
=
null
)
{
$rcube
=
rcube
::
get_instance
();
// @TODO: check if specified mimetype is really supported
return
class_exists
(
'Imagick'
,
false
)
||
self
::
getCommand
(
'im_convert_path'
);
}
/**
* ImageMagick based image properties read.
*/
private
function
identify
()
{
$rcube
=
rcube
::
get_instance
();
// use ImageMagick in command line
if
(
$cmd
=
self
::
getCommand
(
'im_identify_path'
))
{
$args
=
array
(
'in'
=>
$this
->
image_file
,
'format'
=>
"%m %[fx:w] %[fx:h]"
);
$id
=
rcube
::
exec
(
$cmd
.
' 2>/dev/null -format {format} {in}'
,
$args
);
if
(
$id
)
{
return
explode
(
' '
,
strtolower
(
$id
));
}
}
// use PHP's Imagick class
if
(
class_exists
(
'Imagick'
,
false
))
{
try
{
$image
=
new
Imagick
(
$this
->
image_file
);
return
array
(
strtolower
(
$image
->
getImageFormat
()),
$image
->
getImageWidth
(),
$image
->
getImageHeight
(),
);
}
catch
(
Exception
$e
)
{}
}
}
/**
* Check if we have enough memory to load specified image
*
* @param array Hash array with image props like channels, width, height
*
* @return bool True if there's enough memory to process the image, False otherwise
*/
private
function
mem_check
(
$props
)
{
// image size is unknown, we can't calculate required memory
if
(!
$props
[
'width'
])
{
return
true
;
}
// channels: CMYK - 4, RGB - 3
$multip
=
(
$props
[
'channels'
]
?:
3
)
+
1
;
// calculate image size in memory (in bytes)
$size
=
$props
[
'width'
]
*
$props
[
'height'
]
*
$multip
;
return
rcube_utils
::
mem_check
(
$size
);
}
/**
* Get the configured command and make sure it is safe to use.
* We cannot trust configuration, and escapeshellcmd() is useless.
*
* @param string $opt_name Configuration option name
*
* @return bool|string The command or False if not set or invalid
*/
private
static
function
getCommand
(
$opt_name
)
{
static
$error
=
[];
$cmd
=
rcube
::
get_instance
()->
config
->
get
(
$opt_name
);
if
(
empty
(
$cmd
))
{
return
false
;
}
if
(
preg_match
(
'/^(convert|identify)(
\.
exe)?$/i'
,
$cmd
))
{
return
$cmd
;
}
// Executable must exist, also disallow network shares on Windows
if
(
$cmd
[
0
]
!=
"
\\
"
&&
file_exists
(
$cmd
))
{
return
$cmd
;
}
if
(
empty
(
$error
[
$opt_name
]))
{
rcube
::
raise_error
(
"Invalid $opt_name: $cmd"
,
true
,
false
);
$error
[
$opt_name
]
=
true
;
}
return
false
;
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Fri, May 1, 2:15 PM (1 d, 22 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
648020
Default Alt Text
rcube_image.php (17 KB)
Attached To
Mode
R3 roundcubemail
Attached
Detach File
Event Timeline
Log In to Comment