Commit 7ccc5a6b authored by Dries's avatar Dries

- Patch #16358 by James: added toolkit to enable better image handling. The...

- Patch #16358 by James: added toolkit to enable better image handling.  The avatar code and the upload module have been updated to take advantage of the new image API.

There are 5 main functions that modules may now utilize to handle images:

* image_get_info() - this function checks a file.  If it exists and is a valid image file, it will return an array containing things like the pixel dimensions of the image, plus the 'type' and common extension.
* image_scale - resizes a given image to fit within a given width / height dimensions, while maintaining aspect ratio (not distorting the image).  This function can be used to generate thumbnails, or ensure a maximum resolution, etc.
* image_resize - similar to image_scale (but will not respect aspect ratio - may well distort the image).
* image_rotate - rotate an image by X degrees
* image_crop - crops an image to a given rectangle (defined as top-left x/y coordinates plus a width & height of the rectangle).

Contribution modules will now be able to rely on these base manipulation functions to offer additional functionality (such as image nodes, photo galleries, advanced image manipulation, etc).
parent 7931c778
......@@ -23,6 +23,7 @@ Drupal x.x.x, xxxx-xx-xx
* added a simple contact module that allows users to contact each other using e-mail.
- multi-site configuration:
* made it possible to run multiple sites from a single code base.
- added an image API: enables better image handling.
- block system:
* extended the block visibility settings.
- theme system:
......
......@@ -1806,6 +1806,7 @@ function drupal_get_path($type, $name) {
include_once 'includes/tablesort.inc';
include_once 'includes/file.inc';
include_once 'includes/xmlrpc.inc';
include_once 'includes/image.inc';
// Set the Drupal custom error handler.
set_error_handler('error_handler');
......
<?php
// $Id$
/**
* Return a list of available toolkits.
*
* @return An array of toolkit name => descriptive title
*/
function image_get_available_toolkits() {
$toolkits = file_scan_directory('includes', 'image\..*\.inc$');
$output = array();
foreach ($toolkits as $file => $toolkit) {
include_once($file);
$function = str_replace('.', '_', $toolkit->name) . '_info';
if (function_exists($function)) {
$info = $function();
$output[$info['name']] = $info['title'];
}
}
$output['gd'] = t('Built-in GD Toolkit');
return $output;
}
/**
* Retrieve the name of the currently used toolkit.
*
* @return String containing the name of the toolkit.
*/
function image_get_toolkit() {
static $toolkit;
if (!$toolkit) {
$toolkit = variable_get('image_toolkit', 'gd');
if ($toolkit != 'gd') {
include_once 'includes/image.'.$toolkit.'.inc';
}
}
return $toolkit;
}
/**
* Invokes the given method using the currently selected toolkit.
*
* @param $method A string containing the method to invoke.
* @param $params An optional array of parameters to pass to the toolkit method
*
* @return Mixed values (typically Boolean for successful operation)
*/
function image_toolkit_invoke($method, $params = array()) {
$toolkit = image_get_toolkit();
$function = 'image_' . $toolkit . '_'. $method;
if (function_exists($function)) {
return call_user_func_array($function, $params);
}
else {
drupal_set_message(t('%method is not supported by %toolkit.', array('%method' => "<em>$method</em>", '%toolkit' => "<em>$toolkit</em>")));
}
}
/**
* Get details about an image.
*
* @return array containing information about the image
* 'width': image's width in pixels
* 'height': image's height in pixels
* 'extension': commonly used extension for the image
* 'mime_type': image's MIME type ('image/jpeg', 'image/gif', etc.)
*/
function image_get_info($file) {
if (!file_exists($file)) {
return false;
}
$details = false;
$data = @getimagesize($file);
if (is_array($data)) {
$extensions = array('1' => 'gif', '2' => 'jpg', '3' => 'png');
$extension = array_key_exists($data[2], $extensions) ? $extensions[$data[2]] : '';
$details = array('width' => $data[0],
'height' => $data[1],
'extension' => $extension,
'mime_type' => $data['mime']);
}
return $details;
}
/**
* Scales an image to the given width and height while maintaining aspect
* ratio.
*
* @param $source The filepath of the source image
* @param $destination The file path of the destination image
* @param $width The target width
* @param $height The target height
*
* @return True or false, based on success
*/
function image_scale($source, $destination, $width, $height) {
$info = image_get_info($source);
// don't scale up
if ($width > $info['width'] && $height > $info['height']) {
return false;
}
$aspect = $info['height'] / $info['width'];
if ($aspect < $height / $width) {
$width = (int)min($width, $info['width']);
$height = (int)round($width * $aspect);
} else {
$height = (int)min($height, $info['height']);
$width = (int)round($height / $aspect);
}
return image_toolkit_invoke('resize', array($source, $destination, $width, $height));
}
/**
* Resize an image to the given dimensions (ignoring aspect ratio).
*
* @param $source The filepath of the source image.
* @param $destination The file path of the destination image.
* @param $width The target width.
* @param $height The target height.
*/
function image_resize($source, $destination, $width, $height) {
return image_toolkit_invoke('resize', array($source, $destination, $width, $height));
}
/**
* Rotate an image by the given number of degrees.
*
* @param $source The filepath of the source image
* @param $destination The file path of the destination image
* @param $degrees The number of (clockwise) degrees to rotate the image
*/
function image_rotate($source, $destination, $degrees) {
return image_toolkit_invoke('rotate', array($source, $destination, $degrees));
}
/**
* Crop an image to the rectangle specified by the given rectangle.
*
* @param $source The filepath of the source image
* @param $destination The file path of the destination image
* @param $x The top left co-ordinate of the crop area (x axis value)
* @param $y The top left co-ordinate of the crop area (y axis value)
* @param $width The target width
* @param $height The target height
*/
function image_crop($source, $destination, $x, $y, $width, $height) {
return image_toolkit_invoke('crop', array($source, $destination, $x, $y, $width, $height));
}
/**
* GD Toolkit functions
*/
/**
* Verify GD settings (that the extension is actually installed).
*/
function image_gd_settings() {
if (!extension_loaded('gd')) {
drupal_set_message(t('Unable to load the GD toolkit'), 'error');
}
}
/**
* Scale an image to the specified size using GD.
*/
function image_gd_resize($source, $destination, $width, $height) {
if (!file_exists($source)) {
return false;
}
$info = image_get_info($source);
if (!$info) {
return false;
}
$im = image_gd_open($source, $info['extension']);
if (!$im) {
return false;
}
// GD1 doesn't have true color
if (function_exists('imageCreateTrueColor')) {
$res = imageCreateTrueColor($width, $height);
}
else {
$res = imageCreate($width, $height);
}
// GD1 doesn't have copyResampled
if (function_exists('imageCopyResampled')) {
imageCopyResampled($res, $im, 0, 0, 0, 0, $width, $height, $info['width'], $info['height']);
}
else {
imageCopyResized($res, $im, 0, 0, 0, 0, $width, $height, $info['width'], $info['height']);
}
$result = image_gd_close($res, $destination, $info['extension']);
imageDestroy($res);
imageDestroy($im);
return $result;
}
/**
* Rotate an image the given number of degrees.
*/
function image_gd_rotate($source, $destination, $degrees, $bg_color = 0) {
if (!function_exists('imageRotate')) {
return false;
}
$info = image_get_info($source);
if (!$info) {
return false;
}
$im = image_gd_open($source, $info['extension']);
if (!$im) {
return false;
}
$res = imageRotate($im, $degrees, $bg_color);
$result = image_gd_close($res, $destination, $info['extension']);
return $result;
}
/**
* Crop an image using the GD toolkit.
*/
function image_gd_crop($source, $destination, $x, $y, $width, $height) {
$info = image_get_info($source);
if (!$info) {
return false;
}
$im = image_gd_open($source, $info['extension']);
// GD1 doesn't have true color
if (function_exists('imageCreateTrueColor')) {
$res = imageCreateTrueColor($width, $height);
}
else {
$res = imageCreate($width, $height);
}
imageCopy($im, $res, 0, 0, $x, $y, $width, $height);
$result = image_gd_close($res, $destination, $info['extension']);
imageDestroy($res);
imageDestroy($im);
return $result;
}
/**
* GD helper function to create an image resource from a file.
*/
function image_gd_open($file, $extension) {
$extension = str_replace('jpg', 'jpeg', $extension);
$open_func = 'imageCreateFrom'. $extension;
if (!function_exists($open_func)) {
return false;
}
return $open_func($file);
}
/**
* GD helper to write an image resource to a destination file.
*/
function image_gd_close($res, $destination, $extension) {
$extension = str_replace('jpg', 'jpeg', $extension);
$close_func = 'image'. $extension;
if (!function_exists($close_func)) {
return false;
}
return $close_func($res, $destination);
}
?>
......@@ -239,6 +239,17 @@ function system_view_general() {
$group .= form_radios(t('Download method'), 'file_downloads', variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC), array(FILE_DOWNLOADS_PUBLIC => t('Public - files are available using http directly.'), FILE_DOWNLOADS_PRIVATE => t('Private - files are transferred by Drupal.')), t('If you want any sort of access control on the downloading of files, this needs to be set to <em>private</em>. You can change this at any time, however all download URLs will change and there may be unexpected problems so it is not recommended.'));
$output .= form_group(t('File system settings'), $group);
// image handling:
$group = '';
$toolkits_available = image_get_available_toolkits();
if (count($toolkits_available) > 1) {
$group .= form_radios(t('Select an image processing toolkit'), 'image_toolkit', variable_get('image_toolkit', image_get_toolkit()), $toolkits_available);
}
$group .= image_toolkit_invoke('settings');
if ($group) {
$output .= form_group(t('Image handling'), $group);
}
// date settings:
$zones = _system_zonelist();
......@@ -612,7 +623,7 @@ function system_theme_settings() {
// Check for a new uploaded logo, and use that instead.
if ($file = file_check_upload('logo_upload')) {
if (in_array($file->filemime, array('image/jpeg', 'image/gif', 'image/png'))) {
if ($info = image_get_info($file->filepath)) {
$parts = pathinfo($file->filename);
$filename = ($key) ? str_replace('/', '_', $key) . '_logo.' . $parts['extension'] : 'logo.' . $parts['extension'];
......
......@@ -239,6 +239,17 @@ function system_view_general() {
$group .= form_radios(t('Download method'), 'file_downloads', variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC), array(FILE_DOWNLOADS_PUBLIC => t('Public - files are available using http directly.'), FILE_DOWNLOADS_PRIVATE => t('Private - files are transferred by Drupal.')), t('If you want any sort of access control on the downloading of files, this needs to be set to <em>private</em>. You can change this at any time, however all download URLs will change and there may be unexpected problems so it is not recommended.'));
$output .= form_group(t('File system settings'), $group);
// image handling:
$group = '';
$toolkits_available = image_get_available_toolkits();
if (count($toolkits_available) > 1) {
$group .= form_radios(t('Select an image processing toolkit'), 'image_toolkit', variable_get('image_toolkit', image_get_toolkit()), $toolkits_available);
}
$group .= image_toolkit_invoke('settings');
if ($group) {
$output .= form_group(t('Image handling'), $group);
}
// date settings:
$zones = _system_zonelist();
......@@ -612,7 +623,7 @@ function system_theme_settings() {
// Check for a new uploaded logo, and use that instead.
if ($file = file_check_upload('logo_upload')) {
if (in_array($file->filemime, array('image/jpeg', 'image/gif', 'image/png'))) {
if ($info = image_get_info($file->filepath)) {
$parts = pathinfo($file->filename);
$filename = ($key) ? str_replace('/', '_', $key) . '_logo.' . $parts['extension'] : 'logo.' . $parts['extension'];
......
......@@ -63,7 +63,8 @@ function upload_menu($may_cache) {
function upload_admin() {
system_settings_save();
$group .= form_textfield(t('Maximum total file size'), 'upload_maxsize_total', variable_get('upload_maxsize_total', 0), 5, 5, t('The maximum size of a file a user can upload in megabytes. Enter 0 for unlimited.'));
$group .= form_textfield(t('Maximum total file size'), 'upload_maxsize_total', variable_get('upload_maxsize_total', 0), 10, 10, t('The maximum size of a file a user can upload in megabytes. Enter 0 for unlimited.'));
$group .= form_textfield(t('Maximum resolution for uploaded images'), 'upload_max_resolution', variable_get('upload_max_resolution', 0), 10, 10, t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.'));
$output = form_group(t('General settings'), $group);
......@@ -138,7 +139,9 @@ function upload_nodeapi(&$node, $op, $arg) {
if (($file = file_check_upload('upload')) && user_access('upload files')) {
global $user;
$max_size = variable_get("upload_maxsize_total", 0);
$file = _upload_image($file);
$maxsize = variable_get("upload_maxsize_total", 0);
$total_size = upload_count_size() + $filesize;
$total_usersize = upload_count_size($user->uid) + $filesize;
......@@ -371,4 +374,25 @@ function upload_load($node) {
return $files;
}
/**
* Check an upload, if it is an image, make sure it fits within the
* maximum dimensions allowed.
*/
function _upload_image($file) {
$info = image_get_info($file->filepath);
if ($info) {
list($width, $height) = explode('x', variable_get('upload_max_resolution', 0));
if ($width && $height) {
$result = image_scale($file->filepath, $file->filepath, $width, $height);
if ($result) {
$file->filesize = filesize($file->filepath);
drupal_set_message(t('Your image was resized to fit within the maximum allowed resolution of %resolution pixels.', array('%resolution' => variable_get('upload_max_resolution', 0))));
}
}
}
return $file;
}
?>
......@@ -63,7 +63,8 @@ function upload_menu($may_cache) {
function upload_admin() {
system_settings_save();
$group .= form_textfield(t('Maximum total file size'), 'upload_maxsize_total', variable_get('upload_maxsize_total', 0), 5, 5, t('The maximum size of a file a user can upload in megabytes. Enter 0 for unlimited.'));
$group .= form_textfield(t('Maximum total file size'), 'upload_maxsize_total', variable_get('upload_maxsize_total', 0), 10, 10, t('The maximum size of a file a user can upload in megabytes. Enter 0 for unlimited.'));
$group .= form_textfield(t('Maximum resolution for uploaded images'), 'upload_max_resolution', variable_get('upload_max_resolution', 0), 10, 10, t('The maximum allowed image size expressed as WIDTHxHEIGHT (e.g. 640x480). Set to 0 for no restriction.'));
$output = form_group(t('General settings'), $group);
......@@ -138,7 +139,9 @@ function upload_nodeapi(&$node, $op, $arg) {
if (($file = file_check_upload('upload')) && user_access('upload files')) {
global $user;
$max_size = variable_get("upload_maxsize_total", 0);
$file = _upload_image($file);
$maxsize = variable_get("upload_maxsize_total", 0);
$total_size = upload_count_size() + $filesize;
$total_usersize = upload_count_size($user->uid) + $filesize;
......@@ -371,4 +374,25 @@ function upload_load($node) {
return $files;
}
/**
* Check an upload, if it is an image, make sure it fits within the
* maximum dimensions allowed.
*/
function _upload_image($file) {
$info = image_get_info($file->filepath);
if ($info) {
list($width, $height) = explode('x', variable_get('upload_max_resolution', 0));
if ($width && $height) {
$result = image_scale($file->filepath, $file->filepath, $width, $height);
if ($result) {
$file->filesize = filesize($file->filepath);
drupal_set_message(t('Your image was resized to fit within the maximum allowed resolution of %resolution pixels.', array('%resolution' => variable_get('upload_max_resolution', 0))));
}
}
}
return $file;
}
?>
......@@ -227,20 +227,19 @@ function user_validate_picture($file, &$edit, $user) {
// Check that uploaded file is an image, with a maximum file size
// and maximum height/width.
$extension = strtolower(strrchr($file->filename, '.'));
$size = @getimagesize($file->filepath);
$info = image_get_info($file->filepath);
list($maxwidth, $maxheight) = explode('x', variable_get('user_picture_dimensions', '85x85'));
if ((!in_array($size[2], array(1, 2, 3))) || (!in_array($extension, array('.gif', '.jpg', '.png', '.jpeg')))) {
if (!$info || !$info['extension']) {
form_set_error('picture', t('The uploaded file was not an image.'));
}
else if ($file->size > (variable_get('user_picture_file_size', '30') * 1000)) {
form_set_error('picture', t('The uploaded image is too large; the maximum file size is %size kB.', array('%size' => variable_get('user_picture_file_size', '30'))));
}
else if ($size[0] > $maxwidth || $size[1] > $maxheight) {
else if (!image_scale($file->filepath, $file->filepath, $maxwidth, $maxheight)) {
form_set_error('picture', t('The uploaded image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => variable_get('user_picture_dimensions', '85x85'))));
}
else if ($file = file_save_upload('picture', variable_get('user_picture_path', 'pictures') .'/picture-'. $user->uid . $extension, 1)) {
else if (filesize($file->filepath) > (variable_get('user_picture_file_size', '30') * 1000)) {
form_set_error('picture', t('The uploaded image is too large; the maximum file size is %size kB.', array('%size' => variable_get('user_picture_file_size', '30'))));
}
else if ($file = file_save_upload('picture', variable_get('user_picture_path', 'pictures') .'/picture-'. $user->uid . '.' . $info['extension'], 1)) {
$edit['picture'] = $file->filepath;
}
else {
......@@ -403,26 +402,8 @@ function user_perm() {
*/
function user_file_download($file) {
if (strpos($file, variable_get('user_picture_path', 'pictures') .'/picture-') === 0) {
list($width, $height, $type, $attr) = @getimagesize(file_create_path($file));
$types = array(
IMAGETYPE_GIF => 'image/gif',
IMAGETYPE_JPEG => 'image/jpeg',
IMAGETYPE_PNG => 'image/png',
IMAGETYPE_SWF => 'application/x-shockwave-flash',
IMAGETYPE_PSD => 'image/psd',
IMAGETYPE_BMP => 'image/bmp',
IMAGETYPE_TIFF_II => 'image/tiff',
IMAGETYPE_TIFF_MM => 'image/tiff',
IMAGETYPE_JPC => 'application/octet-stream',
IMAGETYPE_JP2 => 'image/jp2',
IMAGETYPE_JPX => 'application/octet-stream',
IMAGETYPE_JB2 => 'application/octet-stream',
IMAGETYPE_SWC => 'application/x-shockwave-flash',
IMAGETYPE_IFF => 'image/iff',
IMAGETYPE_WBMP => 'image/vnd.wap.wbmp',
IMAGETYPE_XBM => 'image/xbm'
);
return array('Content-type: '. $types[$type]);
$info = image_get_info(file_create_path($file));
return array('Content-type: '. $info['mime_type']);
}
}
......
......@@ -227,20 +227,19 @@ function user_validate_picture($file, &$edit, $user) {
// Check that uploaded file is an image, with a maximum file size
// and maximum height/width.
$extension = strtolower(strrchr($file->filename, '.'));
$size = @getimagesize($file->filepath);
$info = image_get_info($file->filepath);
list($maxwidth, $maxheight) = explode('x', variable_get('user_picture_dimensions', '85x85'));
if ((!in_array($size[2], array(1, 2, 3))) || (!in_array($extension, array('.gif', '.jpg', '.png', '.jpeg')))) {
if (!$info || !$info['extension']) {
form_set_error('picture', t('The uploaded file was not an image.'));
}
else if ($file->size > (variable_get('user_picture_file_size', '30') * 1000)) {
form_set_error('picture', t('The uploaded image is too large; the maximum file size is %size kB.', array('%size' => variable_get('user_picture_file_size', '30'))));
}
else if ($size[0] > $maxwidth || $size[1] > $maxheight) {
else if (!image_scale($file->filepath, $file->filepath, $maxwidth, $maxheight)) {
form_set_error('picture', t('The uploaded image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => variable_get('user_picture_dimensions', '85x85'))));
}
else if ($file = file_save_upload('picture', variable_get('user_picture_path', 'pictures') .'/picture-'. $user->uid . $extension, 1)) {
else if (filesize($file->filepath) > (variable_get('user_picture_file_size', '30') * 1000)) {
form_set_error('picture', t('The uploaded image is too large; the maximum file size is %size kB.', array('%size' => variable_get('user_picture_file_size', '30'))));
}
else if ($file = file_save_upload('picture', variable_get('user_picture_path', 'pictures') .'/picture-'. $user->uid . '.' . $info['extension'], 1)) {
$edit['picture'] = $file->filepath;
}
else {
......@@ -403,26 +402,8 @@ function user_perm() {
*/
function user_file_download($file) {
if (strpos($file, variable_get('user_picture_path', 'pictures') .'/picture-') === 0) {
list($width, $height, $type, $attr) = @getimagesize(file_create_path($file));
$types = array(
IMAGETYPE_GIF => 'image/gif',
IMAGETYPE_JPEG => 'image/jpeg',
IMAGETYPE_PNG => 'image/png',
IMAGETYPE_SWF => 'application/x-shockwave-flash',
IMAGETYPE_PSD => 'image/psd',
IMAGETYPE_BMP => 'image/bmp',
IMAGETYPE_TIFF_II => 'image/tiff',
IMAGETYPE_TIFF_MM => 'image/tiff',
IMAGETYPE_JPC => 'application/octet-stream',
IMAGETYPE_JP2 => 'image/jp2',
IMAGETYPE_JPX => 'application/octet-stream',
IMAGETYPE_JB2 => 'application/octet-stream',
IMAGETYPE_SWC => 'application/x-shockwave-flash',
IMAGETYPE_IFF => 'image/iff',
IMAGETYPE_WBMP => 'image/vnd.wap.wbmp',
IMAGETYPE_XBM => 'image/xbm'
);
return array('Content-type: '. $types[$type]);
$info = image_get_info(file_create_path($file));
return array('Content-type: '. $info['mime_type']);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment