diff --git includes/file.inc includes/file.inc
index 472a703..f405ab8 100644
--- includes/file.inc
+++ includes/file.inc
@@ -85,9 +85,7 @@ define('FILE_STATUS_PERMANENT', 1);
 function file_create_url($path) {
   // Strip file_directory_path from $path. We only include relative paths in
   // URLs.
-  if (strpos($path, file_directory_path() . '/') === 0) {
-    $path = trim(substr($path, strlen(file_directory_path())), '\\/');
-  }
+  $path = file_directory_strip($path);
   switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
     case FILE_DOWNLOADS_PUBLIC:
       return $GLOBALS['base_url'] . '/' . file_directory_path() . '/' . str_replace('\\', '/', $path);
@@ -1501,6 +1499,23 @@ function file_directory_path() {
 }
 
 /**
+ * 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 file_directory_strip($path) {
+  // Strip file_directory_path from $path. We only include relative paths in
+  // URLs.
+  if (strpos($path, file_directory_path() . '/') === 0) {
+    $path = trim(substr($path, strlen(file_directory_path())), '\\/');
+  }
+  return $path;
+}
+
+/**
  * Determine the maximum file upload size by querying the PHP settings.
  *
  * @return
diff --git modules/image/image.api.php modules/image/image.api.php
new file mode 100644
index 0000000..8609ad6
--- /dev/null
+++ modules/image/image.api.php
@@ -0,0 +1,106 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Hooks related to image styles and effects.
+ */
+
+/**
+ * @addtogroup hooks
+ * @{
+ */
+
+/**
+ * Define information about image effects provided by a module.
+ *
+ * This hook enables modules to define image manipulation effects for use with
+ * an image style.
+ *
+ * @return
+ *   An array of image effects. This array is keyed on the machine-readable
+ *   effect name. Each effect is defined as an associative array containing the
+ *   following items:
+ *   - "name": The human-readable name of the effect.
+ *   - "function": The function to call to perform this effect.
+ *   - "help": (optional) A brief description of the effect that will be shown
+ *     when adding or configuring this effect.
+ *   - "form": (optional) The name of a function that will return a $form array providing
+ *     a configuration form for this effect.
+ *   - "summary": (optional) The name of a theme function that will output a
+ *     summary of this effect's configuration.
+ */
+function hook_image_effect_info() {
+  $effects = array();
+
+  $effects['mymodule_resize'] = array(
+    'name' => t('Resize'),
+    'help' => t('Resize an image to an exact set of dimensions, ignoring aspect ratio.'),
+    'function' => 'mymodule_resize_image',
+    'form' => 'mymodule_resize_form',
+    'summary' => 'mymodule_resize_summary',
+  );
+
+  return $effects;
+}
+
+/**
+ * Respond to style updating.
+ *
+ * This hook enables modules to update settings that might be affected by
+ * changes to an image. For example, updating a module specific variable to
+ * reflect a change in the style's name.
+ *
+ * @param $style
+ *   The style being updated.
+ * @return
+ *   None.
+ */
+function hook_image_style_save($style) {
+  // If a module defines an image style and that style is renamed by the user
+  // the module should update any references to that style.
+  if (isset($style['old_name']) && $style['old_name'] == variable_get('mymodule_image_style', '')) {
+    variable_set('mymodule_image_style', $style['name']);
+  }
+}
+
+/**
+ * Respond to image style deletion.
+ *
+ * This hook enables modules to update settings when a style is being deleted.
+ * If a style is deleted, a replacement name may be specified in $style['name']
+ * and the style being deleted will be specified in $style['old_name'].
+ *
+ * @param $style
+ *   The style being deleted.
+ * @return
+ *   None.
+ */
+function hook_image_style_delete($style) {
+  // Administrators can choose an optional replacement style when deleting.
+  // Update the modules style variable accordingly.
+  if (isset($style['old_name']) && $style['old_name'] == variable_get('mymodule_image_style', '')) {
+    variable_set('mymodule_image_style', $style['name']);
+  }
+}
+
+/**
+ * Respond to image style flushing.
+ *
+ * This hook enables modules to take effect when a style is being flushed (all
+ * images are being deleted from the server and regenerated). Any
+ * module-specific caches that contain information related to the style should
+ * be cleared using this hook. This hook is called whenever a style is updated,
+ * deleted, any effect associated with the style is update or deleted, or when
+ * the user selects the style flush option.
+ *
+ * @param $style
+ *   The image style being flushed.
+ */
+function hook_image_style_flush($style) {
+  // Empty cached data that contains information about the style.
+  cache_clear_all('*', 'cache_mymodule', TRUE);
+}
+ /**
+  * @} End of "addtogroup hooks".
+  */
diff --git modules/image/image.effects.inc modules/image/image.effects.inc
new file mode 100644
index 0000000..6081294
--- /dev/null
+++ modules/image/image.effects.inc
@@ -0,0 +1,240 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Functions needed to execute image effects provided by Image module.
+ */
+
+/**
+ * Implement hook_image_effect_info().
+ */
+function image_image_effect_info() {
+  $effects = array(
+    'image_resize' => array(
+      'name' => t('Resize'),
+      'help' => t('Resizing will make images an exact set of dimensions. This may cause images to be stretched or shrunk disproportionately.'),
+      'function' => 'image_resize_effect',
+      'form' => 'image_resize_form',
+      'summary' => 'image_resize_summary',
+    ),
+    'image_scale' => array(
+      'name' => t('Scale'),
+      'help' => t('Scaling will maintain the aspect-ratio of the original image. If only a single dimension is specified, the other dimension will be calculated.'),
+      'function' => 'image_scale_effect',
+      'form' => 'image_scale_form',
+      'summary' => 'image_scale_summary',
+    ),
+    'image_scale_and_crop' => array(
+      'name' => t('Scale and Crop'),
+      'help' => t('Scale and crop will maintain the aspect-ratio of the original image, then crop the larger dimension. This is most useful for creating perfectly square thumbnails without stretching the image.'),
+      'function' => 'image_scale_and_crop_effect',
+      'form' => 'image_resize_form',
+      'summary' => 'image_resize_summary',
+    ),
+    'image_crop' => array(
+      'name' => t('Crop'),
+      'help' => t('Cropping will remove portions of an image to make it the specified dimensions.'),
+      'function' => 'image_crop_effect',
+      'form' => 'image_crop_form',
+      'summary' => 'image_crop_summary',
+    ),
+    'image_desaturate' => array(
+      'name' => t('Desaturate'),
+      'help' => t('Desaturate converts an image to grayscale.'),
+      'function' => 'image_desaturate_effect',
+    ),
+    'image_rotate' => array(
+      'name' => t('Rotate'),
+      'help' => t('Rotating an image may cause the dimensions of an image to increase to fit the diagonal.'),
+      'function' => 'image_rotate_effect',
+      'form' => 'image_rotate_form',
+      'summary' => 'image_rotate_summary',
+    ),
+  );
+
+  return $effects;
+}
+
+/**
+ * Implements the default image resize effect.
+ *
+ * @param $image
+ *   An image object.
+ * @param $data
+ *   An array of attributes to use when performing the resize effect with the
+ *   following items:
+ *   - "width": An integer representing the desired width in pixels.
+ *   - "height": An integer representing the desired height in pixels.
+ * @return
+ *   TRUE on success. FALSE on failure to resize image.
+ * @see image_resize()
+ */
+function image_resize_effect(&$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 effect.
+ *
+ * @param $image
+ *   An image object.
+ * @param $data
+ *   An array of attributes to use when performing the scale effect with the
+ *   following items:
+ *   - "width": An integer representing the desired width in pixels.
+ *   - "height": An integer representing the desired height in pixels.
+ *   - "upscale": A Boolean indicating that the image should be upscalled if
+ *     the dimensions are larger than the original image.
+ * @return
+ *   TRUE on success. FALSE on failure to scale image.
+ * @see image_scale()
+ */
+function image_scale_effect(&$image, $data) {
+  // Set sane default values.
+  $data += array(
+    'upscale' => FALSE,
+  );
+
+  // Set impossibly large values if the width and height aren't set.
+  $data['width'] = empty($data['width']) ? PHP_INT_MAX : $data['width'];
+  $data['height'] = empty($data['height']) ? PHP_INT_MAX : $data['height'];
+
+  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 effect.
+ *
+ * @param $image
+ *   An image object.
+ * @param $data
+ *   An array of attributes to use when performing the crop effect with the
+ *   following items:
+ *   - "width": An integer representing the desired width in pixels.
+ *   - "height": An integer representing the desired height in pixels.
+ *   - "anchor": A string describing where the crop should originate in the form
+ *     of "XOFFSET-YOFFSET". XOFFSET is either a number of pixels or
+ *     "left", "center", "right" and YOFFSET is either a number of pixels or
+ *     "top", "center", "bottom".
+ * @return
+ *   TRUE on success. FALSE on failure to crop image.
+ * @see image_crop()
+ */
+function image_crop_effect(&$image, $data) {
+  // Set sane default values.
+  $data += array(
+    'anchor' => 'center-center',
+  );
+
+  list($x, $y) = explode('-', $data['anchor']);
+  $x = image_filter_keyword($x, $image->info['width'], $data['width']);
+  $y = image_filter_keyword($y, $image->info['height'], $data['height']);
+  if (!image_crop($image, $x, $y, $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 effect.
+ *
+ * @param $image
+ *   An image object.
+ * @param $data
+ *   An array of attributes to use when performing the scale and crop effect
+ *   with the following items:
+ *   - "width": An integer representing the desired width in pixels.
+ *   - "height": An integer representing the desired height in pixels.
+ * @return
+ *   TRUE on success. FALSE on failure to scale and crop image.
+ * @see image_scale_and_crop()
+ */
+function image_scale_and_crop_effect(&$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 effect.
+ *
+ * @param $image
+ *   An image object.
+ * @param $data
+ *   An array of attributes to use when performing the desaturate effect.
+ * @return
+ *   TRUE on success. FALSE on failure to desaturate image.
+ * @see image_desaturate()
+ */
+function image_desaturate_effect(&$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 effect.
+ *
+ * @param $image
+ *   An image object.
+ * @param $data
+ *   An array of attributes to use when performing the rotate effect containing
+ *   the following items:
+ *   - "degrees": The number of (clockwise) degrees to rotate the image.
+ *   - "random": A Boolean indicating that a random rotation angle should be
+ *     used for this image. The angle specified in "degrees" is used as a
+ *     positive and negative maximum.
+ *   - "bgcolor": The background color to use for exposed areas of the image.
+ *     Use web-style hex colors (#FFFFFF for white, #000000 for black). Leave
+ *     blank for transparency on image types that support it.
+ * @return
+ *   TRUE on success. FALSE on failure to rotate image.
+ * @see image_rotate().
+ */
+function image_rotate_effect(&$image, $data) {
+  // Set sane default values.
+  $data += array(
+    'degrees' => 0,
+    'bgcolor' => NULL,
+    '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;
+}
diff --git modules/image/image.info modules/image/image.info
new file mode 100644
index 0000000..148a095
--- /dev/null
+++ modules/image/image.info
@@ -0,0 +1,10 @@
+; $Id$
+name = Image
+description = Provides image manipulation tools.
+package = Core
+version = VERSION
+core = 7.x
+files[] = image.module
+files[] = image.effects.inc
+files[] = image.install
+files[] = image.test
diff --git modules/image/image.install modules/image/image.install
new file mode 100644
index 0000000..968449a
--- /dev/null
+++ modules/image/image.install
@@ -0,0 +1,150 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Install, update and uninstall functions for the image module.
+ */
+
+/**
+ * Implement hook_install().
+ */
+function image_install() {
+  drupal_install_schema('image');
+
+  // Create the styles directory and ensure it's writable.
+  $path = file_directory_path() . '/styles';
+  file_check_directory($path, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+}
+
+/**
+ * Implement hook_uninstall().
+ */
+function image_uninstall() {
+  drupal_uninstall_schema('image');
+
+  // Remove the styles directory and generated images.
+  $path = file_directory_path() . '/styles';
+  file_unmanaged_delete_recursive($path);
+}
+
+/**
+ * Implement hook_schema().
+ */
+function image_schema() {
+  $schema = array();
+
+  $schema['cache_image'] = array(
+    'description' => 'Cache table used to store information about image manipulations that are in-progress.',
+    '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_styles'] = array(
+    'description' => 'Stores configuration options for image styles.',
+    'fields' => array(
+      'isid' => array(
+        'description' => 'The primary identifier for an image style.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'name' => array(
+        'description' => 'The style name.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+      ),
+    ),
+    'primary key' => array('isid'),
+    'indexes' => array(
+      'name' => array('name'),
+    ),
+  );
+
+  $schema['image_effects'] = array(
+    'description' => 'Stores configuration options for image effects.',
+    'fields' => array(
+      'ieid' => array(
+        'description' => 'The primary identifier for an image effect.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'isid' => array(
+        'description' => 'The primary identifier for an image style.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'weight' => array(
+        'description' => 'The weight of the effect in the style.',
+        'type' => 'int',
+        'unsigned' => FALSE,
+        'not null' => TRUE,
+        'default' => 0,
+      ),
+      'effect' => array(
+        'description' => 'The unique ID of the effect to be executed.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+      ),
+      'data' => array(
+        'description' => 'The configuration data for the effect.',
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+        'serialize' => TRUE,
+      ),
+    ),
+    'primary key' => array('ieid'),
+    'indexes' => array(
+      'isid' => array('isid'),
+    ),
+  );
+
+  return $schema;
+}
diff --git modules/image/image.module modules/image/image.module
new file mode 100644
index 0000000..f74fb0b
--- /dev/null
+++ modules/image/image.module
@@ -0,0 +1,726 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Exposes global functionality for creating image styles.
+ */
+
+/**
+ * Implement hook_menu().
+ */
+function image_menu() {
+  $items = array();
+
+  $items['image/generate/%image_style'] = array(
+    'title' => 'Generate image style',
+    'page callback' => 'image_style_generate',
+    'page arguments' => array(2),
+    'access callback' => TRUE,
+    'type' => MENU_CALLBACK,
+  );
+
+  return $items;
+}
+
+/**
+ * Implement hook_theme().
+ */
+function image_theme() {
+  return array(
+    'image_style' => array(
+      'arguments' => array('style' => NULL, 'path' => NULL, 'alt' => '', 'title' => '', 'attributes' => NULL, 'getsize' => TRUE),
+    ),
+    'image_resize_summary' => array(
+      'arguments' => array('data' => NULL),
+    ),
+    'image_scale_summary' => array(
+      'arguments' => array('data' => NULL),
+    ),
+    'image_crop_summary' => array(
+      'arguments' => array('data' => NULL),
+    ),
+    'image_rotate_summary' => array(
+      'arguments' => array('data' => NULL),
+    ),
+  );
+}
+
+/**
+ * Implement hook_flush_caches().
+ */
+function image_flush_caches() {
+  return array('cache_image');
+}
+
+/**
+ * Implement hook_file_download().
+ *
+ * Control the access to files underneath the styles directory.
+ */
+function image_file_download($filepath) {
+  if (strpos($filepath, 'styles/') === 0) {
+    $args = explode('/', $filepath);
+    array_shift($args); // Remove the "styles" item.
+    $style_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 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;
+  }
+}
+
+/**
+ * Implement hook_file_move().
+ */
+function image_file_move($file, $source) {
+  // Delete any image derivatives at the original image path.
+  image_path_flush($file->filepath);
+}
+
+/**
+ * Implement hook_file_delete().
+ */
+function image_file_delete($file) {
+  // Delete any image derivatives of this image.
+  image_path_flush($file->filepath);
+}
+
+/**
+ * Clear cached versions of a specific file in all styles.
+ *
+ * @param $path
+ *   The Drupal file path to the original image.
+ */
+function image_path_flush($path) {
+  $path = file_directory_strip($path);
+  foreach (image_styles() as $style) {
+    if ($path = file_create_path('styles/' . $style['name'] . '/' . $path)) {
+      file_unmanaged_delete($path);
+    }
+  }
+}
+
+/**
+ * Get an array of all styles and their settings.
+ *
+ * @return
+ *   An array of styles keyed by the image style ID (isid).
+ * @see image_style_load()
+ */
+function image_styles() {
+  $styles = &drupal_static(__FUNCTION__);
+
+  // Grab from cache or build the array.
+  if (!isset($styles)) {
+    if ($cache = cache_get('image_styles', 'cache')) {
+      $styles = $cache->data;
+    }
+    else {
+      $styles = array();
+      $result = 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);
+      }
+
+      cache_set('image_styles', $styles);
+    }
+  }
+
+  return $styles;
+}
+
+/**
+ * Load a style by style name or ID. May be used as a loader for menu items.
+ *
+ * @param $name
+ *   The name of the style.
+ * @param $isid
+ *   Optional. The numeric id of a style if the name is not known.
+ * @return
+ *   An image style array containing the following keys:
+ *   - "isid": The unique image style ID.
+ *   - "name": The unique image style name.
+ *   - "effects": An array of effects within this style.
+ *   If the style name or ID is not valid, an empty array is returned.
+ * @see image_effect_load()
+ */
+function image_style_load($name = NULL, $isid = NULL) {
+  $styles = image_styles();
+
+  // If retrieving by name.
+  if (isset($name) && isset($styles[$name])) {
+    return $styles[$name];
+  }
+
+  // If retrieving by image style id.
+  if (isset($isid)) {
+    foreach ($styles as $name => $style) {
+      if ($style['isid'] == $isid) {
+        return $style;
+      }
+    }
+  }
+
+  // Otherwise the style was not found.
+  return FALSE;
+}
+
+/**
+ * Save an image style.
+ *
+ * @param style
+ *   An image style array.
+ * @return
+ *   A style array. In the case of a new style, 'isid' will be populated.
+ */
+function image_style_save($style) {
+  if (isset($style['isid']) && is_numeric($style['isid'])) {
+    // Load the existing style to make sure we account for renamed styles.
+    $old_style = image_style_load(NULL, $style['isid']);
+    image_style_flush($old_style);
+    drupal_write_record('image_styles', $style, 'isid');
+    if ($old_style['name'] != $style['name']) {
+      $style['old_name'] = $old_style['name'];
+    }
+  }
+  else {
+    drupal_write_record('image_styles', $style);
+    $style['is_new'] = TRUE;
+  }
+
+  // Let other modules update as necessary on save.
+  module_invoke_all('image_style_save', $style);
+
+  // Clear all caches and flush.
+  image_style_flush($style);
+
+  return $style;
+}
+
+/**
+ * Delete an image style.
+ *
+ * @param $style
+ *   An image style array.
+ * @param $replacement_style_name
+ *   (optional) When deleting a style, specify a replacement style name so
+ *   that existing settings (if any) may be converted to a new style.
+ * @return
+ *   TRUE on success.
+ */
+function image_style_delete($style, $replacement_style_name = '') {
+  image_style_flush($style);
+
+  db_delete('image_effects')->condition('isid', $style['isid'])->execute();
+  db_delete('image_styles')->condition('isid', $style['isid'])->execute();
+
+  // Let other modules update as necessary on save.
+  $style['old_name'] = $style['name'];
+  $style['name'] = $replacement_style_name;
+  module_invoke_all('image_style_delete', $style);
+
+  return TRUE;
+}
+
+/**
+ * Load all the effects for an image style.
+ *
+ * @param $style
+ *   An image style array.
+ * @return
+ *   An array of effects associated with specified style in the format
+ *   array('isid' => array()), or an empty array if the specified style has
+ *   no effects.
+ */
+function image_style_effects($style) {
+  $effects = image_effects();
+  $style_effects = array();
+  foreach ($effects as $effect) {
+    if ($style['isid'] == $effect['isid']) {
+      $style_effects[$effect['ieid']] = $effect;
+    }
+  }
+
+  return $style_effects;
+}
+
+/**
+ * Get an array of image styles 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 styles both key and value are set to style name.
+ */
+function image_style_options($include_empty = TRUE) {
+  $styles = image_styles();
+  $options = array();
+  if ($include_empty && !empty($styles)) {
+    $options[''] = t('<none>');
+  }
+  $options = array_merge($options, drupal_map_assoc(array_keys($styles)));
+  if (empty($options)) {
+    $options[''] = t('No defined styles');
+  }
+  return $options;
+}
+
+/**
+ * Menu callback; Given a style 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 image_style_generate() {
+  $args = func_get_args();
+  $style = array_shift($args);
+  $style_name = $style['name'];
+  $path = implode('/', $args);
+
+  $source = file_create_path($path);
+  $path_md5 = md5($path);
+  $destination = image_style_path($style['name'], $path);
+
+  // Check that it's a defined style and that access was granted by
+  // image_style_generate_url().
+  if (!$style || !cache_get('access:' . $style_name . ':' . $path_md5, 'cache_image')) {
+    drupal_access_denied();
+    exit();
+  }
+
+  // Don't start generating the image if it is already in progress.
+  $cid = 'generate:' . $style_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_style_create_derivative($style, $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();
+  }
+}
+
+/**
+ * Create a new image based on an image style.
+ *
+ * @param $style
+ *   An image style 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_style_create_derivative($style, $source, $destination) {
+  // Get the folder for the final location of this style.
+  $directory = dirname($destination);
+
+  // Build the destination folder tree if it doesn't already exist.
+  if (!file_check_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+    watchdog('image', 'Failed to create style directory: %directory', array('%directory' => $directory), WATCHDOG_ERROR);
+    return FALSE;
+  }
+
+  if (!$image = image_load($source)) {
+    return FALSE;
+  }
+
+  foreach ($style['effects'] as $effect) {
+    image_effect_apply($image, $effect);
+  }
+
+  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;
+}
+
+/**
+ * Flush cached media for a style.
+ *
+ * @param $style
+ *   An image style array.
+ */
+function image_style_flush($style) {
+  $style_directory = realpath(file_directory_path() . '/styles/' . $style['name']);
+  if (is_dir($style_directory)) {
+    file_unmanaged_delete_recursive($style_directory);
+  }
+
+  // Let other modules update as necessary on flush.
+  module_invoke_all('image_style_flush', $style);
+
+  // Clear image style and effect caches.
+  cache_clear_all('image_styles', 'cache');
+  cache_clear_all('image_effects', 'cache');
+  drupal_static_reset('image_styles');
+  drupal_static_reset('image_effects');
+
+  // 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 style.
+ *
+ * If the image has already been created then its location will be returned. If
+ * it does not then image_style_generate_url() will be called.
+ *
+ * @param $style_name
+ *   The name of the style to be used with this image.
+ * @param $path
+ *   The path to the image.
+ * @return
+ *   The absolute URL where a style image can be downloaded, suitable for use
+ *   in an <img> tag. If the site is using the default method for generating
+ *   images, the image may not yet exist and will only be created when a
+ *   visitor's browser requests the file.
+ * @see image_style_generate_url()
+ * @see image_style_path()
+ */
+function image_style_url($style_name, $path) {
+  $style_path = image_style_path($style_name, $path);
+  if (file_exists($style_path)) {
+    return file_create_url($style_path);
+  }
+  return image_style_generate_url($style_name, $path);
+}
+
+/**
+ * Return the URL for an image derivative given a style 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 image/generate/[style_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 $style_name
+ *   The name of the style to be used with this image.
+ * @param $path
+ *   The path to the image.
+ * @return
+ *   The absolute URL where a style image can be downloaded, suitable for use
+ *   in an <img> tag. Requesting the URL will cause the image to be created.
+ * @see image_style_generate()
+ * @see image_style_url()
+ */
+function image_style_generate_url($style_name, $path) {
+  $destination = image_style_path($style_name, $path);
+
+  // If the image already exists use that rather than regenerating it.
+  if (file_exists($destination)) {
+    return image_style_url($style_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 style/image path. This will be
+  // checked by image_style_generate().
+  cache_set('access:' . $style_name . ':' . md5($path), 1, 'cache_image', time() + 600);
+
+  // Generate a callback path for the image.
+  $url = url('image/generate/' . $style_name . '/' . $path, array('absolute' => TRUE));
+  return $url;
+}
+
+/**
+ * Return a relative path to an image when using a style.
+ *
+ * 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 $style_name
+ *   The name of the style to be used with this image.
+ * @param $path
+ *   The path to the image.
+ * @return
+ *   The path to an image style image relative to Drupal's root.
+ * @see image_style_url()
+ */
+function image_style_path($style_name, $path) {
+  return file_directory_path() . '/styles/' . $style_name . '/' . file_directory_strip($path);
+}
+
+/**
+ * Pull in effects exposed by other modules using hook_image_effect_info().
+ *
+ * @return
+ *   An array of effects to be used when transforming images.
+ * @see hook_image_effect_info()
+ * @see image_effect_definition_load()
+ */
+function image_effect_definitions() {
+  $effects = &drupal_static(__FUNCTION__);
+
+  if (!isset($effects)) {
+    if ($cache = cache_get('image_effects') && !empty($cache->data)) {
+      $effects = $cache->data;
+    }
+    else {
+      $effects = array();
+      foreach (module_implements('image_effect_info') as $module) {
+        foreach (module_invoke($module, 'image_effect_info') as $key => $effect) {
+          // Ensure the current toolkit supports the effect.
+          $effect['module'] = $module;
+          $effect['effect'] = $key;
+          $effect['data'] = isset($effect['data']) ? $effect['data'] : array();
+          $effects[$key] = $effect;
+        };
+      }
+      uasort($effects, '_image_effect_definitions_sort');
+      cache_set('image_effects', $effects);
+    }
+  }
+
+  return $effects;
+}
+
+/**
+ * Load the definition for an effect.
+ *
+ * The effect definition is a set of core properties for an effect, not
+ * containing any user-settings. The definition defines various functions to
+ * call when configuring or executing an effect. This loader is mostly for
+ * internal use within image.module. Use image_effect_load() or
+ * image_style_load() to get effects that contain configuration.
+ *
+ * @param $effect
+ *   The name of the effect definition to load.
+ * @return
+ *   An array containing the image effect definition with the following keys:
+ *   - "effect": The unique name for the effect being performed. Usually prefixed
+ *     with the name of the module providing the effect.
+ *   - "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.
+ *   - "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) {
+  $definitions = image_effect_definitions();
+  return isset($definitions[$effect]) ? $definitions[$effect] : FALSE;
+}
+
+/**
+ * Load all image effects from the database.
+ *
+ * @return
+ *   An array of all image effects.
+ * @see image_effect_load()
+ */
+function image_effects() {
+  $effects = &drupal_static(__FUNCTION__);
+
+  if (!isset($effects)) {
+    $effects = array();
+
+    // Add database image effects.
+    $result = db_select('image_effects', NULL, array('fetch' => PDO::FETCH_ASSOC))
+      ->fields('image_effects')
+      ->orderBy('image_effects.weight', 'ASC')
+      ->execute();
+    foreach ($result as $effect) {
+      $effect['data'] = unserialize($effect['data']);
+      $definition = image_effect_definition_load($effect['effect']);
+      // Do not load effects whose definition cannot be found.
+      if ($definition) {
+        $effect = array_merge($definition, $effect);
+        $effects[$effect['ieid']] = $effect;
+      }
+    }
+  }
+
+  return $effects;
+}
+
+/**
+ * Load a single image effect.
+ *
+ * @param $ieid
+ *   The image effect ID.
+ * @return
+ *   An image effect array, consisting of the following keys:
+ *   - "ieid": The unique image effect ID.
+ *   - "isid": The unique image style ID that contains this effect.
+ *   - "weight": The weight of this effect within the image style.
+ *   - "effect": The name of the effect definition that powers this effect.
+ *   - "data": An associative array of configuration options for this effect.
+ *   Besides these keys, the entirety of the image definition is merged into
+ *   the image effect array. Returns FALSE if the specified effect cannot be
+ *   found.
+ * @see image_style_load()
+ * @see image_effect_definition_load()
+ */
+function image_effect_load($ieid) {
+  $effects = image_effects();
+  return isset($effects[$ieid]) ? $effects[$ieid] : FALSE;
+}
+
+/**
+ * Save an image effect.
+ *
+ * @param $effect
+ *   An image effect array.
+ * @return
+ *   An image effect array. In the case of a new effect 'ieid' will be set.
+ */
+function image_effect_save($effect) {
+  if (!empty($effect['ieid'])) {
+    drupal_write_record('image_effects', $effect, 'ieid');
+  }
+  else {
+    drupal_write_record('image_effects', $effect);
+  }
+  $style = image_style_load(NULL, $effect['isid']);
+  image_style_flush($style);
+  return $effect;
+}
+
+/**
+ * Delete an image effect.
+ *
+ * @param $effect
+ *   An image effect array.
+ */
+function image_effect_delete($effect) {
+  db_delete('image_effects')->condition('ieid', $effect['ieid'])->execute();
+  $style = image_style_load(NULL, $effect['isid']);
+  image_style_flush($style);
+}
+
+/**
+ * Given an image object and effect, perform the effect on the file.
+ *
+ * @param $image
+ *   An image object.
+ * @param $effect
+ *   An image effect array.
+ * @return
+ *   TRUE on success. FALSE if unable to perform effect on image.
+ */
+function image_effect_apply(&$image, $effect) {
+  if (drupal_function_exists($effect['function'])) {
+    return call_user_func($effect['function'], $image, $effect['data']);
+  }
+  return FALSE;
+}
+
+/**
+ * Return a themed image using a specific image style.
+ *
+ * @param $style_name
+ *   The name of the style 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.
+ * @ingroup themeable
+ */
+function theme_image_style($style_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.
+  $style_path = image_style_path($style_name, $path);
+  if (!file_exists($style_path)) {
+    $style_path = image_style_url($style_name, $path);
+  }
+  return theme('image', $style_path, $alt, $title, $attributes, $getsize);
+}
+
+/**
+ * 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.
+ *
+ * @param $value
+ * @param $current_pixels
+ * @param $new_pixels
+ */
+function image_filter_keyword($value, $current_pixels, $new_pixels) {
+  switch ($value) {
+    case 'top':
+    case 'left':
+      return 0;
+
+    case 'bottom':
+    case 'right':
+      return $current_pixels - $new_pixels;
+
+    case 'center':
+      return $current_pixels / 2 - $new_pixels / 2;
+  }
+  return $value;
+}
+
+/**
+ * Internal function for sorting image effect definitions through uasort().
+ *
+ * @see image_effect_definitions()
+ */
+function _image_effect_definitions_sort($a, $b) {
+  return strcasecmp($a['name'], $b['name']);
+}
diff --git modules/image/image.test modules/image/image.test
new file mode 100644
index 0000000..763ca72
--- /dev/null
+++ modules/image/image.test
@@ -0,0 +1,229 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Image module tests.
+ */
+
+
+/**
+ * FUNCTIONS STILL TO TEST
+ *
+ * image.effects.inc:
+ *   image_style_generate()
+ *   image_style_create_derivative()
+ *
+ * image.module:
+ *   image_style_load()
+ *   image_style_save()
+ *   image_style_delete()
+ *   image_style_options()
+ *   image_style_flush()
+ *   image_effect_definition_load()
+ *   image_effect_load()
+ *   image_effect_save()
+ *   image_effect_delete()
+ *   image_filter_keyword()
+ */
+
+/**
+ * Tests the functions for generating paths and URLs for image styles.
+ */
+class ImageStylesPathAndUrlUnitTest extends DrupalWebTestCase {
+  protected $style_name;
+  protected $image_with_generated;
+  protected $image_without_generated;
+
+  function getInfo() {
+    return array(
+      'name' => t('Image styles path and URL functions'),
+      'description' => t('Tests functions for generating paths and URLs to image styles.'),
+      'group' => t('Image')
+    );
+  }
+
+  function setUp() {
+    parent::setUp();
+
+    $this->style_name = 'style_foo';
+
+    // Create the directories for the styles.
+    $status = file_check_directory($d = file_directory_path() .'/styles/' . $this->style_name, FILE_CREATE_DIRECTORY);
+    $this->assertNotIdentical(FALSE, $status, t('Created the directory for the generated images for the test style.' ));
+
+    // Make two copies of the file...
+    $file = reset($this->drupalGetTestFiles('image'));
+    $this->image_without_generated = file_unmanaged_copy($file->filepath, NULL, FILE_EXISTS_RENAME);
+    $this->assertNotIdentical(FALSE, $this->image_without_generated, t('Created the without generated image file.'));
+    $this->image_with_generated = file_unmanaged_copy($file->filepath, NULL, FILE_EXISTS_RENAME);
+    $this->assertNotIdentical(FALSE, $this->image_with_generated, t('Created the with generated image file.'));
+    // and create a "generated" file for the one.
+    $status = file_unmanaged_copy($file->filepath, image_style_path($this->style_name, $this->image_with_generated), FILE_EXISTS_REPLACE);
+    $this->assertNotIdentical(FALSE, $status, t('Created a file where the generated image should be.'));
+  }
+
+  /**
+   * Test image_style_path().
+   */
+  function testImageStylePath() {
+    $actual = image_style_path($this->style_name, $this->image_without_generated);
+    $expected = file_directory_path() . '/styles/' . $this->style_name . '/' . basename($this->image_without_generated);
+    $this->assertEqual($actual, $expected, t('Got the path for a file.'));
+  }
+
+  /**
+   * Test image_style_url().
+   */
+  function testImageStyleUrl() {
+    // Test it with no generated file.
+    $actual = image_style_url($this->style_name, $this->image_without_generated);
+    $expected = url('image/generate/' . $this->style_name . '/' . $this->image_without_generated, array('absolute' => TRUE));
+    $this->assertEqual($actual, $expected, t('Got the generate URL for a non-existent file.'));
+
+    // Now test it with a generated file.
+    $actual = image_style_url($this->style_name, $this->image_with_generated);
+    $expected = file_create_url(image_style_path($this->style_name, $this->image_with_generated));
+    $this->assertEqual($actual, $expected, t('Got the download URL for an existing file.'));
+  }
+
+  /**
+   * Test image_style_generate_url().
+   */
+  function testImageStyleGenerateUrl() {
+    // Test it with no generated file.
+    $actual = image_style_generate_url($this->style_name, $this->image_without_generated);
+    $expected = url('image/generate/' . $this->style_name . '/' . $this->image_without_generated, array('absolute' => TRUE));
+    $this->assertEqual($actual, $expected, t('Got the generate URL for a non-existent file.'));
+
+    // Now test it with a generated file.
+    $actual = image_style_generate_url($this->style_name, $this->image_with_generated);
+    $expected = file_create_url(image_style_path($this->style_name, $this->image_with_generated));
+    $this->assertEqual($actual, $expected, t('Got the download URL for an existing file.'));
+  }
+}
+
+/**
+ * Use the image_test.module's mock toolkit to ensure that the effects are
+ * properly passing parameters to the image toolkit.
+ */
+class ImageEffectsUnitTest extends ImageToolkitTestCase {
+  function getInfo() {
+    return array(
+      'name' => t('Image effects'),
+      'description' => t('Test that the image effects pass parameters to the toolkit correctly.'),
+      'group' => t('Image')
+    );
+  }
+
+  function setUp() {
+    parent::setUp('image_test');
+    module_load_include('inc', 'image', 'image.effects');
+  }
+
+  function testEffects() {
+    $effects = image_effects();
+    $this->assertEqual(count($effects), 1, t("Found core's effect."));
+
+    $effect_definitions = image_effect_definitions();
+    $this->assertEqual(count($effect_definitions), 6, t("Found core's effects."));
+  }
+
+  /**
+   * Test the image_resize_effect() function.
+   */
+  function testResizeEffect() {
+    $this->assertTrue(image_resize_effect($this->image, array('width' => 1, 'height' => 2)), t('Function returned the expected value.'));
+    $this->assertToolkitOperationsCalled(array('resize'));
+
+    // Check the parameters.
+    $calls = image_test_get_all_calls();
+    $this->assertEqual($calls['resize'][0][1], 1, t('Width was passed correctly'));
+    $this->assertEqual($calls['resize'][0][2], 2, t('Height was passed correctly'));
+  }
+
+  /**
+   * Test the image_scale_effect() function.
+   */
+  function testScaleEffect() {
+    // @todo: need to test upscaling.
+    $this->assertTrue(image_scale_effect($this->image, array('width' => 10, 'height' => 10)), t('Function returned the expected value.'));
+    $this->assertToolkitOperationsCalled(array('resize'));
+
+    // Check the parameters.
+    $calls = image_test_get_all_calls();
+    $this->assertEqual($calls['resize'][0][1], 10, t('Width was passed correctly'));
+    $this->assertEqual($calls['resize'][0][2], 5, t('Height was based off aspect ratio and passed correctly'));
+  }
+
+  /**
+   * Test the image_crop_effect() function.
+   */
+  function testCropEffect() {
+    // @todo should test the keyword offsets.
+    $this->assertTrue(image_crop_effect($this->image, array('anchor' => 'top-1', 'width' => 3, 'height' => 4)), t('Function returned the expected value.'));
+    $this->assertToolkitOperationsCalled(array('crop'));
+
+    // Check the parameters.
+    $calls = image_test_get_all_calls();
+    $this->assertEqual($calls['crop'][0][1], 0, t('X was passed correctly'));
+    $this->assertEqual($calls['crop'][0][2], 1, t('Y was passed correctly'));
+    $this->assertEqual($calls['crop'][0][3], 3, t('Width was passed correctly'));
+    $this->assertEqual($calls['crop'][0][4], 4, t('Height was passed correctly'));
+  }
+
+  /**
+   * Test the image_scale_and_crop_effect() function.
+   */
+  function testScaleAndCropEffect() {
+    $this->assertTrue(image_scale_and_crop_effect($this->image, array('width' => 5, 'height' => 10)), t('Function returned the expected value.'));
+    $this->assertToolkitOperationsCalled(array('resize', 'crop'));
+
+    // Check the parameters.
+    $calls = image_test_get_all_calls();
+    $this->assertEqual($calls['crop'][0][1], 7.5, t('X was computed and passed correctly'));
+    $this->assertEqual($calls['crop'][0][2], 0, t('Y was computed and passed correctly'));
+    $this->assertEqual($calls['crop'][0][3], 5, t('Width was computed and passed correctly'));
+    $this->assertEqual($calls['crop'][0][4], 10, t('Height was computed and passed correctly'));
+  }
+
+  /**
+   * Test the image_desaturate_effect() function.
+   */
+  function testDesaturateEffect() {
+    $this->assertTrue(image_desaturate_effect($this->image, array()), t('Function returned the expected value.'));
+    $this->assertToolkitOperationsCalled(array('desaturate'));
+
+    // Check the parameters.
+    $calls = image_test_get_all_calls();
+    $this->assertEqual(count($calls['desaturate'][0]), 1, t('Only the image was passed.'));
+  }
+
+  /**
+   * Test the image_rotate_effect() function.
+   */
+  function testRotateEffect() {
+    // @todo: need to test with 'random' => TRUE
+    $this->assertTrue(image_rotate_effect($this->image, array('degrees' => 90, 'bgcolor' => '#fff')), t('Function returned the expected value.'));
+    $this->assertToolkitOperationsCalled(array('rotate'));
+
+    // Check the parameters.
+    $calls = image_test_get_all_calls();
+    $this->assertEqual($calls['rotate'][0][1], 90, t('Degrees were passed correctly'));
+    $this->assertEqual($calls['rotate'][0][2], 0xffffff, t('Background color was passed correctly'));
+  }
+}
+
+/**
+ * Tests creation, deletion, and editing of image styles and effects.
+ */
+class ImageAdminStylesUnitTest extends DrupalWebTestCase {
+
+  function getInfo() {
+    return array(
+      'name' => t('Image styles and effects UI configuration'),
+      'description' => t('Tests creation, deletion, and editing of image styles and effects at the UI level.'),
+      'group' => t('Image')
+    );
+  }
+}
diff --git modules/image/images/sample.jpg modules/image/images/sample.jpg
new file mode 100644
index 0000000..3e57d6a
Binary files /dev/null and modules/image/images/sample.jpg differ
diff --git modules/simpletest/tests/image.test modules/simpletest/tests/image.test
index eb41af2..21c27d5 100644
--- modules/simpletest/tests/image.test
+++ modules/simpletest/tests/image.test
@@ -14,14 +14,6 @@ class ImageToolkitTestCase extends DrupalWebTestCase {
   protected $file;
   protected $image;
 
-  public static function getInfo() {
-    return array(
-      'name' => t('Image toolkit tests'),
-      'description' => t('Check image tookit functions.'),
-      'group' => t('Image API'),
-    );
-  }
-
   function setUp() {
     parent::setUp('image_test');
 
@@ -72,6 +64,19 @@ class ImageToolkitTestCase extends DrupalWebTestCase {
       $this->assertTrue(TRUE, t('No unexpected operations were called.'));
     }
   }
+}
+
+/**
+ * Test that the functions in image.inc correctly pass data to the toolkit.
+ */
+class ImageToolkitUnitTest extends ImageToolkitTestCase {
+  public static function getInfo() {
+    return array(
+      'name' => t('Image toolkit tests'),
+      'description' => t('Check image toolkit functions.'),
+      'group' => t('Image'),
+    );
+  }
 
   /**
    * Check that hook_image_toolkits() is called and only available toolkits are
@@ -207,7 +212,7 @@ class ImageToolkitGdTestCase extends DrupalWebTestCase {
     return array(
       'name' => t('Image GD manipulation tests'),
       'description' => t('Check that core image manipulations work properly: scale, resize, rotate, crop, scale and crop, and desaturate.'),
-      'group' => t('Image API'),
+      'group' => t('Image'),
     );
   }
 
diff --git modules/user/user.admin.inc modules/user/user.admin.inc
index e0581aa..d0b4595 100644
--- modules/user/user.admin.inc
+++ modules/user/user.admin.inc
@@ -360,6 +360,15 @@ function user_admin_settings() {
     '#maxlength' => 255,
     '#description' => t('URL of picture to display for users with no custom picture selected. Leave blank for none.'),
   );
+  if (module_exists('image')) {
+    $form['personalization']['pictures']['settings']['user_picture_style'] = array(
+      '#type' => 'select',
+      '#title' => t('Picture style'),
+      '#options' => image_style_options(TRUE),
+      '#default_value' => variable_get('user_picture_style', ''),
+      '#description' => t('Select an image style to scale user images down without modifying the original image. The maximum dimensions and file size settings below will be applied before the image style is applied. Image styles may be configured in the <a href="!url">Image handling</a> administration area.', array('!url' => url('admin/settings/image-styles'))),
+    );
+  }
   $form['personalization']['pictures']['user_picture_dimensions'] = array(
     '#type' => 'textfield',
     '#title' => t('Picture maximum dimensions'),
diff --git modules/user/user.module modules/user/user.module
index dbbb2d3..308ef57 100644
--- modules/user/user.module
+++ modules/user/user.module
@@ -1176,7 +1176,12 @@ function template_preprocess_user_picture(&$variables) {
     }
     if (isset($filepath)) {
       $alt = t("@user's picture", array('@user' => $account->name ? $account->name : variable_get('anonymous', t('Anonymous'))));
-      $variables['picture'] = theme('image', $filepath, $alt, $alt, '', FALSE);
+      if (module_exists('image') && $style = variable_get('user_picture_style', '')) {
+        $variables['picture'] = theme('image_style', $style, $filepath, $alt, $alt, NULL, FALSE);
+      }
+      else {
+        $variables['picture'] = theme('image', $filepath, $alt, $alt, NULL, FALSE);
+      }
       if (!empty($account->uid) && user_access('access user profiles')) {
         $attributes = array('attributes' => array('title' => t('View user profile.')), 'html' => TRUE);
         $variables['picture'] = l($variables['picture'], "user/$account->uid", $attributes);
diff --git profiles/default/default.profile profiles/default/default.profile
index 9ec0a34..cb837f3 100644
--- profiles/default/default.profile
+++ profiles/default/default.profile
@@ -8,7 +8,7 @@
  *   An array of modules to enable.
  */
 function default_profile_modules() {
-  return array('block', 'color', 'comment', 'help', 'menu', 'path', 'taxonomy', 'dblog', 'search', 'toolbar');
+  return array('block', 'color', 'comment', 'help', 'image', 'menu', 'path', 'taxonomy', 'dblog', 'search', 'toolbar');
 }
 
 /**
@@ -196,6 +196,27 @@ function default_profile_tasks(&$task, $url) {
   // 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'],
+    'effect' => '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');
+  variable_set('user_picture_file_size', '800');
+  variable_set('user_picture_style', 'thumbnail');
+
+  $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.');
