Index: modules/system/system.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.module,v
retrieving revision 1.721
diff -u -r1.721 system.module
--- modules/system/system.module	4 Jul 2009 18:26:42 -0000	1.721
+++ modules/system/system.module	5 Jul 2009 23:36:04 -0000
@@ -481,6 +481,13 @@
     'access callback' => TRUE,
     'type' => MENU_CALLBACK,
   );
+  $items['system/image/%image_preset'] = array(
+    'title' => 'Generate image preset',
+    'page callback' => 'system_image_preset_generate',
+    'page arguments' => array(2),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
   $items['admin'] = array(
     'title' => 'Administer',
     'access arguments' => array('access administration pages'),
@@ -2286,6 +2293,50 @@
 }
 
 /**
+ * Implements hook_file_download().
+ *
+ * Control the access to files underneath the system/files/presets directory.
+ */
+function system_file_download($filepath) {
+  if (strpos($filepath, 'presets/') === 0) {
+    $args = explode('/', $filepath);
+    array_shift($args); // Remove the "presets" item.
+    $preset_name = array_shift($args);
+    $original_path = implode('/', $args);
+
+    // Check that the file exists and is an image.
+    if ($info = image_get_info(file_create_path($filepath))) {
+      // Check the permissions of the original image to grant access to this
+      // image.
+      $headers = module_invoke_all('file_download', $original_path);
+      if (!in_array(-1, $headers)) {
+        return array(
+          'Content-Type: ' . $info['mime_type'],
+          'Content-Length: ' . $info['file_size'],
+        );
+      }
+    }
+    return -1;
+  }
+}
+
+/**
+ * Implements hook_file_move().
+ */
+function system_file_move($file, $source) {
+  // Delete any image derivatives at the original image path.
+  image_path_flush($file->filepath);
+}
+
+/**
+ * Implements hook_file_delete().
+ */
+function system_file_delete($file) {
+  // Delete any image derivatives of this image.
+  image_path_flush($file->filepath);
+}
+
+/**
  * Implement hook_action_info().
  */
 function system_action_info() {
Index: modules/system/system.install
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.install,v
retrieving revision 1.353
diff -u -r1.353 system.install
--- modules/system/system.install	4 Jul 2009 14:45:36 -0000	1.353
+++ modules/system/system.install	5 Jul 2009 23:36:03 -0000
@@ -701,6 +701,8 @@
     'primary key' => array('cid'),
   );
 
+  $schema['cache_image'] = $schema['cache'];
+  $schema['cache_image']['description'] = 'Cache table used to store information about image maniupaltions that are in-progress.';
   $schema['cache_form'] = $schema['cache'];
   $schema['cache_form']['description'] = 'Cache table for the form system to store recently built forms and their storage data, to be used in subsequent page requests.';
   $schema['cache_page'] = $schema['cache'];
@@ -843,6 +845,63 @@
       'nid' => array('nid'),
     ),
   );
+
+  $schema['image_presets'] = array(
+    'fields' => array(
+      'ipid' => array(
+        'description' => 'The primary identifier for an image preset.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE),
+      'name' => array(
+        'description' => 'The preset name.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE),
+    ),
+    'primary key' => array('ipid'),
+    'indexes' => array(
+      'name' => array('name'),
+    ),
+  );
+
+  $schema['image_actions'] = array(
+    'fields' => array(
+      'iaid' => array(
+        'description' => 'The primary identifier for an image action.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE),
+      'ipid' => array(
+        'description' => 'The primary identifier for an image preset.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'weight' => array(
+        'description' => 'The weight of the action in the preset.',
+        'type' => 'int',
+        'unsigned' => FALSE,
+        'not null' => TRUE,
+        'default' => 0),
+      'action' => array(
+        'description' => 'The unique ID of the action to be executed.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE),
+      'data' => array(
+        'description' => 'The configuration data for the action.',
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'serialize' => TRUE),
+    ),
+    'primary key' => array('iaid'),
+    'indexes' => array(
+      'ipid' => array('ipid'),
+    ),
+  );
+
   $schema['menu_router'] = array(
     'description' => 'Maps paths to various callbacks (access, page and title)',
     'fields' => array(
@@ -2226,6 +2285,122 @@
 }
 
 /**
+ * Add tables for image presets and actions.
+ */
+function system_update_7030() {
+  $ret = array();
+  $schema = array();
+
+  $schema['cache_image'] = array(
+    'description' => 'Generic cache table for caching things not separated out into their own tables. Contributed modules may also use this to store cached items.',
+    'fields' => array(
+      'cid' => array(
+        'description' => 'Primary Key: Unique cache ID.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'data' => array(
+        'description' => 'A collection of data to cache.',
+        'type' => 'blob',
+        'not null' => FALSE,
+        'size' => 'big',
+      ),
+      'expire' => array(
+        'description' => 'A Unix timestamp indicating when the cache entry should expire, or 0 for never.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'created' => array(
+        'description' => 'A Unix timestamp indicating when the cache entry was created.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'headers' => array(
+        'description' => 'Any custom HTTP headers to be added to cached data.',
+        'type' => 'text',
+        'not null' => FALSE,
+      ),
+      'serialized' => array(
+        'description' => 'A flag to indicate whether content is serialized (1) or not (0).',
+        'type' => 'int',
+        'size' => 'small',
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+    ),
+    'indexes' => array(
+      'expire' => array('expire'),
+    ),
+    'primary key' => array('cid'),
+  );
+
+  $schema['image_presets'] = array(
+    'fields' => array(
+      'ipid' => array(
+        'description' => 'The primary identifier for an image preset.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE),
+      'name' => array(
+        'description' => 'The preset name.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE),
+    ),
+    'primary key' => array('ipid'),
+    'indexes' => array(
+      'name' => array('name'),
+    ),
+  );
+
+  $schema['image_actions'] = array(
+    'fields' => array(
+      'iaid' => array(
+        'description' => 'The primary identifier for an image action.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE),
+      'ipid' => array(
+        'description' => 'The primary identifier for an image preset.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0),
+      'weight' => array(
+        'description' => 'The weight of the action in the preset.',
+        'type' => 'int',
+        'unsigned' => FALSE,
+        'not null' => TRUE,
+        'default' => 0),
+      'action' => array(
+        'description' => 'The unique ID of the action to be executed.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE),
+      'data' => array(
+        'description' => 'The configuration data for the action.',
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'serialize' => TRUE),
+    ),
+    'primary key' => array('iaid'),
+    'indexes' => array(
+      'ipid' => array('ipid'),
+    ),
+  );
+ 
+  db_create_table($ret, 'cache_image', $schema['cache_image']);
+  db_create_table($ret, 'image_presets', $schema['image_presets']);
+  db_create_table($ret, 'image_actions', $schema['image_actions']);
+  return $ret;
+}
+
+/**
  * @} End of "defgroup updates-6.x-to-7.x"
  * The next series of updates should start at 8000.
  */
Index: modules/system/system.info
===================================================================
RCS file: /cvs/drupal/drupal/modules/system/system.info,v
retrieving revision 1.14
diff -u -r1.14 system.info
--- modules/system/system.info	17 Jun 2009 10:46:49 -0000	1.14
+++ modules/system/system.info	5 Jul 2009 23:36:02 -0000
@@ -8,6 +8,7 @@
 files[] = system.admin.inc
 files[] = system.queue.inc
 files[] = image.gd.inc
+files[] = image.actions.inc
 files[] = system.install
 files[] = system.test
 files[] = system.tar.inc
Index: includes/image.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/image.inc,v
retrieving revision 1.33
diff -u -r1.33 image.inc
--- includes/image.inc	20 Apr 2009 20:02:30 -0000	1.33
+++ includes/image.inc	5 Jul 2009 23:36:00 -0000
@@ -384,5 +384,561 @@
 }
 
 /**
+ * Get an array of all presets and their settings.
+ *
+ * @return
+ *   Array of presets array($pid => array('id' => integer, 'name' => string)).
+ */
+function image_presets() {
+  $presets = &drupal_static(__FUNCTION__, array());
+
+  // Grab from cache or build the array.
+  if ($cache = cache_get('image_presets', 'cache')) {
+    $presets = $cache->data;
+  }
+  else {
+    $result = db_select('image_presets', NULL, array('fetch' => PDO::FETCH_ASSOC))
+      ->fields('image_presets')
+      ->orderBy('name')
+      ->execute();
+    foreach ($result as $preset) {
+      $presets[$preset['name']] = $preset;
+      $presets[$preset['name']]['actions'] = image_preset_actions($preset);
+    }
+
+    cache_set('image_presets', $presets);
+  }
+
+  return $presets;
+}
+
+/**
+ * Get an array of image presets suitable for using as select list options.
+ *
+ * @param $include_empty
+ *   If TRUE a <none> option will be inserted in the options array.
+ * @return
+ *   Array of image presets both key and value are set to preset name.
+ */
+function image_preset_options($include_empty = TRUE) {
+  $presets = image_presets();
+  $options = array();
+  if ($include_empty && !empty($presets)) {
+    $options[''] = t('<none>');
+  }
+  $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' => array('ipid' => int, 'name' => string, 'actions' => array())).
+ *   If the preset name or ID is not valid, an empty array is returned.
+ */
+function image_preset_load($name = NULL, $pid = NULL) {
+  $presets = image_presets();
+
+  // 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');
+    if ($old_preset['name'] != $preset['name']) {
+      $preset['old_name'] = $old_preset['name'];
+    }
+  }
+  else {
+    $preset['is_new'] = TRUE;
+    drupal_write_record('image_presets', $preset);
+  }
+
+  // Let other modules update as necessary on save.
+  module_invoke_all('image_preset_save', $preset);
+
+  // Clear all caches and flush.
+  image_preset_flush($preset);
+
+  return $preset;
+}
+
+/**
+ * Delete an image preset.
+ *
+ * @param $preset
+ *   An image preset array.
+ * @param $replacement_preset_name
+ *   (optional) When deleting a preset, specify a replacement preset name so
+ *   that existing settings (if any) may be converted to a new preset.
+ * @return
+ *   TRUE on success.
+ */
+function image_preset_delete($preset, $replacement_preset_name = '') {
+  image_preset_flush($preset);
+
+  db_delete('image_actions')->condition('ipid', $preset['ipid'])->execute();
+  db_delete('image_presets')->condition('ipid', $preset['ipid'])->execute();
+
+  // Let other modules update as necessary on save.
+  $preset['old_name'] = $preset['name'];
+  $preset['name'] = $replacement_preset_name;
+  module_invoke_all('image_preset_delete', $preset);
+
+  return TRUE;
+}
+
+/**
+ * Load all the actions for an image preset.
+ *
+ * @param $preset
+ *   An image preset array.
+ * @return
+ *   An array of actions associated with specified preset in the format
+ *   array('ipid' => array()), or an empty array if the specified preset has
+ *   no actions.
+ */
+function image_preset_actions($preset) {
+  $actions = image_actions();
+  $preset_actions = array();
+  foreach ($actions as $action) {
+    if ($preset['ipid'] == $action['ipid']) {
+      $preset_actions[$action['iaid']] = $action;
+    }
+  }
+
+  return $preset_actions;
+}
+
+/**
+ * Flush cached media for a preset.
+ *
+ * @param $preset
+ *   An image preset array.
+ */
+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);
+  }
+
+  // Let other modules update as necessary on flush.
+  module_invoke_all('image_preset_flush', $preset);
+
+  // Clear image preset and action caches.
+  cache_clear_all('image_presets', 'cache');
+  cache_clear_all('image_actions', 'cache');
+  drupal_static_reset('image_presets');
+  drupal_static_reset('image_actions');
+
+  // Clear page caches when flushing.
+  cache_clear_all('*', 'cache_block', TRUE);
+  cache_clear_all('*', 'cache_page', TRUE);
+}
+
+/**
+ * Return the complete URL to an image when using a preset.
+ *
+ * If the image has already been created then its location will be returned. If
+ * it does not then image_preset_generate_url() will be called.
+ *
+ * @param $preset_name
+ *   The name of the preset to be used with this image.
+ * @param $path
+ *   The path to the image.
+ * @return
+ *   The absolute URL to a preset image. If the site is using the default
+ *   method for generating images, the may not yet and will only be created
+ *   when a visitor's browser requests the file.
+ * @see image_preset_generate_url()
+ */
+function image_preset_url($preset_name, $path) {
+  $preset_path = image_preset_path($preset_name, $path);
+  if (file_exists($preset_path)) {
+    return file_create_url($preset_path);
+  }
+  return image_preset_generate_url($preset_name, $path);
+}
+
+/**
+ * Return a relative path to an image when using a preset.
+ *
+ * The path returned by this function may not exist. The default generation
+ * method only creates images when they are requested by a user's browser.
+ *
+ * @param $preset_name
+ *   The name of the preset to be used with this image.
+ * @param $path
+ *   The path to the image.
+ * @return
+ *   The path to an image preset image relative to Drupal's root.
+ */
+function image_preset_path($preset_name, $path) {
+  return file_directory_path() . '/presets/' . $preset_name . '/' . _image_strip_file_directory($path);
+}
+
+/**
+ * Given an image preset and path return a path where the derivative image can
+ * be downloaded.
+ *
+ * @param $preset_name
+ *   The name of the preset to be used with this image.
+ * @param $path
+ *   The path to the image.
+ * @return
+ *   An absolute URL where the image can be downloaded. If the site is using
+ *   the default method for generating images, the may not yet and will only
+ *   be created when a visitor's browser requests the file.
+ * @see system_image_preset_generate_url()
+ */
+function image_preset_generate_url($preset_name, $path) {
+  $path = _image_strip_file_directory($path);
+
+  if (!$preset = image_preset_load($preset_name)) {
+    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;
+  }
+
+  $default_method = 'system_image_preset_generate_url';
+  $method = variable_get('image_preset_generation_method', $default_method);
+  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 exist.
+  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_load($source)) {
+    return FALSE;
+  }
+
+  foreach ($preset['actions'] as $action) {
+    if (!empty($action['data'])) {
+      // Make sure the width and height are computed first so they can be used
+      // in relative x/y offsets like 'center' or 'bottom'.
+      if (isset($action['data']['width'])) {
+        $action['data']['width'] = _image_filter_percent($action['data']['width'], $image->info['width']);
+      }
+      if (isset($action['data']['height'])) {
+        $action['data']['height'] = _image_filter_percent($action['data']['height'], $image->info['height']);
+      }
+      if (isset($action['data']['xoffset'])) {
+        $action['data']['xoffset'] = _image_filter_percent($action['data']['xoffset'], $image->info['width'], $action['data']['width']);
+        $action['data']['xoffset'] = _image_filter_keyword($action['data']['xoffset'], $image->info['width'], $action['data']['width']);
+      }
+      if (isset($action['data']['yoffset'])) {
+        $action['data']['yoffset'] = _image_filter_percent($action['data']['yoffset'], $image->info['height'], $action['data']['height']);
+        $action['data']['yoffset'] = _image_filter_keyword($action['data']['yoffset'], $image->info['height'], $action['data']['height']);
+      }
+    }
+    image_action_apply($image, $action);
+  }
+
+  if (!image_save($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.
+ *
+ * @return
+ *   Array of image actions.
+ */
+function image_actions() {
+  $actions = &drupal_static(__FUNCTION__);
+
+  if (!isset($actions)) {
+    $actions = array();
+
+    // Add database image actions.
+    $result = db_select('image_actions', NULL, array('fetch' => PDO::FETCH_ASSOC))
+      ->fields('image_actions')
+      ->orderBy('image_actions.weight', 'ASC')
+      ->execute();
+    foreach ($result as $action) {
+      $action['data'] = unserialize($action['data']);
+      $definition = image_action_definition_load($action['action']);
+      // 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 $toolkit
+ *   Name of the image toolkit to use. Toolkit set by image_get_toolkit()
+ *   will be used if none is specified.
+ * @return
+ *   An array of actions to be used when transforming images.
+ */
+function image_action_definitions($toolkit = NULL) {
+  $actions = &drupal_static(__FUNCTION__);
+
+  if (!isset($toolkit)) {
+    $toolkit = image_get_toolkit();
+  }
+
+  if (!isset($actions)) {
+    if ($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.
+          $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.
+ * @return
+ *   An array containing at least the following values.
+ *   array(
+ *    'action' => Unique name of the action being performed.
+ *    'module' => Name of module providing the action.
+ *    'description' => A description of the action.
+ *   )
+ */
+function image_action_definition_load($action) {
+  $definition_cache = &drupal_static(__FUNCTION__);
+
+  if (!isset($definition_cache[$action])) {
+    $definitions = image_action_definitions();
+    $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.
+ * @return
+ *   An image action array or FALSE if the specified action can not be found.
+ */
+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.
+ * @return
+ *   An image action array. In the case of a new action 'iaid' will be set.
+ */
+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);
+  return $action;
+}
+
+/**
+ * Delete an image action.
+ *
+ * @param $action
+ *   An image action array.
+ */
+function image_action_delete($action) {
+  db_delete('image_actions')->condition('iaid', $action['iaid'])->execute();
+  $preset = image_preset_load(NULL, $action['ipid']);
+  image_preset_flush($preset);
+}
+
+/**
+ * Given an image object and action, perform the action on the file.
+ *
+ * @param $image
+ *   An image object.
+ * @param $action
+ *   An image action array.
+ * @return
+ *   TRUE on success. FALSE if unable to perform action on image.
+ */
+function image_action_apply(&$image, $action) {
+  if (drupal_function_exists($action['function'])) {
+    return call_user_func($action['function'], $image, $action['data']);
+  }
+  return FALSE;
+}
+
+/**
+ * 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 a pixel offset.
+ */
+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.
+ *
+ * @param $path
+ *   Path to a file that may be in Drupal's files directory.
+ * @return
+ *   String with Drupal's files directory removed from it.
+ */
+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().
+ *
+ * @see image_action_definitions()
+ */
+function _image_actions_definitions_sort($a, $b) {
+  return strcasecmp($a['name'], $b['name']);
+}
+
+/**
  * @} End of "defgroup image".
  */
Index: includes/theme.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/theme.inc,v
retrieving revision 1.497
diff -u -r1.497 theme.inc
--- includes/theme.inc	2 Jul 2009 04:27:22 -0000	1.497
+++ includes/theme.inc	5 Jul 2009 23:36:02 -0000
@@ -1336,11 +1336,46 @@
  *   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 '<img src="' . check_url($url) . '" alt="' . check_plain($alt) . '" title="' . check_plain($title) . '" ' . (isset($image_attributes) ? $image_attributes : '') . $attributes . ' />';
+  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 '<img src="' . check_url($url) . '" alt="' . check_plain($alt) . '" title="' . check_plain($title) . '" ' . (isset($image_attributes) ? $image_attributes : '') . $attributes . ' />';
+}
+
+/**
+ * Return a themed image using a specific image preset.
+ *
+ * @param $preset_name
+ *   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_name, $path, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE) {
+  // theme_image() can only honor the $getsize parameter with local file paths.
+  // The derivative image is not created until it has been requested so the file
+  // may not yet exist, in this case we just fallback to the URL.
+  $preset_path = image_preset_path($preset_name, $path);
+  if (!file_exists($preset_path)) {
+    $preset_path = image_preset_url($preset_name, $path);
+  }
+  return theme('image', $preset_path, $alt, $title, $attributes, $getsize);
 }
 
 /**
Index: includes/common.inc
===================================================================
RCS file: /cvs/drupal/drupal/includes/common.inc,v
retrieving revision 1.930
diff -u -r1.930 common.inc
--- includes/common.inc	4 Jul 2009 18:26:42 -0000	1.930
+++ includes/common.inc	5 Jul 2009 23:35:59 -0000
@@ -4071,6 +4071,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),
     ),
@@ -4737,7 +4740,7 @@
   node_types_rebuild();
   // Don't clear cache_form - in-progress form submissions may break.
   // Ordered so clearing the page cache will always be the last action.
-  $core = array('cache', 'cache_filter', 'cache_registry', 'cache_page');
+  $core = array('cache', 'cache_filter', 'cache_registry', 'cache_page', 'cache_image');
   $cache_tables = array_merge(module_invoke_all('flush_caches'), $core);
   foreach ($cache_tables as $table) {
     cache_clear_all('*', $table, TRUE);
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,327 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Image actions provided by Drupal core.
+ */
+
+/**
+ * Implements hook_image_actions.
+ *
+ * @return array
+ *   An array of information on the actions implemented by a module. The array
+ *   contains a sub-array for each action node type, with the machine-readable
+ *   action name as the key. Each sub-array has the following possible
+ *    attributes:
+ *    - name: The human-readable name of the action. Required.
+ *    - description: a brief description of the action. Required.
+ *    - function: The name of the function that will perform this action.
+ *      Required.
+ *    - form: The name of the function providing a configuration form for this
+ *      action. Required.
+ *    - summary: The name of a theme function for outputting a summary of the
+ *      action configuration. Required.
+ *    - file: The name of the include file the action can be found in relative
+ *      to the implementing module's path.
+ *    - file path: The directory path to the include file. If left empty, this
+ *      will default to the implementing module's directory.
+ */
+function system_image_actions() {
+  $actions = array(
+    'system_image_resize' => 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 grayscale.'),
+      'function' => 'system_image_desaturate_image',
+    ),
+    '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;
+}
+
+/**
+ * Return the URL for derivative given a preset and image path.
+ *
+ * This function is the default image generation method. It returns a URL for
+ * an image that can be used in an <img> tag. When the browser requests the
+ * image at system/image/[preset_name]/[path] the image is generated if it does
+ * not already exist and then served to the browser. This allows each image to
+ * have its own PHP instance (and memory limit) for generation of the new image.
+ *
+ * @param $preset
+ *   An image preset array.
+ * @param $path
+ *   The local path of the image to be generated, excluding the files
+ *   directory.
+ * @return
+ *   Absolute URL where the image can be downloaded, suitale for use in an
+ *   <img> tag. Requesting the URL will cause the image to be created.
+ * @see system_image_preset_generate()
+ * @see image_preset_generate_url()
+ */
+function system_image_preset_generate_url($preset, $path) {
+  $destination = image_preset_path($preset['name'], $path);
+
+  // If the image already exists return true.
+  if (file_exists($destination)) {
+    return image_preset_url($preset['name'], $path);
+  }
+
+  // Disable page cache for this request. This prevents anonymous users from
+  // needlessly hitting the image generation URL when the image already exists.
+  $GLOBALS['conf']['cache'] = CACHE_DISABLED;
+
+  // Set a cache entry to grant access to this preset/image path. This will be
+  // checked by system_image_preset_generate().
+  cache_set('access:' . $preset['name'] . ':' . md5($path), 1, 'cache_image', time() + 600);
+
+  // Generate a callback path for the image.
+  $url = url('system/image/' . $preset['name'] . '/' . $path, array('absolute' => TRUE));
+  return $url;
+}
+
+/**
+ * Menu callback; Given a preset and image path, generate a derivative.
+ *
+ * This menu callback is always served after checking a token to prevent
+ * generation of unnecessary images. After generating an image transfer it to
+ * the requesting agent via file_transfer().
+ */
+function system_image_preset_generate() {
+  $args = func_get_args();
+  $preset = array_shift($args);
+  $preset_name = $preset['name'];
+  $path = implode('/', $args);
+
+  $source = file_create_path($path);
+  $path_md5 = md5($path);
+  $destination = image_preset_path($preset['name'], $path);
+
+  // Check that it's a defined preset and that access was granted by
+  // system_image_preset_generate_url().
+  if (!$preset || !cache_get('access:' . $preset_name . ':' . $path_md5, 'cache_image')) {
+    drupal_access_denied();
+    exit();
+  }
+
+  // Don't start generating the image if it is already in progress.
+  $cid = 'generate:' . $preset_name . ':' . $path_md5;
+  if (cache_get($cid, 'cache_image')) {
+    print t('Image generation in progress, please try again shortly.');
+    exit();
+  }
+
+  // If the image has already been generated then send it.
+  if ($image = image_load($destination)) {
+    file_transfer($image->source, array('Content-type: ' . $image->info['mime_type'], 'Content-length: ' . $image->info['file_size']));
+  }
+
+  // Set a cache entry designating this image as being in-process.
+  cache_set($cid, $destination, 'cache_image');
+
+  // Try to generate the image.
+  if (image_preset_create_derivative($preset, $source, $destination)) {
+    $image = image_load($destination);
+    cache_clear_all($cid, 'cache_image');
+    file_transfer($image->source, array('Content-type: ' . $image->info['mime_type'], 'Content-length: ' . $image->info['file_size']));
+  }
+  else {
+    cache_clear_all($cid, 'cache_image');
+    watchdog('image', 'Unable to generate the derived image located at %path.', $destination);
+    print t('Error generating image.');
+    exit();
+  }
+}
+
+/**
+ * Access callback for system/image.
+ *
+ * Ensure this request is made by Drupal by checking for a valid site token.
+ */
+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);
+}
+
+/**
+ * Implements the default image resize action.
+ *
+ * @param $image
+ *   An image object.
+ * @param $data
+ *   An array of attributes to use when performing the resize action.
+ *   array('width' => int, 'height' => int)
+ * @return
+ *   TRUE on success. FALSE on failure to resize image.
+ * @see image_resize()
+ */
+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('%image' => $image, '%data' => print_r($data, TRUE)), WATCHDOG_ERROR);
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/**
+ * Implements the default image scale action.
+ *
+ * @param $image
+ *   An image object.
+ * @param $data
+ *   An array of attributes to use when performing the scale action.
+ *   array('width' => int, 'height' => int, 'upscale' => bool)
+ * @return
+ *   TRUE on success. FALSE on failure to scale image.
+ * @see image_scale()
+ */
+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('%image' => $image, '%data' => print_r($data, TRUE)), WATCHDOG_ERROR);
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/**
+ * Implements the default image crop action.
+ *
+ * @param $image
+ *   An image object.
+ * @param $data
+ *   An array containting attributes to use when preforming the crop action.
+ *   array('xoffset' => int, 'yoffset' => int, 'width' => int, 'height' => int)
+ * @return
+ *   TRUE on success. FALSE on failure to crop image.
+ * @see image_crop()
+ */
+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('%image' => $image, '%data' => print_r($data, TRUE)), WATCHDOG_ERROR);
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/**
+ * Implements the default image scale and crop action.
+ *
+ * @param $image
+ *   An image object.
+ * @param $data
+ *   An array of attributes to use when performing the scale and crop action.
+ *   array('width' => int, 'height' => int)
+ * @return
+ *   TRUE on success. FALSE on failure to scale and crop image.
+ * @see image_scale_and_crop()
+ */
+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('%image' => $image, '%data' => print_r($data, TRUE))), WATCHDOG_ERROR);
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/**
+ * Implements the default image desaturate action.
+ *
+ * @param $image
+ *   An image object.
+ * @param $data
+ *   An array of attributes to use when performing the desaturate action.
+ * @return
+ *   TRUE on success. FALSE on failure to desaturate image.
+ * @see image_desaturate()
+ */
+function system_image_desaturate_image(&$image, $data) {
+  if (!image_desaturate($image)) {
+    watchdog('image', 'Image desaturate failed. image: %image, data: %data.', array('%image' => $image, '%data' => print_r($data, TRUE)), WATCHDOG_ERROR);
+    return FALSE;
+  }
+  return TRUE;
+}
+
+/**
+ * Implements the default image rotate action.
+ *
+ * @param $image
+ *   An image object.
+ * @param $data
+ *   An array of attributes to use when performing the rotate action.
+ *   array('degrees' => int, 'random' => bool, 'bgcolor' => string)
+ * @return
+ *   TRUE on success. FALSE on failure to rotate image.
+ * @see image_rotate().
+ */
+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;
+
+  // Convert short #FFF syntax to full #FFFFFF syntax.
+  if (strlen($data['bgcolor']) == 4) {
+    $c = $data['bgcolor'];
+    $data['bgcolor'] = $c[0] . $c[1] . $c[1] . $c[2] . $c[2] . $c[3] . $c[3];
+  }
+
+  // Convert #FFFFFF syntax to hexadecimal colors.
+  if ($data['bgcolor'] != '') {
+    $data['bgcolor'] = hexdec(str_replace('#', '0x', $data['bgcolor']));
+  }
+  else {
+    $data['bgcolor'] = NULL;
+  }
+
+  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('%image' => $image, '%data' => print_r($data, TRUE))), WATCHDOG_ERROR);
+    return FALSE;
+  }
+  return TRUE;
+}
