Index: modules/image/image.test
===================================================================
RCS file: /cvs/drupal/drupal/modules/image/image.test,v
retrieving revision 1.9
diff -u -r1.9 image.test
--- modules/image/image.test	12 Oct 2009 05:22:57 -0000	1.9
+++ modules/image/image.test	13 Oct 2009 04:48:15 -0000
@@ -134,17 +134,6 @@
   }
 
   /**
-   * Test the image_effects() and image_effect_definitions() functions.
-   */
-  function testEffects() {
-    $effects = image_effects();
-    $this->assertEqual(count($effects), 1, t("Found core's image effect."));
-
-    $effect_definitions = image_effect_definitions();
-    $this->assertEqual(count($effect_definitions), 6, t("Found core's image effects."));
-  }
-
-  /**
    * Test the image_resize_effect() function.
    */
   function testResizeEffect() {
Index: modules/image/image.admin.inc
===================================================================
RCS file: /cvs/drupal/drupal/modules/image/image.admin.inc,v
retrieving revision 1.12
diff -u -r1.12 image.admin.inc
--- modules/image/image.admin.inc	9 Oct 2009 01:00:00 -0000	1.12
+++ modules/image/image.admin.inc	13 Oct 2009 04:48:14 -0000
@@ -39,21 +39,46 @@
   $title = t('Edit %name style', array('%name' => $style['name']));
   drupal_set_title($title, PASS_THROUGH);
 
+  // Adjust this form for styles that must be overridden to edit.
+  $editable = (bool) ($style['storage'] & IMAGE_STORAGE_EDITABLE);
+
+  if (!$editable && empty($form_state['input'])) {
+    drupal_set_message(t('This image style is currently being provided by a module. Click the "Override defaults" button to change its settings.'), 'warning');
+  }
+
   $form_state['image_style'] = $style;
   $form['#tree'] = TRUE;
   $form['#attached']['css'][drupal_get_path('module', 'image') . '/image.admin.css'] = array('preprocess' => FALSE);
 
-  // Allow the name of the style to be changed.
-  $form['name'] = array(
-    '#type' => 'textfield',
-    '#size' => '64',
-    '#title' => t('Image style name'),
-    '#default_value' => $style['name'],
-    '#description' => t('The name is used in URLs for generated images. Use only lowercase alphanumeric characters, underscores (_), and hyphens (-).'),
-    '#element_validate' => array('image_style_name_validate'),
-    '#required' => TRUE,
+  // Show the thumbnail preview.
+  $form['preview'] = array(
+    '#type' => 'item',
+    '#title' => t('Preview'),
+    '#markup' => theme('image_style_preview', array('style' => $style)),
   );
 
+  // Allow the name of the style to be changed, unless this style is
+  // provided by a module's hook_default_image_styles().
+  if ($style['storage'] & IMAGE_STORAGE_MODULE) {
+    $form['name'] = array(
+      '#type' => 'item',
+      '#title' => t('Image style name'),
+      '#markup' => $style['name'],
+      '#description' => t('This image style is being provided by %module module and may not be renamed.', array('%module' => $style['module'])),
+    );
+  }
+  else {
+    $form['name'] = array(
+      '#type' => 'textfield',
+      '#size' => '64',
+      '#title' => t('Image style name'),
+      '#default_value' => $style['name'],
+      '#description' => t('The name is used in URLs for generated images. Use only lowercase alphanumeric characters, underscores (_), and hyphens (-).'),
+      '#element_validate' => array('image_style_name_validate'),
+      '#required' => TRUE,
+    );
+  }
+
   // Build the list of existing image effects for this image style.
   $form['effects'] = array(
     '#theme' => 'image_style_effects',
@@ -69,12 +94,15 @@
     $form['effects'][$ieid]['weight'] = array(
       '#type' => 'weight',
       '#default_value' => $effect['weight'],
+      '#access' => $editable,
     );
     $form['effects'][$ieid]['configure'] = array(
       '#markup' => isset($effect['form callback']) ? l(t('edit'), 'admin/config/media/image-styles/edit/' . $style['name'] . '/effects/' . $effect['ieid'] ) : '',
+      '#access' => $editable,
     );
     $form['effects'][$ieid]['remove'] = array(
       '#markup' => l(t('delete'), 'admin/config/media/image-styles/edit/' . $style['name'] . '/effects/' . $effect['ieid'] . '/delete'),
+      '#access' => $editable,
     );
   }
 
@@ -86,6 +114,7 @@
   $form['effects']['new'] = array(
     '#tree' => FALSE,
     '#weight' => isset($form_state['input']['weight']) ? $form_state['input']['weight'] : NULL,
+    '#access' => $editable,
   );
   $form['effects']['new']['new'] = array(
     '#type' => 'select',
@@ -102,15 +131,18 @@
     '#submit' => array('image_style_form_submit', 'image_style_form_add_submit'),
   );
 
-  // Show the current preview of the style and the submit button.
-  $form['preview'] = array(
-    '#type' => 'item',
-    '#title' => t('Preview'),
-    '#markup' => theme('image_style_preview', array('style' => $style)),
+  // Show the Override or Submit button for this style.
+  $form['override'] = array(
+    '#type' => 'submit',
+    '#value' => t('Override defaults'),
+    '#validate' => array(),
+    '#submit' => array('image_style_form_override_submit'),
+    '#access' => !$editable,
   );
   $form['submit'] = array(
     '#type' => 'submit',
     '#value' => t('Update style'),
+    '#access' => $editable,
   );
 
   return $form;
@@ -149,12 +181,20 @@
 }
 
 /**
+ * Submit handler for overriding a module-defined style.
+ */
+function image_style_form_override_submit($form, &$form_state) {
+  drupal_set_message(t('The %style style has been overridden, allowing you to change its settings.', array('%style' => $form_state['image_style']['name'])));
+  image_default_style_save($form_state['image_style']);
+}
+
+/**
  * Submit handler for saving an image style.
  */
 function image_style_form_submit($form, &$form_state) {
   // Update the image style name if it has changed.
   $style = $form_state['image_style'];
-  if ($style['name'] != $form_state['values']['name']) {
+  if (isset($form_state['values']['name']) && $style['name'] != $form_state['values']['name']) {
     $style['name'] = $form_state['values']['name'];
   }
 
@@ -269,6 +309,30 @@
 }
 
 /**
+ * Confirmation form to revert a database style to its default.
+ */
+function image_style_revert_form($form, $form_state, $style) {
+  $form_state['image_style'] = $style;
+
+  return confirm_form(
+    $form,
+    t('Reset the %style style?', array('%style' => $style['name'])),
+    'admin/config/media/image-styles',
+    t('Resetting this style will revert it to the defaults provided by the @module module.', array('@module' => $style['module'])),
+    t('Reset'),  t('Cancel')
+  );
+}
+
+/**
+ * Submit handler to convert an overridden style to its default.
+ */
+function image_style_revert_form_submit($form, &$form_state) {
+  drupal_set_message(t('The %style style has been revert to its defaults.', array('%style' => $form_state['image_style']['name'])));
+  image_default_style_revert($form_state['image_style']);
+  $form_state['redirect'] = 'admin/config/media/image-styles';
+}
+
+/**
  * Form builder; Form for adding and editing image effects.
  *
  * This form is used universally for editing all image effects. Each effect adds
@@ -565,7 +629,7 @@
 function theme_image_style_list($variables) {
   $styles = $variables['styles'];
 
-  $header = array(t('Style name'), array('data' => t('Operations'), 'colspan' => 3));
+  $header = array(t('Style name'), t('Storage'), array('data' => t('Operations'), 'colspan' => 3));
   $rows = array();
   foreach ($styles as $style) {
     $row = array();
@@ -575,8 +639,21 @@
         'class' => array('image-style-link'),
       ),
     );
-    $row[] = l(t('edit'), 'admin/config/media/image-styles/edit/' . $style['name'], $link_attributes);
-    $row[] = l(t('delete'), 'admin/config/media/image-styles/delete/' . $style['name'], $link_attributes);
+    if ($style['storage'] == IMAGE_STORAGE_NORMAL) {
+      $row[] = t('Normal');
+      $row[] = l(t('edit'), 'admin/config/media/image-styles/edit/' . $style['name'], $link_attributes);
+      $row[] = l(t('delete'), 'admin/config/media/image-styles/delete/' . $style['name'], $link_attributes);
+    }
+    elseif ($style['storage'] == IMAGE_STORAGE_OVERRIDE) {
+      $row[] = t('Overridden');
+      $row[] = l(t('edit'), 'admin/config/media/image-styles/edit/' . $style['name'], $link_attributes);
+      $row[] = l(t('revert'), 'admin/config/media/image-styles/revert/' . $style['name'], $link_attributes);
+    }
+    else {
+      $row[] = t('Default');
+      $row[] = l(t('edit'), 'admin/config/media/image-styles/edit/' . $style['name'], $link_attributes);
+      $row[] = '';
+    }
     $rows[] = $row;
   }
 
@@ -623,10 +700,12 @@
       $row[] = array('data' => '', 'colspan' => 2);
     }
 
-    $rows[] = array(
-      'data' => $row,
-      'class' => array('draggable'),
-    );
+    if (!isset($form[$key]['#access']) || $form[$key]['#access']) {
+      $rows[] = array(
+        'data' => $row,
+        'class' => !empty($form[$key]['weight']['#access']) ? array('draggable') : array(),
+      );
+    }
   }
 
   $header = array(
@@ -635,7 +714,7 @@
     array('data' => t('Operations'), 'colspan' => 2),
   );
 
-  if (count($rows) == 1) {
+  if (count($rows) == 1 && $form['new']['#access']) {
     array_unshift($rows, array(array(
       'data' => t('There are currently no effects in this style. Add one by selecting an option below.'),
       'colspan' => 4,
Index: modules/image/image.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/image/image.module,v
retrieving revision 1.19
diff -u -r1.19 image.module
--- modules/image/image.module	12 Oct 2009 05:22:57 -0000	1.19
+++ modules/image/image.module	13 Oct 2009 04:48:15 -0000
@@ -6,6 +6,31 @@
  * Exposes global functionality for creating image styles.
  */
 
+/**
+ * Image style constant for user presets in the database.
+ */
+define('IMAGE_STORAGE_NORMAL', 1);
+
+/**
+ * Image style constant for user presets that override module-defined presets.
+ */
+define('IMAGE_STORAGE_OVERRIDE', 2);
+
+/**
+ * Image style constant for module-defined presets in code.
+ */
+define('IMAGE_STORAGE_DEFAULT', 4);
+
+/**
+ * Image style constant to represent an editable preset.
+ */
+define('IMAGE_STORAGE_EDITABLE', IMAGE_STORAGE_NORMAL | IMAGE_STORAGE_OVERRIDE);
+
+/**
+ * Image style constant to represent any module-based preset.
+ */
+define('IMAGE_STORAGE_MODULE', IMAGE_STORAGE_OVERRIDE | IMAGE_STORAGE_DEFAULT);
+
 // Load all Field module hooks for Image.
 require_once DRUPAL_ROOT . '/modules/image/image.field.inc';
 
@@ -31,7 +56,7 @@
       return '<p>' . t('Image styles commonly provide thumbnail sizes by scaling and cropping images, but can also add various effects before an image is displayed. When an image is displayed with a style, a new file is created and the original image is left unchanged.') . '</p>';
     case 'admin/config/media/image-styles/edit/%/add/%':
     case 'admin/config/media/image-styles/edit/%/effects/%':
-      $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[6]);
+      $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[6], $arg[4]);
       return isset($effect['help']) ? ('<p>' . $effect['help'] . '</p>') : NULL;
   }
 }
@@ -79,7 +104,7 @@
     'title' => 'Edit style',
     'description' => 'Configure an image style.',
     'page callback' => 'drupal_get_form',
-    'page arguments' => array('image_style_form', 5),
+    'page arguments' => array('image_style_form', 5, TRUE),
     'access arguments' => array('administer image styles'),
     'type' => MENU_CALLBACK,
     'file' => 'image.admin.inc',
@@ -87,15 +112,27 @@
   $items['admin/config/media/image-styles/delete/%image_style'] = array(
     'title' => 'Delete style',
     'description' => 'Delete an image style.',
+    'load arguments' => array(NULL, (string) IMAGE_STORAGE_NORMAL),
     'page callback' => 'drupal_get_form',
     'page arguments' => array('image_style_delete_form', 5, TRUE),
     'access arguments' => array('administer image styles'),
     'type' => MENU_CALLBACK,
     'file' => 'image.admin.inc',
   );
+  $items['admin/config/media/image-styles/revert/%image_style'] = array(
+    'title' => 'Revert style',
+    'description' => 'Revert an image style.',
+    'load arguments' => array(NULL, (string) IMAGE_STORAGE_OVERRIDE),
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('image_style_revert_form', 5, TRUE),
+    'access arguments' => array('administer image styles'),
+    'type' => MENU_CALLBACK,
+    'file' => 'image.admin.inc',
+  );
   $items['admin/config/media/image-styles/edit/%image_style/effects/%image_effect'] = array(
     'title' => 'Edit image effect',
     'description' => 'Edit an exiting effect within a style.',
+    'load arguments' => array(5, (string) IMAGE_STORAGE_EDITABLE),
     'page callback' => 'drupal_get_form',
     'page arguments' => array('image_effect_form', 5, 7),
     'access arguments' => array('administer image styles'),
@@ -105,6 +142,7 @@
   $items['admin/config/media/image-styles/edit/%image_style/effects/%image_effect/delete'] = array(
     'title' => 'Delete image effect',
     'description' => 'Delete an exiting effect from a style.',
+    'load arguments' => array(5, (string) IMAGE_STORAGE_EDITABLE),
     'page callback' => 'drupal_get_form',
     'page arguments' => array('image_effect_delete_form', 5, 7),
     'access arguments' => array('administer image styles'),
@@ -114,6 +152,7 @@
   $items['admin/config/media/image-styles/edit/%image_style/add/%image_effect_definition'] = array(
     'title' => 'Add image effect',
     'description' => 'Add a new effect to a style.',
+    'load arguments' => array(5),
     'page callback' => 'drupal_get_form',
     'page arguments' => array('image_effect_form', 5, 7),
     'access arguments' => array('administer image styles'),
@@ -278,6 +317,45 @@
 }
 
 /**
+ * Implement hook_image_default_styles().
+ */
+function image_image_default_styles() {
+  $styles = array();
+
+  $styles['thumbnail'] = array(
+    'effects' => array(
+      array(
+        'name' => 'image_scale',
+        'data' => array('width' => 100, 'height' => 100, 'upscale' => 1),
+        'weight' => 0,
+      ),
+    )
+  );
+
+  $styles['medium'] = array(
+    'effects' => array(
+      array(
+        'name' => 'image_scale',
+        'data' => array('width' => 220, 'height' => 220, 'upscale' => 1),
+        'weight' => 0,
+      ),
+    )
+  );
+
+  $styles['large'] = array(
+    'effects' => array(
+      array(
+        'name' => 'image_scale',
+        'data' => array('width' => 640, 'height' => 640, 'upscale' => 1),
+        'weight' => 0,
+      ),
+    )
+  );
+
+  return $styles;
+}
+
+/**
  * Clear cached versions of a specific file in all styles.
  *
  * @param $path
@@ -310,15 +388,44 @@
     }
     else {
       $styles = array();
-      $result = db_select('image_styles', NULL, array('fetch' => PDO::FETCH_ASSOC))
+
+      // Select the module-defined styles.
+      foreach (module_implements('image_default_styles') as $module) {
+        $module_styles = module_invoke($module, 'image_default_styles');
+        foreach ($module_styles as $style_name => $style) {
+          $style['name'] = $style_name;
+          $style['module'] = $module;
+          $style['storage'] = IMAGE_STORAGE_DEFAULT;
+          foreach ($style['effects'] as $ieid => $effect) {
+            $definition = image_effect_definition_load($effect['name']);
+            $effect = array_merge($definition, $effect);
+            $effect['ieid'] = $ieid;
+            $style['effects'][$ieid] = $effect;
+          }
+          $styles[$style_name] = $style;
+        }
+      }
+
+      // Select all the user-defined styles.
+      $user_styles = db_select('image_styles', NULL, array('fetch' => PDO::FETCH_ASSOC))
         ->fields('image_styles')
         ->orderBy('name')
-        ->execute();
-      foreach ($result as $style) {
-        $styles[$style['name']] = $style;
-        $styles[$style['name']]['effects'] = image_style_effects($style);
+        ->execute()
+        ->fetchAllAssoc('name', PDO::FETCH_ASSOC);
+
+      // Allow the user styles to override the module styles.
+      foreach ($user_styles as $style_name => $style) {
+        $style['module'] = NULL;
+        $style['storage'] = IMAGE_STORAGE_NORMAL;
+        $style['effects'] = image_style_effects($style);
+        if (isset($styles[$style_name]['module'])) {
+          $style['module'] = $styles[$style_name]['module'];
+          $style['storage'] = IMAGE_STORAGE_OVERRIDE;
+        }
+        $styles[$style_name] = $style;
       }
 
+      drupal_alter('image_styles', $styles);
       cache_set('image_styles', $styles);
     }
   }
@@ -333,6 +440,14 @@
  *   The name of the style.
  * @param $isid
  *   Optional. The numeric id of a style if the name is not known.
+ * @param $include
+ *   If set, this loader will restrict to a specific type of image style, may be
+ *   one of the defined Image style constants:
+ *   - IMAGE_STORAGE_NORMAL
+ *   - IMAGE_STORAGE_OVERRIDE
+ *   - IMAGE_STORAGE_DEFAULT
+ *   - IMAGE_STORAGE_EDITABLE (same as NORMAL or OVERRIDE)
+ *   - IMAGE_STORAGE_MODULE (same as OVERRIDE or DEFAULT)
  * @return
  *   An image style array containing the following keys:
  *   - "isid": The unique image style ID.
@@ -341,23 +456,29 @@
  *   If the image style name or ID is not valid, an empty array is returned.
  * @see image_effect_load()
  */
-function image_style_load($name = NULL, $isid = NULL) {
+function image_style_load($name = NULL, $isid = NULL, $include = NULL) {
   $styles = image_styles();
 
   // If retrieving by name.
   if (isset($name) && isset($styles[$name])) {
-    return $styles[$name];
+    $style = $styles[$name];
   }
 
   // If retrieving by image style id.
-  if (isset($isid)) {
-    foreach ($styles as $name => $style) {
-      if ($style['isid'] == $isid) {
-        return $style;
+  if (!isset($name) && isset($isid)) {
+    foreach ($styles as $name => $database_style) {
+      if (isset($database_style['isid']) && $database_style['isid'] == $isid) {
+        break;
       }
     }
   }
 
+  // Restrict to the specific type of flag. This bitwise operation basically
+  // states "if the storage is X, then allow".
+  if (isset($style) && (!isset($include) || ($style['storage'] & (int) $include))) {
+    return $style;
+  }
+
   // Otherwise the style was not found.
   return FALSE;
 }
@@ -655,6 +776,44 @@
 }
 
 /**
+ * Save a default image style to the database.
+ *
+ * @param style
+ *   An image style array provided by a module.
+ * @return
+ *   An image style array. The returned style array will include the new 'isid'
+ *   assigned to the style.
+ */
+function image_default_style_save($style) {
+  $style = image_style_save($style);
+  $effects = array();
+  foreach ($style['effects'] as $effect) {
+    $effect['isid'] = $style['isid'];
+    image_effect_save($effect);
+    $effects[$effect['ieid']] = $effect;
+  }
+  $style['effects'] = $effects;
+  return $style;
+}
+
+/**
+ * Revert the changes made by users to a default image style.
+ *
+ * @param style
+ *   An image style array.
+ * @return
+ *   Boolean TRUE if the operation succeeded.
+ */
+function image_default_style_revert($style) {
+  image_style_flush($style);
+
+  db_delete('image_effects')->condition('isid', $style['isid'])->execute();
+  db_delete('image_styles')->condition('isid', $style['isid'])->execute();
+
+  return TRUE;
+}
+
+/**
  * Pull in image effects exposed by modules implementing hook_image_effect_info().
  *
  * @return
@@ -700,6 +859,8 @@
  *
  * @param $effect
  *   The name of the effect definition to load.
+ * @param $style
+ *   An image style array to which this effect will be added.
  * @return
  *   An array containing the image effect definition with the following keys:
  *   - "effect": The unique name for the effect being performed. Usually prefixed
@@ -707,12 +868,22 @@
  *   - "module": The module providing the effect.
  *   - "help": A description of the effect.
  *   - "function": The name of the function that will execute the effect.
- *   - "form": i'm (optional) The name of a function to configure the effect.
+ *   - "form": (optional) The name of a function to configure the effect.
  *   - "summary": (optional) The name of a theme function that will display a
  *     one-line summary of the effect. Does not include the "theme_" prefix.
  */
-function image_effect_definition_load($effect) {
+function image_effect_definition_load($effect, $style_name = NULL) {
   $definitions = image_effect_definitions();
+
+  // If a style is specified, do not allow loading of default style
+  // effects.
+  if (isset($style_name)) {
+    $style = image_style_load($style_name, NULL);
+    if ($style['storage'] == IMAGE_STORAGE_DEFAULT) {
+      return FALSE;
+    }
+  }
+
   return isset($definitions[$effect]) ? $definitions[$effect] : FALSE;
 }
 
@@ -753,6 +924,17 @@
  *
  * @param $ieid
  *   The image effect ID.
+ * @param $style_name
+ *   The image style name.
+ * @param $include
+ *   If set, this loader will restrict to a specific type of image style, may be
+ *   one of the defined Image style constants:
+ *   - IMAGE_STORAGE_NORMAL
+ *   - IMAGE_STORAGE_OVERRIDE
+ *   - IMAGE_STORAGE_DEFAULT
+ *   - IMAGE_STORAGE_EDITABLE (same as NORMAL or OVERRIDE)
+ *   - IMAGE_STORAGE_MODULE (same as OVERRIDE or DEFAULT)
+ *   Leaving as NULL will load any effect regardless of storage.
  * @return
  *   An image effect array, consisting of the following keys:
  *   - "ieid": The unique image effect ID.
@@ -766,9 +948,11 @@
  * @see image_style_load()
  * @see image_effect_definition_load()
  */
-function image_effect_load($ieid) {
-  $effects = image_effects();
-  return isset($effects[$ieid]) ? $effects[$ieid] : FALSE;
+function image_effect_load($ieid, $style_name, $include = NULL) {
+  if (($style = image_style_load($style_name, NULL, $include)) && isset($style['effects'][$ieid])) {
+    return $style['effects'][$ieid];
+  }
+  return FALSE;
 }
 
 /**
Index: profiles/default/default.install
===================================================================
RCS file: /cvs/drupal/drupal/profiles/default/default.install,v
retrieving revision 1.9
diff -u -r1.9 default.install
--- profiles/default/default.install	8 Oct 2009 07:58:47 -0000	1.9
+++ profiles/default/default.install	13 Oct 2009 04:48:15 -0000
@@ -153,16 +153,6 @@
   // Don't display date and author information for page nodes by default.
   variable_set('node_submitted_page', FALSE);
 
-  // Create an image style.
-  $style = array('name' => 'thumbnail');
-  $style = image_style_save($style);
-  $effect = array(
-    'isid' => $style['isid'],
-    'name' => 'image_scale_and_crop',
-    'data' => array('width' => '85', 'height' => '85'),
-  );
-  image_effect_save($effect);
-
   // Enable user picture support and set the default to a square thumbnail option.
   variable_set('user_pictures', '1');
   variable_set('user_picture_dimensions', '1024x1024');
