Commit 0ea65350 authored by Dries's avatar Dries

- Patch #373613 by quicksketch and drewish: in order to operate on images multiple

  times (such as crop, scale, then desaturate) without quality loss, we need to
  pass images by their raw GD (or other library) resources rather than re-opening
  the same image repeatedly, which causes wasted processing and loss of quality when
  using JPEG images.  This patch reworks the image toolkits, adds some new image
  manipulations and adds some impressive SimpleTests.
parent 3faf46dc
......@@ -1183,13 +1183,11 @@ function file_validate_image_resolution($file, $maximum_dimensions = 0, $minimum
list($width, $height) = explode('x', $maximum_dimensions);
if ($info['width'] > $width || $info['height'] > $height) {
// Try to resize the image to fit the dimensions.
if (image_get_toolkit() && image_scale($file->filepath, $file->filepath, $width, $height)) {
if ($image = image_load($file->filepath)) {
image_scale($image, $width, $height);
image_save($image);
$file->filesize = $image->info['file_size'];
drupal_set_message(t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $maximum_dimensions)));
// Clear the cached filesize and refresh the image information.
clearstatcache();
$info = image_get_info($file->filepath);
$file->filesize = $info['file_size'];
}
else {
$errors[] = t('The image is too large; the maximum dimensions are %dimensions pixels.', array('%dimensions' => $maximum_dimensions));
......
This diff is collapsed.
This diff is collapsed.
; $Id$
name = "Image test"
description = "Support module for image toolkit tests."
package = Testing
version = VERSION
core = 7.x
files[] = image_test.module
hidden = TRUE
<?php
// $Id$
/**
* @file
* Helper module for the image tests.
*/
/**
* Implementation of hook_image_toolkits().
*/
function image_test_image_toolkits() {
return array(
'test' => array(
'title' => t('A dummy toolkit that works'),
'available' => TRUE,
),
'broken' => array(
'title' => t('A dummy toolkit that is "broken"'),
'available' => FALSE,
),
);
}
/**
* Reset/initialize the history of calls to the toolkit functions.
*
* @see image_test_get_all_calls().
*/
function image_test_reset() {
// Keep track of calls to these operations
$results = array(
'load' => array(),
'save' => array(),
'settings' => array(),
'resize' => array(),
'crop' => array(),
'desaturate' => array(),
);
variable_set('image_test_results', $results);
}
/**
* Get an array with the all the calls to the toolkits since image_test_reset()
* was called.
*
* @return
* An array keyed by operation name ('load', 'save', 'settings', 'resize',
* 'crop', 'desaturate') with values being arrays of parameters passed to
* each call.
*/
function image_test_get_all_calls() {
return variable_get('image_test_results', array());
}
/**
* Store the values passed to a toolkit call.
*
* @param $op
* One of the image toolkit operations: 'load', 'save', 'settings', 'resize',
* 'crop', 'desaturate'.
* @param $args
* Values passed to hook.
* @see image_test_get_all_calls()
* @see image_test_reset()
*/
function _image_test_log_call($op, $args) {
$results = variable_get('image_test_results', array());
$results[$op][] = $args;
variable_set('image_test_results', $results);
}
/**
* Image tookit's settings operation.
*/
function image_test_settings() {
_image_test_log_call('settings', array());
return array();
}
/**
* Image tookit's load operation.
*/
function image_test_load(stdClass $image) {
_image_test_log_call('load', array($image));
return $image;
}
/**
* Image tookit's save operation.
*/
function image_test_save(stdClass $image, $destination) {
_image_test_log_call('save', array($image, $destination));
// Return false so that image_save() doesn't try to chmod the destination
// file that we didn't bother to create.
return FALSE;
}
/**
* Image tookit's crop operation.
*/
function image_test_crop(stdClass $image, $x, $y, $width, $height) {
_image_test_log_call('crop', array($image, $x, $y, $width, $height));
return TRUE;
}
/**
* Image tookit's resize operation.
*/
function image_test_resize(stdClass $image, $width, $height) {
_image_test_log_call('resize', array($image, $width, $height));
return TRUE;
}
/**
* Image tookit's desaturate operation.
*/
function image_test_desaturate(stdClass $image) {
_image_test_log_call('desaturate', array($image));
return TRUE;
}
......@@ -11,13 +11,6 @@
* @{
*/
/**
* Retrieve information about the toolkit.
*/
function image_gd_info() {
return array('name' => 'gd', 'title' => t('GD2 image manipulation toolkit'));
}
/**
* Retrieve settings for the GD2 toolkit.
*/
......@@ -76,146 +69,176 @@ function image_gd_check_settings() {
/**
* Scale an image to the specified size using GD.
*
* @param $image
* An image object. The $image->resource, $image->info['width'], and
* $image->info['height'] values will be modified by this call.
* @param $width
* The new width of the resized image, in pixels.
* @param $height
* The new height of the resized image, in pixels.
* @return
* TRUE or FALSE, based on success.
*
* @see image_resize()
*/
function image_gd_resize($source, $destination, $width, $height) {
if (!file_exists($source)) {
return FALSE;
}
function image_gd_resize(stdClass $image, $width, $height) {
$res = image_gd_create_tmp($image, $width, $height);
$info = image_get_info($source);
if (!$info) {
if (!imagecopyresampled($res, $image->resource, 0, 0, 0, 0, $width, $height, $image->info['width'], $image->info['height'])) {
return FALSE;
}
$im = image_gd_open($source, $info['extension']);
if (!$im) {
return FALSE;
}
$res = imagecreatetruecolor($width, $height);
if ($info['extension'] == 'png') {
$transparency = imagecolorallocatealpha($res, 0, 0, 0, 127);
imagealphablending($res, FALSE);
imagefilledrectangle($res, 0, 0, $width, $height, $transparency);
imagealphablending($res, TRUE);
imagesavealpha($res, TRUE);
}
elseif ($info['extension'] == 'gif') {
// If we have a specific transparent color.
$transparency_index = imagecolortransparent($im);
if ($transparency_index >= 0) {
// Get the original image's transparent color's RGB values.
$transparent_color = imagecolorsforindex($im, $transparency_index);
// Allocate the same color in the new image resource.
$transparency_index = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
// Completely fill the background of the new image with allocated color.
imagefill($res, 0, 0, $transparency_index);
// Set the background color for new image to transparent.
imagecolortransparent($res, $transparency_index);
// Find number of colors in the images palette.
$number_colors = imagecolorstotal($im);
// Convert from true color to palette to fix transparency issues.
imagetruecolortopalette($res, TRUE, $number_colors);
}
}
imagecopyresampled($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;
imagedestroy($image->resource);
// Update image object.
$image->resource = $res;
$image->info['width'] = $width;
$image->info['height'] = $height;
return TRUE;
}
/**
* Rotate an image the given number of degrees.
* Crop an image using the GD toolkit.
*
* @param $image
* An image object. The $image->resource, $image->info['width'], and
* $image->info['height'] values will be modified by this call.
* @param $x
* The starting x offset at which to start the crop, in pixels.
* @param $y
* The starting y offset at which to start the crop, in pixels.
* @param $width
* The width of the cropped area, in pixels.
* @param $height
* The height of the cropped area, in pixels.
* @return
* TRUE or FALSE, based on success.
*
* @see image_crop()
*/
function image_gd_rotate($source, $destination, $degrees, $background = 0x000000) {
if (!function_exists('imageRotate')) {
return FALSE;
}
$info = image_get_info($source);
if (!$info) {
return FALSE;
}
function image_gd_crop(stdClass $image, $x, $y, $width, $height) {
$res = image_gd_create_tmp($image, $width, $height);
$im = image_gd_open($source, $info['extension']);
if (!$im) {
if (!imagecopyresampled($res, $image->resource, 0, 0, $x, $y, $width, $height, $width, $height)) {
return FALSE;
}
$res = imageRotate($im, $degrees, $background);
$result = image_gd_close($res, $destination, $info['extension']);
return $result;
// Destroy the original image and return the modified image.
imagedestroy($image->resource);
$image->resource = $res;
$image->info['width'] = $width;
$image->info['height'] = $height;
return TRUE;
}
/**
* Crop an image using the GD toolkit.
* Convert an image resource to grayscale.
*
* Note that transparent GIFs loose transparency when desaturated.
*
* @param $image
* An image object. The $image->resource value will be modified by this call.
* @return
* TRUE or FALSE, based on success.
*
* @see image_desaturate()
*/
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']);
$res = imageCreateTrueColor($width, $height);
imageCopy($res, $im, 0, 0, $x, $y, $width, $height);
$result = image_gd_close($res, $destination, $info['extension']);
imageDestroy($res);
imageDestroy($im);
return $result;
function image_gd_desaturate(stdClass $image) {
return imagefilter($image->resource, IMG_FILTER_GRAYSCALE);
}
/**
* GD helper function to create an image resource from a file.
*
* @param $file
* A string file path where the image should be saved.
* @param $extension
* A string containing one of the following extensions: gif, jpg, jpeg, png.
* @param $image
* An image object. The $image->resource value will populated by this call.
* @return
* An image resource, or FALSE on error.
* TRUE or FALSE, based on success.
*
* @see image_load()
*/
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);
function image_gd_load(stdClass $image) {
$extension = str_replace('jpg', 'jpeg', $image->info['extension']);
$function = 'imagecreatefrom'. $extension;
return (function_exists($function) && $image->resource = $function($image->source));
}
/**
* GD helper to write an image resource to a destination file.
*
* @param $res
* An image resource created with image_gd_open().
* @param $image
* An image object.
* @param $destination
* A string file path where the image should be saved.
* @param $extension
* A string containing one of the following extensions: gif, jpg, jpeg, png.
* @return
* Boolean indicating success.
* TRUE or FALSE, based on success.
*
* @see image_save()
*/
function image_gd_close($res, $destination, $extension) {
$extension = str_replace('jpg', 'jpeg', $extension);
$close_func = 'image' . $extension;
if (!function_exists($close_func)) {
function image_gd_save(stdClass $image, $destination) {
$extension = str_replace('jpg', 'jpeg', $image->info['extension']);
$function = 'image'. $extension;
if (!function_exists($function)) {
return FALSE;
}
if ($extension == 'jpeg') {
return $close_func($res, $destination, variable_get('image_jpeg_quality', 75));
return $function($image->resource, $destination, variable_get('image_jpeg_quality', 75));
}
else {
return $close_func($res, $destination);
// Always save PNG images with full transparency.
if ($extension == 'png') {
imagealphablending($image->resource, FALSE);
imagesavealpha($image->resource, TRUE);
}
return $function($image->resource, $destination);
}
}
/**
* Create a truecolor image preserving transparency from a provided image.
*
* @param $image
* An image object.
* @param $width
* The new width of the new image, in pixels.
* @param $height
* The new height of the new image, in pixels.
* @return
* A GD image handle.
*/
function image_gd_create_tmp(stdClass $image, $width, $height) {
$res = imagecreatetruecolor($width, $height);
if ($image->info['extension'] == 'gif') {
// Grab transparent color index from image resource.
$transparent = imagecolortransparent($image->resource);
if ($transparent >= 0) {
// The original must have a transparent color, allocate to the new image.
$transparent_color = imagecolorsforindex($image->resource, $transparent);
$transparent = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
// Flood with our new transparent color.
imagefill($res, 0, 0, $transparent);
imagecolortransparent($res, $transparent);
}
}
elseif ($image->info['extension'] == 'png') {
imagealphablending($res, FALSE);
$transparency = imagecolorallocatealpha($res, 0, 0, 0, 127);
imagefill($res, 0, 0, $transparency);
imagealphablending($res, TRUE);
imagesavealpha($res, TRUE);
}
else {
imagefill($res, 0, 0, imagecolorallocate($res, 255, 255, 255));
}
return $res;
}
/**
* @} End of "ingroup image".
*/
......@@ -1462,19 +1462,33 @@ function system_file_system_settings() {
*/
function system_image_toolkit_settings() {
$toolkits_available = image_get_available_toolkits();
$current_toolkit = image_get_toolkit();
if (count($toolkits_available) == 0) {
variable_del('image_toolkit');
$form['image_toolkit_help'] = array(
'#markup' => t("No image toolkits were detected. Drupal includes support for <a href='!gd-link'>PHP's built-in image processing functions</a> but they were not detected on this system. You should consult your system administrator to have them enabled, or try using a third party toolkit.", array('gd-link' => url('http://php.net/gd'))),
);
return $form;
}
if (count($toolkits_available) > 1) {
$form['image_toolkit'] = array(
'#type' => 'radios',
'#title' => t('Select an image processing toolkit'),
'#default_value' => image_get_toolkit(),
'#default_value' => $current_toolkit,
'#options' => $toolkits_available
);
}
elseif (count($toolkits_available) == 1) {
else {
variable_set('image_toolkit', key($toolkits_available));
}
$form['image_toolkit_settings'] = image_toolkit_invoke('settings');
// Get the toolkit's settings form.
$function = 'image_' . $current_toolkit . '_settings';
if (drupal_function_exists($function)) {
$form['image_toolkit_settings'] = $function();
}
return system_settings_form($form, TRUE);
}
......
......@@ -365,17 +365,40 @@ function hook_init() {
}
/**
* Define image toolkits provided by this module.
*
* The file which includes each toolkit's functions must be declared as part of
* the files array in the module .info file so that the registry will find and
* parse it.
*
* @return
* An array of image toolkit names.
*/
* Define image toolkits provided by this module.
*
* The file which includes each toolkit's functions must be declared as part of
* the files array in the module .info file so that the registry will find and
* parse it.
*
* The toolkit's functions must be named image_toolkitname_operation().
* where the operation may be:
* - 'load': Required. See image_gd_load() for usage.
* - 'save': Required. See image_gd_save() for usage.
* - 'settings': Optional. See image_gd_settings() for usage.
* - 'resize': Optional. See image_gd_resize() for usage.
* - 'crop': Optional. See image_gd_crop() for usage.
* - 'desaturate': Optional. See image_gd_desaturate() for usage.
*
* @return
* An array with the toolkit name as keys and sub-arrays with these keys:
* - 'title': A string with the toolkit's title.
* - 'available': A Boolean value to indicate that the toolkit is operating
* properly, e.g. all required libraries exist.
*
* @see system_image_toolkits()
*/
function hook_image_toolkits() {
return array('gd');
return array(
'working' => array(
'title' => t('A toolkit that works.'),
'available' => TRUE,
),
'broken' => array(
'title' => t('A toolkit that is "broken" and will not be listed.'),
'available' => FALSE,
),
);
}
/**
......
......@@ -2290,5 +2290,10 @@ function theme_meta_generator_header($version = VERSION) {
* Implementation of hook_image_toolkits().
*/
function system_image_toolkits() {
return array('gd');
return array(
'gd' => array(
'title' => t('GD2 image manipulation toolkit'),
'available' => drupal_function_exists('image_gd_check_settings') && image_gd_check_settings(),
),
);
}
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