');
+ }
+ $options = array_merge($options, drupal_map_assoc(array_keys($presets)));
+ if (empty($options)) {
+ $options[''] = t('No defined presets');
+ }
+ return $options;
+}
+
+/**
+ * Load a preset by preset name or ID. May be used as a loader for menu items.
+ *
+ * @param $name
+ * The name of the preset.
+ * @param $pid
+ * Optional. The numeric id of a preset if the name is not known.
+ *
+ * @return
+ * An image preset with the format of
+ * array('name' => string, 'id' => integer).
+ * If the preset name or ID is not valid, an empty array is returned.
+ */
+function image_preset_load($name = NULL, $pid = NULL, $reset = FALSE) {
+ $presets = image_presets($reset);
+
+ // If retrieving by name.
+ if (isset($name) && isset($presets[$name])) {
+ return $presets[$name];
+ }
+
+ // If retrieving by PID.
+ if (isset($pid)) {
+ foreach ($presets as $name => $preset) {
+ if ($preset['ipid'] == $pid) {
+ return $preset;
+ }
+ }
+ }
+
+ // Otherwise the preset was not found.
+ return FALSE;
+}
+
+/**
+ * Save an image preset.
+ *
+ * @param preset
+ * An image preset array.
+ * @return
+ * A preset array. In the case of a new preset, 'ipid' will be populated.
+ */
+function image_preset_save($preset) {
+ if (isset($preset['ipid']) && is_numeric($preset['ipid'])) {
+ // Load the existing preset to make sure we account for renamed presets.
+ $old_preset = image_preset_load(NULL, $preset['ipid']);
+ image_preset_flush($old_preset);
+ drupal_write_record('image_presets', $preset, 'ipid');
+ }
+ else {
+ drupal_write_record('image_presets', $preset);
+ }
+
+ // Reset presets cache.
+ image_presets(TRUE);
+ menu_rebuild();
+
+ return $preset;
+}
+
+/**
+ * Delete an image preset.
+ *
+ * @param preset
+ * An image preset array.
+ */
+function image_preset_delete($preset) {
+ image_preset_flush($preset);
+ db_query('DELETE FROM {image_actions} where ipid = %d', $preset['ipid']);
+ db_query('DELETE FROM {image_presets} where ipid = %d', $preset['ipid']);
+ image_presets(TRUE);
+ menu_rebuild();
+ return TRUE;
+}
+
+/**
+ * Load all the actions for an image preset.
+ *
+ * @param $preset
+ * An image preset array.
+ * @param $reset
+ * If set to TRUE the internal cache image actions will be reset.
+ */
+function image_preset_actions($preset, $reset = FALSE) {
+ static $actions;
+
+ if ($reset || !isset($actions)) {
+ $actions = array();
+ $definitions = image_action_definitions($reset);
+ $result = db_query('SELECT * FROM {image_actions} where ipid = %d ORDER BY weight ASC', $preset['ipid']);
+ while ($action = db_fetch_array($result)) {
+ $action['data'] = unserialize($action['data']);
+ $action = array_merge($definitions[$action['action']], $action);
+ $actions[$preset['ipid']][$action['iaid']] = $action;
+ }
+ }
+
+ return isset($actions[$preset['ipid']]) ? $actions[$preset['ipid']] : array();
+}
+
+/**
+ * Flush cached media for a preset.
+ *
+ * @param $preset
+ * An image preset.
+ */
+function image_preset_flush($preset) {
+ $preset_directory = realpath(file_directory_path() .'/presets/'. $preset['name']);
+ if (is_dir($preset_directory)) {
+ file_unmanaged_delete_recursive($preset_directory);
+ }
+}
+
+/**
+ * Return a complete URL to an image when using a preset.
+ *
+ * @param $preset_name
+ * The name of the preset to be used with this image.
+ * @param $path
+ * The path to the image.
+ * @return
+ * The complete absolute URL to a preset image.
+ */
+function image_preset_url($preset_name, $path) {
+ $path = _image_strip_file_directory($path);
+ return file_create_url(file_create_path() .'/presets/'. $preset_name .'/'. $path);
+}
+
+/**
+ * Return a relative path to an image when using a preset.
+ *
+ * @param $preset_name
+ * The name of the preset to be used with this image.
+ * @param $path
+ * The path to the image.
+ * @param $file_system
+ * Optional. By default this will return the file system path, which may be
+ * relative to the Drupal root or an absolute path on the server. If set to
+ * FALSE, this will return the Drupal path instead, suitable for requesting
+ * the image.
+ * @param
+ * The path to an image preset image. If $file_system is TRUE the path may
+ * be used in an is_file() request. If $file_system is FALSE the path may
+ * be used in theme('image_preset') or url() requests since it contains a
+ * Drupal path.
+ */
+function image_preset_path($preset_name, $path, $file_system = TRUE) {
+ $path = _image_strip_file_directory($path);
+ if ($file_system || variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC) == FILE_DOWNLOADS_PUBLIC) {
+ return file_directory_path() . '/presets/' . $preset_name . '/' . $path;
+ }
+ else {
+ return 'system/files/presets/' . $preset_name . '/' . $path;
+ }
+}
+
+/**
+ * Given an image preset and path, generate an image derivative.
+ *
+ * @param $preset_name
+ * The name of the preset to be used with this image.
+ * @param $path
+ * The path to the image.
+ * @return
+ * TRUE if the image is able to be generated successfully or FALSE if unable
+ * to create the image. This function may return NULL when the image
+ * generation is in progress but not complete.
+ */
+function image_preset_generate($preset_name, $path) {
+ $default_method = 'system_image_preset_request';
+ $path = _image_strip_file_directory($path);
+ $method = variable_get('image_preset_generation_method', $default_method);
+ $preset = image_preset_load($preset_name);
+
+ if (empty($preset)) {
+ watchdog('image', 'A derivative of the image %path could not be generated because the preset %preset does not exist.', array('%path' => $path, '%preset' => $preset_name), WATCHDOG_ERROR);
+ return FALSE;
+ }
+
+ if (drupal_function_exists($method)) {
+ return $method($preset, $path);
+ }
+ else {
+ return $default_method($preset, $path);
+ }
+}
+
+/**
+ * Create a new image based on an image preset.
+ *
+ * @param $preset
+ * An image preset array.
+ * @param $source
+ * Path of the source file.
+ * @param $destination
+ * Path of the destination file.
+ * @return
+ * TRUE if an image derivative is generated, FALSE if no image
+ * derivative is generated. NULL if the derivative is being generated.
+ */
+function image_preset_create_derivative($preset, $source, $destination) {
+ // Get the folder for the final location of this preset.
+ $directory = dirname($destination);
+
+ // Build the destination folder tree if it doesn't already exists.
+ if (!file_check_directory($directory, FILE_CREATE_DIRECTORY) && !mkdir($directory, 0775, TRUE)) {
+ watchdog('image', 'Failed to create preset directory: %directory', array('%directory' => $directory), WATCHDOG_ERROR);
+ return FALSE;
+ }
+
+ if (!$image = image_open($source)) {
+ return FALSE;
+ }
+
+ foreach ($preset['actions'] as $action) {
+ if (!empty($action['data'])) {
+ // Find new width and height of the image if available. This helps
+ // generate appropriate offsets when using keyword values like "center".
+ if (isset($action['data']['width'])) {
+ $width = image_filter_value('width', $action['data']['width'], $image->info['width'], $image->info['height']);
+ }
+ if (isset($action['data']['height'])) {
+ $height = image_filter_value('height', $action['data']['height'], $image->info['width'], $image->info['height']);
+ }
+ // Run image filter on each value.
+ foreach ($action['data'] as $key => $value) {
+ $action['data'][$key] = image_filter_value($key, $value, $image->info['width'], $image->info['height'], $width, $height);
+ }
+ }
+ image_action_apply($image, $action);
+ }
+
+ if (!image_close($image, $destination)) {
+ if (file_exists($destination)) {
+ watchdog('image', 'Cached image file %destination already exists. There may be an issue with your rewrite configuration.', array('%destination' => $destination), WATCHDOG_ERROR);
+ }
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+/**
+ * Clear cached versions of a specific file in all presets.
+ *
+ * @param $path
+ * The Drupal file path to the original image.
+ */
+function image_path_flush($path) {
+ $path = _image_strip_file_directory($path);
+ foreach (image_presets() as $preset) {
+ if ($path = file_create_path('presets/'. $preset['name'] .'/'. $path)) {
+ file_unmanaged_delete($path);
+ }
+ }
+}
+
+/**
+ * Load all image actions from the database.
+ *
+ * @param $reset
+ * If TRUE, the internal list of actions will be regenerated.
+ */
+function image_actions($reset = FALSE) {
+ static $actions;
+
+ if (!isset($actions) || $reset) {
+ $actions = array();
+
+ // Add database image actions.
+ $result = db_query('SELECT * FROM {image_actions}', NULL, array('fetch' => PDO::FETCH_ASSOC));
+ foreach ($result as $action) {
+ $action['data'] = unserialize($action['data']);
+ $definition = image_action_definition_load($action['action'], $reset);
+ // Do not load actions whose definition cannot be found.
+ if ($definition) {
+ $action = array_merge($definition, $action);
+ $actions[$action['iaid']] = $action;
+ }
+ }
+ }
+
+ return $actions;
+}
+
+/**
+ * Pull in actions exposed by other modules using hook_image_actions().
+ *
+ * @param $reset
+ * If TRUE, regenerate the internal cache of action definitions.
+ * @return
+ * An array of actions to be used when transforming images.
+ */
+function image_action_definitions($toolkit = NULL, $reset = FALSE) {
+ static $actions;
+
+ if (!isset($toolkit)) {
+ $toolkit = image_get_toolkit();
+ }
+
+ if (!isset($actions) || $reset) {
+ if (!$reset && ($cache = cache_get('image_actions')) && !empty($cache->data)) {
+ $actions = $cache->data;
+ }
+ else {
+ $actions = array();
+ foreach (module_implements('image_actions') as $module) {
+ foreach (module_invoke($module, 'image_actions') as $key => $action) {
+ // Ensure the current toolkit supports the
+ $action['module'] = $module;
+ $action['action'] = $key;
+ $action['data'] = isset($action['data']) ? $action['data'] : array();
+ $actions[$key] = $action;
+ };
+ }
+ uasort($actions, '_image_actions_definitions_sort');
+ cache_set('image_actions', $actions);
+ }
+ }
+
+ return $actions;
+}
+
+/**
+ * Load the definition for an action.
+ *
+ * The action definition is a set of default values that applies to an action
+ * regardless of user settings. This definition consists of an array containing
+ * at least the following values:
+ * - action: The unique name for the action being performed. Usually prefixed
+ * with the name of the module providing the action.
+ * - module: The module providing the action.
+ * - description: A description of the action.
+ *
+ * @param $action
+ * The name of the action definition to load.
+ * @param $reset
+ * If TRUE, regenerate the internal cache of action definitions.
+ */
+function image_action_definition_load($action, $reset = FALSE) {
+ static $definition_cache;
+
+ if (!isset($definition_cache[$action]) || $reset) {
+ $definitions = image_action_definitions($reset);
+ $definition = (isset($definitions[$action])) ? $definitions[$action] : array();
+ $definition_cache[$action] = $definition;
+ }
+
+ return isset($definition_cache[$action]) ? $definition_cache[$action] : FALSE;
+}
+
+/**
+ * Load a single image action.
+ *
+ * @param $iaid
+ * The image action ID.
+ */
+function image_action_load($iaid) {
+ $actions = image_actions();
+ return isset($actions[$iaid]) ? $actions[$iaid] : FALSE;
+}
+
+/**
+ * Save an image action.
+ *
+ * @param $action
+ * An image action array.
+ */
+function image_action_save($action) {
+ if (!empty($action['iaid'])) {
+ drupal_write_record('image_actions', $action, 'iaid');
+ }
+ else {
+ drupal_write_record('image_actions', $action);
+ }
+ $preset = image_preset_load(NULL, $action['ipid']);
+ image_preset_flush($preset);
+ image_presets(TRUE);
+ return $action;
+}
+
+/**
+ * Delete an image action.
+ *
+ * @param $action
+ * An image action array.
+ */
+function image_action_delete($action) {
+ db_query('DELETE FROM {image_actions} WHERE iaid = %d', $action['iaid']);
+ $preset = image_preset_load(NULL, $action['ipid']);
+ image_preset_flush($preset);
+ image_presets(TRUE);
+}
+
+/**
+ * Given an image object and action, perform the action on the file.
+ */
+function image_action_apply(&$image, $action) {
+ if (drupal_function_exists($action['function'])) {
+ return call_user_func($action['function'], $image, $action['data']);
+ }
+ return FALSE;
+}
+
+/**
+ * Filter key word values such as 'top', 'right', 'center', and percentages.
+ *
+ * All returned values are in pixels relative to the passed in height and width.
+ */
+function image_filter_value($key, $value, $current_width, $current_height, $new_width = null, $new_height = null) {
+ switch ($key) {
+ case 'width':
+ $value = _image_filter_percent($value, $current_width);
+ break;
+ case 'height':
+ $value = _image_filter_percent($value, $current_height);
+ break;
+ case 'xoffset':
+ $value = _image_filter_keyword($value, $current_width, $new_width);
+ break;
+ case 'yoffset':
+ $value = _image_filter_keyword($value, $current_height, $new_height);
+ break;
+ }
+ return $value;
+}
+
+/**
+ * Accept a percentage and return it in pixels.
+ */
+function _image_filter_percent($value, $current_pixels) {
+ if (strpos($value, '%') !== false) {
+ $value = str_replace('%', '', $value) * 0.01 * $current_pixels;
+ }
+ return $value;
+}
+
+/**
+ * Accept a keyword (center, top, left, etc) and return it as an offset in pixels.
+ */
+function _image_filter_keyword($value, $current_pixels, $new_pixels) {
+ switch ($value) {
+ case 'top':
+ case 'left':
+ $value = 0;
+ break;
+ case 'bottom':
+ case 'right':
+ $value = $current_pixels - $new_pixels;
+ break;
+ case 'center':
+ $value = $current_pixels/2 - $new_pixels/2;
+ break;
+ }
+ return $value;
+}
+
+/**
+ * Remove a possible leading file directory path from the given path.
+ */
+function _image_strip_file_directory($path) {
+ $dirpath = file_directory_path();
+ $dirlen = strlen($dirpath);
+ if (substr($path, 0, $dirlen + 1) == $dirpath .'/') {
+ $path = substr($path, $dirlen + 1);
+ }
+ return $path;
+}
+
+/**
+ * Internal function for sorting image action definitions through uasort().
*/
-function image_crop($source, $destination, $x, $y, $width, $height) {
- return image_toolkit_invoke('crop', array($source, $destination, $x, $y, $width, $height));
+function _image_actions_definitions_sort($a, $b) {
+ return strcasecmp($a['name'], $b['name']);
}
/**
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.469
diff -u -r1.469 theme.inc
--- includes/theme.inc 5 Feb 2009 03:42:56 -0000 1.469
+++ includes/theme.inc 16 Feb 2009 09:10:40 -0000
@@ -1211,11 +1211,44 @@
* A string containing the image tag.
*/
function theme_image($path, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE) {
- if (!$getsize || (is_file($path) && (list($width, $height, $type, $image_attributes) = @getimagesize($path)))) {
- $attributes = drupal_attributes($attributes);
- $url = (url($path) == $path) ? $path : (base_path() . $path);
- return '';
+ if ($getsize && (is_file($path))) {
+ list($width, $height, $type, $image_attributes) = @getimagesize($path);
}
+
+ $attributes = drupal_attributes($attributes);
+ $url = (url($path) == $path) ? $path : (base_path() . $path);
+ return '';
+}
+
+/**
+ * Return a themed image using a specific image preset.
+ *
+ * @param $preset
+ * The name of the preset to be used to alter the original image.
+ * @param $path
+ * The path of the image file relative to the Drupal files directory.
+ * This function does not work with images outside the files directory nor
+ * with remotely hosted images.
+ * @param $alt
+ * The alternative text for text-based browsers.
+ * @param $title
+ * The title text is displayed when the image is hovered in some popular browsers.
+ * @param $attributes
+ * Associative array of attributes to be placed in the img tag.
+ * @param $getsize
+ * If set to TRUE, the image's dimension are fetched and added as width/height attributes.
+ * @return
+ * A string containing the image tag.
+ */
+function theme_image_preset($preset, $path, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE) {
+ $real_path = image_preset_path($preset, $path);
+ $url_path = image_preset_path($preset, $path, FALSE);
+
+ if (!file_exists($real_path)) {
+ image_preset_generate($preset, $path);
+ }
+
+ return theme('image', $url_path, $alt, $title, $attributes, $getsize);
}
/**
Index: includes/file.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/file.inc,v
retrieving revision 1.157
diff -u -r1.157 file.inc
--- includes/file.inc 13 Feb 2009 00:39:01 -0000 1.157
+++ includes/file.inc 16 Feb 2009 09:10:39 -0000
@@ -1183,13 +1183,11 @@
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_open($file->filepath)) {
+ image_scale($image, $width, $height);
+ image_close($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));
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.867
diff -u -r1.867 common.inc
--- includes/common.inc 13 Feb 2009 04:43:00 -0000 1.867
+++ includes/common.inc 16 Feb 2009 09:10:39 -0000
@@ -3546,6 +3546,9 @@
'image' => array(
'arguments' => array('path' => NULL, 'alt' => '', 'title' => '', 'attributes' => NULL, 'getsize' => TRUE),
),
+ 'image_preset' => array(
+ 'arguments' => array('preset' => NULL, 'path' => NULL, 'alt' => '', 'title' => '', 'attributes' => NULL, 'getsize' => TRUE),
+ ),
'breadcrumb' => array(
'arguments' => array('breadcrumb' => NULL),
),
Index: profiles/default/default.profile
===================================================================
RCS file: /cvs/drupal/drupal/profiles/default/default.profile,v
retrieving revision 1.37
diff -u -r1.37 default.profile
--- profiles/default/default.profile 3 Feb 2009 12:30:14 -0000 1.37
+++ profiles/default/default.profile 16 Feb 2009 09:10:47 -0000
@@ -132,6 +132,25 @@
// Don't display date and author information for page nodes by default.
variable_set('node_submitted_page', FALSE);
+ // Create an image preset.
+ $preset = array('name' => 'thumbnail_square');
+ $preset = image_preset_save($preset);
+ $action = array(
+ 'ipid' => $preset['ipid'],
+ 'action' => 'system_image_scale_and_crop',
+ 'data' => array('width' => '85', 'height' => '85'),
+ );
+ image_action_save($action);
+
+ // Enable user picture support and set the default to a square thumbnail option.
+ variable_set('user_pictures', '1');
+ variable_set('user_picture_preset', 'thumbnail_square');
+
+ $theme_settings = theme_get_settings();
+ $theme_settings['toggle_node_user_picture'] = '1';
+ $theme_settings['toggle_comment_user_picture'] = '1';
+ variable_set('theme_settings', $theme_settings);
+
// Create a default vocabulary named "Tags", enabled for the 'article' content type.
$description = st('Use tags to group articles on similar topics into categories.');
$help = st('Enter a comma-separated list of words.');
Index: modules/system/system.admin_image.inc
===================================================================
RCS file: modules/system/system.admin_image.inc
diff -N modules/system/system.admin_image.inc
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/system/system.admin_image.inc 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,651 @@
+ $preset['name']));
+}
+
+/**
+ * Form builder; Edit a preset name and action order.
+ *
+ * @ingroup forms
+ * @see system_image_preset_form_submit()
+ */
+function system_image_preset_form($form_state, $preset = array()) {
+ $form = array();
+
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#size' => '64',
+ '#title' => t('Preset name'),
+ '#default_value' => $preset['name'],
+ '#description' => t('The name is used in URLs for generated images. Only use lowercase alphanumeric characters, underscores (_), and hyphens (-) for preset names.'),
+ '#element_validate' => array('system_image_preset_name_validate'),
+ );
+
+ $form['preview'] = array(
+ '#markup' => theme('image_preset', 'thumbnail', 'picture.jpg'),
+ );
+
+ $form['ipid'] = array(
+ '#type' => 'value',
+ '#value' => $preset['ipid'],
+ );
+
+ $form['actions'] = array(
+ '#tree' => TRUE,
+ '#type' => 'markup',
+ '#theme' => 'admin_image_preset_actions',
+ );
+
+ foreach ($preset['actions'] as $i => $action) {
+ $action_form['name'] = array(
+ '#markup' => $action['name'],
+ );
+ $action_form['action'] = array(
+ '#type' => 'value',
+ '#value' => $action['action'],
+ );
+ $action_form['iaid'] = array(
+ '#type' => 'value',
+ '#value' => $action['iaid'],
+ );
+ $action_form['ipid'] = array(
+ '#type' => 'value',
+ '#value' => $action['ipid'],
+ );
+
+ $action_form['summary'] = array(
+ '#markup' => theme($action['summary'], $action['data']),
+ );
+ $action_form['data'] = array(
+ '#type' => 'value',
+ '#value' => $action['data'],
+ );
+ $action_form['weight'] = array(
+ '#type' => 'weight',
+ '#default_value' => $action['weight'],
+ );
+ $action_form['configure'] = array(
+ '#markup' => l(t('Configure'), 'admin/settings/images/presets/'. $preset['name'] .'/'. $action['iaid'] ),
+ );
+ $action_form['remove'] = array(
+ '#markup' => l(t('Delete'), 'admin/settings/images/presets/'. $preset['name'] .'/'. $action['iaid'] .'/delete'),
+ );
+ $form['actions'][$i] = $action_form;
+ }
+
+ $form['new_actions'] = array(
+ '#tree' => TRUE,
+ '#type' => 'fieldset',
+ '#title' => t('New actions'),
+ '#collapsible' => FALSE,
+ '#theme' => 'admin_image_preset_actions_add',
+ '#preset' => $preset,
+ );
+
+ foreach (image_action_definitions() as $action => $definition) {
+ $form['new_actions'][$action] = array(
+ '#type' => 'value',
+ '#value' => $definition,
+ );
+ }
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Update preset'),
+ );
+ return $form;
+}
+
+/**
+ * Submit handler for saving an image preset name and action weights.
+ */
+function system_image_preset_form_submit($form, &$form_state) {
+ // Update the preset name if it has changed.
+ if ($preset = image_preset_load(NULL, $form_state['values']['ipid'])) {
+ if ($preset['name'] != $form_state['values']['name']) {
+ $preset['name'] = $form_state['values']['name'];
+ image_preset_save($preset);
+ $form_state['redirect'] = 'admin/settings/images/presets/' . $preset['name'];
+ }
+ }
+
+ // Update the individual action weights.
+ foreach ($form_state['values']['actions'] as $new_action) {
+ $action = image_action_load($new_action['iaid']);
+ $action['weight'] = $new_action['weight'];
+ image_action_save($action);
+ }
+
+ drupal_set_message('Changes to the preset have been saved.');
+}
+
+/**
+ * Form builder; Form for adding and editing image actions.
+ *
+ * @ingroup forms
+ * @see system_image_preset_add_form_submit()
+ */
+function system_image_preset_add_form($form_state) {
+ $form = array();
+ $form['name'] = array(
+ '#type' => 'textfield',
+ '#size' => '64',
+ '#title' => t('Preset name'),
+ '#default_value' => '',
+ '#description' => t('The name is used in URLs for generated images. Only use lowercase alphanumeric characters, underscores (_), and hyphens (-) for preset names.'),
+ '#element_validate' => array('system_image_preset_name_validate'),
+ );
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => t('Create new preset'),
+ );
+ return $form;
+}
+
+/**
+ * Submit handler for adding a new preset.
+ */
+function system_image_preset_add_form_submit($form, &$form_state) {
+ $preset = array('name' => $form_state['values']['name']);
+ $preset = image_preset_save($preset);
+ drupal_set_message(t('Preset %name was created.', array('%name' => $preset['name'])));
+ $form_state['redirect'] = 'admin/settings/images/presets/'. $preset['name'];
+}
+
+/**
+ * Element validate function to ensure unique, safe preset names.
+ */
+function system_image_preset_name_validate($element, $form_state) {
+ // Check for duplicates.
+ $presets = image_presets();
+ if (isset($presets[$element['#value']]) && (!isset($form_state['values']['ipid']) || $presets[$element['#value']]['ipid'] != $form_state['values']['ipid'])) {
+ form_set_error($element['#name'], t('The image preset name %name is already in use.', array('%name' => $element['#value'])));
+ }
+
+ // Check for illegal characters in preset names.
+ if (preg_match('/[^0-9a-z_\-]/', $element['#value'])) {
+ form_set_error($element['#name'], t('Please only use lowercase alphanumeric characters, underscores (_), and hyphens (-) for preset names.'));
+ }
+}
+
+/**
+ * Form builder; Form for deleting an image preset.
+ *
+ * @ingroup forms
+ * @see system_image_preset_delete_form_submit()
+ */
+function system_image_preset_delete_form($form_state, $preset = array()) {
+ if (empty($preset)) {
+ drupal_set_message(t('The specified preset was not found.'), 'error');
+ drupal_goto('admin/settings/images/presets');
+ }
+
+ $form = array();
+ $form['name'] = array(
+ '#type' => 'value',
+ '#value' => $preset['name'],
+ );
+ $form['ipid'] = array(
+ '#type' => 'value',
+ '#value' => $preset['ipid'],
+ );
+ return confirm_form(
+ $form,
+ t('Are you sure you want to delete the preset %preset?', array('%preset' => $preset['name'])),
+ 'admin/settings/images/presets',
+ t('All images that have been generated for this preset will be permanently deleted.'),
+ t('Delete'), t('Cancel')
+ );
+}
+
+/**
+ * Submit handler to delete an image preset.
+ */
+function system_image_preset_delete_form_submit($form, &$form_state) {
+ $preset = image_preset_load(NULL, $form_state['values']['ipid']);
+ image_preset_delete($preset);
+ drupal_set_message(t('Preset %name was deleted.', array('%name' => $preset['name'])));
+ $form_state['redirect'] = 'admin/settings/images/presets';
+}
+
+/**
+ * Form builder; Confirm flushing a preset's cached images.
+ *
+ * @ingroup forms
+ * @see system_image_preset_flush_form_submit()
+ */
+function system_image_preset_flush_form(&$form_state, $preset = array()) {
+ if (empty($preset)) {
+ drupal_set_message(t('The specified preset was not found.'), 'error');
+ $form_state['redirect'] = 'admin/settings/images/presets';
+ }
+
+ $form = array();
+ $form['name'] = array(
+ '#type' => 'value',
+ '#value' => $preset['name'],
+ );
+ $form['ipid'] = array(
+ '#type' => 'value',
+ '#value' => $preset['ipid'],
+ );
+ return confirm_form(
+ $form,
+ t('Are you sure you want to flush the %preset preset?', array('%preset' => $preset['name'])),
+ 'admin/settings/images/presets',
+ t('This will delete all the generated images for the %preset preset. Regenerating the images may cause a temporary increase in your server\'s load.', array('%preset' => $preset['name'])),
+ t('Flush'), t('Cancel')
+ );
+}
+
+/**
+ * Submit handler for flushing an image preset's cached files.
+ */
+function system_image_preset_flush_form_submit($form, &$form_state) {
+ $preset = image_preset_load(NULL, $form_state['values']['ipid']);
+ image_preset_flush($preset);
+ drupal_set_message(t('Preset %name was flushed.', array('%name' => $preset['name'])));
+ $form_state['redirect'] = 'admin/settings/images/presets';
+}
+
+/**
+ * Menu title callback; Title for editing, deleting, and adding image actions.
+ */
+function system_image_action_title($string, $action) {
+ return t($string, array('!name' => $action['name']));
+}
+
+/**
+ * Form builder; Form for adding and editing image actions.
+ *
+ * This form is used universally for editing all actions. Each action adds its
+ * own custom section to the form by calling the form function specified in
+ * hook_image_actions().
+ *
+ * @ingroup forms
+ * @see hook_image_actions()
+ * @see system_image_actions()
+ * @see system_image_resize_form()
+ * @see system_image_scale_form()
+ * @see system_image_crop_form()
+ * @see system_image_desaturate_form()
+ * @see system_image_action_form_submit()
+ */
+function system_image_action_form($form_state, $preset, $action) {
+ $form = array(
+ '#tree' => TRUE,
+ '#image_preset' => $preset,
+ '#image_action' => $action,
+ );
+
+ if (drupal_function_exists($action['form'])) {
+ $form['data'] = call_user_func($action['form'], $action['data']);
+ }
+
+ $form['submit'] = array(
+ '#type' => 'submit',
+ '#value' => isset($action['iaid']) ? t('Update action') : t('Add action'),
+ );
+ return $form;
+}
+
+/**
+ * Submit handler for updating an image action.
+ */
+function system_image_action_form_submit($form, &$form_state) {
+ $preset = $form['#image_preset'];
+ $action = array_merge($form['#image_action'], $form_state['values']);
+ $action['ipid'] = $preset['ipid'];
+ if (empty($action['iaid'])) {
+ $action['weight'] = count($preset['actions']);
+ }
+
+ image_action_save($action);
+ drupal_set_message(t('The action was successfully updated.'));
+ $form_state['redirect'] = 'admin/settings/images/presets/' . $preset['name'];
+}
+
+/**
+ * Form builder; Form for deleting an image action.
+ *
+ * @ingroup forms
+ * @see system_image_action_delete_form_submit()
+ */
+function system_image_action_delete_form($form_state, $preset, $action) {
+ $form = array(
+ '#image_preset' => $preset,
+ '#image_action' => $action,
+ );
+
+ $question = t('Are you sure you want to delete the @action action from the %preset preset?', array('%preset' => $preset['name'], '@action' => $action['name']));
+ $description = t('Deleting this action will regenerate all images for the %preset preset.', array('%preset' => $preset['name']));
+ return confirm_form($form, $question, 'admin/settings/images/presets/' . $preset['name'], $description, t('Delete'));
+}
+
+/**
+ * Submit handler to delete an image action.
+ */
+function system_image_action_delete_form_submit($form, &$form_state) {
+ $preset = $form['#image_preset'];
+ $action = $form['#image_action'];
+
+ image_action_delete($action);
+ drupal_set_message(t('The image action %name has been deleted.', array('%name' => $action['name'])));
+ $form_state['redirect'] = 'admin/settings/images/presets/'. $preset['name'];
+}
+
+/**
+ * Form structure for the image resize form.
+ *
+ * Note that this is not a complete form, it only contains the portion of the
+ * form for configuring the resize options. Therefor it does not not need to
+ * include metadata about the action, nor a submit button.
+ *
+ * @param $data
+ * The current configuration for this resize action.
+ */
+function system_image_resize_form($action) {
+ $form['width'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Width'),
+ '#default_value' => isset($action['width']) ? $action['width'] : '',
+ '#description' => t('Enter a width in pixels or as a percentage. i.e. 500 or 80%.'),
+ '#required' => TRUE,
+ );
+ $form['height'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Height'),
+ '#default_value' => isset($action['height']) ? $action['height'] : '',
+ '#description' => t('Enter a height in pixels or as a percentage. i.e. 500 or 80%.'),
+ '#required' => TRUE,
+ );
+ return $form;
+}
+
+/**
+ * Form structure for the image scale form.
+ *
+ * Note that this is not a complete form, it only contains the portion of the
+ * form for configuring the scale options. Therefor it does not not need to
+ * include metadata about the action, nor a submit button.
+ *
+ * @param $data
+ * The current configuration for this scale action.
+ */
+function system_image_scale_form($data) {
+ $form = system_image_resize_form($data);
+ $form['width']['#required'] = FALSE;
+ $form['height']['#required'] = FALSE;
+ $form['upscale'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => (isset($data['upscale'])) ? $data['upscale'] : 0,
+ '#title' => t('Allow Upscaling'),
+ '#description' => t('Let scale make images larger than their original size'),
+ );
+ return $form;
+}
+
+/**
+ * Form structure for the image crop form.
+ *
+ * Note that this is not a complete form, it only contains the portion of the
+ * form for configuring the crop options. Therefor it does not not need to
+ * include metadata about the action, nor a submit button.
+ *
+ * @param $data
+ * The current configuration for this crop action.
+ */
+function system_image_crop_form($data) {
+ $data += array(
+ 'width' => '',
+ 'height' => '',
+ 'xoffset' => '',
+ 'yoffset' => '',
+ );
+
+ $form['width'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Width'),
+ '#default_value' => $data['width'],
+ '#description' => t('Enter a width in pixels or as a percentage. i.e. 500 or 80%.'),
+ '#required' => TRUE,
+ );
+ $form['height'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Height'),
+ '#default_value' => $data['height'],
+ '#description' => t('Enter a height in pixels or as a percentage. i.e. 500 or 80%.'),
+ '#required' => TRUE,
+ );
+ $form['xoffset'] = array(
+ '#type' => 'textfield',
+ '#title' => t('X offset'),
+ '#default_value' => $data['xoffset'],
+ '#description' => t('Enter an offset in pixels or use a keyword: left, center, or right.'),
+ );
+ $form['yoffset'] = array(
+ '#type' => 'textfield',
+ '#title' => t('Y offset'),
+ '#default_value' => $data['yoffset'],
+ '#description' => t('Enter an offset in pixels or use a keyword: top, center, or bottom.'),
+ );
+ return $form;
+}
+
+function system_image_desaturate_form($data) {
+ return array(
+ '#markup' => '' . t('The desaturate action needs no configuration. Confirm the addition of this action.') . '
',
+ );
+}
+
+/**
+ * Form structure for the image rotate form.
+ *
+ * Note that this is not a complete form, it only contains the portion of the
+ * form for configuring the rotate options. Therefor it does not not need to
+ * include metadata about the action, nor a submit button.
+ *
+ * @param $data
+ * The current configuration for this rotate action.
+ */
+function system_image_rotate_form($data) {
+ $form['degrees'] = array(
+ '#type' => 'textfield',
+ '#default_value' => (isset($data['degrees'])) ? $data['degrees'] : 0,
+ '#title' => t('Rotation angle'),
+ '#description' => t('The number of degrees the image should be rotated. Positive numbers are clockwise, negative are counter-clockwise.'),
+ '#required' => TRUE,
+ );
+ $form['random'] = array(
+ '#type' => 'checkbox',
+ '#default_value' => (isset($data['random'])) ? $data['random'] : 0,
+ '#title' => t('Randomize'),
+ '#description' => t('Randomize the rotation angle for each image. The angle specified above is used as a maximum.'),
+ );
+ $form['bgcolor'] = array(
+ '#type' => 'textfield',
+ '#default_value' => (isset($data['bgcolor'])) ? $data['bgcolor'] : '#FFFFFF',
+ '#title' => t('Background color'),
+ '#description' => t('The background color to use for exposed areas of the image. Use web-style hex colors (#FFFFFF for white, #000000 for black).'),
+ '#required' => TRUE,
+ );
+ return $form;
+}
+
+/**
+ * Display the page containing the list of image presets.
+ *
+ * @param $presets
+ * An array of all the image presets returned by image_get_presets().
+ * @see image_get_presets()
+ * @ingroup themeable
+ */
+function theme_admin_image_presets($presets) {
+ $header = array(t('Preset name'), array('data' => t('Operations'), 'colspan' => 3));
+ $rows = array();
+ foreach ($presets as $preset) {
+ $row = array();
+ $row[] = l($preset['name'], 'admin/settings/images/presets/'. $preset['name']);
+ $link_attributes = array(
+ 'attributes' => array(
+ 'class' => 'image-preset-link',
+ ),
+ );
+ $row[] = l(t('Edit'), 'admin/settings/images/presets/'. $preset['name'], $link_attributes);
+ $row[] = l(t('Delete'), 'admin/settings/images/presets/'. $preset['name'] .'/delete', $link_attributes);
+ $row[] = l(t('Flush'), 'admin/settings/images/presets/'. $preset['name'] .'/flush', $link_attributes);
+ $rows[] = $row;
+ }
+
+ if (empty($rows)) {
+ $rows[] = array(array(
+ 'colspan' => 4,
+ 'data' => t('There are currently no presets. Add a new one.', array('!url' => url('admin/settings/images/presets/add'))),
+ ));
+ }
+
+ return theme('table', $header, $rows);
+}
+
+/**
+ * Theme callback for listing the actions within a specific image preset.
+ *
+ * @param $form
+ * An associative array containing the structure of the actions group.
+ * @ingroup themeable
+ */
+function theme_admin_image_preset_actions($form) {
+ $header = array(t('Action'), t('Settings'), t('Weight'), '','');
+ $rows = array();
+ foreach(element_children($form) as $key) {
+ if (!is_numeric($key)) {
+ continue;
+ }
+ $row = array();
+ $form[$key]['weight']['#attributes']['class'] = 'image-action-order-weight';
+ $row[] = drupal_render($form[$key]['name']);
+ $row[] = drupal_render($form[$key]['summary']);
+ $row[] = drupal_render($form[$key]['weight']);
+ $row[] = drupal_render($form[$key]['configure']);
+ $row[] = drupal_render($form[$key]['remove']);
+ $rows[] = array(
+ 'data' => $row,
+ 'class' => 'draggable',
+ );
+ }
+
+ if (empty($rows)) {
+ $rows[] = array(array(
+ 'colspan' => 5,
+ 'data' => t('There are currently no actions in this preset. Add one by selecting a preset from the New actions fieldset.'),
+ ));
+ }
+
+ $output = theme('table', $header, $rows, array('id' => 'image-preset-actions'));
+ drupal_add_tabledrag('image-preset-actions', 'order', 'sibling', 'image-action-order-weight');
+ return $output;
+}
+
+/**
+ * Theme callback for adding new actions to a preset.
+ *
+ * @param $form
+ * An associative array containing the structure of the new actions fieldset.
+ * @ingroup themeable
+ */
+function theme_admin_image_preset_actions_add($form) {
+ $preset = $form['#preset'];
+
+ $output = '';
+ foreach (element_children($form) as $action) {
+ $definition = $form[$action]['#value'];
+ $output .= '- ' . l(t('Add !action', array('!action' => $definition['name'])), 'admin/settings/images/presets/' . $preset['name'] . '/add/' . $action) . '
';
+ $output .= '- ' . $definition['description'] .'
';
+ }
+ $output .= '
';
+ return $output;
+}
+
+/**
+ * Image resize action summary output.
+ *
+ * @param $data
+ * The current configuration for this resize action.
+ * @ingroup themeable
+ */
+function theme_system_image_resize_summary($data) {
+ return t('width') . ': '. $data['width'] . ', ' . t('height') . ': ' . $data['height'];
+}
+
+/**
+ * Image scale action summary output.
+ *
+ * @param $data
+ * The current configuration for this scale action.
+ * @ingroup themeable
+ */
+function theme_system_image_scale_summary($data) {
+ $output = theme('system_image_resize_summary', $data);
+ $output .= ', ' . t('upscale') . ': ';
+ $output .= $data['upscale'] ? t('Yes') : t('No');
+ return $output;
+}
+
+/**
+ * Image crop action summary output.
+ *
+ * @param $data
+ * The current configuration for this crop action.
+ * @ingroup themeable
+ */
+function theme_system_image_crop_summary($data) {
+ $output = '';
+ $output .= t('width') . ': ' . ($data['width'] ? $data['width'] : t('auto'));
+ $output .= ', ' . t('height') . ': ' . ($data['height'] ? $data['height'] : t('auto'));
+ $output .= ', ' . t('x offset') . ': ' . (empty($data['xoffset']) ? 0 : $data['xoffset']);
+ $output .= ', ' . t('y offset') . ': ' . (empty($data['yoffset']) ? 0 : $data['yoffset']);
+ return $output;
+}
+
+/**
+ * Image desaturate action summary output.
+ *
+ * @param $data
+ * The current configuration for this desaturate action.
+ * @ingroup themeable
+ */
+function theme_system_image_desaturate_summary($data) {
+ return '';
+}
+
+/**
+ * Image rotate action summary output.
+ *
+ * @param $data
+ * The current configuration for this rotate action.
+ * @ingroup themeable
+ */
+function theme_system_image_rotate_summary($data) {
+ $output = t('degrees: ') . $data['degrees'] .', ';
+ $output .= t('randomize: ') . (($data['random']) ? t('Yes') : t('No')) .', ';
+ $output .= t('background: ') . $data['bgcolor'];
+ return $output;
+}
Index: modules/simpletest/tests/image.test
===================================================================
RCS file: modules/simpletest/tests/image.test
diff -N modules/simpletest/tests/image.test
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/simpletest/tests/image.test 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,266 @@
+ $value) {
+ if ($color_b[$key] != $value) {
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+ }
+
+}
+
+/**
+ * Test the core image manipulation functions.
+ */
+class ImageManipulateTestCase extends ImageTestCase {
+ function getInfo() {
+ return array(
+ 'name' => t('Image manipulation tests'),
+ 'description' => t('Check that core image manipulations work properly: scale, resize, rotate, crop, scale and crop, and desaturate.'),
+ 'group' => t('Image API'),
+ );
+ }
+
+ /**
+ * Function for finding a pixel's RGBa values.
+ */
+ function getPixelColor($image, $x, $y) {
+ $color_index = imagecolorat($image->resource, $x, $y);
+
+ $transparent_index = imagecolortransparent($image->resource);
+ if ($color_index == $transparent_index) {
+ return array(0, 0, 0, 127);
+ }
+
+ return array_values(imagecolorsforindex($image->resource, $color_index));
+ }
+
+ /**
+ * Since PHP can't visually check that our images have been manipulated
+ * properly, build a list of expected color values for each of the corners and
+ * the expected height and widths for the final images.
+ */
+ function testGdManipulations() {
+ // If GD isn't available don't bother testing this.
+ if (!drupal_function_exists('image_gd_check_settings') || !image_gd_check_settings()) {
+ $this->pass(t('Image manipulations for the GD toolkit were skipped because the GD toolkit is not available.'));
+ return;
+ }
+
+ // Typically the corner colors will be unchanged. These colors are in the
+ // order of top-left, top-right, bottom-right, bottom-left.
+ $default_corners = array($this->red, $this->green, $this->blue, $this->transparent);
+
+ // A list of files that will be tested.
+ $files = array(
+ 'image-test.png',
+ 'image-test.gif',
+ 'image-test.jpg',
+ );
+
+ // Setup a list of tests to perform on each type.
+ $operations = array(
+ 'resize' => array(
+ 'function' => 'resize',
+ 'arguments' => array(20, 10),
+ 'width' => 20,
+ 'height' => 10,
+ 'corners' => $default_corners,
+ ),
+ 'scale_x' => array(
+ 'function' => 'scale',
+ 'arguments' => array(20, NULL),
+ 'width' => 20,
+ 'height' => 10,
+ 'corners' => $default_corners,
+ ),
+ 'scale_y' => array(
+ 'function' => 'scale',
+ 'arguments' => array(NULL, 10),
+ 'width' => 20,
+ 'height' => 10,
+ 'corners' => $default_corners,
+ ),
+ 'upscale_x' => array(
+ 'function' => 'scale',
+ 'arguments' => array(80, NULL, TRUE),
+ 'width' => 80,
+ 'height' => 40,
+ 'corners' => $default_corners,
+ ),
+ 'upscale_y' => array(
+ 'function' => 'scale',
+ 'arguments' => array(NULL, 40, TRUE),
+ 'width' => 80,
+ 'height' => 40,
+ 'corners' => $default_corners,
+ ),
+ 'crop' => array(
+ 'function' => 'crop',
+ 'arguments' => array(12, 4, 16, 12),
+ 'width' => 16,
+ 'height' => 12,
+ 'corners' => array_fill(0, 4, $this->white),
+ ),
+ 'scale_and_crop' => array(
+ 'function' => 'scale_and_crop',
+ 'arguments' => array(10, 8),
+ 'width' => 10,
+ 'height' => 8,
+ 'corners' => array_fill(0, 4, $this->black),
+ ),
+ 'rotate_5' => array(
+ 'function' => 'rotate',
+ 'arguments' => array(5, 0xFF00FF), // Fuchsia background.
+ 'width' => 42,
+ 'height' => 24,
+ 'corners' => array_fill(0, 4, $this->fuchsia),
+ ),
+ 'rotate_90' => array(
+ 'function' => 'rotate',
+ 'arguments' => array(90, 0xFF00FF), // Fuchsia background.
+ 'width' => 20,
+ 'height' => 40,
+ 'corners' => array($this->fuchsia, $this->red, $this->green, $this->blue),
+ ),
+ 'rotate_transparent_5' => array(
+ 'function' => 'rotate',
+ 'arguments' => array(5),
+ 'width' => 42,
+ 'height' => 24,
+ 'corners' => array_fill(0, 4, $this->transparent),
+ ),
+ 'rotate_transparent_90' => array(
+ 'function' => 'rotate',
+ 'arguments' => array(90),
+ 'width' => 20,
+ 'height' => 40,
+ 'corners' => array($this->transparent, $this->red, $this->green, $this->blue),
+ ),
+ 'desaturate' => array(
+ 'function' => 'desaturate',
+ 'arguments' => array(),
+ 'height' => 20,
+ 'width' => 40,
+ // Grayscale corners are a bit funky. Each of the corners are a shade of
+ // gray. The values of these were determined simply by looking at the
+ // final image to see what desaturated colors end up being.
+ 'corners' => array(array_fill(0, 3, 76) + array(3 => 0), array_fill(0, 3, 149) + array(3 => 0), array_fill(0, 3, 29) + array(3 => 0), array_fill(0, 3, 0) + array(3 => 127)),
+ ),
+ );
+
+ foreach ($files as $file) {
+ foreach ($operations as $op => $values) {
+ // Open up a fresh image.
+ $image = image_open($this->originalFileDirectory . '/simpletest/' . $file, 'gd');
+ if (!$image) {
+ $this->fail(t('Could not open image %file.', array('%file' => $file)));
+ continue 2;
+ }
+
+ // Transparent GIFs and the imagefilter function don't work together.
+ // There is a todo in image.gd.inc to correct this.
+ if ($image->info['extension'] == 'gif') {
+ if ($op == 'desaturate') {
+ $values['corners'][3] = $this->white;
+ }
+ }
+
+ // Perform our operation.
+ $function = 'image_'. $values['function'];
+ $arguments = array();
+ $arguments[] = &$image;
+ $arguments = array_merge($arguments, $values['arguments']);
+ call_user_func_array($function, $arguments);
+
+ // To keep from flooding the test with assert values, make a general value
+ // for whether each group of values fail.
+ $correct_dimensions_real = TRUE;
+ $correct_dimensions_object = TRUE;
+ $correct_colors = TRUE;
+
+ // Check the real dimensions of the image first.
+ if (imagesy($image->resource) != $values['height'] || imagesx($image->resource) != $values['width']) {
+ $correct_dimensions_real = FALSE;
+ }
+
+ // Check that the image object has an accurate record of the dimensions.
+ if ($image->info['width'] != $values['width'] || $image->info['height'] != $values['height']) {
+ $correct_dimensions_object = FALSE;
+ }
+ // Now check each of the corners to assure actions like rotate and
+ // desaturate had the proper actions apply.
+ foreach ($values['corners'] as $key => $corner) {
+ // Get the location of the corner.
+ switch ($key) {
+ case 0:
+ $x = 0;
+ $y = 0;
+ break;
+ case 1:
+ $x = $values['width'] - 1;
+ $y = 0;
+ break;
+ case 2:
+ $x = $values['width'] - 1;
+ $y = $values['height'] - 1;
+ break;
+ case 3:
+ $x = 0;
+ $y = $values['height'] - 1;
+ break;
+ }
+ $color = $this->getPixelColor($image, $x, $y);
+ $correct_colors = $this->colorsAreEqual($color, $corner);
+ }
+
+ $directory = file_directory_path() . '/imagetests';
+ file_check_directory($directory, FILE_CREATE_DIRECTORY);
+ image_close($image,$directory . '/' . $op . '.' . $image->info['extension']);
+
+ $this->assertTrue($correct_dimensions_real, t('Image %file after %action action has proper dimensions.', array('%file' => $file, '%action' => $op)));
+ $this->assertTrue($correct_dimensions_object, t('Image %file object after %action action is reporting the proper height and width values.', array('%file' => $file, '%action' => $op)));
+ // JPEG colors will always be messed up due to compression.
+ if ($image->info['extension'] != 'jpg') {
+ $this->assertTrue($correct_colors, t('Image %file object after %action action has the correct color placement.', array('%file' => $file, '%action' => $op)));
+ }
+ }
+ }
+
+ }
+}
Index: modules/system/image.actions.inc
===================================================================
RCS file: modules/system/image.actions.inc
diff -N modules/system/image.actions.inc
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ modules/system/image.actions.inc 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,228 @@
+ array(
+ 'name' => t('Resize'),
+ 'description' => t('Resize an image to an exact set of dimensions, ignoring aspect ratio.'),
+ 'function' => 'system_image_resize_image',
+ 'form' => 'system_image_resize_form',
+ 'summary' => 'system_image_resize_summary',
+ ),
+ 'system_image_scale' => array(
+ 'name' => t('Scale'),
+ 'description' => t('Resize an image maintaining the original aspect-ratio (only one value necessary).'),
+ 'function' => 'system_image_scale_image',
+ 'form' => 'system_image_scale_form',
+ 'summary' => 'system_image_scale_summary',
+ ),
+ 'system_image_scale_and_crop' => array(
+ 'name' => t('Scale and Crop'),
+ 'description' => t('Resize an image maintaining the original aspect-ratio, then crop the center of the image to specific dimensions.'),
+ 'function' => 'system_image_scale_and_crop_image',
+ 'form' => 'system_image_resize_form',
+ 'summary' => 'system_image_resize_summary',
+ ),
+ 'system_image_crop' => array(
+ 'name' => t('Crop'),
+ 'description' => t('Crop an image to the rectangle specified by the given offsets and dimensions.'),
+ 'function' => 'system_image_crop_image',
+ 'form' => 'system_image_crop_form',
+ 'summary' => 'system_image_crop_summary',
+ ),
+ 'system_image_desaturate' => array(
+ 'name' => t('Desaturate'),
+ 'description' => t('Convert an image to grey scale.'),
+ 'function' => 'system_image_desaturate_image',
+ 'form' => 'system_image_desaturate_form',
+ 'summary' => 'system_image_desaturate_summary',
+ ),
+ 'system_image_rotate' => array(
+ 'name' => t('Rotate'),
+ 'description' => t('Rotate an image.'),
+ 'function' => 'system_image_rotate_image',
+ 'form' => 'system_image_rotate_form',
+ 'summary' => 'system_image_rotate_summary',
+ ),
+ );
+
+ return $actions;
+}
+
+/**
+ * Image generation callback. Given a preset and image, request a derivative.
+ *
+ * This function is the default image generation method. It generates individual
+ * images by making separate HTTP requests to system/image/[preset_name]/[path].
+ * This allows each image to have it's own PHP instance (and memory limit) for
+ * generation of the new images. Upon successful generation, the path to the
+ * new image is returned.
+ *
+ * @param $preset
+ * An image preset array.
+ * @param $path
+ * The local path of the image to be generated, excluding the files directory.
+ * @return
+ * TRUE if the image was generated successfully. Returns FALSE if the image
+ * could not be generated.
+ * @see system_image_preset_generate()
+ * @see image_preset_generate()
+ */
+function system_image_preset_request($preset, $path) {
+ $destination = image_preset_path($preset['name'], $path);
+ $image_url = image_preset_url($preset['name'], $path);
+
+ // Check that the image doesn't already exist. If so, just return the path.
+ if (file_exists($destination)) {
+ return TRUE;
+ }
+
+ // Generate an HTTP request to create the new image.
+ $token = drupal_get_token($path);
+ $session_id = session_id();
+ $url = url('system/image/' . $preset['name'] . '/' . $path, array('absolute' => TRUE, 'query' => array('token' => $token, 'session' => $session_id)));
+ $result = drupal_http_request($url);
+
+ return $result->code == 200 ? TRUE : FALSE;
+}
+
+/**
+ * Menu callback. Given a preset and image path, generate a derivative.
+ */
+function system_image_preset_generate() {
+ $args = func_get_args();
+ $preset_name = array_shift($args);
+ $preset = image_preset_load($preset_name);
+ $path = implode('/', $args);
+ $source = file_create_path($path);
+ $destination = image_preset_path($preset['name'], $path);
+
+ if (!$preset || !isset($_GET['session']) || !isset($_GET['token'])) {
+ drupal_access_denied();
+ exit();
+ }
+
+ // We have to manually generate the valid token, since the server is making
+ // the request as an anonymous user with a different session id.
+ $valid_token = md5($_GET['session'] . $path . drupal_get_private_key());
+
+ if ($_GET['token'] != $valid_token) {
+ drupal_access_denied();
+ exit;
+ }
+
+ $result = image_preset_create_derivative($preset, $source, $destination);
+ switch ($result) {
+ case TRUE:
+ print 'TRUE';
+ exit;
+ case FALSE:
+ print 'FALSE';
+ exit;
+ case NULL:
+ print 'IN PROGRESS';
+ exit;
+ }
+}
+
+/**
+ * Access callback for system/image. Ensure this request is made by Drupal.
+ */
+function system_image_preset_generate_access() {
+ $args = func_get_args();
+ $preset_name = array_shift($args);
+ $path = implode('/', $args);
+
+ return isset($_GET['token']) && drupal_valid_token($_GET['token'], $path);
+}
+
+function system_image_resize_image(&$image, $data) {
+ if (!image_resize($image, $data['width'], $data['height'])) {
+ watchdog('image', 'Image resize failed. image: %image, data: %data.', array('%path' => $image, '%data' => print_r($data, TRUE)), WATCHDOG_ERROR);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+function system_image_scale_image(&$image, $data) {
+ // Set impossibly large values if the width and height aren't set.
+ $data['width'] = $data['width'] ? $data['width'] : 9999999;
+ $data['height'] = $data['height'] ? $data['height'] : 9999999;
+ if (!image_scale($image, $data['width'], $data['height'], $data['upscale'])) {
+ watchdog('image', 'Image scale failed. image: %image, data: %data.', array('%path' => $image, '%data' => print_r($data, true)), WATCHDOG_ERROR);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+function system_image_crop_image(&$image, $data) {
+ if (!image_crop($image, $data['xoffset'], $data['yoffset'], $data['width'], $data['height'])) {
+ watchdog('image', 'Image crop failed. image: %image, data: %data.', array('%path' => $image, '%data' => print_r($data, true)), WATCHDOG_ERROR);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+function system_image_scale_and_crop_image(&$image, $data) {
+ if (!image_scale_and_crop($image, $data['width'], $data['height'])) {
+ watchdog('image', t('Image scale and crop failed. image: %image, data: %data.', array('%path' => $image, '%data' => print_r($data, true))), WATCHDOG_ERROR);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+function system_image_desaturate_image(&$image, $data) {
+ if (!image_desaturate($image)) {
+ watchdog('image', 'Image desaturate failed. image: %image, data: %data.', array('%path' => $image, '%data' => print_r($data, true)), WATCHDOG_ERROR);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+function system_image_rotate_image(&$image, $data) {
+ // Set sane default values.
+ $data['degrees'] = $data['degrees'] ? $data['degrees'] : 0;
+ $data['random'] = $data['random'] ? $data['random'] : false;
+ $data['bgcolor'] = $data['bgcolor'] ? $data['bgcolor'] : '#FFFFFF';
+
+ // Manipulate the if we need to randomize, and convert to proper colors.
+ $data['bgcolor'] = '0x'. str_replace('#', '', $data['bgcolor']);
+
+ if (!empty($data['random'])) {
+ $degrees = abs((float)$data['degrees']);
+ $data['degrees'] = rand(-1 * $degrees, $degrees);
+ }
+
+ if (!image_rotate($image, $data['degrees'], $data['bgcolor'])) {
+ watchdog('image', t('Image rotate failed. image: %image, data: %data.', array('%path' => $image, '%data' => print_r($data, true))), WATCHDOG_ERROR);
+ return FALSE;
+ }
+ return TRUE;
+}