diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php
index d3bceb8..ce81657 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php
@@ -104,8 +104,8 @@ function testSchemaMapping() {
     $expected['mapping']['label']['type'] = 'label';
     $expected['mapping']['effects']['type'] = 'sequence';
     $expected['mapping']['effects']['sequence'][0]['type'] = 'mapping';
-    $expected['mapping']['effects']['sequence'][0]['mapping']['name']['type'] = 'string';
-    $expected['mapping']['effects']['sequence'][0]['mapping']['data']['type'] = 'image.effect.[%parent.name]';
+    $expected['mapping']['effects']['sequence'][0]['mapping']['id']['type'] = 'string';
+    $expected['mapping']['effects']['sequence'][0]['mapping']['data']['type'] = 'image.effect.[%parent.id]';
     $expected['mapping']['effects']['sequence'][0]['mapping']['weight']['type'] = 'integer';
     $expected['mapping']['effects']['sequence'][0]['mapping']['ieid']['type'] = 'string';
     $expected['mapping']['langcode']['label'] = 'Default language';
@@ -191,7 +191,7 @@ function testSchemaData() {
     $this->assertTrue(count($effects) == 1, 'Got an array with effects for image.style.large data');
     $ieid = key($effects->getValue());
     $effect = $effects[$ieid];
-    $this->assertTrue(count($effect['data']) && $effect['name']->getValue() == 'image_scale', 'Got data for the image scale effect from metadata.');
+    $this->assertTrue(count($effect['data']) && $effect['id']->getValue() == 'image_scale', 'Got data for the image scale effect from metadata.');
     $this->assertEqual($effect['data']['width']->getType(), 'integer', 'Got the right type for the scale effect width.');
     $this->assertEqual($effect['data']['width']->getValue(), 480, 'Got the right value for the scale effect width.' );
 
diff --git a/core/modules/image/config/image.style.large.yml b/core/modules/image/config/image.style.large.yml
index 30bb1af..3c0ccb9 100644
--- a/core/modules/image/config/image.style.large.yml
+++ b/core/modules/image/config/image.style.large.yml
@@ -2,7 +2,7 @@ name: large
 label: 'Large (480x480)'
 effects:
   ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d:
-    name: image_scale
+    id: image_scale
     data:
       width: '480'
       height: '480'
diff --git a/core/modules/image/config/image.style.medium.yml b/core/modules/image/config/image.style.medium.yml
index 1047f86..0cad324 100644
--- a/core/modules/image/config/image.style.medium.yml
+++ b/core/modules/image/config/image.style.medium.yml
@@ -2,7 +2,7 @@ name: medium
 label: 'Medium (220x220)'
 effects:
   bddf0d06-42f9-4c75-a700-a33cafa25ea0:
-    name: image_scale
+    id: image_scale
     data:
       width: '220'
       height: '220'
diff --git a/core/modules/image/config/image.style.thumbnail.yml b/core/modules/image/config/image.style.thumbnail.yml
index 5834812..557237b 100644
--- a/core/modules/image/config/image.style.thumbnail.yml
+++ b/core/modules/image/config/image.style.thumbnail.yml
@@ -2,7 +2,7 @@ name: thumbnail
 label: 'Thumbnail (100x100)'
 effects:
   1cfec298-8620-4749-b100-ccb6c4500779:
-    name: image_scale
+    id: image_scale
     data:
       width: '100'
       height: '100'
diff --git a/core/modules/image/config/schema/image.schema.yml b/core/modules/image/config/schema/image.schema.yml
index 6916f19..5c4692a 100644
--- a/core/modules/image/config/schema/image.schema.yml
+++ b/core/modules/image/config/schema/image.schema.yml
@@ -26,10 +26,10 @@ image.style.*:
       sequence:
         - type: mapping
           mapping:
-            name:
+            id:
               type: string
             data:
-              type: image.effect.[%parent.name]
+              type: image.effect.[%parent.id]
             weight:
               type: integer
             ieid:
diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc
index 02af74e..e8dfe20 100644
--- a/core/modules/image/image.admin.inc
+++ b/core/modules/image/image.admin.inc
@@ -38,6 +38,7 @@ function image_style_list() {
 function image_style_form($form, &$form_state, $style) {
   $title = t('Edit style %name', array('%name' => $style->label()));
   drupal_set_title($title, PASS_THROUGH);
+  $manager = Drupal::service('plugin.manager.image.effect');
 
   $form_state['image_style'] = $style;
   $form['#tree'] = TRUE;
@@ -70,23 +71,22 @@ function image_style_form($form, &$form_state, $style) {
     '#theme' => 'image_style_effects',
   );
   if (!empty($style->effects)) {
-    foreach ($style->effects as $key => $effect) {
+    foreach ($style->effects as $key => $effect_info) {
+      $effect = $manager->getInstance($effect_info);
       $form['effects'][$key]['#weight'] = isset($form_state['input']['effects']) ? $form_state['input']['effects'][$key]['weight'] : NULL;
       $form['effects'][$key]['label'] = array(
-        '#markup' => check_plain($effect['label']),
-      );
-      $form['effects'][$key]['summary'] = array(
-        '#markup' => isset($effect['summary theme']) ? theme($effect['summary theme'], array('data' => $effect['data'])) : '',
+        '#markup' => check_plain($effect->label()),
       );
+      $form['effects'][$key]['summary'] = $effect->getSummary();
       $form['effects'][$key]['weight'] = array(
         '#type' => 'weight',
-        '#title' => t('Weight for @title', array('@title' => $effect['label'])),
+        '#title' => t('Weight for @title', array('@title' => $effect->label())),
         '#title_display' => 'invisible',
-        '#default_value' => $effect['weight'],
+        '#default_value' => $effect_info['weight'],
       );
 
       $links = array();
-      if (isset($effect['form callback'])) {
+      if ($effect->hasForm()) {
         $links['edit'] = array(
           'title' => t('edit'),
           'href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key,
@@ -104,7 +104,7 @@ function image_style_form($form, &$form_state, $style) {
         '#type' => 'link',
         '#title' => t('edit'),
         '#href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key,
-        '#access' => isset($effect['form callback']),
+        '#access' => $effect->hasForm(),
       );
       $form['effects'][$key]['remove'] = array(
         '#type' => 'link',
@@ -116,7 +116,9 @@ function image_style_form($form, &$form_state, $style) {
 
   // Build the new image effect addition form and add it to the effect list.
   $new_effect_options = array();
-  foreach (image_effect_definitions() as $effect => $definition) {
+  $effects = $manager->getDefinitions();
+  uasort($effects, '_image_effect_definitions_sort');
+  foreach ($effects as $effect => $definition) {
     $new_effect_options[$effect] = $definition['label'];
   }
   $form['effects']['new'] = array(
@@ -168,17 +170,17 @@ function image_style_form_add_validate($form, &$form_state) {
 function image_style_form_add_submit($form, &$form_state) {
   $style = $form_state['image_style'];
   // Check if this field has any configuration options.
-  $effect = image_effect_definition_load($form_state['values']['new']);
+  $effect = Drupal::service('plugin.manager.image.effect')->getDefinition($form_state['values']['new']);
 
   // Load the configuration form for this option.
-  if (isset($effect['form callback'])) {
+  if (!$effect['no_form']) {
     $path = 'admin/config/media/image-styles/manage/' . $style->id() . '/add/' . $form_state['values']['new'];
     $form_state['redirect'] = array($path, array('query' => array('weight' => $form_state['values']['weight'])));
   }
   // If there's no form, immediately add the image effect.
   else {
     $effect = array(
-      'name' => $effect['name'],
+      'id' => $effect['id'],
       'data' => array(),
       'weight' => $form_state['values']['weight'],
     );
@@ -198,7 +200,7 @@ function image_style_form_submit($form, &$form_state) {
     foreach ($form_state['values']['effects'] as $ieid => $effect_data) {
       if (isset($style->effects[$ieid])) {
         $effect = array(
-          'name' => $style->effects[$ieid]['name'],
+          'id' => $style->effects[$ieid]['id'],
           'data' => $style->effects[$ieid]['data'],
           'weight' => $effect_data['weight'],
           'ieid' => $ieid,
@@ -262,246 +264,6 @@ function image_style_add_form_submit($form, &$form_state) {
 }
 
 /**
- * Form builder; Form for adding and editing image effects.
- *
- * This form is used universally for editing all image effects. Each effect adds
- * its own custom section to the form by calling the 'form callback' specified
- * in hook_image_effect_info().
- *
- * @param $form_state
- *   An associative array containing the current state of the form.
- * @param $style
- *   An image style array.
- * @param $effect
- *   An image effect array.
- *
- * @ingroup forms
- * @see image_resize_form()
- * @see image_scale_form()
- * @see image_rotate_form()
- * @see image_crop_form()
- * @see image_effect_form_submit()
- */
-function image_effect_form($form, &$form_state, $style, $effect) {
-  // If there's no configuration for this image effect, return to
-  // the image style page.
-  if (!isset($effect['form callback'])) {
-    return new RedirectResponse(url('admin/config/media/image-styles/manage/' . $style->id(), array('absolute' => TRUE)));
-  }
-  $form_state['image_style'] = $style;
-  $form_state['image_effect'] = $effect;
-
-  if (!empty($effect['ieid'])) {
-    $title = t('Edit %label effect', array('%label' => $effect['label']));
-  }
-  else{
-    $title = t('Add %label effect', array('%label' => $effect['label']));
-  }
-  drupal_set_title($title, PASS_THROUGH);
-
-  $form['#attached']['css'][drupal_get_path('module', 'image') . '/css/image.admin.css'] = array();
-
-  $form['ieid'] = array(
-    '#type' => 'value',
-    '#value' => !empty($effect['ieid']) ? $effect['ieid'] : NULL,
-  );
-  $form['name'] = array(
-    '#type' => 'value',
-    '#value' => $effect['name'],
-  );
-
-  $form['data'] = call_user_func($effect['form callback'], $effect['data']);
-  $form['data']['#tree'] = TRUE;
-
-  // Check the URL for a weight, then the image effect, otherwise use default.
-  $weight = Drupal::request()->query->get('weight');
-  $form['weight'] = array(
-    '#type' => 'hidden',
-    '#value' => isset($weight) ? intval($weight) : (isset($effect['weight']) ? $effect['weight'] : count($style->effects)),
-  );
-
-  $form['actions'] = array('#type' => 'actions');
-  $form['actions']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => !empty($effect['ieid']) ? t('Update effect') : t('Add effect'),
-  );
-  $form['actions']['cancel'] = array(
-    '#type' => 'link',
-    '#title' => t('Cancel'),
-    '#href' => 'admin/config/media/image-styles/manage/' . $style->id(),
-  );
-
-  return $form;
-}
-
-/**
- * Submit handler for updating an image effect.
- */
-function image_effect_form_submit($form, &$form_state) {
-  form_state_values_clean($form_state);
-
-  $effect = $form_state['values'];
-  $style = $form_state['image_style'];
-  image_effect_save($style, $effect);
-
-  drupal_set_message(t('The image effect was successfully applied.'));
-  $form_state['redirect'] = 'admin/config/media/image-styles/manage/' . $style->id();
-}
-
-/**
- * Element validate handler to ensure a hexadecimal color value.
- */
-function image_effect_color_validate($element, &$form_state) {
-  if ($element['#value'] != '') {
-    $hex_value = preg_replace('/^#/', '', $element['#value']);
-    if (!preg_match('/^#[0-9A-F]{3}([0-9A-F]{3})?$/', $element['#value'])) {
-      form_error($element, t('!name must be a hexadecimal color value.', array('!name' => $element['#title'])));
-    }
-  }
-}
-
-/**
- * Element validate handler to ensure that either a height or a width is
- * specified.
- */
-function image_effect_scale_validate($element, &$form_state) {
-  if (empty($element['width']['#value']) && empty($element['height']['#value'])) {
-    form_error($element, t('Width and height can not both be blank.'));
-  }
-}
-
-/**
- * Form structure for the image resize form.
- *
- * Note that this is not a complete form, it only contains the portion of the
- * form for configuring the resize options. Therefore it does not not need to
- * include metadata about the effect, nor a submit button.
- *
- * @param $data
- *   The current configuration for this resize effect.
- */
-function image_resize_form($data) {
-  $form['width'] = array(
-    '#type' => 'number',
-    '#title' => t('Width'),
-    '#default_value' => isset($data['width']) ? $data['width'] : '',
-    '#field_suffix' => ' ' . t('pixels'),
-    '#required' => TRUE,
-    '#min' => 1,
-  );
-  $form['height'] = array(
-    '#type' => 'number',
-    '#title' => t('Height'),
-    '#default_value' => isset($data['height']) ? $data['height'] : '',
-    '#field_suffix' => ' ' . t('pixels'),
-    '#required' => TRUE,
-    '#min' => 1,
-  );
-  return $form;
-}
-
-/**
- * Form structure for the image scale form.
- *
- * Note that this is not a complete form, it only contains the portion of the
- * form for configuring the scale options. Therefore it does not not need to
- * include metadata about the effect, nor a submit button.
- *
- * @param $data
- *   The current configuration for this scale effect.
- */
-function image_scale_form($data) {
-  $form = image_resize_form($data);
-  $form['#element_validate'] = array('image_effect_scale_validate');
-  $form['width']['#required'] = FALSE;
-  $form['height']['#required'] = FALSE;
-  $form['upscale'] = array(
-    '#type' => 'checkbox',
-    '#default_value' => (isset($data['upscale'])) ? $data['upscale'] : 0,
-    '#title' => t('Allow Upscaling'),
-    '#description' => t('Let scale make images larger than their original size'),
-  );
-  return $form;
-}
-
-/**
- * Form structure for the image crop form.
- *
- * Note that this is not a complete form, it only contains the portion of the
- * form for configuring the crop options. Therefore it does not not need to
- * include metadata about the effect, nor a submit button.
- *
- * @param $data
- *   The current configuration for this crop effect.
- */
-function image_crop_form($data) {
-  $data += array(
-    'width' => '',
-    'height' => '',
-    'anchor' => 'center-center',
-  );
-
-  $form = image_resize_form($data);
-  $form['anchor'] = array(
-    '#type' => 'radios',
-    '#title' => t('Anchor'),
-    '#options' => array(
-      'left-top'      => t('Top') . ' ' . t('Left'),
-      'center-top'    => t('Top') . ' ' . t('Center'),
-      'right-top'     => t('Top') . ' ' . t('Right'),
-      'left-center'   => t('Center') . ' ' . t('Left'),
-      'center-center' => t('Center'),
-      'right-center'  => t('Center') . ' ' . t('Right'),
-      'left-bottom'   => t('Bottom') . ' ' . t('Left'),
-      'center-bottom' => t('Bottom') . ' ' . t('Center'),
-      'right-bottom'  => t('Bottom') . ' ' . t('Right'),
-    ),
-    '#theme' => 'image_anchor',
-    '#default_value' => $data['anchor'],
-    '#description' => t('The part of the image that will be retained during the crop.'),
-  );
-
-  return $form;
-}
-
-/**
- * Form structure for the image rotate form.
- *
- * Note that this is not a complete form, it only contains the portion of the
- * form for configuring the rotate options. Therefore it does not not need to
- * include metadata about the effect, nor a submit button.
- *
- * @param $data
- *   The current configuration for this rotate effect.
- */
-function image_rotate_form($data) {
-  $form['degrees'] = array(
-    '#type' => 'number',
-    '#default_value' => (isset($data['degrees'])) ? $data['degrees'] : 0,
-    '#title' => t('Rotation angle'),
-    '#description' => t('The number of degrees the image should be rotated. Positive numbers are clockwise, negative are counter-clockwise.'),
-    '#field_suffix' => '&deg;',
-    '#required' => TRUE,
-  );
-  $form['bgcolor'] = array(
-    '#type' => 'textfield',
-    '#default_value' => (isset($data['bgcolor'])) ? $data['bgcolor'] : '#FFFFFF',
-    '#title' => t('Background color'),
-    '#description' => t('The background color to use for exposed areas of the image. Use web-style hex colors (#FFFFFF for white, #000000 for black). Leave blank for transparency on image types that support it.'),
-    '#size' => 7,
-    '#maxlength' => 7,
-    '#element_validate' => array('image_effect_color_validate'),
-  );
-  $form['random'] = array(
-    '#type' => 'checkbox',
-    '#default_value' => (isset($data['random'])) ? $data['random'] : 0,
-    '#title' => t('Randomize'),
-    '#description' => t('Randomize the rotation angle for each image. The angle specified above is used as a maximum.'),
-  );
-  return $form;
-}
-
-/**
  * Returns HTML for the page containing the list of image styles.
  *
  * @param $variables
diff --git a/core/modules/image/image.api.php b/core/modules/image/image.api.php
index d2990f5..471f9fa 100644
--- a/core/modules/image/image.api.php
+++ b/core/modules/image/image.api.php
@@ -11,58 +11,16 @@
  */
 
 /**
- * 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:
- *   - "label": The human-readable name of the effect.
- *   - "effect callback": The function to call to perform this image effect.
- *   - "dimensions passthrough": (optional) Set this item if the effect doesn't
- *     change the dimensions of the image.
- *   - "dimensions callback": (optional) The function to call to transform
- *     dimensions for this effect.
- *   - "help": (optional) A brief description of the effect that will be shown
- *     when adding or configuring this image effect.
- *   - "form callback": (optional) The name of a function that will return a
- *     $form array providing a configuration form for this image effect.
- *   - "summary theme": (optional) The name of a theme function that will output
- *     a summary of this image effect's configuration.
- *
- * @see hook_image_effect_info_alter()
- */
-function hook_image_effect_info() {
-  $effects = array();
-
-  $effects['mymodule_resize'] = array(
-    'label' => t('Resize'),
-    'help' => t('Resize an image to an exact set of dimensions, ignoring aspect ratio.'),
-    'effect callback' => 'mymodule_resize_effect',
-    'dimensions callback' => 'mymodule_resize_dimensions',
-    'form callback' => 'mymodule_resize_form',
-    'summary theme' => 'mymodule_resize_summary',
-  );
-
-  return $effects;
-}
-
-/**
- * Alter the information provided in hook_image_effect_info().
+ * Alter the information provided in \Drupal\image\Annotation\ImageEffect.
  *
  * @param $effects
  *   The array of image effects, keyed on the machine-readable effect name.
- *
- * @see hook_image_effect_info()
  */
 function hook_image_effect_info_alter(&$effects) {
   // Override the Image module's crop effect with more options.
-  $effects['image_crop']['effect callback'] = 'mymodule_crop_effect';
-  $effects['image_crop']['dimensions callback'] = 'mymodule_crop_dimensions';
-  $effects['image_crop']['form callback'] = 'mymodule_crop_form';
+  $effects['image_crop']['effect_callback'] = 'mymodule_crop_effect';
+  $effects['image_crop']['dimensions_callback'] = 'mymodule_crop_dimensions';
+  $effects['image_crop']['form_callback'] = 'mymodule_crop_form';
 }
 
 /**
diff --git a/core/modules/image/image.effects.inc b/core/modules/image/image.effects.inc
deleted file mode 100644
index 619b09b..0000000
--- a/core/modules/image/image.effects.inc
+++ /dev/null
@@ -1,322 +0,0 @@
-<?php
-
-/**
- * @file
- * Functions needed to execute image effects provided by Image module.
- */
-
-/**
- * Implements hook_image_effect_info().
- */
-function image_image_effect_info() {
-  $effects = array(
-    'image_resize' => array(
-      'label' => t('Resize'),
-      'help' => t('Resizing will make images an exact set of dimensions. This may cause images to be stretched or shrunk disproportionately.'),
-      'effect callback' => 'image_resize_effect',
-      'dimensions callback' => 'image_resize_dimensions',
-      'form callback' => 'image_resize_form',
-      'summary theme' => 'image_resize_summary',
-    ),
-    'image_scale' => array(
-      'label' => 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.'),
-      'effect callback' => 'image_scale_effect',
-      'dimensions callback' => 'image_scale_dimensions',
-      'form callback' => 'image_scale_form',
-      'summary theme' => 'image_scale_summary',
-    ),
-    'image_scale_and_crop' => array(
-      'label' => 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.'),
-      'effect callback' => 'image_scale_and_crop_effect',
-      'dimensions callback' => 'image_resize_dimensions',
-      'form callback' => 'image_resize_form',
-      'summary theme' => 'image_resize_summary',
-    ),
-    'image_crop' => array(
-      'label' => t('Crop'),
-      'help' => t('Cropping will remove portions of an image to make it the specified dimensions.'),
-      'effect callback' => 'image_crop_effect',
-      'dimensions callback' => 'image_resize_dimensions',
-      'form callback' => 'image_crop_form',
-      'summary theme' => 'image_crop_summary',
-    ),
-    'image_desaturate' => array(
-      'label' => t('Desaturate'),
-      'help' => t('Desaturate converts an image to grayscale.'),
-      'effect callback' => 'image_desaturate_effect',
-      'dimensions passthrough' => TRUE,
-    ),
-    'image_rotate' => array(
-      'label' => t('Rotate'),
-      'help' => t('Rotating an image may cause the dimensions of an image to increase to fit the diagonal.'),
-      'effect callback' => 'image_rotate_effect',
-      'dimensions callback' => 'image_rotate_dimensions',
-      'form callback' => 'image_rotate_form',
-      'summary theme' => 'image_rotate_summary',
-    ),
-  );
-
-  return $effects;
-}
-
-/**
- * Image effect callback; Resize an image resource.
- *
- * @param object $image
- *   An image object returned by image_load().
- * @param array $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 bool
- *   TRUE on success. FALSE on failure to resize image.
- *
- * @see image_resize()
- */
-function image_resize_effect($image, array $data) {
-  if (!image_resize($image, $data['width'], $data['height'])) {
-    watchdog('image', 'Image resize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
-    return FALSE;
-  }
-  return TRUE;
-}
-
-/**
- * Image dimensions callback; Resize.
- *
- * @param array $dimensions
- *   Dimensions to be modified - an array with components width and height, in
- *   pixels.
- * @param array $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.
- */
-function image_resize_dimensions(array &$dimensions, array $data) {
-  // The new image will have the exact dimensions defined for the effect.
-  $dimensions['width'] = $data['width'];
-  $dimensions['height'] = $data['height'];
-}
-
-/**
- * Image effect callback; Scale an image resource.
- *
- * @param object $image
- *   An image object returned by image_load().
- * @param array $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 upscaled if the
- *     dimensions are larger than the original image.
- *
- * @return bool
- *   TRUE on success. FALSE on failure to scale image.
- *
- * @see image_scale()
- */
-function image_scale_effect($image, array $data) {
-  // Set sane default values.
-  $data += array(
-    'width' => NULL,
-    'height' => NULL,
-    'upscale' => FALSE,
-  );
-
-  if (!image_scale($image, $data['width'], $data['height'], $data['upscale'])) {
-    watchdog('image', 'Image scale failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
-    return FALSE;
-  }
-  return TRUE;
-}
-
-/**
- * Image dimensions callback; Scale.
- *
- * @param array $dimensions
- *   Dimensions to be modified - an array with components width and height, in
- *   pixels.
- * @param array $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 upscaled if the
- *     dimensions are larger than the original image.
- */
-function image_scale_dimensions(array &$dimensions, array $data) {
-  if ($dimensions['width'] && $dimensions['height']) {
-    image_dimensions_scale($dimensions, $data['width'], $data['height'], $data['upscale']);
-  }
-}
-
-/**
- * Image effect callback; Crop an image resource.
- *
- * @param object $image
- *   An image object returned by image_load().
- * @param array $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 bool
- *   TRUE on success. FALSE on failure to crop image.
- *
- * @see image_crop()
- */
-function image_crop_effect($image, array $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 using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
-    return FALSE;
-  }
-  return TRUE;
-}
-
-/**
- * Image effect callback; Scale and crop an image resource.
- *
- * @param object $image
- *   An image object returned by image_load().
- * @param array $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 bool
- *   TRUE on success. FALSE on failure to scale and crop image.
- *
- * @see image_scale_and_crop()
- */
-function image_scale_and_crop_effect($image, array $data) {
-  if (!image_scale_and_crop($image, $data['width'], $data['height'])) {
-    watchdog('image', 'Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
-    return FALSE;
-  }
-  return TRUE;
-}
-
-/**
- * Image effect callback; Desaturate (grayscale) an image resource.
- *
- * @param object $image
- *   An image object returned by image_load().
- * @param array $data
- *   An array of attributes to use when performing the desaturate effect.
- *
- * @return bool
- *   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 using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
-    return FALSE;
-  }
-  return TRUE;
-}
-
-/**
- * Image effect callback; Rotate an image resource.
- *
- * @param object $image
- *   An image object returned by image_load().
- * @param array $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 bool
- *   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', 'Image rotate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
-    return FALSE;
-  }
-  return TRUE;
-}
-
-/**
- * Image dimensions callback; Rotate.
- *
- * @param array $dimensions
- *   Dimensions to be modified - an array with components width and height, in
- *   pixels.
- * @param array $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.
- */
-function image_rotate_dimensions(array &$dimensions, array $data) {
-  // If the rotate is not random and the angle is a multiple of 90 degrees,
-  // then the new dimensions can be determined.
-  if (!$data['random'] && ((int) ($data['degrees']) == $data['degrees']) && ($data['degrees'] % 90 == 0)) {
-    if ($data['degrees'] % 180 != 0) {
-      $temp = $dimensions['width'];
-      $dimensions['width'] = $dimensions['height'];
-      $dimensions['height'] = $temp;
-    }
-  }
-  else {
-    $dimensions['width'] = $dimensions['height'] = NULL;
-  }
-}
diff --git a/core/modules/image/image.install b/core/modules/image/image.install
index fd440da..78eabeb 100644
--- a/core/modules/image/image.install
+++ b/core/modules/image/image.install
@@ -133,6 +133,10 @@ function _image_update_get_style_with_effects(array $style) {
     $uuid = new Uuid();
     $effect['ieid'] = $uuid->generate();
 
+    // Use 'id' instead of 'name'.
+    $effect['id'] = $effect['name'];
+    unset($effect['name']);
+
     $effects[$effect['ieid']] = $effect;
   }
   return $effects;
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 9a3dd64..6ea2b92 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -9,6 +9,8 @@
 use Drupal\Core\Language\Language;
 use Drupal\field\Plugin\Core\Entity\Field;
 use Drupal\field\Plugin\Core\Entity\FieldInstance;
+use Drupal\image\ImageEffectInterface;
+use Drupal\image\ImageStyleInterface;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\BinaryFileResponse;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@@ -76,10 +78,15 @@ function image_help($path, $arg) {
     case 'admin/config/media/image-styles':
       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/manage/%/add/%':
-      $effect = image_effect_definition_load($arg[7]);
+      $effect = Drupal::service('plugin.manager.image.effect')->getDefinition($arg[7]);
       return isset($effect['help']) ? ('<p>' . $effect['help'] . '</p>') : NULL;
     case 'admin/config/media/image-styles/manage/%/effects/%':
-      $effect = ($arg[5] == 'add') ? image_effect_definition_load($arg[6]) : image_effect_load($arg[6], $arg[4]);
+      if ($arg[5] == 'add') {
+        $effect = Drupal::service('plugin.manager.image.effect')->getDefinition($arg[6]);
+      }
+      else {
+        $effect = image_effect_load($arg[6], $arg[4]);
+      }
       return isset($effect['help']) ? ('<p>' . $effect['help'] . '</p>') : NULL;
   }
 }
@@ -168,28 +175,20 @@ function image_menu() {
     'weight' => 10,
     'route_name' => 'image_style_delete',
   );
-  $items['admin/config/media/image-styles/manage/%image_style/effects/%image_effect'] = array(
+  $items['admin/config/media/image-styles/manage/%/effects/%'] = array(
     'title' => 'Edit image effect',
     'description' => 'Edit an existing 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'),
-    'file' => 'image.admin.inc',
+    'route_name' => 'image_effect_edit_form',
   );
-  $items['admin/config/media/image-styles/manage/%image_style/effects/%image_effect/delete'] = array(
+  $items['admin/config/media/image-styles/manage/%image_style/effects/%/delete'] = array(
     'title' => 'Delete image effect',
     'description' => 'Delete an existing effect from a style.',
     'route_name' => 'image_effect_delete',
   );
-  $items['admin/config/media/image-styles/manage/%image_style/add/%image_effect_definition'] = array(
+  $items['admin/config/media/image-styles/manage/%/add/%'] = 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'),
-    'file' => 'image.admin.inc',
+    'route_name' => 'image_effect_add_form',
   );
 
   return $items;
@@ -216,35 +215,45 @@ function image_theme() {
     // Theme functions in image.admin.inc.
     'image_style_list' => array(
       'variables' => array('styles' => NULL),
+      'file' => 'image.admin.inc',
     ),
     'image_style_effects' => array(
       'render element' => 'form',
+      'file' => 'image.admin.inc',
     ),
     'image_style_preview' => array(
       'variables' => array('style' => NULL),
+      'file' => 'image.admin.inc',
     ),
     'image_anchor' => array(
       'render element' => 'element',
+      'file' => 'image.admin.inc',
     ),
     'image_resize_summary' => array(
       'variables' => array('data' => NULL),
+      'file' => 'image.admin.inc',
     ),
     'image_scale_summary' => array(
       'variables' => array('data' => NULL),
+      'file' => 'image.admin.inc',
     ),
     'image_crop_summary' => array(
       'variables' => array('data' => NULL),
+      'file' => 'image.admin.inc',
     ),
     'image_rotate_summary' => array(
       'variables' => array('data' => NULL),
+      'file' => 'image.admin.inc',
     ),
 
     // Theme functions in image.field.inc.
     'image_widget' => array(
       'render element' => 'element',
+      'file' => 'image.field.inc',
     ),
     'image_formatter' => array(
       'variables' => array('item' => NULL, 'path' => NULL, 'image_style' => NULL),
+      'file' => 'image.field.inc',
     ),
   );
 }
@@ -525,8 +534,9 @@ function image_style_create_derivative($style, $source, $destination) {
   }
 
   if (!empty($style->effects)) {
+    $manager = Drupal::service('plugin.manager.image.effect');
     foreach ($style->effects as $effect) {
-      image_effect_apply($image, $effect);
+      $manager->getInstance($effect)->processEffect($image);
     }
   }
 
@@ -552,20 +562,13 @@ function image_style_create_derivative($style, $source, $destination) {
  *   pixels.
  */
 function image_style_transform_dimensions($style_name, array &$dimensions) {
-  module_load_include('inc', 'image', 'image.effects');
   $style = entity_load('image_style', $style_name);
 
   if (!empty($style->effects)) {
+    $manager = Drupal::service('plugin.manager.image.effect');
     foreach ($style->effects as $effect) {
-      if (isset($effect['dimensions passthrough'])) {
-        continue;
-      }
-
-      if (isset($effect['dimensions callback'])) {
-        $effect['dimensions callback']($dimensions, $effect['data']);
-      }
-      else {
-        $dimensions['width'] = $dimensions['height'] = NULL;
+      if (!$effect['dimensions_passthrough']) {
+        $manager->getInstance($effect)->processDimensions($dimensions);
       }
     }
   }
@@ -704,51 +707,6 @@ function image_style_path($style_name, $uri) {
 }
 
 /**
- * Returns a set of image effects.
- *
- * These image effects are exposed by modules implementing
- * hook_image_effect_info().
- *
- * @return
- *   An array of image effects to be used when transforming images.
- * @see hook_image_effect_info()
- * @see image_effect_definition_load()
- */
-function image_effect_definitions() {
-  $language_interface = language(Language::TYPE_INTERFACE);
-
-  // hook_image_effect_info() includes translated strings, so each language is
-  // cached separately.
-  $langcode = $language_interface->id;
-
-  $effects = &drupal_static(__FUNCTION__);
-
-  if (!isset($effects)) {
-    if ($cache = cache()->get("image_effects:$langcode")) {
-      $effects = $cache->data;
-    }
-    else {
-      $effects = array();
-      include_once __DIR__ . '/image.effects.inc';
-      foreach (module_implements('image_effect_info') as $module) {
-        foreach (module_invoke($module, 'image_effect_info') as $name => $effect) {
-          // Ensure the current toolkit supports the effect.
-          $effect['module'] = $module;
-          $effect['name'] = $name;
-          $effect['data'] = isset($effect['data']) ? $effect['data'] : array();
-          $effects[$name] = $effect;
-        }
-      }
-      uasort($effects, '_image_effect_definitions_sort');
-      drupal_alter('image_effect_info', $effects);
-      cache()->set("image_effects:$langcode", $effects);
-    }
-  }
-
-  return $effects;
-}
-
-/**
  * Loads the definition for an image effect.
  *
  * The effect definition is a set of core properties for an image effect, not
@@ -771,8 +729,7 @@ function image_effect_definitions() {
  *     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] : NULL;
+  return Drupal::service('plugin.manager.image.effect')->getDefinition($effect);
 }
 
 /**
@@ -792,13 +749,10 @@ function image_effect_definition_load($effect) {
  *   Besides these keys, the entirety of the image definition is merged into
  *   the image effect array. Returns NULL if the specified effect cannot be
  *   found.
- * @see image_effect_definition_load()
  */
 function image_effect_load($ieid, $style_name) {
   if (($style = entity_load('image_style', $style_name)) && isset($style->effects[$ieid])) {
     $effect = $style->effects[$ieid];
-    $definition = image_effect_definition_load($effect['name']);
-    $effect = array_merge($definition, $effect);
     // @todo The effect's key name within the style is unknown. It *should* be
     //   identical to the ieid, but that is in no way guaranteed. And of course,
     //   the ieid key *within* the effect is senseless duplication in the first
@@ -811,6 +765,12 @@ function image_effect_load($ieid, $style_name) {
   }
   return NULL;
 }
+function image_effect_load_plugin($ieid, $style_name) {
+  if (($style = entity_load('image_style', $style_name)) && isset($style->effects[$ieid])) {
+    $effect = $style->effects[$ieid];
+    return Drupal::service('plugin.manager.image.effect')->getInstance($effect);
+  }
+}
 
 /**
  * Saves an image effect.
@@ -826,7 +786,7 @@ function image_effect_load($ieid, $style_name) {
 function image_effect_save($style, &$effect) {
   // Remove all values that are not properties of an image effect.
   // @todo Convert image effects into plugins.
-  $effect = array_intersect_key($effect, array_flip(array('ieid', 'module', 'name', 'data', 'weight')));
+  $effect = array_intersect_key($effect, array_flip(array('ieid', 'module', 'id', 'data', 'weight')));
 
   // Generate a unique image effect ID for a new effect.
   if (empty($effect['ieid'])) {
@@ -842,36 +802,6 @@ function image_effect_save($style, &$effect) {
 }
 
 /**
- * Deletes an image effect.
- *
- * @param ImageStyle $style
- *   The image style this effect belongs to.
- * @param $effect
- *   An image effect array.
- */
-function image_effect_delete($style, $effect) {
-  unset($style->effects[$effect['ieid']]);
-  $style->save();
-  image_style_flush($style);
-}
-
-/**
- * Applies an image effect to the image object.
- *
- * @param $image
- *   An image object returned by image_load().
- * @param $effect
- *   An image effect array.
- * @return
- *   TRUE on success. FALSE if unable to perform the image effect on the image.
- */
-function image_effect_apply($image, $effect) {
-  module_load_include('inc', 'image', 'image.effects');
-  $function = $effect['effect callback'];
-  return $function($image, $effect['data']);
-}
-
-/**
  * Returns HTML for an image using a specific image style.
  *
  * @param $variables
@@ -946,11 +876,9 @@ function image_filter_keyword($value, $current_pixels, $new_pixels) {
 
 /**
  * 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']);
+  return strcasecmp($a['id'], $b['id']);
 }
 
 /**
diff --git a/core/modules/image/image.routing.yml b/core/modules/image/image.routing.yml
index b178d33..01a7d53 100644
--- a/core/modules/image/image.routing.yml
+++ b/core/modules/image/image.routing.yml
@@ -11,3 +11,17 @@ image_effect_delete:
     _form: '\Drupal\image\Form\ImageEffectDeleteForm'
   requirements:
     _permission: 'administer image styles'
+
+image_effect_add_form:
+  pattern: '/admin/config/media/image-styles/manage/{image_style}/add/{image_effect}'
+  defaults:
+    _form: '\Drupal\image\Form\ImageEffectAddForm'
+  requirements:
+    _permission: 'administer image styles'
+
+image_effect_edit_form:
+  pattern: '/admin/config/media/image-styles/manage/{image_style}/effects/{image_effect}'
+  defaults:
+    _form: '\Drupal\image\Form\ImageEffectEditForm'
+  requirements:
+    _permission: 'administer image styles'
diff --git a/core/modules/image/image.services.yml b/core/modules/image/image.services.yml
new file mode 100644
index 0000000..0658bf7
--- /dev/null
+++ b/core/modules/image/image.services.yml
@@ -0,0 +1,4 @@
+services:
+  plugin.manager.image.effect:
+    class: Drupal\image\ImageEffectManager
+    arguments: ['@container.namespaces', '@cache.cache', '@language_manager', '@module_handler']
diff --git a/core/modules/image/lib/Drupal/image/Annotation/ImageEffect.php b/core/modules/image/lib/Drupal/image/Annotation/ImageEffect.php
new file mode 100644
index 0000000..7d2038c
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Annotation/ImageEffect.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Annotation\ImageEffect.
+ */
+
+namespace Drupal\image\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines an image effect annotation object.
+ *
+ * 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:
+ *   - "label": The human-readable name of the effect.
+ *   - "effect_callback": The function to call to perform this image effect.
+ *   - "dimensions_callback": (optional) The function to call to transform
+ *     dimensions for this effect.
+ *   - "help": (optional) A brief description of the effect that will be shown
+ *     when adding or configuring this image effect.
+ *   - "form_callback": (optional) The name of a function that will return a
+ *     $form array providing a configuration form for this image effect.
+ *   - "summary_theme": (optional) The name of a theme function that will output
+ *     a summary of this image effect's configuration.
+ *
+ * @see hook_image_effect_info_alter()
+ *
+ * @Annotation
+ */
+class ImageEffect extends Plugin {
+
+  /**
+   * The plugin ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The human-readable name of the type.
+   *
+   * @ingroup plugin_translatable
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   */
+  public $label;
+
+  /**
+   * The human-readable name of the type.
+   *
+   * @ingroup plugin_translatable
+   *
+   * @var \Drupal\Core\Annotation\Translation
+   */
+  public $help = '';
+
+  public $data = array();
+
+  public $dimensions_passthrough = FALSE;
+
+  public $summary_theme = '';
+
+  public $no_form = FALSE;
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Form/ImageEffectAddForm.php b/core/modules/image/lib/Drupal/image/Form/ImageEffectAddForm.php
new file mode 100644
index 0000000..2bd65a2
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectAddForm.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Form\ImageEffectAddForm.
+ */
+
+namespace Drupal\image\Form;
+
+use Drupal\image\ImageStyleInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @todo.
+ */
+class ImageEffectAddForm extends ImageEffectFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state, Request $request = NULL, ImageStyleInterface $image_style = NULL, $image_effect = NULL) {
+    $this->imageEffect = $this->effectManager->createInstance($image_effect);
+    if (!$this->imageEffect->hasForm()) {
+      return new RedirectResponse(url('admin/config/media/image-styles/manage/' . $image_style->id(), array('absolute' => TRUE)));
+    }
+    drupal_set_title(t('Add %label effect', array('%label' => $this->imageEffect->label())), PASS_THROUGH);
+
+    $form = parent::buildForm($form, $form_state, $request, $image_style);
+
+    $form['actions']['submit']['#value'] = t('Add effect');
+
+    return $form;
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php b/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php
index 50bf2bb..4bd2044 100644
--- a/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php
+++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php
@@ -7,14 +7,22 @@
 
 namespace Drupal\image\Form;
 
+use Drupal\Core\Controller\ControllerInterface;
 use Drupal\Core\Form\ConfirmFormBase;
+use Drupal\image\ImageEffectManager;
 use Drupal\image\Plugin\Core\Entity\ImageStyle;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Form for deleting an image effect.
  */
-class ImageEffectDeleteForm extends ConfirmFormBase {
+class ImageEffectDeleteForm extends ConfirmFormBase implements ControllerInterface {
+
+  /**
+   * @var \Drupal\image\ImageEffectManager
+   */
+  protected $effectManager;
 
   /**
    * The image style containing the image effect to be deleted.
@@ -26,15 +34,28 @@ class ImageEffectDeleteForm extends ConfirmFormBase {
   /**
    * The image effect to be deleted.
    *
-   * @var array;
+   * @var \Drupal\image\ImageEffectInterface
    */
   protected $imageEffect;
 
+  public function __construct(ImageEffectManager $effect_manager) {
+    $this->effectManager = $effect_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.image.effect')
+    );
+  }
+
   /**
    * {@inheritdoc}
    */
   public function getQuestion() {
-    return t('Are you sure you want to delete the @effect effect from the %style style?', array('%style' => $this->imageStyle->label(), '@effect' => $this->imageEffect['label']));
+    return t('Are you sure you want to delete the @effect effect from the %style style?', array('%style' => $this->imageStyle->label(), '@effect' => $this->imageEffect->label()));
   }
 
   /**
@@ -63,7 +84,7 @@ public function getFormID() {
    */
   public function buildForm(array $form, array &$form_state, $image_style = NULL, $image_effect = NULL, Request $request = NULL) {
     $this->imageStyle = $image_style;
-    $this->imageEffect = image_effect_load($image_effect, $this->imageStyle->id());
+    $this->imageEffect = $this->effectManager->getInstance($this->imageStyle->effects[$image_effect]);
 
     return parent::buildForm($form, $form_state, $request);
   }
@@ -72,8 +93,8 @@ public function buildForm(array $form, array &$form_state, $image_style = NULL,
    * {@inheritdoc}
    */
   public function submitForm(array &$form, array &$form_state) {
-    image_effect_delete($this->imageStyle, $this->imageEffect);
-    drupal_set_message(t('The image effect %name has been deleted.', array('%name' => $this->imageEffect['label'])));
+    $this->imageStyle->deleteImageEffect($this->imageEffect);
+    drupal_set_message(t('The image effect %name has been deleted.', array('%name' => $this->imageEffect->label())));
     $form_state['redirect'] = 'admin/config/media/image-styles/manage/' . $this->imageStyle->id();
   }
 
diff --git a/core/modules/image/lib/Drupal/image/Form/ImageEffectEditForm.php b/core/modules/image/lib/Drupal/image/Form/ImageEffectEditForm.php
new file mode 100644
index 0000000..501d7d0
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectEditForm.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Form\ImageEffectEditForm.
+ */
+
+namespace Drupal\image\Form;
+
+use Drupal\image\ImageStyleInterface;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @todo.
+ */
+class ImageEffectEditForm extends ImageEffectFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state, Request $request = NULL, ImageStyleInterface $image_style = NULL, $image_effect = NULL) {
+    $image_effect_info = $image_style->effects[$image_effect];
+    $this->imageEffect = $this->effectManager->getInstance($image_effect_info);
+    if (!$this->imageEffect->hasForm()) {
+      return new RedirectResponse(url('admin/config/media/image-styles/manage/' . $image_style->id(), array('absolute' => TRUE)));
+    }
+    drupal_set_title(t('Edit %label effect', array('%label' => $this->imageEffect->label())), PASS_THROUGH);
+
+    $form = parent::buildForm($form, $form_state, $request, $image_style);
+
+    $form['actions']['submit']['#value'] = t('Update effect');
+
+    // If the weight is not specified in the URL, use the default.
+    if (isset($image_effect_info['weight']) && !$request->query->has('weight')) {
+      $form['weight']['#value'] = $image_effect_info['weight'];
+    }
+
+    return $form;
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Form/ImageEffectFormBase.php b/core/modules/image/lib/Drupal/image/Form/ImageEffectFormBase.php
new file mode 100644
index 0000000..c1227e9
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectFormBase.php
@@ -0,0 +1,113 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Form\ImageEffectFormBase.
+ */
+
+namespace Drupal\image\Form;
+
+use Drupal\Core\Controller\ControllerInterface;
+use Drupal\Core\Form\FormInterface;
+use Drupal\image\ImageEffectManager;
+use Drupal\image\ImageStyleInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * @todo.
+ */
+class ImageEffectFormBase implements FormInterface, ControllerInterface {
+
+  /**
+   * @var \Drupal\image\ImageEffectManager
+   */
+  protected $effectManager;
+
+  /**
+   * @var \Drupal\image\ImageStyleInterface
+   */
+  protected $imageStyle;
+
+  /**
+   * @var \Drupal\image\ImageEffectInterface
+   */
+  protected $imageEffect;
+
+  public function __construct(ImageEffectManager $effect_manager) {
+    $this->effectManager = $effect_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.image.effect')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    return 'image_effect_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, array &$form_state, Request $request = NULL, ImageStyleInterface $image_style = NULL) {
+    $this->imageStyle = $image_style;
+
+    $form['#attached']['css'][drupal_get_path('module', 'image') . '/css/image.admin.css'] = array();
+    $form['ieid'] = array(
+      '#type' => 'value',
+      '#value' => NULL,
+    );
+    $form['id'] = array(
+      '#type' => 'value',
+      '#value' => $this->imageEffect->getPluginId(),
+    );
+
+    $form['data'] = $this->imageEffect->getForm();
+    $form['data']['#tree'] = TRUE;
+
+    // Check the URL for a weight, then the image effect, otherwise use default.
+    $form['weight'] = array(
+      '#type' => 'hidden',
+      '#value' => $request->query->has('weight') ? intval($request->query->get('weight')) : count($this->imageStyle->effects),
+    );
+
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#button_type' => 'primary',
+    );
+    $form['actions']['cancel'] = array(
+      '#type' => 'link',
+      '#title' => t('Cancel'),
+      '#href' => 'admin/config/media/image-styles/manage/' . $this->imageStyle->id(),
+    );
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    form_state_values_clean($form_state);
+
+    image_effect_save($this->imageStyle, $form_state['values']);
+
+    drupal_set_message(t('The image effect was successfully applied.'));
+    $form_state['redirect'] = 'admin/config/media/image-styles/manage/' . $this->imageStyle->id();
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/ImageEffectBase.php b/core/modules/image/lib/Drupal/image/ImageEffectBase.php
new file mode 100644
index 0000000..ce24699
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/ImageEffectBase.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Annotation\ImageEffectBase.
+ */
+
+namespace Drupal\image;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginBase;
+
+/**
+ * @todo.
+ */
+abstract class ImageEffectBase extends ContainerFactoryPluginBase implements ImageEffectInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processDimensions(array &$dimensions) {
+    $dimensions['width'] = $dimensions['height'] = NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSummary() {
+    return array(
+      '#markup' => '',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getForm() {
+    return array();
+  }
+
+  public function hasForm() {
+    $definition = $this->getPluginDefinition();
+    return !$definition['no_form'];
+  }
+
+  public function label() {
+    $definition = $this->getPluginDefinition();
+    return $definition['label'];
+  }
+
+  public function getUuid() {
+    return isset($this->configuration['ieid']) ? $this->configuration['ieid'] : '';
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/ImageEffectInterface.php b/core/modules/image/lib/Drupal/image/ImageEffectInterface.php
new file mode 100644
index 0000000..910c864
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/ImageEffectInterface.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Annotation\ImageEffectInterface.
+ */
+
+namespace Drupal\image;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+
+/**
+ * @todo.
+ */
+interface ImageEffectInterface extends PluginInspectionInterface {
+
+  /**
+   * Applies an image effect to the image object.
+   *
+   * @param \stdClass $image
+   *   An image object returned by image_load().
+   *
+   * @return bool
+   *   TRUE on success. FALSE if unable to perform the image effect on the image.
+   */
+  public function processEffect($image);
+
+  /**
+   * Determines the dimensions of the styled image.
+   *
+   * @param array $dimensions
+   *   Dimensions to be modified - an array with components width and height, in
+   *   pixels.
+   */
+  public function processDimensions(array &$dimensions);
+
+  public function getSummary();
+
+  public function getForm();
+
+  public function hasForm();
+
+  public function label();
+
+  public function getUuid();
+
+}
diff --git a/core/modules/image/lib/Drupal/image/ImageEffectManager.php b/core/modules/image/lib/Drupal/image/ImageEffectManager.php
new file mode 100644
index 0000000..b75c80c7
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/ImageEffectManager.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\ImageEffectManager.
+ */
+
+namespace Drupal\image;
+
+use Drupal\Core\Cache\CacheBackendInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Language\LanguageManager;
+use Drupal\Core\Plugin\DefaultPluginManager;
+
+/**
+ * Manages image effect plugins.
+ */
+class ImageEffectManager extends DefaultPluginManager {
+
+  /**
+   * Constructs a ImageEffectManager object.
+   *
+   * @param \Traversable $namespaces
+   *   An object that implements \Traversable which contains the root paths
+   *   keyed by the corresponding namespace to look for plugin implementations.
+   * @param \Drupal\Core\Cache\CacheBackendInterface $cache_backend
+   *   Cache backend instance to use.
+   * @param \Drupal\Core\Language\LanguageManager $language_manager
+   *   The language manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler to invoke the alter hook with.
+   */
+  public function __construct(\Traversable $namespaces, CacheBackendInterface $cache_backend, LanguageManager $language_manager, ModuleHandlerInterface $module_handler) {
+    $annotation_namespaces = array('Drupal\image\Annotation' => $namespaces['Drupal\image']);
+    parent::__construct('ImageEffect', $namespaces, $annotation_namespaces, 'Drupal\image\Annotation\ImageEffect');
+
+    $this->alterInfo($module_handler, 'image_effect_info');
+    $this->setCacheBackend($cache_backend, $language_manager, 'image_effect');
+  }
+
+  /**
+   * Returns a preconfigured image effect plugin.
+   *
+   * @param array $options
+   *   An array of options that can be used to determine a suitable plugin to
+   *   instantiate and how to configure it.
+   *
+   * @return \Drupal\image\ImageEffectInterface
+   *   An image effect plugin.
+   */
+  public function getInstance(array $options) {
+    $options += array('data' => array());
+    if (isset($options['ieid'])) {
+      $options['data']['ieid'] = $options['ieid'];
+    }
+    return $this->createInstance($options['id'], $options['data']);
+  }
+
+  /**
+   * Returns a preconfigured image effect plugin.
+   *
+   * @param string $plugin_id
+   *   The ID of the plugin being instantiated.
+   * @param array $configuration
+   *   An array of configuration relevant to the plugin instance.
+   *
+   * @return \Drupal\image\ImageEffectInterface
+   *   An image effect plugin.
+   */
+  public function createInstance($plugin_id, array $configuration = array()) {
+    return parent::createInstance($plugin_id, $configuration);
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/ImageStyleInterface.php b/core/modules/image/lib/Drupal/image/ImageStyleInterface.php
index 87367d3..0607724 100644
--- a/core/modules/image/lib/Drupal/image/ImageStyleInterface.php
+++ b/core/modules/image/lib/Drupal/image/ImageStyleInterface.php
@@ -8,10 +8,13 @@
 namespace Drupal\image;
 
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\image\ImageEffectInterface;
 
 /**
  * Provides an interface defining an image style entity.
  */
 interface ImageStyleInterface extends ConfigEntityInterface {
 
+  public function deleteImageEffect(ImageEffectInterface $effect);
+
 }
diff --git a/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php b/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php
index 3f35f5b..64917d8 100644
--- a/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php
+++ b/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php
@@ -19,13 +19,16 @@ class ImageStyleStorageController extends ConfigStorageController {
    * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::attachLoad().
    */
   protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
+    $manager = \Drupal::service('plugin.manager.image.effect');
     foreach ($queried_entities as $style) {
       if (!empty($style->effects)) {
+        /*
         foreach ($style->effects as $ieid => $effect) {
-          $definition = image_effect_definition_load($effect['name']);
+          $definition = $manager->getDefinition($effect['id']);
           $effect = array_merge($definition, $effect);
           $style->effects[$ieid] = $effect;
         }
+        //*/
         // Sort effects by weight.
         uasort($style->effects, 'drupal_sort_weight');
       }
diff --git a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php
index 2e99768..abb8f21 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/Core/Entity/ImageStyle.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\image\ImageEffectInterface;
 use Drupal\image\ImageStyleInterface;
 
 /**
@@ -148,4 +149,10 @@ protected static function replaceImageStyle(ImageStyle $style) {
     }
   }
 
+  public function deleteImageEffect(ImageEffectInterface $effect) {
+    unset($this->effects[$effect->getUuid()]);
+    $this->save();
+    image_style_flush($this);
+  }
+
 }
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php
new file mode 100644
index 0000000..391564a
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php
@@ -0,0 +1,84 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Plugin\ImageEffect\CropImageEffect.
+ */
+
+namespace Drupal\image\Plugin\ImageEffect;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\image\Annotation\ImageEffect;
+
+/**
+ * Crops an image resource.
+ *
+ * @ImageEffect(
+ *   id = "image_crop",
+ *   label = @Translation("Crop"),
+ *   help = @Translation("Resizing will make images an exact set of dimensions. This may cause images to be stretched or shrunk disproportionately.")
+ * )
+ */
+class CropImageEffect extends ResizeImageEffect {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processEffect($image) {
+    // Set sane default values.
+    $this->configuration += array(
+      'anchor' => 'center-center',
+    );
+
+    list($x, $y) = explode('-', $this->configuration['anchor']);
+    $x = image_filter_keyword($x, $image->info['width'], $this->configuration['width']);
+    $y = image_filter_keyword($y, $image->info['height'], $this->configuration['height']);
+    if (!image_crop($image, $x, $y, $this->configuration['width'], $this->configuration['height'])) {
+      watchdog('image', 'Image crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSummary() {
+    return array(
+      '#theme' => 'image_crop_summary',
+      '#data' => $this->configuration,
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getForm() {
+    $this->configuration += array(
+      'width' => '',
+      'height' => '',
+      'anchor' => 'center-center',
+    );
+    $form = parent::getForm();
+    $form['anchor'] = array(
+      '#type' => 'radios',
+      '#title' => t('Anchor'),
+      '#options' => array(
+        'left-top'      => t('Top') . ' ' . t('Left'),
+        'center-top'    => t('Top') . ' ' . t('Center'),
+        'right-top'     => t('Top') . ' ' . t('Right'),
+        'left-center'   => t('Center') . ' ' . t('Left'),
+        'center-center' => t('Center'),
+        'right-center'  => t('Center') . ' ' . t('Right'),
+        'left-bottom'   => t('Bottom') . ' ' . t('Left'),
+        'center-bottom' => t('Bottom') . ' ' . t('Center'),
+        'right-bottom'  => t('Bottom') . ' ' . t('Right'),
+      ),
+      '#theme' => 'image_anchor',
+      '#default_value' => $this->configuration['anchor'],
+      '#description' => t('The part of the image that will be retained during the crop.'),
+    );
+    return $form;
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php
new file mode 100644
index 0000000..9150032
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php
@@ -0,0 +1,38 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Plugin\ImageEffect\DesaturateImageEffect.
+ */
+
+namespace Drupal\image\Plugin\ImageEffect;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\image\Annotation\ImageEffect;
+use Drupal\image\ImageEffectBase;
+
+/**
+ * Desaturates (grayscale) an image resource.
+ *
+ * @ImageEffect(
+ *   id = "image_desaturate",
+ *   label = @Translation("Desaturate"),
+ *   help = @Translation("Desaturate converts an image to grayscale."),
+ *   no_form = TRUE,
+ *   dimensions_passthrough = TRUE
+ * )
+ */
+class DesaturateImageEffect extends ImageEffectBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processEffect($image) {
+    if (!image_desaturate($image)) {
+      watchdog('image', 'Image desaturate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php
new file mode 100644
index 0000000..a40f7d3
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Plugin\ImageEffect\Resize.
+ */
+
+namespace Drupal\image\Plugin\ImageEffect;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\image\Annotation\ImageEffect;
+use Drupal\image\ImageEffectBase;
+
+/**
+ * Resizes an image resource.
+ *
+ * @ImageEffect(
+ *   id = "image_resize",
+ *   label = @Translation("Resize"),
+ *   help = @Translation("Resizing will make images an exact set of dimensions. This may cause images to be stretched or shrunk disproportionately.")
+ * )
+ */
+class ResizeImageEffect extends ImageEffectBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processEffect($image) {
+    if (!image_resize($image, $this->configuration['width'], $this->configuration['height'])) {
+      watchdog('image', 'Image resize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processDimensions(array &$dimensions) {
+    // The new image will have the exact dimensions defined for the effect.
+    $dimensions['width'] = $this->configuration['width'];
+    $dimensions['height'] = $this->configuration['height'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSummary() {
+    return array(
+      '#theme' => 'image_resize_summary',
+      '#data' => $this->configuration,
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getForm() {
+    $form['width'] = array(
+      '#type' => 'number',
+      '#title' => t('Width'),
+      '#default_value' => isset($this->configuration['width']) ? $this->configuration['width'] : '',
+      '#field_suffix' => ' ' . t('pixels'),
+      '#required' => TRUE,
+      '#min' => 1,
+    );
+    $form['height'] = array(
+      '#type' => 'number',
+      '#title' => t('Height'),
+      '#default_value' => isset($this->configuration['height']) ? $this->configuration['height'] : '',
+      '#field_suffix' => ' ' . t('pixels'),
+      '#required' => TRUE,
+      '#min' => 1,
+    );
+    return $form;
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php
new file mode 100644
index 0000000..51ed51d
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php
@@ -0,0 +1,131 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Plugin\ImageEffect\RotateImageEffect.
+ */
+
+namespace Drupal\image\Plugin\ImageEffect;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\image\Annotation\ImageEffect;
+use Drupal\image\ImageEffectBase;
+
+/**
+ * @todo.
+ *
+ * @ImageEffect(
+ *   id = "image_rotate",
+ *   label = @Translation("Rotate"),
+ *   help = @Translation("Rotating an image may cause the dimensions of an image to increase to fit the diagonal.")
+ * )
+ */
+class RotateImageEffect extends ImageEffectBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processEffect($image) {
+    // Set sane default values.
+    $this->configuration += array(
+      'degrees' => 0,
+      'bgcolor' => NULL,
+      'random' => FALSE,
+    );
+
+    // Convert short #FFF syntax to full #FFFFFF syntax.
+    if (strlen($this->configuration['bgcolor']) == 4) {
+      $c = $this->configuration['bgcolor'];
+      $this->configuration['bgcolor'] = $c[0] . $c[1] . $c[1] . $c[2] . $c[2] . $c[3] . $c[3];
+    }
+
+    // Convert #FFFFFF syntax to hexadecimal colors.
+    if ($this->configuration['bgcolor'] != '') {
+      $this->configuration['bgcolor'] = hexdec(str_replace('#', '0x', $this->configuration['bgcolor']));
+    }
+    else {
+      $this->configuration['bgcolor'] = NULL;
+    }
+
+    if (!empty($this->configuration['random'])) {
+      $degrees = abs((float) $this->configuration['degrees']);
+      $this->configuration['degrees'] = rand(-1 * $degrees, $degrees);
+    }
+
+    if (!image_rotate($image, $this->configuration['degrees'], $this->configuration['bgcolor'])) {
+      watchdog('image', 'Image rotate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processDimensions(array &$dimensions) {
+    // If the rotate is not random and the angle is a multiple of 90 degrees,
+    // then the new dimensions can be determined.
+    if (!$this->configuration['random'] && ((int) ($this->configuration['degrees']) == $this->configuration['degrees']) && ($this->configuration['degrees'] % 90 == 0)) {
+      if ($this->configuration['degrees'] % 180 != 0) {
+        $temp = $dimensions['width'];
+        $dimensions['width'] = $dimensions['height'];
+        $dimensions['height'] = $temp;
+      }
+    }
+    else {
+      $dimensions['width'] = $dimensions['height'] = NULL;
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSummary() {
+    return array(
+      '#theme' => 'image_rotate_summary',
+      '#data' => $this->configuration,
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getForm() {
+    $form['degrees'] = array(
+      '#type' => 'number',
+      '#default_value' => (isset($this->configuration['degrees'])) ? $this->configuration['degrees'] : 0,
+      '#title' => t('Rotation angle'),
+      '#description' => t('The number of degrees the image should be rotated. Positive numbers are clockwise, negative are counter-clockwise.'),
+      '#field_suffix' => '&deg;',
+      '#required' => TRUE,
+    );
+    $form['bgcolor'] = array(
+      '#type' => 'textfield',
+      '#default_value' => (isset($this->configuration['bgcolor'])) ? $this->configuration['bgcolor'] : '#FFFFFF',
+      '#title' => t('Background color'),
+      '#description' => t('The background color to use for exposed areas of the image. Use web-style hex colors (#FFFFFF for white, #000000 for black). Leave blank for transparency on image types that support it.'),
+      '#size' => 7,
+      '#maxlength' => 7,
+      '#element_validate' => array(array($this, 'validateColorEffect')),
+    );
+    $form['random'] = array(
+      '#type' => 'checkbox',
+      '#default_value' => (isset($this->configuration['random'])) ? $this->configuration['random'] : 0,
+      '#title' => t('Randomize'),
+      '#description' => t('Randomize the rotation angle for each image. The angle specified above is used as a maximum.'),
+    );
+    return $form;
+  }
+
+  /**
+   * Validates to ensure a hexadecimal color value.
+   */
+  public function validateColorEffect(array $element, array &$form_state) {
+    if ($element['#value'] != '') {
+      if (!preg_match('/^#[0-9A-F]{3}([0-9A-F]{3})?$/', $element['#value'])) {
+        form_error($element, t('!name must be a hexadecimal color value.', array('!name' => $element['#title'])));
+      }
+    }
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php
new file mode 100644
index 0000000..9ee30f0
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php
@@ -0,0 +1,35 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Plugin\ImageEffect\ScaleAndCropImageEffect.
+ */
+
+namespace Drupal\image\Plugin\ImageEffect;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\image\Annotation\ImageEffect;
+
+/**
+ * Scales and crops an image resource.
+ *
+ * @ImageEffect(
+ *   id = "image_scale_and_crop",
+ *   label = @Translation("Scale and crop"),
+ *   help = @Translation("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.")
+ * )
+ */
+class ScaleAndCropImageEffect extends ResizeImageEffect {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processEffect($image) {
+    if (!image_scale_and_crop($image, $this->configuration['width'], $this->configuration['height'])) {
+      watchdog('image', 'Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php
new file mode 100644
index 0000000..45a6724
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php
@@ -0,0 +1,88 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Plugin\ImageEffect\ScaleImageEffect.
+ */
+
+namespace Drupal\image\Plugin\ImageEffect;
+
+use Drupal\Component\Image\Image;
+use Drupal\Core\Annotation\Translation;
+use Drupal\image\Annotation\ImageEffect;
+
+/**
+ * Scales an image resource.
+ *
+ * @ImageEffect(
+ *   id = "image_scale",
+ *   label = @Translation("Scale"),
+ *   help = @Translation("Scaling will maintain the aspect-ratio of the original image. If only a single dimension is specified, the other dimension will be calculated.")
+ * )
+ */
+class ScaleImageEffect extends ResizeImageEffect {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processEffect($image) {
+    // Set sane default values.
+    $this->configuration += array(
+      'width' => NULL,
+      'height' => NULL,
+      'upscale' => FALSE,
+    );
+
+    if (!image_scale($image, $this->configuration['width'], $this->configuration['height'], $this->configuration['upscale'])) {
+      watchdog('image', 'Image scale failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processDimensions(array &$dimensions) {
+    if ($dimensions['width'] && $dimensions['height']) {
+      Image::scaleDimensions($dimensions, $this->configuration['width'], $this->configuration['height'], $this->configuration['upscale']);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSummary() {
+    return array(
+      '#theme' => 'image_scale_summary',
+      '#data' => $this->configuration,
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getForm() {
+    $form = parent::getForm();
+    $form['#element_validate'] = array(array($this, 'validateScaleEffect'));
+    $form['width']['#required'] = FALSE;
+    $form['height']['#required'] = FALSE;
+    $form['upscale'] = array(
+      '#type' => 'checkbox',
+      '#default_value' => (isset($this->configuration['upscale'])) ? $this->configuration['upscale'] : 0,
+      '#title' => t('Allow Upscaling'),
+      '#description' => t('Let scale make images larger than their original size'),
+    );
+    return $form;
+  }
+
+  /**
+   * Validates to ensure that either a height or a width is specified.
+   */
+  public function validateScaleEffect(array $element, array &$form_state) {
+    if (empty($element['width']['#value']) && empty($element['height']['#value'])) {
+      form_error($element, t('Width and height can not both be blank.'));
+    }
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php
index 973a8ef..23681b1 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php
@@ -132,10 +132,10 @@ function testStyle() {
     $ieids = array();
     foreach ($style->effects as $ieid => $effect) {
       // Store the ieid for later use.
-      $ieids[$effect['name']] = $ieid;
+      $ieids[$effect['id']] = $ieid;
       $this->drupalGet($style_path . '/effects/' . $ieid);
-      foreach ($effect_edits[$effect['name']] as $field => $value) {
-        $this->assertFieldByName($field, $value, format_string('The %field field in the %effect effect has the correct value of %value.', array('%field' => $field, '%effect' => $effect['name'], '%value' => $value)));
+      foreach ($effect_edits[$effect['id']] as $field => $value) {
+        $this->assertFieldByName($field, $value, format_string('The %field field in the %effect effect has the correct value of %value.', array('%field' => $field, '%effect' => $effect['id'], '%value' => $value)));
       }
     }
 
@@ -157,7 +157,7 @@ function testStyle() {
     $effects_order = array_values($style->effects);
     $order_correct = TRUE;
     foreach ($effects_order as $index => $effect) {
-      if ($effect_edits_order[$index] != $effect['name']) {
+      if ($effect_edits_order[$index] != $effect['id']) {
         $order_correct = FALSE;
       }
     }
@@ -203,7 +203,7 @@ function testStyle() {
     $effects_order = array_values($style->effects);
     $order_correct = TRUE;
     foreach ($effects_order as $index => $effect) {
-      if ($effect_edits_order[$index] != $effect['name']) {
+      if ($effect_edits_order[$index] != $effect['id']) {
         $order_correct = FALSE;
       }
     }
@@ -219,7 +219,8 @@ function testStyle() {
     $this->drupalPost($style_path . '/effects/' . $ieids['image_crop'] . '/delete', array(), t('Delete'));
     // Confirm that the form submission was successful.
     $this->assertResponse(200);
-    $this->assertRaw(t('The image effect %name has been deleted.', array('%name' => $style->effects[$ieids['image_crop']]['label'])));
+    $image_crop_effect = $this->container->get('plugin.manager.image.effect')->getInstance($style->effects[$ieids['image_crop']]);
+    $this->assertRaw(t('The image effect %name has been deleted.', array('%name' => $image_crop_effect->label())));
     // Confirm that there is no longer a link to the effect.
     $this->assertNoLinkByHref($style_path . '/effects/' . $ieids['image_crop'] . '/delete');
     // Refresh the image style information and verify that the effect was
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php
index e7552d2..b105628 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php
@@ -59,7 +59,7 @@ function testImageDimensions() {
 
     // Scale an image that is wider than it is high.
     $effect = array(
-      'name' => 'image_scale',
+      'id' => 'image_scale',
       'data' => array(
         'width' => 120,
         'height' => 90,
@@ -81,7 +81,7 @@ function testImageDimensions() {
 
     // Rotate 90 degrees anticlockwise.
     $effect = array(
-      'name' => 'image_rotate',
+      'id' => 'image_rotate',
       'data' => array(
         'degrees' => -90,
         'random' => FALSE,
@@ -102,7 +102,7 @@ function testImageDimensions() {
 
     // Scale an image that is higher than it is wide (rotated by previous effect).
     $effect = array(
-      'name' => 'image_scale',
+      'id' => 'image_scale',
       'data' => array(
         'width' => 120,
         'height' => 90,
@@ -124,7 +124,7 @@ function testImageDimensions() {
 
     // Test upscale disabled.
     $effect = array(
-      'name' => 'image_scale',
+      'id' => 'image_scale',
       'data' => array(
         'width' => 400,
         'height' => 200,
@@ -146,7 +146,7 @@ function testImageDimensions() {
 
     // Add a desaturate effect.
     $effect = array(
-      'name' => 'image_desaturate',
+      'id' => 'image_desaturate',
       'data' => array(),
       'weight' => 4,
     );
@@ -164,7 +164,7 @@ function testImageDimensions() {
 
     // Add a random rotate effect.
     $effect = array(
-      'name' => 'image_rotate',
+      'id' => 'image_rotate',
       'data' => array(
         'degrees' => 180,
         'random' => TRUE,
@@ -183,7 +183,7 @@ function testImageDimensions() {
 
     // Add a crop effect.
     $effect = array(
-      'name' => 'image_crop',
+      'id' => 'image_crop',
       'data' => array(
         'width' => 30,
         'height' => 30,
@@ -205,13 +205,14 @@ function testImageDimensions() {
 
     // Rotate to a non-multiple of 90 degrees.
     $effect = array(
-      'name' => 'image_rotate',
+      'id' => 'image_rotate',
       'data' => array(
         'degrees' => 57,
         'random' => FALSE,
       ),
       'weight' => 7,
     );
+    $effect_plugin = $this->container->get('plugin.manager.image.effect')->getInstance($effect);
 
     image_effect_save($style, $effect);
     $img_tag = theme_image_style($variables);
@@ -221,12 +222,12 @@ function testImageDimensions() {
     $this->assertResponse(200, 'Image was generated at the URL.');
     $this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
 
-    image_effect_delete($style, $effect);
+    $style->deleteImageEffect($effect_plugin);
 
     // Ensure that an effect with no dimensions callback unsets the dimensions.
     // This ensures compatibility with 7.0 contrib modules.
     $effect = array(
-      'name' => 'image_module_test_null',
+      'id' => 'image_module_test_null',
       'data' => array(),
       'weight' => 8,
     );
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php
index 7f24796..d5c8e9d 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php
@@ -22,6 +22,13 @@ class ImageEffectsTest extends ToolkitTestBase {
    */
   public static $modules = array('image', 'image_test', 'image_module_test');
 
+  /**
+   * The image effect manager.
+   *
+   * @var \Drupal\image\ImageEffectManager
+   */
+  protected $manager;
+
   public static function getInfo() {
     return array(
       'name' => 'Image effects',
@@ -30,17 +37,19 @@ public static function getInfo() {
     );
   }
 
-  function setUp() {
+  public function setUp() {
     parent::setUp();
-
-    module_load_include('inc', 'image', 'image.effects');
+    $this->manager = $this->container->get('plugin.manager.image.effect');
   }
 
   /**
    * Test the image_resize_effect() function.
    */
   function testResizeEffect() {
-    $this->assertTrue(image_resize_effect($this->image, array('width' => 1, 'height' => 2)), 'Function returned the expected value.');
+    $this->assertImageEffect('image_resize', array(
+      'width' => 1,
+      'height' => 2,
+    ));
     $this->assertToolkitOperationsCalled(array('resize'));
 
     // Check the parameters.
@@ -54,7 +63,10 @@ function testResizeEffect() {
    */
   function testScaleEffect() {
     // @todo: need to test upscaling.
-    $this->assertTrue(image_scale_effect($this->image, array('width' => 10, 'height' => 10)), 'Function returned the expected value.');
+    $this->assertImageEffect('image_scale', array(
+      'width' => 10,
+      'height' => 10,
+    ));
     $this->assertToolkitOperationsCalled(array('resize'));
 
     // Check the parameters.
@@ -68,7 +80,11 @@ function testScaleEffect() {
    */
   function testCropEffect() {
     // @todo should test the keyword offsets.
-    $this->assertTrue(image_crop_effect($this->image, array('anchor' => 'top-1', 'width' => 3, 'height' => 4)), 'Function returned the expected value.');
+    $this->assertImageEffect('image_crop', array(
+      'anchor' => 'top-1',
+      'width' => 3,
+      'height' => 4,
+    ));
     $this->assertToolkitOperationsCalled(array('crop'));
 
     // Check the parameters.
@@ -83,7 +99,10 @@ function testCropEffect() {
    * Test the image_scale_and_crop_effect() function.
    */
   function testScaleAndCropEffect() {
-    $this->assertTrue(image_scale_and_crop_effect($this->image, array('width' => 5, 'height' => 10)), 'Function returned the expected value.');
+    $this->assertImageEffect('image_scale_and_crop', array(
+      'width' => 5,
+      'height' => 10,
+    ));
     $this->assertToolkitOperationsCalled(array('resize', 'crop'));
 
     // Check the parameters.
@@ -98,7 +117,7 @@ function testScaleAndCropEffect() {
    * Test the image_desaturate_effect() function.
    */
   function testDesaturateEffect() {
-    $this->assertTrue(image_desaturate_effect($this->image, array()), 'Function returned the expected value.');
+    $this->assertImageEffect('image_desaturate', array());
     $this->assertToolkitOperationsCalled(array('desaturate'));
 
     // Check the parameters.
@@ -111,7 +130,10 @@ function testDesaturateEffect() {
    */
   function testRotateEffect() {
     // @todo: need to test with 'random' => TRUE
-    $this->assertTrue(image_rotate_effect($this->image, array('degrees' => 90, 'bgcolor' => '#fff')), 'Function returned the expected value.');
+    $this->assertImageEffect('image_rotate', array(
+      'degrees' => 90,
+      'bgcolor' => '#fff',
+    ));
     $this->assertToolkitOperationsCalled(array('rotate'));
 
     // Check the parameters.
@@ -127,15 +149,35 @@ function testImageEffectsCaching() {
     $image_effect_definitions_called = &drupal_static('image_module_test_image_effect_info_alter');
 
     // First call should grab a fresh copy of the data.
-    $effects = image_effect_definitions();
+    $manager = $this->container->get('plugin.manager.image.effect');
+    $effects = $manager->getDefinitions();
     $this->assertTrue($image_effect_definitions_called === 1, 'image_effect_definitions() generated data.');
 
     // Second call should come from cache.
-    drupal_static_reset('image_effect_definitions');
     drupal_static_reset('image_module_test_image_effect_info_alter');
-    $cached_effects = image_effect_definitions();
-    $this->assertTrue(is_null($image_effect_definitions_called), 'image_effect_definitions() returned data from cache.');
+    $cached_effects = $manager->getDefinitions();
+    $this->assertTrue($image_effect_definitions_called === 0, 'image_effect_definitions() returned data from cache.');
 
     $this->assertTrue($effects == $cached_effects, 'Cached effects are the same as generated effects.');
   }
+
+  /**
+   * Asserts the effect processing of an image effect plugin.
+   *
+   * @param string $effect_name
+   *   The name of the image effect to test.
+   * @param array $data
+   *   The data to pass to the image effect.
+   *
+   * @return bool
+   *   TRUE if the assertion succeeded, FALSE otherwise.
+   */
+  protected function assertImageEffect($effect_name, array $data) {
+    $effect = $this->manager->getInstance(array(
+      'id' => $effect_name,
+      'data' => $data,
+    ));
+    return $this->assertTrue($effect->processEffect($this->image), 'Function returned the expected value.');
+  }
+
 }
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php
index 3b9be9f..4a6491b 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php
@@ -20,10 +20,8 @@
  * image.module:
  *   image_style_options()
  *   image_style_flush()
- *   image_effect_definition_load()
  *   image_effect_load()
  *   image_effect_save()
- *   image_effect_delete()
  *   image_filter_keyword()
  */
 
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php
index 298819a..08b4d28 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php
@@ -102,7 +102,7 @@ function testFlush() {
     $style_path = 'admin/config/media/image-styles/manage/' . $style->id();
     $ieids = array();
     foreach ($style->effects as $ieid => $effect) {
-      $ieids[$effect['name']] = $ieid;
+      $ieids[$effect['id']] = $ieid;
     }
     $this->drupalPost($style_path . '/effects/' . $ieids['image_scale'] . '/delete', array(), t('Delete'));
     $this->assertResponse(200);
diff --git a/core/modules/image/tests/image_module_test.module b/core/modules/image/tests/image_module_test.module
deleted file mode 100644
index 7cc6cdd..0000000
--- a/core/modules/image/tests/image_module_test.module
+++ /dev/null
@@ -1,51 +0,0 @@
-<?php
-
-/**
- * @file
- * Provides Image module hook implementations for testing purposes.
- */
-
-function image_module_test_file_download($uri) {
-  $default_uri = Drupal::state()->get('image.test_file_download') ?: FALSE;
-  if ($default_uri == $uri) {
-    return array('X-Image-Owned-By' => 'image_module_test');
-  }
-}
-
-/**
- * Implements hook_image_effect_info().
- */
-function image_module_test_image_effect_info() {
-  $effects = array(
-    'image_module_test_null' => array(
-    'effect callback' => 'image_module_test_null_effect',
-    ),
-  );
-
-  return $effects;
-}
-
-/**
- * Image effect callback; Null.
- *
- * @param $image
- *   An image object returned by image_load().
- * @param $data
- *   An array with no attributes.
- *
- * @return
- *   TRUE
- */
-function image_module_test_null_effect(array &$image, array $data) {
-  return TRUE;
-}
-
-/**
- * Implements hook_image_effect_info_alter().
- *
- * Used to keep a count of cache misses in image_effect_definitions().
- */
-function image_module_test_image_effect_info_alter(&$effects) {
-  $image_effects_definition_called = &drupal_static(__FUNCTION__, 0);
-  $image_effects_definition_called++;
-}
diff --git a/core/modules/image/tests/image_module_test.info.yml b/core/modules/image/tests/modules/image_module_test/image_module_test.info.yml
similarity index 100%
rename from core/modules/image/tests/image_module_test.info.yml
rename to core/modules/image/tests/modules/image_module_test/image_module_test.info.yml
diff --git a/core/modules/image/tests/modules/image_module_test/image_module_test.module b/core/modules/image/tests/modules/image_module_test/image_module_test.module
new file mode 100644
index 0000000..7d64d2c
--- /dev/null
+++ b/core/modules/image/tests/modules/image_module_test/image_module_test.module
@@ -0,0 +1,23 @@
+<?php
+
+/**
+ * @file
+ * Provides Image module hook implementations for testing purposes.
+ */
+
+function image_module_test_file_download($uri) {
+  $default_uri = Drupal::state()->get('image.test_file_download') ?: FALSE;
+  if ($default_uri == $uri) {
+    return array('X-Image-Owned-By' => 'image_module_test');
+  }
+}
+
+/**
+ * Implements hook_image_effect_info_alter().
+ *
+ * Used to keep a count of cache misses in \Drupal\image\ImageEffectManager.
+ */
+function image_module_test_image_effect_info_alter(&$effects) {
+  $image_effects_definition_called = &drupal_static(__FUNCTION__, 0);
+  $image_effects_definition_called++;
+}
diff --git a/core/modules/image/tests/modules/image_module_test/lib/Drupal/image_module_test/Plugin/ImageEffect/NullTestImageEffect.php b/core/modules/image/tests/modules/image_module_test/lib/Drupal/image_module_test/Plugin/ImageEffect/NullTestImageEffect.php
new file mode 100644
index 0000000..2550f56
--- /dev/null
+++ b/core/modules/image/tests/modules/image_module_test/lib/Drupal/image_module_test/Plugin/ImageEffect/NullTestImageEffect.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image_module_test\Plugin\ImageEffect\NullTestImageEffect.
+ */
+namespace Drupal\image_module_test\Plugin\ImageEffect;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\image\Annotation\ImageEffect;
+use Drupal\image\ImageEffectBase;
+
+/**
+ * @todo.
+ *
+ * @ImageEffect(
+ *   id = "image_module_test_null",
+ *   label = @Translation("Image module test"),
+ *   no_form = TRUE
+ * )
+ */
+class NullTestImageEffect extends ImageEffectBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function processEffect($image) {
+    return TRUE;
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/ImageUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/ImageUpgradePathTest.php
index fc24813..ba9bd59 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/ImageUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/ImageUpgradePathTest.php
@@ -38,7 +38,7 @@ public function testImageStyleUpgrade() {
       'name' => 'test-custom',
       'effects' => array(
         'image_rotate' => array(
-          'name' => 'image_rotate',
+          'id' => 'image_rotate',
           'data' => array(
             'degrees' => '90',
             'bgcolor' => '#FFFFFF',
@@ -47,7 +47,7 @@ public function testImageStyleUpgrade() {
           'weight' => '1',
         ),
         'image_desaturate' => array(
-          'name' => 'image_desaturate',
+          'id' => 'image_desaturate',
           'data' => array(),
           'weight' => '2',
         ),
@@ -57,7 +57,7 @@ public function testImageStyleUpgrade() {
       'name' => 'thumbnail',
       'effects' => array (
         'image_scale' => array(
-          'name' => 'image_scale',
+          'id' => 'image_scale',
           'data' => array (
             'width' => '177',
             'height' => '177',
@@ -73,11 +73,11 @@ public function testImageStyleUpgrade() {
       // during by the image style upgrade functions.
       foreach ($config->get('effects') as $uuid => $effect) {
         // Copy placeholder data.
-        $style['effects'][$uuid] = $style['effects'][$effect['name']];
+        $style['effects'][$uuid] = $style['effects'][$effect['id']];
         // Set the missing ieid key as this is unknown because it is a UUID.
         $style['effects'][$uuid]['ieid'] = $uuid;
         // Remove the placeholder data.
-        unset($style['effects'][$effect['name']]);
+        unset($style['effects'][$effect['id']]);
       }
       $this->assertEqual($this->sortByKey($style), $config->get(), format_string('@first is equal to @second.', array(
         '@first' => var_export($this->sortByKey($style), TRUE),
