Index: image.admin.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/image/image.admin.inc,v
retrieving revision 1.9.2.2
diff -u -p -r1.9.2.2 image.admin.inc
--- image.admin.inc	3 Aug 2010 17:43:00 -0000	1.9.2.2
+++ image.admin.inc	12 Oct 2010 07:51:56 -0000
@@ -60,6 +60,20 @@ function image_admin_settings() {
     );
   }
 
+  // Allow for third-party modules (imagecache) to add to the available preset
+  // operations.
+  // This will include our own image.module built-in actions also.
+  // @see image_image_operations()
+  $operations = module_invoke_all('image_operations', TRUE);
+  // Convert the operation info to a select list.
+  $operation_options = array();
+  foreach ($operations as $operation_id => $operation) {
+    $operation_options[$operation_id] = $operation['name'];
+    if ($operation['module'] != 'image') {
+      $operation_options[$operation_id] .= ' (' . $operation['module'] . ')';
+    }
+  }
+
   foreach ($sizes as $key => $size) {
     $form['image_sizes'][$key]['label'] = array(
       '#type' => 'textfield',
@@ -77,20 +91,34 @@ function image_admin_settings() {
     $form['image_sizes'][$key]['operation'] = array(
       '#type' => 'select',
       '#default_value' => $size['operation'],
-      '#options' => array('scale' => t('Scale image'), 'scale_crop' => t('Scale and crop image')),
-    );
-    $form['image_sizes'][$key]['width'] = array(
-      '#type' => 'textfield',
-      '#default_value' => $size['width'],
-      '#size' => 5,
-      '#maxlength' => 5,
-    );
-    $form['image_sizes'][$key]['height'] = array(
-      '#type' => 'textfield',
-      '#default_value' => $size['height'],
-      '#size' => 5,
-      '#maxlength' => 5,
+      '#options' => $operation_options,
     );
+    // Only show size fields for operations that ask for it
+    if ($operations[$size['operation']]['needs_dimensions']) {
+      $form['image_sizes'][$key]['width'] = array(
+        '#type' => 'textfield',
+        '#default_value' => $size['width'],
+        '#size' => 5,
+        '#maxlength' => 5,
+      );
+      $form['image_sizes'][$key]['height'] = array(
+        '#type' => 'textfield',
+        '#default_value' => $size['height'],
+        '#size' => 5,
+        '#maxlength' => 5,
+      );
+    }
+    elseif (isset($operations[$size['operation']])) {
+      // Allow the providing module to put something informative here
+      // instead of the dimensions.
+      $operation = $operations[$size['operation']];
+      if (!empty($operation['extra'])) {
+        $form['image_sizes'][$key]['extra'] = array(
+          '#type' => 'markup',
+          '#value' => $operation['extra'],
+        );
+      }
+    }
     $form['image_sizes'][$key]['link'] = array(
       '#type' => 'select',
       '#default_value' => $size['link'],
@@ -110,12 +138,21 @@ function image_admin_settings() {
  */
 function image_admin_settings_validate($form, &$form_state) {
   // Check that the sizes provided have the required amount of information.
+  $operations = module_invoke_all('image_operations');
   $image_sizes = $form_state['values']['image_sizes'];
   foreach (element_children($image_sizes) as $key) {
-    // If there's a label they must provide at either a height or width.
-    if ($key != IMAGE_ORIGINAL && !empty($image_sizes[$key]['label'])) {
-      if (empty($image_sizes[$key]['width']) && empty($image_sizes[$key]['height'])) {
-        form_set_error("image_sizes][$key][width", t('You must specify width, height or both dimensions.'));
+    $size = $image_sizes[$key];
+    $operation = $operations[$size['operation']];
+    // If there's a label, may be required to provide either a height or width.
+
+    // Note, this validation is weak here now, as it will only validate when
+    // all inputs are as expected.
+    // Checking only 'needs_dimensions' or only width & height makes it
+    // impossible to switch operation types and still validate without a form
+    // rebuild (which I cannot force from here).
+    if ($key != IMAGE_ORIGINAL && !empty($size['label']) && !empty($operation['needs_dimensions']) && isset($size['width']) && isset($size['height'])) {
+      if (empty($size['width']) && empty($size['height'])) {
+        form_set_error("image_sizes][$key][label", t('You must specify width, height or both dimensions.'));
       }
     }
   }
@@ -172,7 +209,9 @@ function image_admin_settings_submit($fo
     else if (isset($form_state['values']['image_sizes'][$key]) && isset($old_sizes[$key])) {
       // Did the operation, height or width change?
       foreach (array('operation', 'height', 'width') as $field) {
-        $rebuild |= ($form_state['values']['image_sizes'][$key][$field] != $old_sizes[$key][$field]);
+        if (isset($form_state['values']['image_sizes'][$key][$field]) && isset($old_sizes[$key][$field]) && ($form_state['values']['image_sizes'][$key][$field] != $old_sizes[$key][$field])) {
+          $rebuild = TRUE;
+        }
       }
     }
   }
@@ -191,8 +230,18 @@ function theme_image_settings_sizes_form
     $row = array();
     $row[] = drupal_render($form[$key]['label']);
     $row[] = drupal_render($form[$key]['operation']);
-    $row[] = drupal_render($form[$key]['width']);
-    $row[] = drupal_render($form[$key]['height']);
+    // If it's a third-party action, don't show the dimensions,
+    // show its provided info instead.
+    if (!empty($form[$key]['extra'])) {
+      $row[] = array(
+        'colspan' => 2,
+        'data' => drupal_render($form[$key]['extra']),
+      );
+    }
+    else {
+      $row[] = drupal_render($form[$key]['width']);
+      $row[] = drupal_render($form[$key]['height']);
+    }
     $row[] = drupal_render($form[$key]['link']);
     $rows[] = $row;
   }
Index: image.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/image/Attic/image.module,v
retrieving revision 1.322.2.4
diff -u -p -r1.322.2.4 image.module
--- image.module	18 Aug 2010 16:40:06 -0000	1.322.2.4
+++ image.module	12 Oct 2010 07:51:57 -0000
@@ -787,6 +787,73 @@ function _image_check_settings() {
 }
 
 /**
+ * Implementation of (our own) hook_image_operations(). 
+ * 
+ * Declare the operations we make available for use as derivative sizes.
+ * Returns an array of operation definitions, keyed by operation id.
+ * 
+ * The operation definition may contain the keys:
+ *   - name: Human-readable name for this action
+ *   - module: Name of the module that provides the operation
+ *   - callback: Name of the function that performs the operation
+ *   - extra: If present, it may provide some additional information about the
+ * operation, such as a link to its configuration page
+ * 
+ * See _image_build_derivative_scale() for the "callback" function signature.
+ * 
+ * @return an array of image operation definitions.
+ * 
+ * @see image_admin_settings()
+ */
+function image_image_operations() {
+  $operations = array(
+    'scale' => array(
+      'name' => t('Scale image'),
+      'module' => 'image',
+      'callback' => '_image_build_derivative_scale',
+      'needs_dimensions' => TRUE,
+      // This indicates that basic width x height fields should be displayed 
+      // on the configuration form
+    ),
+    'scale_crop' => array(
+      'name' => t('Scale and crop image'),
+      'module' => 'image',
+      'callback' => '_image_build_derivative_scale_and_crop',
+      'needs_dimensions' => TRUE,
+    ),
+  );
+  return $operations;
+}
+
+/**
+ * Generate a single image derivative, using the 'scale' operation. Invoked as a
+ * named callback from within _image_build_derivatives().
+ *
+ * @param $original_path
+ *   Source image
+ * @param $destination
+ *   Save   image as
+ * @param $derivative_info
+ *   An array of data that image.module settings form may save. Normally this
+ * includes the derivative name, operation id, and width and height.
+ * $derivative_info['operation'] will be of the form 'imagecache-n' where n is a
+ * preset id that we will build
+ * 
+ * @see _image_build_derivatives()
+ */
+function _image_build_derivative_scale($original_path, $destination, $derivative_info = array()) {
+  return image_scale($original_path, $destination, $derivative_info['width'], $derivative_info['height']);
+}
+
+/**
+ * Generate a single image derivative, using the 'scale and crop' operation.
+ * Invoked as a named callback from within _image_build_derivatives().
+ */
+function _image_build_derivative_scale_and_crop($original_path, $destination, $derivative_info = array()) {
+  return image_scale_and_crop($original_path, $destination,  $derivative_info['width'], $derivative_info['height']);
+}
+
+/**
  * Determine which sizes of derivative images need to be built for this image.
  *
  * @param $image_path
@@ -810,6 +877,11 @@ function image_get_derivative_sizes($ima
     if ($key == IMAGE_ORIGINAL) {
       continue;
     }
+    // If not one of our own processess, always rebuild.
+    if ($size['operation'] != 'scale' && $size['operation'] != 'scale_crop') {
+      $sizes[$key] = $size;
+      continue;
+    }
 
     // If the original isn't bigger than the requested size then there's no
     // need to resize it.
@@ -849,24 +921,31 @@ function _image_build_derivatives($node,
 
   // Resize for the necessary sizes.
   $image_info = image_get_info($original_path);
+  $operations = module_invoke_all('image_operations');
   foreach ($needed_sizes as $key => $size) {
     $destination = _image_filename($original_path, $key, $temp);
 
     $status = FALSE;
-    switch ($size['operation']) {
-      // Depending on the operation, the image will be scaled or resized & cropped
-      case 'scale':
-        $status = image_scale($original_path, $destination, $size['width'], $size['height']);
-        break;
-
-      case 'scale_crop':
-        $status = image_scale_and_crop($original_path, $destination, $size['width'], $size['height']);
-        break;
+
+    // Depending on the operation, different callbacks will be invoked.
+    // Normally, for scale or scale_crop, that will mean calling our own 
+    // callbacks declared in hook_image_operations() and executed in 
+    // _image_build_derivative_scale() _image_build_derivative_scale_and_crop().
+
+    $operation = $operations[$size['operation']];
+    // Be very careful not to die if the needed module or setting is missing.
+    if (!empty($operation['callback']) && function_exists($operation['callback'])) {
+      $status = $operation['callback']($original_path, $destination, $size);
+    }
+    else {
+      watchdog('image', 'When building image derivative %key, could not access the callback function [%operation_callback] to process the operation %operation. Not building this derivative image.', array('%key' => $key, '%operation_name' => $operation['name'], '%operation_callback' => $operation['callback']), WATCHDOG_ERROR);
+      $status = FALSE;
     }
 
     if (!$status) {
       drupal_set_message(t('Unable to create scaled %label image.', array('%label' => $size['label'])), 'error');
-      return FALSE;
+      // One failure may not mean that we should give up entirely. 
+      // Make the other derivatives anyway.
     }
     // Set standard file permissions for webserver-generated files
     @chmod($destination, 0664);
