diff --git a/core/includes/common.inc b/core/includes/common.inc
index 4829751..2075af9 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3083,7 +3083,6 @@ function _drupal_bootstrap_code() {
   require_once __DIR__ . '/tablesort.inc';
   require_once __DIR__ . '/file.inc';
   require_once __DIR__ . '/unicode.inc';
-  require_once __DIR__ . '/image.inc';
   require_once __DIR__ . '/form.inc';
   require_once __DIR__ . '/mail.inc';
   require_once __DIR__ . '/ajax.inc';
diff --git a/core/includes/image.inc b/core/includes/image.inc
deleted file mode 100644
index 9e2d06f..0000000
--- a/core/includes/image.inc
+++ /dev/null
@@ -1,332 +0,0 @@
-<?php
-
-/**
- * @file
- * API for manipulating images.
- */
-
-use Drupal\system\Plugin\ImageToolkitInterface;
-use Drupal\Component\Image\Image;
-
-/**
- * @defgroup image Image toolkits
- * @{
- * Functions for image file manipulations.
- *
- * Drupal's image toolkits provide an abstraction layer for common image file
- * manipulations like scaling, cropping, and rotating. The abstraction frees
- * module authors from the need to support multiple image libraries, and it
- * allows site administrators to choose the library that's best for them.
- *
- * PHP includes the GD library by default so a GD toolkit is installed with
- * Drupal. Other toolkits like ImageMagick are available from contrib modules.
- * GD works well for small images, but using it with larger files may cause PHP
- * to run out of memory. In contrast the ImageMagick library does not suffer
- * from this problem, but it requires the ISP to have installed additional
- * software.
- *
- * Image toolkits are discovered using the Plugin system using
- * \Drupal\system\Plugin\ImageToolkitManager. The toolkit must then be enabled
- * using the admin/config/media/image-toolkit form.
- *
- * Only one toolkit may be selected at a time. If a module author wishes to call
- * a specific toolkit they can check that it is installed by calling
- * \Drupal\system\Plugin\ImageToolkitManager::getAvailableToolkits(), and then
- * calling its functions directly.
- */
-
-/**
- * Gets details about an image.
- *
- * Drupal supports GIF, JPG and PNG file formats when used with the GD
- * toolkit, and may support others, depending on which toolkits are
- * installed.
- *
- * @param string $filepath
- *   String specifying the path of the image file.
- * @param \Drupal\system\Plugin\ImageToolkitInterface $toolkit
- *   (optional) An image toolkit object to override the default.
- *
- * @return array
- *   FALSE, if the file could not be found or is not an image. Otherwise, a
- *   keyed array containing information about the image:
- *   - "width": Width, in pixels.
- *   - "height": Height, in pixels.
- *   - "extension": Commonly used file extension for the image.
- *   - "mime_type": MIME type ('image/jpeg', 'image/gif', 'image/png').
- *   - "file_size": File size in bytes.
- */
-function image_get_info($filepath, ImageToolkitInterface $toolkit = NULL) {
-  $details = FALSE;
-  if (!is_file($filepath) && !is_uploaded_file($filepath)) {
-    return $details;
-  }
-
-  if ($toolkit === NULL) {
-    $toolkit = Drupal::service('image.toolkit');
-  }
-  if ($toolkit) {
-    $image = new stdClass();
-    $image->source = $filepath;
-    $image->toolkit = $toolkit;
-    $details = $toolkit->getInfo($image);
-    if (isset($details) && is_array($details)) {
-      $details['file_size'] = filesize($filepath);
-    }
-  }
-
-  return $details;
-}
-
-/**
- * Scales an image to the exact width and height given.
- *
- * This function achieves the target aspect ratio by cropping the original image
- * equally on both sides, or equally on the top and bottom. This function is
- * useful to create uniform sized avatars from larger images.
- *
- * The resulting image always has the exact target dimensions.
- *
- * @param object $image
- *   An image object returned by image_load().
- * @param int $width
- *   The target width, in pixels.
- * @param int $height
- *   The target height, in pixels.
- *
- * @return bool
- *   TRUE on success, FALSE on failure.
- *
- * @see image_load()
- * @see image_resize()
- * @see image_crop()
- */
-function image_scale_and_crop($image, $width, $height) {
-  $scale = max($width / $image->info['width'], $height / $image->info['height']);
-  $x = ($image->info['width'] * $scale - $width) / 2;
-  $y = ($image->info['height'] * $scale - $height) / 2;
-
-  if (image_resize($image, $image->info['width'] * $scale, $image->info['height'] * $scale)) {
-    return image_crop($image, $x, $y, $width, $height);
-  }
-  return FALSE;
-}
-
-/**
- * Scales image dimensions while maintaining aspect ratio.
- *
- * @deprecated as of Drupal 8.0. Use
- *   \Drupal\Component\Image\Image::scaleDimensions() directly instead.
- *
- * @see image_scale()
- */
-function image_dimensions_scale(array &$dimensions, $width = NULL, $height = NULL, $upscale = FALSE) {
-  return Image::scaleDimensions($dimensions, $width, $height, $upscale);
-}
-
-/**
- * Scales an image while maintaining aspect ratio.
- *
- * The resulting image can be smaller for one or both target dimensions.
- *
- * @param object $image
- *   An image object returned by image_load().
- * @param int $width
- *   (optional) The target width, in pixels. This value is omitted then the
- *   scaling will based only on the height value.
- * @param int $height
- *   (optional) The target height, in pixels. This value is omitted then the
- *   scaling will based only on the width value.
- * @param bool $upscale
- *   (optional) Boolean indicating that files smaller than the dimensions will
- *   be scaled up. This generally results in a low quality image.
- *
- * @return bool
- *   TRUE on success, FALSE on failure.
- *
- * @see image_dimensions_scale()
- * @see image_load()
- * @see image_scale_and_crop()
- */
-function image_scale($image, $width = NULL, $height = NULL, $upscale = FALSE) {
-  $dimensions = $image->info;
-
-  // Scale the dimensions - if they don't change then just return success.
-  if (!image_dimensions_scale($dimensions, $width, $height, $upscale)) {
-    return TRUE;
-  }
-
-  return image_resize($image, $dimensions['width'], $dimensions['height']);
-}
-
-/**
- * Resizes an image to the given dimensions (ignoring aspect ratio).
- *
- * @param object $image
- *   An image object returned by image_load().
- * @param int $width
- *   The target width, in pixels.
- * @param int $height
- *   The target height, in pixels.
- *
- * @return bool
- *   TRUE on success, FALSE on failure.
- *
- * @see image_load()
- * @see \Drupal\system\Plugin\ImageToolkitInterface::resize()
- */
-function image_resize($image, $width, $height) {
-  $width = (int) round($width);
-  $height = (int) round($height);
-
-  return $image->toolkit->resize($image, $width, $height);
-}
-
-/**
- * Rotates an image by the given number of degrees.
- *
- * @param $image
- *   An image object returned by image_load().
- * @param int $degrees
- *   The number of (clockwise) degrees to rotate the image.
- * @param string $background
- *   (optional) An hexadecimal integer specifying the background color to use
- *   for the uncovered area of the image after the rotation. E.g. 0x000000 for
- *   black, 0xff00ff for magenta, and 0xffffff for white. For images that
- *   support transparency, this will default to transparent. Otherwise it will
- *   be white.
- *
- * @return bool
- *   TRUE on success, FALSE on failure.
- *
- * @see image_load()
- * @see \Drupal\system\Plugin\ImageToolkitInterface::rotate()
- */
-function image_rotate($image, $degrees, $background = NULL) {
-  return $image->toolkit->rotate($image, $degrees, $background);
-}
-
-/**
- * Crops an image to a rectangle specified by the given dimensions.
- *
- * @param $image
- *   An image object returned by image_load().
- * @param int $x
- *   The top left coordinate, in pixels, of the crop area (x axis value).
- * @param int $y
- *   The top left coordinate, in pixels, of the crop area (y axis value).
- * @param int $width
- *   The target width, in pixels.
- * @param int $height
- *   The target height, in pixels.
- *
- * @return bool
- *   TRUE on success, FALSE on failure.
- *
- * @see image_load()
- * @see image_scale_and_crop()
- * @see \Drupal\system\Plugin\ImageToolkitInterface::crop()
- */
-function image_crop($image, $x, $y, $width, $height) {
-  $aspect = $image->info['height'] / $image->info['width'];
-  if (empty($height)) $height = $width / $aspect;
-  if (empty($width)) $width = $height * $aspect;
-
-  $width = (int) round($width);
-  $height = (int) round($height);
-
-  return $image->toolkit->crop($image, $x, $y, $width, $height);
-}
-
-/**
- * Converts an image to grayscale.
- *
- * @param $image
- *   An image object returned by image_load().
- *
- * @return bool
- *   TRUE on success, FALSE on failure.
- *
- * @see image_load()
- * @see \Drupal\system\Plugin\ImageToolkitInterface::desaturate()
- */
-function image_desaturate($image) {
-  return $image->toolkit->desaturate($image);
-}
-
-/**
- * Loads an image file and returns an image object.
- *
- * Any changes to the file are not saved until image_save() is called.
- *
- * @param string $file
- *   Path to an image file.
- * @param \Drupal\system\Plugin\ImageToolkitInterface $toolkit
- *   (optional) Image toolkit object to override the default.
- *
- * @return object
- *   An image object or FALSE if there was a problem loading the file. The
- *   image object has the following properties:
- *    - 'source' - The original file path.
- *    - 'info' - The array of information returned by image_get_info()
- *    - 'toolkit' - The name of the image toolkit requested when the image was
- *      loaded.
- *   Image toolkits may add additional properties. The caller is advised not to
- *   monkey about with them.
- *
- * @see image_save()
- * @see image_get_info()
- */
-function image_load($file, ImageToolkitInterface $toolkit = NULL) {
-  if ($toolkit === NULL) {
-    $toolkit = Drupal::service('image.toolkit');
-  }
-  if ($toolkit) {
-    $image = new stdClass();
-    $image->source = $file;
-    $image->info = image_get_info($file, $toolkit);
-    if (isset($image->info) && is_array($image->info)) {
-      $image->toolkit = $toolkit;
-      if ($toolkit->load($image)) {
-        return $image;
-      }
-    }
-  }
-  return FALSE;
-}
-
-/**
- * Closes the image and saves the changes to a file.
- *
- * @param object $image
- *   An image object returned by image_load(). The object's 'info' property
- *   will be updated if the file is saved successfully.
- * @param $destination
- *   (optional) Destination path where the image should be saved. If it is empty
- *   the original image file will be overwritten.
- *
- * @return bool
- *   TRUE on success, FALSE on failure.
- *
- * @see image_load()
- * @see \Drupal\system\Plugin\ImageToolkitInterface::save()
- */
-function image_save($image, $destination = NULL) {
-  if (empty($destination)) {
-    $destination = $image->source;
-  }
-  if ($return = $image->toolkit->save($image, $destination)) {
-    // Clear the cached file size and refresh the image information.
-    clearstatcache(TRUE, $destination);
-    $image->info = image_get_info($destination, $image->toolkit);
-
-    if (drupal_chmod($destination)) {
-      return $return;
-    }
-  }
-  return FALSE;
-}
-
-/**
- * @} End of "defgroup image".
- */
diff --git a/core/lib/Drupal/Core/Image/ImageFile.php b/core/lib/Drupal/Core/Image/ImageFile.php
new file mode 100644
index 0000000..564c9da
--- /dev/null
+++ b/core/lib/Drupal/Core/Image/ImageFile.php
@@ -0,0 +1,432 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Image\Image.
+ */
+
+namespace Drupal\Core\Image;
+
+use Drupal\Component\Image\Image;
+use Drupal\system\Plugin\ImageToolkitInterface;
+
+/**
+ * Defines an image object to represent an image file.
+ *
+ * @see \Drupal\system\Plugin\ImageToolkitInterface
+ * @see \Drupal\image\ImageEffectInterface
+ *
+ * @ingroup image
+ */
+class ImageFile {
+
+  /**
+   * String specifying the path of the image file.
+   *
+   * @var string
+   */
+  protected $source;
+
+  /**
+   * An associative array containing information about the image:
+   *   - width: Width, in pixels.
+   *   - height: Height, in pixels.
+   *   - extension: Commonly used file extension for the image.
+   *   - mime_type: MIME type ('image/jpeg', 'image/gif', 'image/png').
+   *   - file_size: File size in bytes.
+   *
+   * @var array
+   */
+  protected $info;
+
+  /**
+   * An image toolkit object.
+   *
+   * @var \Drupal\system\Plugin\ImageToolkitInterface
+   */
+  protected $toolkit;
+
+  /**
+   * An image file handle.
+   *
+   * @var resource
+   */
+  protected $resource;
+
+  /**
+   * Constructs a new ImageFile object.
+   *
+   * @param string $source
+   *   The path to an image file.
+   */
+  public function __construct($source) {
+    $this->source = $source;
+  }
+
+  /**
+   * Sets a value for a given key.
+   *
+   * @param string $key
+   *   The key to set the value for.
+   * @param mixed $value
+   *   The value to set for this key.
+   *
+   * @return self
+   *   Returns this image file.
+   */
+  public function set($key, $value) {
+    $this->info[$key] = $value;
+    return $this;
+  }
+
+  /**
+   * Replaces the file info.
+   *
+   * @param array $info
+   *   Information about this file.
+   *
+   * @return self
+   *   Returns this image file.
+   */
+  public function setInfo(array $info) {
+    $this->info = $info;
+    return $this;
+  }
+
+  /**
+   * Retrieves specific information about this image file.
+   *
+   * @param string $key
+   *   The key of the info to retrieve, e.g., 'height', 'width', 'extension',
+   *   'mime_type', 'file_size'.
+   *
+   * @return mixed|null
+   *   The value requested, or NULL.
+   */
+  public function get($key) {
+    if (!$this->info) {
+      $this->processInfo();
+    }
+
+    if (isset($this->info[$key])) {
+      return $this->info[$key];
+    }
+    return NULL;
+  }
+
+  /**
+   * Sets the image file resource.
+   *
+   * @param resource $resource
+   *   The image file handle.
+   *
+   * @return self
+   *   Returns this image file.
+   */
+  public function setResource($resource) {
+    $this->resource = $resource;
+    return $this;
+  }
+
+  /**
+   * Determines if this image file has a resource set.
+   *
+   * @return bool
+   *   TRUE if this image file has a resource set, FALSE otherwise.
+   */
+  public function hasResource() {
+    return (bool) $this->resource;
+  }
+
+  /**
+   * Retrieves the image file resource.
+   *
+   * @return resource
+   *   The image file handle.
+   */
+  public function getResource() {
+    if (!$this->hasResource()) {
+      $this->processInfo();
+      $this->getToolkit()->load($this);
+    }
+    return $this->resource;
+  }
+
+  /**
+   * Sets the source path of the image file.
+   *
+   * @param string $source
+   *   A string specifying the path of the image file.
+   *
+   * @return self
+   *   Returns this image file.
+   */
+  public function setSource($source) {
+    $this->source = $source;
+    return $this;
+  }
+
+  /**
+   * Retrieves the source path of the image file.
+   *
+   * @return string
+   *   The source path of the image file.
+   */
+  public function getSource() {
+    return $this->source;
+  }
+
+  /**
+   * Sets a custom image toolkit.
+   *
+   * @param \Drupal\system\Plugin\ImageToolkitInterface $toolkit
+   *   The image toolkit to use for this image file.
+   *
+   * @return self
+   *   Returns this image file.
+   */
+  public function setToolkit(ImageToolkitInterface $toolkit) {
+    $this->toolkit = $toolkit;
+    return $this;
+  }
+
+  /**
+   * Returns the image toolkit being used for this image file.
+   *
+   * If a custom toolkit was not specified, this will fallback to the default.
+   *
+   * @return \Drupal\system\Plugin\ImageToolkitInterface
+   *   The image toolkit used for this image file.
+   */
+  protected function getToolkit() {
+    if (!$this->toolkit) {
+      $this->toolkit = \Drupal::service('image.toolkit');
+    }
+    return $this->toolkit;
+  }
+
+  /**
+   * Returns the ID of the image toolkit used for this image file.
+   *
+   * @return string
+   *   The ID of the image toolkit.
+   */
+  public function getToolkitId() {
+    return $this->getToolkit()->getPluginId();
+  }
+
+  /**
+   * Gets details about an image.
+   *
+   * Drupal supports GIF, JPG and PNG file formats when used with the GD
+   * toolkit, and may support others, depending on which toolkits are
+   * installed.
+   *
+   * @return array|bool
+   *   FALSE, if the file could not be found or is not an image. Otherwise, a
+   *   keyed array containing information about the image:
+   *   - "width": Width, in pixels.
+   *   - "height": Height, in pixels.
+   *   - "extension": Commonly used file extension for the image.
+   *   - "mime_type": MIME type ('image/jpeg', 'image/gif', 'image/png').
+   *   - "file_size": File size in bytes.
+   */
+  public function getInfo() {
+    if ($this->processInfo()) {
+      return $this->info;
+    }
+    return FALSE;
+  }
+
+  /**
+   * Closes the image and saves the changes to a file.
+   *
+   * @param string|null $destination
+   *   (optional) Destination path where the image should be saved. If it is empty
+   *   the original image file will be overwritten.
+   *
+   * @return bool
+   *   TRUE on success, FALSE on failure.
+   *
+   * @see \Drupal\system\Plugin\ImageToolkitInterface::save()
+   */
+  public function save($destination = NULL) {
+    if (empty($destination)) {
+      $destination = $this->getSource();
+    }
+    if ($return = $this->getToolkit()->save($this, $destination)) {
+      // Clear the cached file size and refresh the image information.
+      clearstatcache(TRUE, $destination);
+      $this->setSource($destination);
+      $this->processInfo();
+
+      if (drupal_chmod($destination)) {
+        return $return;
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Prepares the image information.
+   *
+   * @return bool
+   *   FALSE, if the file could not be found or is not an image. Otherwise, the
+   *   image information is populated.
+   */
+  protected function processInfo() {
+    $destination = $this->getSource();
+    if (!is_file($destination) && !is_uploaded_file($destination)) {
+      return FALSE;
+    }
+
+    if ($details = $this->getToolkit()->getInfo($this)) {
+      $details['file_size'] = filesize($destination);
+      $this->setInfo($details);
+    }
+    return TRUE;
+  }
+
+  /**
+   * Scales an image while maintaining aspect ratio.
+   *
+   * The resulting image can be smaller for one or both target dimensions.
+   *
+   * @param int $width
+   *   (optional) The target width, in pixels. This value is omitted then the
+   *   scaling will based only on the height value.
+   * @param int $height
+   *   (optional) The target height, in pixels. This value is omitted then the
+   *   scaling will based only on the width value.
+   * @param bool $upscale
+   *   (optional) Boolean indicating that files smaller than the dimensions will
+   *   be scaled up. This generally results in a low quality image.
+   *
+   * @return bool
+   *   TRUE on success, FALSE on failure.
+   */
+  public function scale($width = NULL, $height = NULL, $upscale = FALSE) {
+    $dimensions = array(
+      'width' => $this->get('width'),
+      'height' => $this->get('height'),
+    );
+
+    // Scale the dimensions - if they don't change then just return success.
+    if (!Image::scaleDimensions($dimensions, $width, $height, $upscale)) {
+      return TRUE;
+    }
+
+    return $this->resize($dimensions['width'], $dimensions['height']);
+
+  }
+
+  /**
+   * Scales an image to the exact width and height given.
+   *
+   * This function achieves the target aspect ratio by cropping the original image
+   * equally on both sides, or equally on the top and bottom. This function is
+   * useful to create uniform sized avatars from larger images.
+   *
+   * The resulting image always has the exact target dimensions.
+   *
+   * @param int $width
+   *   The target width, in pixels.
+   * @param int $height
+   *   The target height, in pixels.
+   *
+   * @return bool
+   *   TRUE on success, FALSE on failure.
+   */
+  public function scaleAndCrop($width, $height) {
+    $scale = max($width / $this->get('width'), $height / $this->get('height'));
+    $x = ($this->get('width') * $scale - $width) / 2;
+    $y = ($this->get('height') * $scale - $height) / 2;
+
+    if ($this->resize($this->get('width') * $scale, $this->get('height') * $scale)) {
+      return $this->crop($x, $y, $width, $height);
+    }
+    return FALSE;
+  }
+
+  /**
+   * Crops an image to a rectangle specified by the given dimensions.
+   *
+   * @param int $x
+   *   The top left coordinate, in pixels, of the crop area (x axis value).
+   * @param int $y
+   *   The top left coordinate, in pixels, of the crop area (y axis value).
+   * @param int $width
+   *   The target width, in pixels.
+   * @param int $height
+   *   The target height, in pixels.
+   *
+   * @return bool
+   *   TRUE on success, FALSE on failure.
+   *
+   * @see \Drupal\system\Plugin\ImageToolkitInterface::crop()
+   */
+  function crop($x, $y, $width, $height) {
+    $aspect = $this->get('height') / $this->get('width');
+    if (empty($height)) $height = $width / $aspect;
+    if (empty($width)) $width = $height * $aspect;
+
+    $width = (int) round($width);
+    $height = (int) round($height);
+
+    return $this->getToolkit()->crop($this, $x, $y, $width, $height);
+  }
+
+  /**
+   * Resizes an image to the given dimensions (ignoring aspect ratio).
+   *
+   * @param int $width
+   *   The target width, in pixels.
+   * @param int $height
+   *   The target height, in pixels.
+   *
+   * @return bool
+   *   TRUE on success, FALSE on failure.
+   *
+   * @see \Drupal\system\Plugin\ImageToolkitInterface::resize()
+   */
+  public function resize($width, $height) {
+    $width = (int) round($width);
+    $height = (int) round($height);
+
+    return $this->getToolkit()->resize($this, $width, $height);
+  }
+
+  /**
+   * Converts an image to grayscale.
+   *
+   * @return bool
+   *   TRUE on success, FALSE on failure.
+   *
+   * @see \Drupal\system\Plugin\ImageToolkitInterface::desaturate()
+   */
+  public function desaturate() {
+    return $this->getToolkit()->desaturate($this);
+  }
+
+  /**
+   * Rotates an image by the given number of degrees.
+   *
+   * @param int $degrees
+   *   The number of (clockwise) degrees to rotate the image.
+   * @param string $background
+   *   (optional) An hexadecimal integer specifying the background color to use
+   *   for the uncovered area of the image after the rotation. E.g. 0x000000 for
+   *   black, 0xff00ff for magenta, and 0xffffff for white. For images that
+   *   support transparency, this will default to transparent. Otherwise it will
+   *   be white.
+   *
+   * @return bool
+   *   TRUE on success, FALSE on failure.
+   *
+   * @see \Drupal\system\Plugin\ImageToolkitInterface::rotate()
+   */
+  public function rotate($degrees, $background = NULL) {
+    return $this->getToolkit()->rotate($this, $degrees, $background);
+  }
+
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php
index d3bceb8..f29320f 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigSchemaTest.php
@@ -104,10 +104,10 @@ 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']['effects']['sequence'][0]['mapping']['uuid']['type'] = 'string';
     $expected['mapping']['langcode']['label'] = 'Default language';
     $expected['mapping']['langcode']['type'] = 'string';
 
@@ -189,9 +189,9 @@ function testSchemaData() {
 
     // The function is_array() doesn't work with ArrayAccess, so we use count().
     $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.');
+    $uuid = key($effects->getValue());
+    $effect = $effects[$uuid];
+    $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/file/file.module b/core/modules/file/file.module
index bcdb460..2ddeb7a 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -5,11 +5,11 @@
  * Defines a "managed_file" Form API field and a "file" field for Field module.
  */
 
+use Drupal\Core\Image\ImageFile;
 use Drupal\file\Plugin\Core\Entity\File;
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Template\Attribute;
 use Symfony\Component\HttpFoundation\JsonResponse;
-use Drupal\file\FileUsage\DatabaseFileUsageBackend;
 use Drupal\file\FileUsage\FileUsageInterface;
 use Drupal\Core\Ajax\AjaxResponse;
 use Drupal\Core\Ajax\ReplaceCommand;
@@ -408,7 +408,7 @@ function file_validate_size(File $file, $file_limit = 0, $user_limit = 0) {
 }
 
 /**
- * Checks that the file is recognized by image_get_info() as an image.
+ * Checks that the file is recognized by ImageFile::getInfo() as an image.
  *
  * @param Drupal\file\File $file
  *   A file entity.
@@ -421,7 +421,8 @@ function file_validate_size(File $file, $file_limit = 0, $user_limit = 0) {
 function file_validate_is_image(File $file) {
   $errors = array();
 
-  $info = image_get_info($file->getFileUri());
+  $image = new ImageFile($file->getFileUri());
+  $info = $image->getInfo();
   if (!$info || empty($info['extension'])) {
     $errors[] = t('Only JPEG, PNG and GIF images are allowed.');
   }
@@ -456,16 +457,18 @@ function file_validate_image_resolution(File $file, $maximum_dimensions = 0, $mi
   $errors = array();
 
   // Check first that the file is an image.
-  if ($info = image_get_info($file->getFileUri())) {
+  $image = new ImageFile($file->getFileUri());
+  if ($info = $image->getInfo()) {
     if ($maximum_dimensions) {
       // Check that it is smaller than the given dimensions.
       list($width, $height) = explode('x', $maximum_dimensions);
       if ($info['width'] > $width || $info['height'] > $height) {
         // Try to resize the image to fit the dimensions.
-        if ($image = image_load($file->getFileUri())) {
-          image_scale($image, $width, $height);
-          image_save($image);
-          $file->filesize = $image->info['file_size'];
+        $image = new ImageFile($file->getFileUri());
+        if ($image->getResource()) {
+          $image->scale($width, $height);
+          $image->save();
+          $file->filesize = $image->get('file_size');
           drupal_set_message(t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $maximum_dimensions)));
         }
         else {
diff --git a/core/modules/file/lib/Drupal/file/Tests/ValidatorTest.php b/core/modules/file/lib/Drupal/file/Tests/ValidatorTest.php
index 8012c4d..9af755d 100644
--- a/core/modules/file/lib/Drupal/file/Tests/ValidatorTest.php
+++ b/core/modules/file/lib/Drupal/file/Tests/ValidatorTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\file\Tests;
 
+use Drupal\Core\Image\ImageFile;
+
 /**
  *  This will run tests against the file validation functions (file_validate_*).
  */
@@ -87,9 +89,9 @@ function testFileValidateImageResolution() {
       $errors = file_validate_image_resolution($this->image, '10x5');
       $this->assertEqual(count($errors), 0, 'No errors should be reported when an oversized image can be scaled down.', 'File');
 
-      $info = image_get_info($this->image->getFileUri());
-      $this->assertTrue($info['width'] <= 10, 'Image scaled to correct width.', 'File');
-      $this->assertTrue($info['height'] <= 5, 'Image scaled to correct height.', 'File');
+      $image = new ImageFile($this->image->getFileUri());
+      $this->assertTrue($image->get('width') <= 10, 'Image scaled to correct width.', 'File');
+      $this->assertTrue($image->get('height') <= 5, 'Image scaled to correct height.', 'File');
 
       drupal_unlink('temporary://druplicon.png');
     }
diff --git a/core/modules/image/config/image.style.large.yml b/core/modules/image/config/image.style.large.yml
index 30bb1af..cbd1c0c 100644
--- a/core/modules/image/config/image.style.large.yml
+++ b/core/modules/image/config/image.style.large.yml
@@ -2,11 +2,11 @@ name: large
 label: 'Large (480x480)'
 effects:
   ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d:
-    name: image_scale
+    id: image_scale
     data:
       width: '480'
       height: '480'
       upscale: '1'
     weight: '0'
-    ieid: ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d
+    uuid: ddd73aa7-4bd6-4c85-b600-bdf2b1628d1d
 langcode: en
diff --git a/core/modules/image/config/image.style.medium.yml b/core/modules/image/config/image.style.medium.yml
index 1047f86..ba38b73 100644
--- a/core/modules/image/config/image.style.medium.yml
+++ b/core/modules/image/config/image.style.medium.yml
@@ -2,11 +2,11 @@ name: medium
 label: 'Medium (220x220)'
 effects:
   bddf0d06-42f9-4c75-a700-a33cafa25ea0:
-    name: image_scale
+    id: image_scale
     data:
       width: '220'
       height: '220'
       upscale: '1'
     weight: '0'
-    ieid: bddf0d06-42f9-4c75-a700-a33cafa25ea0
+    uuid: bddf0d06-42f9-4c75-a700-a33cafa25ea0
 langcode: en
diff --git a/core/modules/image/config/image.style.thumbnail.yml b/core/modules/image/config/image.style.thumbnail.yml
index 5834812..0368dd3 100644
--- a/core/modules/image/config/image.style.thumbnail.yml
+++ b/core/modules/image/config/image.style.thumbnail.yml
@@ -2,11 +2,11 @@ name: thumbnail
 label: 'Thumbnail (100x100)'
 effects:
   1cfec298-8620-4749-b100-ccb6c4500779:
-    name: image_scale
+    id: image_scale
     data:
       width: '100'
       height: '100'
       upscale: '1'
     weight: '0'
-    ieid: 1cfec298-8620-4749-b100-ccb6c4500779
+    uuid: 1cfec298-8620-4749-b100-ccb6c4500779
 langcode: en
diff --git a/core/modules/image/config/schema/image.schema.yml b/core/modules/image/config/schema/image.schema.yml
index 6916f19..871c0cc 100644
--- a/core/modules/image/config/schema/image.schema.yml
+++ b/core/modules/image/config/schema/image.schema.yml
@@ -26,13 +26,13 @@ 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:
+            uuid:
               type: string
     langcode:
       type: string
diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc
index 1cd2155..39c40df 100644
--- a/core/modules/image/image.admin.inc
+++ b/core/modules/image/image.admin.inc
@@ -5,7 +5,9 @@
  * Administration pages for image settings.
  */
 
-use Symfony\Component\HttpFoundation\RedirectResponse;
+use Drupal\Component\Utility\String;
+use Drupal\Core\Image\ImageFile;
+use Drupal\image\ImageStyleInterface;
 
 /**
  * Menu callback; Listing of all current image styles.
@@ -35,7 +37,7 @@ function image_style_list() {
  * @ingroup forms
  * @see image_style_form_submit()
  */
-function image_style_form($form, &$form_state, $style) {
+function image_style_form($form, &$form_state, ImageStyleInterface $style) {
   $title = t('Edit style %name', array('%name' => $style->label()));
   drupal_set_title($title, PASS_THROUGH);
 
@@ -69,54 +71,55 @@ function image_style_form($form, &$form_state, $style) {
   $form['effects'] = array(
     '#theme' => 'image_style_effects',
   );
-  if (!empty($style->effects)) {
-    foreach ($style->effects as $key => $effect) {
-      $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'])) : '',
-      );
-      $form['effects'][$key]['weight'] = array(
-        '#type' => 'weight',
-        '#title' => t('Weight for @title', array('@title' => $effect['label'])),
-        '#title_display' => 'invisible',
-        '#default_value' => $effect['weight'],
-      );
+  foreach ($style->getEffects()->sort() as $effect) {
+    $key = $effect->getUuid();
+    $form['effects'][$key]['#weight'] = isset($form_state['input']['effects']) ? $form_state['input']['effects'][$key]['weight'] : NULL;
+    $form['effects'][$key]['label'] = array(
+      '#markup' => String::checkPlain($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_display' => 'invisible',
+      '#default_value' => $effect->getWeight(),
+    );
 
-      $links = array();
-      if (isset($effect['form callback'])) {
-        $links['edit'] = array(
-          'title' => t('edit'),
-          'href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key,
-        );
-      }
-      $links['delete'] = array(
-        'title' => t('delete'),
-        'href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key . '/delete',
-      );
-      $form['effects'][$key]['operations'] = array(
-        '#type' => 'operations',
-        '#links' => $links,
-      );
-      $form['effects'][$key]['configure'] = array(
-        '#type' => 'link',
-        '#title' => t('edit'),
-        '#href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key,
-        '#access' => isset($effect['form callback']),
-      );
-      $form['effects'][$key]['remove'] = array(
-        '#type' => 'link',
-        '#title' => t('delete'),
-        '#href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key . '/delete',
+    $links = array();
+    if ($effect->hasForm()) {
+      $links['edit'] = array(
+        'title' => t('edit'),
+        'href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key,
       );
     }
+    $links['delete'] = array(
+      'title' => t('delete'),
+      'href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key . '/delete',
+    );
+    $form['effects'][$key]['operations'] = array(
+      '#type' => 'operations',
+      '#links' => $links,
+    );
+    $form['effects'][$key]['configure'] = array(
+      '#type' => 'link',
+      '#title' => t('edit'),
+      '#href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key,
+      '#access' => $effect->hasForm(),
+    );
+    $form['effects'][$key]['remove'] = array(
+      '#type' => 'link',
+      '#title' => t('delete'),
+      '#href' => 'admin/config/media/image-styles/manage/' . $style->id() . '/effects/' . $key . '/delete',
+    );
   }
 
   // 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 = Drupal::service('plugin.manager.image.effect')->getDefinitions();
+  uasort($effects, function ($a, $b) {
+    return strcasecmp($a['id'], $b['id']);
+  });
+  foreach ($effects as $effect => $definition) {
     $new_effect_options[$effect] = $definition['label'];
   }
   $form['effects']['new'] = array(
@@ -168,21 +171,21 @@ 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'],
     );
-    image_effect_save($style, $effect);
+    $style->saveImageEffect($effect);
     drupal_set_message(t('The image effect was successfully applied.'));
   }
 }
@@ -195,15 +198,9 @@ function image_style_form_submit($form, &$form_state) {
 
   // Update image effect weights.
   if (!empty($form_state['values']['effects'])) {
-    foreach ($form_state['values']['effects'] as $ieid => $effect_data) {
-      if (isset($style->effects[$ieid])) {
-        $effect = array(
-          'name' => $style->effects[$ieid]['name'],
-          'data' => $style->effects[$ieid]['data'],
-          'weight' => $effect_data['weight'],
-          'ieid' => $ieid,
-        );
-        $style->effects[$ieid] = $effect;
+    foreach ($form_state['values']['effects'] as $uuid => $effect_data) {
+      if ($style->getEffects()->has($uuid)) {
+        $style->getEffect($uuid)->setWeight($effect_data['weight']);
       }
     }
   }
@@ -262,246 +259,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
@@ -621,7 +378,8 @@ function theme_image_style_preview($variables) {
 
   // Set up original file information.
   $original_path = $sample_image;
-  $original_image = image_get_info($original_path);
+  $original_image = new ImageFile($original_path);
+  $original_image = $original_image->getInfo();
   if ($original_image['width'] > $original_image['height']) {
     $original_width = min($original_image['width'], $sample_width);
     $original_height = round($original_width / $original_image['width'] * $original_image['height']);
@@ -638,7 +396,8 @@ function theme_image_style_preview($variables) {
   if (!file_exists($preview_file)) {
     $style->createDerivative($original_path, $preview_file);
   }
-  $preview_image = image_get_info($preview_file);
+  $preview_image = new ImageFile($preview_file);
+  $preview_image = $preview_image->getInfo();
   if ($preview_image['width'] > $preview_image['height']) {
     $preview_width = min($preview_image['width'], $sample_width);
     $preview_height = round($preview_width / $preview_image['width'] * $preview_image['height']);
diff --git a/core/modules/image/image.api.php b/core/modules/image/image.api.php
index bc7ace8..7678142 100644
--- a/core/modules/image/image.api.php
+++ b/core/modules/image/image.api.php
@@ -11,58 +11,14 @@
  */
 
 /**
- * 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';
+  // Override the Image module's 'Scale and Crop' effect label.
+  $effects['image_scale_and_crop']['label'] = t('Bangers and Mash');
 }
 
 /**
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.field.inc b/core/modules/image/image.field.inc
index 9609960..8dece3d 100644
--- a/core/modules/image/image.field.inc
+++ b/core/modules/image/image.field.inc
@@ -7,6 +7,7 @@
 
 use Drupal\Component\Utility\NestedArray;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Image\ImageFile;
 
 /**
  * Implements hook_field_info().
@@ -226,13 +227,11 @@ function _image_field_resolution_validate($element, &$form_state) {
  * Implements hook_field_presave().
  */
 function image_field_presave(EntityInterface $entity, $field, $instance, $langcode, &$items) {
-
   // Determine the dimensions if necessary.
   foreach ($items as &$item) {
     if (!isset($item['width']) || !isset($item['height'])) {
-      $info = image_get_info(file_load($item['target_id'])->getFileUri());
-
-      if (is_array($info)) {
+      $image = new ImageFile(file_load($item['target_id'])->getFileUri());
+      if ($info = $image->getInfo()) {
         $item['width'] = $info['width'];
         $item['height'] = $info['height'];
       }
@@ -301,9 +300,8 @@ function image_field_widget_process($element, &$form_state, $form) {
       $variables['height'] = $element['#value']['height'];
     }
     else {
-      $info = image_get_info($file->getFileUri());
-
-      if (is_array($info)) {
+      $image = new ImageFile($file->getFileUri());
+      if ($info = $image->getInfo()) {
         $variables['width'] = $info['width'];
         $variables['height'] = $info['height'];
       }
diff --git a/core/modules/image/image.install b/core/modules/image/image.install
index fd440da..58cf110 100644
--- a/core/modules/image/image.install
+++ b/core/modules/image/image.install
@@ -6,7 +6,6 @@
  */
 
 use Drupal\Component\Uuid\Uuid;
-use Drupal\field\Plugin\Core\Entity\Field;
 
 /**
  * Implements hook_install().
@@ -126,14 +125,19 @@ function _image_update_get_style_with_effects(array $style) {
     ->condition('isid', $style['isid'])
     ->execute();
   foreach ($result as $effect) {
-    unset($effect['isid']);
     $effect['data'] = unserialize($effect['data']);
 
     // Generate a unique image effect ID for the effect.
     $uuid = new Uuid();
-    $effect['ieid'] = $uuid->generate();
+    $effect['uuid'] = $uuid->generate();
 
-    $effects[$effect['ieid']] = $effect;
+    // Use 'id' instead of 'name'.
+    $effect['id'] = $effect['name'];
+
+    // Clear out legacy keys.
+    unset($effect['isid'], $effect['ieid'], $effect['name']);
+
+    $effects[$effect['uuid']] = $effect;
   }
   return $effects;
 }
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 4e786bd..315f064 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -6,10 +6,9 @@
  */
 
 use Drupal\Core\Entity\EntityInterface;
-use Drupal\Core\Language\Language;
+use Drupal\Core\Image\ImageFile;
 use Drupal\field\Plugin\Core\Entity\Field;
 use Drupal\field\Plugin\Core\Entity\FieldInstance;
-use Drupal\Component\Uuid\Uuid;
 use Drupal\file\Plugin\Core\Entity\File;
 use Drupal\image\ImageStyleInterface;
 use Drupal\image\Plugin\Core\Entity\ImageStyle;
@@ -72,10 +71,10 @@ 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]);
+      $effect = entity_load('image_style', $arg[5])->getEffect($arg[7])->getPluginDefinition();
       return isset($effect['help']) ? ('<p>' . $effect['help'] . '</p>') : NULL;
   }
 }
@@ -164,28 +163,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;
@@ -212,35 +203,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',
     ),
   );
 }
@@ -298,7 +299,8 @@ function image_file_download($uri) {
     $original_uri = file_uri_scheme($uri) . '://' . implode('/', $args);
 
     // Check that the file exists and is an image.
-    if ($info = image_get_info($uri)) {
+    $image = new ImageFile($uri);
+    if ($info = $image->getInfo()) {
       // Check the permissions of the original to grant access to this image.
       $headers = module_invoke_all('file_download', $original_uri);
       // Confirm there's at least one module granting access and none denying access.
@@ -416,169 +418,6 @@ function image_style_deliver(ImageStyleInterface $style, $scheme) {
 }
 
 /**
- * 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
- * containing any user-settings. The definition defines various functions to
- * call when configuring or executing an image effect. This loader is mostly for
- * internal use within image.module. Use image_effect_load() or
- * entity_load() to get image effects that contain configuration.
- *
- * @param $effect
- *   The name of the effect definition to load.
- * @return
- *   An array containing the image effect definition with the following keys:
- *   - "effect": The unique name for the effect being performed. Usually prefixed
- *     with the name of the module providing the effect.
- *   - "module": The module providing the effect.
- *   - "help": A description of the effect.
- *   - "function": The name of the function that will execute the effect.
- *   - "form": (optional) The name of a function to configure the effect.
- *   - "summary": (optional) The name of a theme function that will display a
- *     one-line summary of the effect. Does not include the "theme_" prefix.
- */
-function image_effect_definition_load($effect) {
-  $definitions = image_effect_definitions();
-  return isset($definitions[$effect]) ? $definitions[$effect] : NULL;
-}
-
-/**
- * Loads a single image effect.
- *
- * @param $ieid
- *   The image effect ID.
- * @param $style_name
- *   The image style name.
- *
- * @return
- *   An image effect array, consisting of the following keys:
- *   - "ieid": The unique image effect ID.
- *   - "weight": The weight of this image effect within the image style.
- *   - "name": The name of the effect definition that powers this image effect.
- *   - "data": An array of configuration options for this image 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
-    //   place. This problem can be eliminated in many places, but especially
-    //   for loaded menu arguments like %image_effect, the actual router
-    //   callbacks don't have access to 'ieid' anymore (unless resorting to
-    //   dirty %index and %map tricks).
-    $effect['ieid'] = $ieid;
-    return $effect;
-  }
-  return NULL;
-}
-
-/**
- * Saves an image effect.
- *
- * @param ImageStyle $style
- *   The image style this effect belongs to.
- * @param array $effect
- *   An image effect array. Passed by reference.
- *
- * @return array
- *   The saved image effect array. The 'ieid' key will be set for the effect.
- */
-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')));
-
-  // Generate a unique image effect ID for a new effect.
-  if (empty($effect['ieid'])) {
-    $uuid = new Uuid();
-    $effect['ieid'] = $uuid->generate();
-  }
-  $style->effects[$effect['ieid']] = $effect;
-  $style->save();
-}
-
-/**
- * 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();
-}
-
-/**
- * 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
@@ -656,15 +495,6 @@ 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']);
-}
-
-/**
  * Implements hook_entity_presave().
  *
  * Transforms default image of image field from array into single value at save.
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..efe1358
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Annotation/ImageEffect.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Annotation\ImageEffect.
+ */
+
+namespace Drupal\image\Annotation;
+
+use Drupal\Component\Annotation\Plugin;
+
+/**
+ * Defines an image effect annotation object.
+ *
+ * @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 = '';
+
+  /**
+   * Determines if this image effect provides a form.
+   *
+   * @var bool
+   */
+  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..106cb08
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectAddForm.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Form\ImageEffectAddForm.
+ */
+
+namespace Drupal\image\Form;
+
+use Drupal\Core\Controller\ControllerInterface;
+use Drupal\image\ImageEffectManager;
+use Drupal\image\ImageStyleInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Provides an add form for image effects.
+ */
+class ImageEffectAddForm extends ImageEffectFormBase implements ControllerInterface {
+
+  /**
+   * The image effect manager.
+   *
+   * @var \Drupal\image\ImageEffectManager
+   */
+  protected $effectManager;
+
+  /**
+   * Constructs a new ImageEffectAddForm.
+   *
+   * @param \Drupal\image\ImageEffectManager $effect_manager
+   *   The image effect manager.
+   */
+  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}
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   * @param \Drupal\image\ImageStyleInterface $image_style
+   *   The image style.
+   * @param string $image_effect
+   *   The image effect ID.
+   *
+   * @return array|\Symfony\Component\HttpFoundation\RedirectResponse
+   *   The form structure.
+   */
+  public function buildForm(array $form, array &$form_state, Request $request = NULL, ImageStyleInterface $image_style = NULL, $image_effect = NULL) {
+    $form = parent::buildForm($form, $form_state, $request, $image_style, $image_effect);
+
+    drupal_set_title(t('Add %label effect', array('%label' => $this->imageEffect->label())), PASS_THROUGH);
+    $form['actions']['submit']['#value'] = t('Add effect');
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function prepareImageEffect($image_effect) {
+    $image_effect = $this->effectManager->createInstance($image_effect);
+    // Set the initial weight so this effect comes last.
+    $image_effect->setWeight(count($this->imageStyle->getEffects()));
+    return $image_effect;
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php b/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php
index 50bf2bb..330d626 100644
--- a/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php
+++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectDeleteForm.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Form\ConfirmFormBase;
 use Drupal\image\Plugin\Core\Entity\ImageStyle;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 
 /**
@@ -26,7 +27,7 @@ class ImageEffectDeleteForm extends ConfirmFormBase {
   /**
    * The image effect to be deleted.
    *
-   * @var array;
+   * @var \Drupal\image\ImageEffectInterface
    */
   protected $imageEffect;
 
@@ -34,7 +35,7 @@ class ImageEffectDeleteForm extends ConfirmFormBase {
    * {@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 +64,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->imageStyle->getEffect($image_effect);
 
     return parent::buildForm($form, $form_state, $request);
   }
@@ -72,8 +73,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..671c065
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectEditForm.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Form\ImageEffectEditForm.
+ */
+
+namespace Drupal\image\Form;
+
+use Drupal\image\ImageStyleInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Provides an edit form for image effects.
+ */
+class ImageEffectEditForm extends ImageEffectFormBase {
+
+  /**
+   * {@inheritdoc}
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   * @param \Drupal\image\ImageStyleInterface $image_style
+   *   The image style.
+   * @param string $image_effect
+   *   The image effect ID.
+   *
+   * @return array|\Symfony\Component\HttpFoundation\RedirectResponse
+   *   The form structure.
+   */
+  public function buildForm(array $form, array &$form_state, Request $request = NULL, ImageStyleInterface $image_style = NULL, $image_effect = NULL) {
+    $form = parent::buildForm($form, $form_state, $request, $image_style, $image_effect);
+
+    drupal_set_title(t('Edit %label effect', array('%label' => $this->imageEffect->label())), PASS_THROUGH);
+    $form['actions']['submit']['#value'] = t('Update effect');
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function prepareImageEffect($image_effect) {
+    return $this->imageStyle->getEffect($image_effect);
+  }
+
+}
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..c472f58
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Form/ImageEffectFormBase.php
@@ -0,0 +1,125 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Form\ImageEffectFormBase.
+ */
+
+namespace Drupal\image\Form;
+
+use Drupal\Core\Form\FormInterface;
+use Drupal\image\ImageStyleInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+
+/**
+ * Provides a base form for image effects.
+ */
+abstract class ImageEffectFormBase implements FormInterface {
+
+  /**
+   * The image style.
+   *
+   * @var \Drupal\image\ImageStyleInterface
+   */
+  protected $imageStyle;
+
+  /**
+   * The image effect.
+   *
+   * @var \Drupal\image\ImageEffectInterface
+   */
+  protected $imageEffect;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormID() {
+    return 'image_effect_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   The current request.
+   * @param \Drupal\image\ImageStyleInterface $image_style
+   *   The image style.
+   * @param string $image_effect
+   *   The image effect ID.
+   *
+   * @return array
+   *   The form structure.
+   *
+   * @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
+   */
+  public function buildForm(array $form, array &$form_state, Request $request = NULL, ImageStyleInterface $image_style = NULL, $image_effect = NULL) {
+    $this->imageStyle = $image_style;
+    $this->imageEffect = $this->prepareImageEffect($image_effect);
+
+    if (!$this->imageEffect->hasForm()) {
+      throw new NotFoundHttpException();
+    }
+
+    $form['#attached']['css'][drupal_get_path('module', 'image') . '/css/image.admin.css'] = array();
+    $form['uuid'] = array(
+      '#type' => 'value',
+      '#value' => $this->imageEffect->getUuid(),
+    );
+    $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')) : $this->imageEffect->getWeight(),
+    );
+
+    $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);
+    $this->imageStyle->saveImageEffect($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();
+  }
+
+  /**
+   * Converts an image effect ID into an object.
+   *
+   * @param string $image_effect
+   *   The image effect ID.
+   *
+   * @return \Drupal\image\ImageEffectInterface
+   *   The image effect object.
+   */
+  abstract protected function prepareImageEffect($image_effect);
+
+}
diff --git a/core/modules/image/lib/Drupal/image/ImageEffectBag.php b/core/modules/image/lib/Drupal/image/ImageEffectBag.php
new file mode 100644
index 0000000..0bdee3b
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/ImageEffectBag.php
@@ -0,0 +1,124 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\ImageEffectBag.
+ */
+
+namespace Drupal\image;
+
+use Drupal\Component\Plugin\PluginBag;
+use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Component\Utility\MapArray;
+use Drupal\Component\Uuid\Uuid;
+
+/**
+ * A collection of image effects.
+ */
+class ImageEffectBag extends PluginBag {
+
+  /**
+   * The manager used to instantiate the plugins.
+   *
+   * @var \Drupal\Component\Plugin\PluginManagerInterface
+   */
+  protected $manager;
+
+  /**
+   * The initial configuration for each image effect in the bag.
+   *
+   * @var array
+   */
+  protected $configurations = array();
+
+  /**
+   * Constructs a new ImageEffectBag.
+   *
+   * @param \Drupal\Component\Plugin\PluginManagerInterface $manager
+   *   The manager to be used for instantiating plugins.
+   * @param array $configurations
+   *   (optional) An associative array containing the initial configuration for
+   *   each tour in the bag, keyed by plugin instance ID.
+   */
+  public function __construct(PluginManagerInterface $manager, array $configurations = array()) {
+    $this->manager = $manager;
+    $this->configurations = $configurations;
+
+    if (!empty($configurations)) {
+      $this->instanceIDs = MapArray::copyValuesToKeys(array_keys($configurations));
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function initializePlugin($instance_id) {
+    if (!isset($this->pluginInstances[$instance_id])) {
+      $configuration = $this->configurations[$instance_id] + array('data' => array());
+      $this->pluginInstances[$instance_id] = $this->manager->createInstance($configuration['id'], $configuration);
+    }
+  }
+
+  /**
+   * Returns the current configuration of all image effects in this bag.
+   *
+   * @return array
+   *   An associative array keyed by image effect UUID, whose values are image
+   *   effect configurations.
+   */
+  public function export() {
+    $instances = array();
+    $this->rewind();
+    foreach ($this as $instance_id => $instance) {
+      $instances[$instance_id] = $instance->export();
+    }
+    return $instances;
+  }
+
+  /**
+   * Removes an instance ID.
+   *
+   * @param string $instance_id
+   *   An image effect instance IDs.
+   */
+  public function removeInstanceID($instance_id) {
+    unset($this->instanceIDs[$instance_id], $this->configurations[$instance_id]);
+    $this->remove($instance_id);
+  }
+
+  /**
+   * Updates the configuration for an image effect instance.
+   *
+   * If there is no plugin instance yet, a new will be instantiated. Otherwise,
+   * the existing instance is updated with the new configuration.
+   *
+   * @param array $configuration
+   *   The image effect configuration to set.
+   *
+   * @return string
+   */
+  public function setConfig(array $configuration) {
+    // Derive the instance ID from the configuration.
+    if (empty($configuration['uuid'])) {
+      $uuid_generator = new Uuid();
+      $configuration['uuid'] = $uuid_generator->generate();
+    }
+    $instance_id = $configuration['uuid'];
+    $this->configurations[$instance_id] = $configuration;
+    $this->get($instance_id)->setPluginConfiguration($configuration);
+    $this->addInstanceID($instance_id);
+    return $instance_id;
+  }
+
+  /**
+   * Sorts all image effect instances in this bag.
+   *
+   * @return self
+   */
+  public function sort() {
+    uasort($this->configurations, 'drupal_sort_weight');
+    $this->instanceIDs = MapArray::copyValuesToKeys(array_keys($this->configurations));
+    return $this;
+  }
+
+}
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..9149a6d
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/ImageEffectBase.php
@@ -0,0 +1,126 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Annotation\ImageEffectBase.
+ */
+
+namespace Drupal\image;
+
+use Drupal\Component\Plugin\PluginBase;
+
+/**
+ * Provides a base class for image effects.
+ */
+abstract class ImageEffectBase extends PluginBase implements ImageEffectInterface {
+
+  /**
+   * The image effect ID.
+   *
+   * @var string
+   */
+  protected $uuid;
+
+  /**
+   * The weight of the image effect.
+   *
+   * @var int|string
+   */
+  protected $weight = '';
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(array $configuration, $plugin_id, array $plugin_definition) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+
+    $this->setPluginConfiguration($configuration);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transformDimensions(array &$dimensions) {
+    $dimensions['width'] = $dimensions['height'] = NULL;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSummary() {
+    return array(
+      '#markup' => '',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getForm() {
+    return array();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasForm() {
+    return !$this->pluginDefinition['no_form'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function label() {
+    return $this->pluginDefinition['label'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getUuid() {
+    return $this->uuid;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setWeight($weight) {
+    $this->weight = $weight;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWeight() {
+    return $this->weight;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function export() {
+    return array(
+      'uuid' => $this->getUuid(),
+      'id' => $this->getPluginId(),
+      'weight' => $this->getWeight(),
+      'data' => $this->configuration,
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setPluginConfiguration(array $configuration) {
+    $configuration += array(
+      'data' => array(),
+      'uuid' => '',
+      'weight' => '',
+    );
+    $this->configuration = $configuration['data'];
+    $this->uuid = $configuration['uuid'];
+    $this->weight = $configuration['weight'];
+    return $this;
+  }
+
+}
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..027f9c0
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/ImageEffectInterface.php
@@ -0,0 +1,122 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Annotation\ImageEffectInterface.
+ */
+
+namespace Drupal\image;
+
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Core\Image\ImageFile;
+
+/**
+ * Defines the interface for image effects.
+ */
+interface ImageEffectInterface extends PluginInspectionInterface {
+
+  /**
+   * Applies an image effect to the image object.
+   *
+   * @param \Drupal\Core\Image\ImageFile $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 applyEffect(ImageFile $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 transformDimensions(array &$dimensions);
+
+  /**
+   * Returns a render array summarizing the configuration of the image effect.
+   *
+   * @return array
+   *   A render array.
+   */
+  public function getSummary();
+
+  /**
+   * Builds the part of the image effect form specific to this image effect.
+   *
+   * This method is only responsible for the form elements specific to this
+   * image effect. All other aspects of the form are handled by calling code.
+   *
+   * @return array
+   *   A render array.
+   */
+  public function getForm();
+
+  /**
+   * Determines if a form should be provided to configure this image effect.
+   *
+   * @return bool
+   *   TRUE if this image effect provides a form, FALSE otherwise.
+   */
+  public function hasForm();
+
+  /**
+   * Returns the image effect label.
+   *
+   * @return string
+   *   The image effect label.
+   */
+  public function label();
+
+  /**
+   * Returns the unique ID representing the image effect.
+   *
+   * @return string
+   *   The image effect ID.
+   */
+  public function getUuid();
+
+  /**
+   * Returns the weight of the image effect.
+   *
+   * @return int|string
+   *   Either the integer weight of the image effect, or an empty string.
+   */
+  public function getWeight();
+
+  /**
+   * Sets the weight for this image effect.
+   *
+   * @param int $weight
+   *   The weight for this image effect.
+   *
+   * @return self
+   *   This image effect.
+   */
+  public function setWeight($weight);
+
+  /**
+   * Exports the complete configuration of this image effect instance.
+   *
+   * @return array
+   */
+  public function export();
+
+  /**
+   * Sets the configuration for this image effect.
+   *
+   * @param array $configuration
+   *   An associative array containing:
+   *   - uuid: (optional) The image effect ID.
+   *   - weight: (optional) The weight of the image effect.
+   *   - data: (optional) An array of configuration specific to this image
+   *     effect type.
+   *
+   * @return self
+   *   This image effect.
+   */
+  public function setPluginConfiguration(array $configuration);
+
+}
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..95fc4c1
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/ImageEffectManager.php
@@ -0,0 +1,31 @@
+<?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 {
+
+  /**
+   * {@inheritdoc}
+   */
+  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');
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/ImageStyleInterface.php b/core/modules/image/lib/Drupal/image/ImageStyleInterface.php
index d7803cd..441a8e7 100644
--- a/core/modules/image/lib/Drupal/image/ImageStyleInterface.php
+++ b/core/modules/image/lib/Drupal/image/ImageStyleInterface.php
@@ -8,6 +8,7 @@
 namespace Drupal\image;
 
 use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\image\ImageEffectInterface;
 
 /**
  * Provides an interface defining an image style entity.
@@ -15,6 +16,47 @@
 interface ImageStyleInterface extends ConfigEntityInterface {
 
   /**
+   * Returns a specific image effect.
+   *
+   * @param string $effect
+   *   The image effect ID.
+   *
+   * @return \Drupal\image\ImageEffectInterface
+   *   The image effect object.
+   */
+  public function getEffect($effect);
+
+  /**
+   * Returns the image effects for this style.
+   *
+   * @return \Drupal\image\ImageEffectBag|\Drupal\image\ImageEffectInterface[]
+   *   The image effect plugin bag.
+   */
+  public function getEffects();
+
+  /**
+   * Saves an image effect for this style.
+   *
+   * @param array $configuration
+   *   An array of image effect configuration.
+   *
+   * @return string
+   *   The image effect ID.
+   */
+  public function saveImageEffect(array $configuration);
+
+  /**
+   * Deletes an image effect from this style.
+   *
+   * @param \Drupal\image\ImageEffectInterface $effect
+   *   The image effect object.
+   *
+   * @return self
+   *   This image style.
+   */
+  public function deleteImageEffect(ImageEffectInterface $effect);
+
+  /**
    * Delivers an image derivative.
    *
    * Transfers a generated image derivative to the requesting agent. Modules may
@@ -75,6 +117,9 @@ public function buildUrl($path, $clean_urls = NULL);
    * @param string $path
    *   (optional) The original image path or URI. If it's supplied, only this
    *   image derivative will be flushed.
+   *
+   * @return self
+   *   This image style.
    */
   public function flush($path = NULL);
 
diff --git a/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php b/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php
deleted file mode 100644
index 3f35f5b..0000000
--- a/core/modules/image/lib/Drupal/image/ImageStyleStorageController.php
+++ /dev/null
@@ -1,36 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\image\ImageStyleStorageController.
- */
-
-namespace Drupal\image;
-
-use Drupal\Core\Config\Entity\ConfigStorageController;
-use Drupal\Core\Config\Config;
-
-/**
- * Defines a controller class for image styles.
- */
-class ImageStyleStorageController extends ConfigStorageController {
-
-  /**
-   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::attachLoad().
-   */
-  protected function attachLoad(&$queried_entities, $revision_id = FALSE) {
-    foreach ($queried_entities as $style) {
-      if (!empty($style->effects)) {
-        foreach ($style->effects as $ieid => $effect) {
-          $definition = image_effect_definition_load($effect['name']);
-          $effect = array_merge($definition, $effect);
-          $style->effects[$ieid] = $effect;
-        }
-        // Sort effects by weight.
-        uasort($style->effects, 'drupal_sort_weight');
-      }
-    }
-    parent::attachLoad($queried_entities, $revision_id);
-  }
-
-}
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 7dd36c0..c158cb9 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
@@ -7,13 +7,16 @@
 
 namespace Drupal\image\Plugin\Core\Entity;
 
+use Drupal\Component\Utility\Crypt;
+use Drupal\Component\Utility\Url;
 use Drupal\Core\Config\Entity\ConfigEntityBase;
 use Drupal\Core\Entity\Annotation\EntityType;
 use Drupal\Core\Annotation\Translation;
 use Drupal\Core\Entity\EntityStorageControllerInterface;
+use Drupal\Core\Image\ImageFile;
+use Drupal\image\ImageEffectBag;
+use Drupal\image\ImageEffectInterface;
 use Drupal\image\ImageStyleInterface;
-use Drupal\Component\Utility\Crypt;
-use Drupal\Component\Utility\Url;
 use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\BinaryFileResponse;
@@ -31,7 +34,7 @@
  *     "form" = {
  *       "delete" = "Drupal\image\Form\ImageStyleDeleteForm"
  *     },
- *     "storage" = "Drupal\image\ImageStyleStorageController"
+ *     "storage" = "Drupal\Core\Config\Entity\ConfigStorageController"
  *   },
  *   uri_callback = "image_style_entity_uri",
  *   config_prefix = "image.style",
@@ -77,7 +80,12 @@ class ImageStyle extends ConfigEntityBase implements ImageStyleInterface {
    *
    * @var array
    */
-  public $effects;
+  protected $effects = array();
+
+  /**
+   * @var \Drupal\image\ImageEffectBag
+   */
+  protected $effectsBag;
 
   /**
    * Overrides Drupal\Core\Entity\Entity::id().
@@ -164,6 +172,50 @@ protected static function replaceImageStyle(ImageStyleInterface $style) {
   /**
    * {@inheritdoc}
    */
+  public function getEffect($effect) {
+    return $this->getEffects()->get($effect);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getEffects() {
+    if (!$this->effectsBag) {
+      $this->effectsBag = new ImageEffectBag(\Drupal::service('plugin.manager.image.effect'), $this->effects);
+    }
+    return $this->effectsBag;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function saveImageEffect(array $configuration) {
+    $effect_id = $this->getEffects()->setConfig($configuration);
+    $this->save();
+    return $effect_id;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function deleteImageEffect(ImageEffectInterface $effect) {
+    $this->getEffects()->removeInstanceID($effect->getUuid());
+    $this->save();
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getExportProperties() {
+    $properties = parent::getExportProperties();
+    $properties['effects'] = $this->getEffects()->sort()->export();
+    return $properties;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function deliver($scheme, $target) {
     $original_uri = $scheme . '://' . $target;
 
@@ -225,11 +277,11 @@ public function deliver($scheme, $target) {
     }
 
     if ($success) {
-      $image = image_load($derivative_uri);
-      $uri = $image->source;
+      $image = new ImageFile($derivative_uri);
+      $uri = $image->getSource();
       $headers += array(
-        'Content-Type' => $image->info['mime_type'],
-        'Content-Length' => $image->info['file_size'],
+        'Content-Type' => $image->get('mime_type'),
+        'Content-Length' => $image->get('file_size'),
       );
       return new BinaryFileResponse($uri, 200, $headers);
     }
@@ -312,7 +364,7 @@ public function flush($path = NULL) {
       if (file_exists($derivative_uri)) {
         file_unmanaged_delete($derivative_uri);
       }
-      return;
+      return $this;
     }
 
     // Delete the style directory in each registered wrapper.
@@ -322,17 +374,19 @@ public function flush($path = NULL) {
     }
 
     // Let other modules update as necessary on flush.
-    \Drupal::moduleHandler()->invokeAll('image_style_flush', array($this));
+    $module_handler = \Drupal::moduleHandler();
+    $module_handler->invokeAll('image_style_flush', array($this));
 
     // Clear field caches so that formatters may be added for this style.
     field_info_cache_clear();
     drupal_theme_rebuild();
 
     // Clear page caches when flushing.
-    if (\Drupal::moduleHandler()->moduleExists('block')) {
-      cache('block')->deleteAll();
+    if ($module_handler->moduleExists('block')) {
+      \Drupal::cache('block')->deleteAll();
     }
-    cache('page')->deleteAll();
+    \Drupal::cache('page')->deleteAll();
+    return $this;
   }
 
   /**
@@ -348,17 +402,16 @@ public function createDerivative($original_uri, $derivative_uri) {
       return FALSE;
     }
 
-    if (!$image = image_load($original_uri)) {
+    $image = new ImageFile($original_uri);
+    if (!$image->getResource()) {
       return FALSE;
     }
 
-    if (!empty($this->effects)) {
-      foreach ($this->effects as $effect) {
-        image_effect_apply($image, $effect);
-      }
+    foreach ($this->getEffects() as $effect) {
+      $effect->applyEffect($image);
     }
 
-    if (!image_save($image, $derivative_uri)) {
+    if (!$image->save($derivative_uri)) {
       if (file_exists($derivative_uri)) {
         watchdog('image', 'Cached image file %destination already exists. There may be an issue with your rewrite configuration.', array('%destination' => $derivative_uri), WATCHDOG_ERROR);
       }
@@ -372,21 +425,8 @@ public function createDerivative($original_uri, $derivative_uri) {
    * {@inheritdoc}
    */
   public function transformDimensions(array &$dimensions) {
-    module_load_include('inc', 'image', 'image.effects');
-
-    if (!empty($this->effects)) {
-      foreach ($this->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;
-        }
-      }
+    foreach ($this->getEffects() as $effect) {
+      $effect->transformDimensions($dimensions);
     }
   }
 
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..d8d7766
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php
@@ -0,0 +1,85 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Plugin\ImageEffect\CropImageEffect.
+ */
+
+namespace Drupal\image\Plugin\ImageEffect;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageFile;
+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 applyEffect(ImageFile $image) {
+    // Set sane default values.
+    $this->configuration += array(
+      'anchor' => 'center-center',
+    );
+
+    list($x, $y) = explode('-', $this->configuration['anchor']);
+    $x = image_filter_keyword($x, $image->get('width'), $this->configuration['width']);
+    $y = image_filter_keyword($y, $image->get('height'), $this->configuration['height']);
+    if (!$image->crop($x, $y, $this->configuration['width'], $this->configuration['height'])) {
+      watchdog('image', 'Image crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->get('mime_type'), '%dimensions' => $image->get('width') . 'x' . $image->get('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..8ffc071
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Plugin\ImageEffect\DesaturateImageEffect.
+ */
+
+namespace Drupal\image\Plugin\ImageEffect;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageFile;
+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
+ * )
+ */
+class DesaturateImageEffect extends ImageEffectBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transformDimensions(array &$dimensions) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applyEffect(ImageFile $image) {
+    if (!$image->desaturate()) {
+      watchdog('image', 'Image desaturate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->get('mime_type'), '%dimensions' => $image->get('width') . 'x' . $image->get('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..d80e0ce
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Plugin\ImageEffect\Resize.
+ */
+
+namespace Drupal\image\Plugin\ImageEffect;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageFile;
+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 applyEffect(ImageFile $image) {
+    if (!$image->resize($this->configuration['width'], $this->configuration['height'])) {
+      watchdog('image', 'Image resize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->get('mime_type'), '%dimensions' => $image->get('width') . 'x' . $image->get('height')), WATCHDOG_ERROR);
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transformDimensions(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..8d1ca17
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php
@@ -0,0 +1,132 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Plugin\ImageEffect\RotateImageEffect.
+ */
+
+namespace Drupal\image\Plugin\ImageEffect;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageFile;
+use Drupal\image\Annotation\ImageEffect;
+use Drupal\image\ImageEffectBase;
+
+/**
+ * Rotates an image resource.
+ *
+ * @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 applyEffect(ImageFile $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($this->configuration['degrees'], $this->configuration['bgcolor'])) {
+      watchdog('image', 'Image rotate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->get('mime_type'), '%dimensions' => $image->get('width') . 'x' . $image->get('height')), WATCHDOG_ERROR);
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transformDimensions(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..eff9b11
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\Plugin\ImageEffect\ScaleAndCropImageEffect.
+ */
+
+namespace Drupal\image\Plugin\ImageEffect;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageFile;
+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 applyEffect(ImageFile $image) {
+    if (!$image->scaleAndCrop($this->configuration['width'], $this->configuration['height'])) {
+      watchdog('image', 'Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->get('mime_type'), '%dimensions' => $image->get('width') . 'x' . $image->get('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..72e9646
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php
@@ -0,0 +1,89 @@
+<?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\Core\Image\ImageFile;
+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 applyEffect(ImageFile $image) {
+    // Set sane default values.
+    $this->configuration += array(
+      'width' => NULL,
+      'height' => NULL,
+      'upscale' => FALSE,
+    );
+
+    if (!$image->scale($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->getToolkitId(), '%path' => $image->getSource(), '%mimetype' => $image->get('mime_type'), '%dimensions' => $image->get('width') . 'x' . $image->get('height')), WATCHDOG_ERROR);
+      return FALSE;
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transformDimensions(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 88eb3ad..f34b246 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php
@@ -129,23 +129,23 @@ function testStyle() {
 
     // Confirm that all effects on the image style have settings on the effect
     // edit form that match what was saved.
-    $ieids = array();
-    foreach ($style->effects as $ieid => $effect) {
-      // Store the ieid for later use.
-      $ieids[$effect['name']] = $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)));
+    $uuids = array();
+    foreach ($style->getEffects() as $uuid => $effect) {
+      // Store the uuid for later use.
+      $uuids[$effect->getPluginId()] = $uuid;
+      $this->drupalGet($style_path . '/effects/' . $uuid);
+      foreach ($effect_edits[$effect->getPluginId()] 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->getPluginId(), '%value' => $value)));
       }
     }
 
     // Assert that every effect was saved.
     foreach (array_keys($effect_edits) as $effect_name) {
-      $this->assertTrue(isset($ieids[$effect_name]), format_string(
-        'A %effect_name effect was saved with ID %ieid',
+      $this->assertTrue(isset($uuids[$effect_name]), format_string(
+        'A %effect_name effect was saved with ID %uuid',
         array(
           '%effect_name' => $effect_name,
-          '%ieid' => $ieids[$effect_name],
+          '%uuid' => $uuids[$effect_name],
         )));
     }
 
@@ -154,12 +154,13 @@ function testStyle() {
     // Confirm the order of effects is maintained according to the order we
     // added the fields.
     $effect_edits_order = array_keys($effect_edits);
-    $effects_order = array_values($style->effects);
     $order_correct = TRUE;
-    foreach ($effects_order as $index => $effect) {
-      if ($effect_edits_order[$index] != $effect['name']) {
+    $index = 0;
+    foreach ($style->getEffects()->sort() as $effect) {
+      if ($effect_edits_order[$index] != $effect->getPluginId()) {
         $order_correct = FALSE;
       }
+      $index++;
     }
     $this->assertTrue($order_correct, 'The order of the effects is correctly set by default.');
 
@@ -172,8 +173,8 @@ function testStyle() {
       'name' => $style_name,
       'label' => $style_label,
     );
-    foreach ($style->effects as $ieid => $effect) {
-      $edit['effects[' . $ieid . '][weight]'] = $weight;
+    foreach ($style->getEffects() as $uuid => $effect) {
+      $edit['effects[' . $uuid . '][weight]'] = $weight;
       $weight--;
     }
 
@@ -200,12 +201,13 @@ function testStyle() {
 
     // Confirm the new style order was saved.
     $effect_edits_order = array_reverse($effect_edits_order);
-    $effects_order = array_values($style->effects);
     $order_correct = TRUE;
-    foreach ($effects_order as $index => $effect) {
-      if ($effect_edits_order[$index] != $effect['name']) {
+    $index = 0;
+    foreach ($style->getEffects()->sort() as $effect) {
+      if ($effect_edits_order[$index] != $effect->getPluginId()) {
         $order_correct = FALSE;
       }
+      $index++;
     }
     $this->assertTrue($order_correct, 'The order of the effects is correctly set by default.');
 
@@ -216,19 +218,20 @@ function testStyle() {
     $this->assertEqual($this->getImageCount($style), 1, format_string('Image style %style image %file successfully generated.', array('%style' => $style->label(), '%file' => $image_path)));
 
     // Delete the 'image_crop' effect from the style.
-    $this->drupalPost($style_path . '/effects/' . $ieids['image_crop'] . '/delete', array(), t('Delete'));
+    $this->drupalPost($style_path . '/effects/' . $uuids['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 = $style->getEffect($uuids['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');
+    $this->assertNoLinkByHref($style_path . '/effects/' . $uuids['image_crop'] . '/delete');
     // Refresh the image style information and verify that the effect was
     // actually deleted.
     $style = entity_load_unchanged('image_style', $style->id());
-    $this->assertFalse(isset($style->effects[$ieids['image_crop']]), format_string(
-      'Effect with ID %ieid no longer found on image style %style',
+    $this->assertFalse($style->getEffects()->has($uuids['image_crop']), format_string(
+      'Effect with ID %uuid no longer found on image style %style',
       array(
-        '%ieid' => $ieids['image_crop'],
+        '%uuid' => $uuids['image_crop'],
         '%style' => $style->label,
       )));
 
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php
index 8a08dee..da013be 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\image\Tests;
 
+use Drupal\Core\Image\ImageFile;
 use Drupal\simpletest\WebTestBase;
 
 /**
@@ -53,13 +54,13 @@ function testImageDimensions() {
       'height' => 20,
     );
     // Verify that the original image matches the hard-coded values.
-    $image_info = image_get_info($original_uri);
-    $this->assertEqual($image_info['width'], $variables['width']);
-    $this->assertEqual($image_info['height'], $variables['height']);
+    $image_file = new ImageFile($original_uri);
+    $this->assertEqual($image_file->get('width'), $variables['width']);
+    $this->assertEqual($image_file->get('height'), $variables['height']);
 
     // Scale an image that is wider than it is high.
     $effect = array(
-      'name' => 'image_scale',
+      'id' => 'image_scale',
       'data' => array(
         'width' => 120,
         'height' => 90,
@@ -68,20 +69,20 @@ function testImageDimensions() {
       'weight' => 0,
     );
 
-    image_effect_save($style, $effect);
+    $style->saveImageEffect($effect);
     $img_tag = theme_image_style($variables);
     $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" width="120" height="60" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
     $this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
-    $image_info = image_get_info($generated_uri);
-    $this->assertEqual($image_info['width'], 120);
-    $this->assertEqual($image_info['height'], 60);
+    $image_file = new ImageFile($generated_uri);
+    $this->assertEqual($image_file->get('width'), 120);
+    $this->assertEqual($image_file->get('height'), 60);
 
     // Rotate 90 degrees anticlockwise.
     $effect = array(
-      'name' => 'image_rotate',
+      'id' => 'image_rotate',
       'data' => array(
         'degrees' => -90,
         'random' => FALSE,
@@ -89,20 +90,20 @@ function testImageDimensions() {
       'weight' => 1,
     );
 
-    image_effect_save($style, $effect);
+    $style->saveImageEffect($effect);
     $img_tag = theme_image_style($variables);
     $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" width="60" height="120" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
     $this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
-    $image_info = image_get_info($generated_uri);
-    $this->assertEqual($image_info['width'], 60);
-    $this->assertEqual($image_info['height'], 120);
+    $image_file = new ImageFile($generated_uri);
+    $this->assertEqual($image_file->get('width'), 60);
+    $this->assertEqual($image_file->get('height'), 120);
 
     // 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,
@@ -111,20 +112,20 @@ function testImageDimensions() {
       'weight' => 2,
     );
 
-    image_effect_save($style, $effect);
+    $style->saveImageEffect($effect);
     $img_tag = theme_image_style($variables);
     $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" width="45" height="90" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
     $this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
-    $image_info = image_get_info($generated_uri);
-    $this->assertEqual($image_info['width'], 45);
-    $this->assertEqual($image_info['height'], 90);
+    $image_file = new ImageFile($generated_uri);
+    $this->assertEqual($image_file->get('width'), 45);
+    $this->assertEqual($image_file->get('height'), 90);
 
     // Test upscale disabled.
     $effect = array(
-      'name' => 'image_scale',
+      'id' => 'image_scale',
       'data' => array(
         'width' => 400,
         'height' => 200,
@@ -133,38 +134,38 @@ function testImageDimensions() {
       'weight' => 3,
     );
 
-    image_effect_save($style, $effect);
+    $style->saveImageEffect($effect);
     $img_tag = theme_image_style($variables);
     $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" width="45" height="90" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
     $this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
-    $image_info = image_get_info($generated_uri);
-    $this->assertEqual($image_info['width'], 45);
-    $this->assertEqual($image_info['height'], 90);
+    $image_file = new ImageFile($generated_uri);
+    $this->assertEqual($image_file->get('width'), 45);
+    $this->assertEqual($image_file->get('height'), 90);
 
     // Add a desaturate effect.
     $effect = array(
-      'name' => 'image_desaturate',
+      'id' => 'image_desaturate',
       'data' => array(),
       'weight' => 4,
     );
 
-    image_effect_save($style, $effect);
+    $style->saveImageEffect($effect);
     $img_tag = theme_image_style($variables);
     $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" width="45" height="90" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
     $this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
-    $image_info = image_get_info($generated_uri);
-    $this->assertEqual($image_info['width'], 45);
-    $this->assertEqual($image_info['height'], 90);
+    $image_file = new ImageFile($generated_uri);
+    $this->assertEqual($image_file->get('width'), 45);
+    $this->assertEqual($image_file->get('height'), 90);
 
     // Add a random rotate effect.
     $effect = array(
-      'name' => 'image_rotate',
+      'id' => 'image_rotate',
       'data' => array(
         'degrees' => 180,
         'random' => TRUE,
@@ -172,7 +173,7 @@ function testImageDimensions() {
       'weight' => 5,
     );
 
-    image_effect_save($style, $effect);
+    $style->saveImageEffect($effect);
     $img_tag = theme_image_style($variables);
     $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
@@ -183,7 +184,7 @@ function testImageDimensions() {
 
     // Add a crop effect.
     $effect = array(
-      'name' => 'image_crop',
+      'id' => 'image_crop',
       'data' => array(
         'width' => 30,
         'height' => 30,
@@ -192,20 +193,20 @@ function testImageDimensions() {
       'weight' => 6,
     );
 
-    image_effect_save($style, $effect);
+    $style->saveImageEffect($effect);
     $img_tag = theme_image_style($variables);
     $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" width="30" height="30" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
     $this->drupalGet($url);
     $this->assertResponse(200, 'Image was generated at the URL.');
     $this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
-    $image_info = image_get_info($generated_uri);
-    $this->assertEqual($image_info['width'], 30);
-    $this->assertEqual($image_info['height'], 30);
+    $image_file = new ImageFile($generated_uri);
+    $this->assertEqual($image_file->get('width'), 30);
+    $this->assertEqual($image_file->get('height'), 30);
 
     // Rotate to a non-multiple of 90 degrees.
     $effect = array(
-      'name' => 'image_rotate',
+      'id' => 'image_rotate',
       'data' => array(
         'degrees' => 57,
         'random' => FALSE,
@@ -213,7 +214,7 @@ function testImageDimensions() {
       'weight' => 7,
     );
 
-    image_effect_save($style, $effect);
+    $effect_id = $style->saveImageEffect($effect);
     $img_tag = theme_image_style($variables);
     $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" alt="" />');
     $this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
@@ -221,17 +222,18 @@ 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);
+    $effect_plugin = $style->getEffect($effect_id);
+    $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,
     );
 
-    image_effect_save($style, $effect);
+    $style->saveImageEffect($effect);
     $img_tag = theme_image_style($variables);
     $this->assertEqual($img_tag, '<img class="image-style-test" src="' . $url . '" alt="" />');
   }
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php
index 7f24796..74f41ec 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,32 @@ 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->createInstance($effect_name, array('data' => $data));
+    return $this->assertTrue($effect->applyEffect($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 b1d1716..7d0f054 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldTestBase.php
@@ -20,10 +20,6 @@
  * image.module:
  *   image_style_options()
  *   \Drupal\image\ImageStyleInterface::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/ImageFieldValidateTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldValidateTest.php
index 1cd0ce9..3aa81b9 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldValidateTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldValidateTest.php
@@ -7,6 +7,8 @@
 
 namespace Drupal\image\Tests;
 
+use Drupal\Core\Image\ImageFile;
+
 /**
  * Test class to check for various validations.
  */
@@ -37,11 +39,11 @@ function testResolution() {
     $image_that_is_too_big = FALSE;
     $image_that_is_too_small = FALSE;
     foreach ($this->drupalGetTestFiles('image') as $image) {
-      $info = image_get_info($image->uri);
-      if ($info['width'] > $max_resolution) {
+      $image_file = new ImageFile($image->uri);
+      if ($image_file->get('width') > $max_resolution) {
         $image_that_is_too_big = $image;
       }
-      if ($info['width'] < $min_resolution) {
+      if ($image_file->get('width') < $min_resolution) {
         $image_that_is_too_small = $image;
       }
       if ($image_that_is_too_small && $image_that_is_too_big) {
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageItemTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageItemTest.php
index 5e7472f..8b28fa4 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageItemTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageItemTest.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Entity\Field\FieldInterface;
 use Drupal\Core\Entity\Field\FieldItemInterface;
+use Drupal\Core\Image\ImageFile;
 use Drupal\field\Tests\FieldUnitTestBase;
 
 /**
@@ -78,9 +79,9 @@ public function testImageItem() {
     $this->assertEqual($entity->image_test->target_id, $this->image->id());
     $this->assertEqual($entity->image_test->alt, $alt);
     $this->assertEqual($entity->image_test->title, $title);
-    $info = image_get_info('public://example.jpg');
-    $this->assertEqual($entity->image_test->width, $info['width']);
-    $this->assertEqual($entity->image_test->height, $info['height']);
+    $image = new ImageFile('public://example.jpg');
+    $this->assertEqual($entity->image_test->width, $image->get('width'));
+    $this->assertEqual($entity->image_test->height, $image->get('height'));
     $this->assertEqual($entity->image_test->entity->id(), $this->image->id());
     $this->assertEqual($entity->image_test->entity->uuid(), $this->image->uuid());
 
@@ -98,9 +99,9 @@ public function testImageItem() {
     $entity->save();
     $this->assertEqual($entity->image_test->entity->id(), $image2->id());
     $this->assertEqual($entity->image_test->entity->getFileUri(), $image2->getFileUri());
-    $info = image_get_info('public://example-2.jpg');
-    $this->assertEqual($entity->image_test->width, $info['width']);
-    $this->assertEqual($entity->image_test->height, $info['height']);
+    $image = new ImageFile('public://example-2.jpg');
+    $this->assertEqual($entity->image_test->width, $image->get('width'));
+    $this->assertEqual($entity->image_test->height, $image->get('height'));
     $this->assertEqual($entity->image_test->alt, $new_alt);
 
     // Check that the image item can be set to the referenced file directly.
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php
index 101171b..b5c3776 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageStyleFlushTest.php
@@ -100,11 +100,11 @@ function testFlush() {
     // Remove the 'image_scale' effect and updates the style, which in turn
     // forces an image style flush.
     $style_path = 'admin/config/media/image-styles/manage/' . $style->id();
-    $ieids = array();
-    foreach ($style->effects as $ieid => $effect) {
-      $ieids[$effect['name']] = $ieid;
+    $uuids = array();
+    foreach ($style->getEffects() as $uuid => $effect) {
+      $uuids[$effect->getPluginId()] = $uuid;
     }
-    $this->drupalPost($style_path . '/effects/' . $ieids['image_scale'] . '/delete', array(), t('Delete'));
+    $this->drupalPost($style_path . '/effects/' . $uuids['image_scale'] . '/delete', array(), t('Delete'));
     $this->assertResponse(200);
     $this->drupalPost($style_path, array(), t('Update style'));
     $this->assertResponse(200);
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
index cae45d7..c9be258 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\image\Tests;
 
+use Drupal\Core\Image\ImageFile;
 use Drupal\simpletest\WebTestBase;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -153,9 +154,9 @@ function doImageStyleUrlAndPathTests($scheme, $clean_url = TRUE, $extra_slash =
     $this->assertResponse(200, 'Image was generated at the URL.');
     $this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
     $this->assertRaw(file_get_contents($generated_uri), 'URL returns expected file.');
-    $generated_image_info = image_get_info($generated_uri);
-    $this->assertEqual($this->drupalGetHeader('Content-Type'), $generated_image_info['mime_type'], 'Expected Content-Type was reported.');
-    $this->assertEqual($this->drupalGetHeader('Content-Length'), $generated_image_info['file_size'], 'Expected Content-Length was reported.');
+    $image = new ImageFile($generated_uri);
+    $this->assertEqual($this->drupalGetHeader('Content-Type'), $image->get('mime_type'), 'Expected Content-Type was reported.');
+    $this->assertEqual($this->drupalGetHeader('Content-Length'), $image->get('file_size'), 'Expected Content-Length was reported.');
     if ($scheme == 'private') {
       $this->assertEqual($this->drupalGetHeader('Expires'), 'Sun, 19 Nov 1978 05:00:00 GMT', 'Expires header was sent.');
       $this->assertNotEqual(strpos($this->drupalGetHeader('Cache-Control'), 'no-cache'), FALSE, 'Cache-Control header contains \'no-cache\' to prevent caching.');
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..807439a
--- /dev/null
+++ b/core/modules/image/tests/modules/image_module_test/lib/Drupal/image_module_test/Plugin/ImageEffect/NullTestImageEffect.php
@@ -0,0 +1,33 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image_module_test\Plugin\ImageEffect\NullTestImageEffect.
+ */
+
+namespace Drupal\image_module_test\Plugin\ImageEffect;
+
+use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageFile;
+use Drupal\image\Annotation\ImageEffect;
+use Drupal\image\ImageEffectBase;
+
+/**
+ * Performs no operation on an image resource.
+ *
+ * @ImageEffect(
+ *   id = "image_module_test_null",
+ *   label = @Translation("Image module test"),
+ *   no_form = TRUE
+ * )
+ */
+class NullTestImageEffect extends ImageEffectBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applyEffect(ImageFile $image) {
+    return TRUE;
+  }
+
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
index 06cb4a6..70be018 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Plugin\PluginBase;
 use Drupal\Component\Annotation\Plugin;
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageFile;
 use Drupal\system\Plugin\ImageToolkitInterface;
 
 /**
@@ -50,33 +51,33 @@ public function settingsFormSubmit($form, &$form_state) {
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::resize().
    */
-  public function resize($image, $width, $height) {
+  public function resize(ImageFile $image, $width, $height) {
     $res = $this->createTmp($image, $width, $height);
 
-    if (!imagecopyresampled($res, $image->resource, 0, 0, 0, 0, $width, $height, $image->info['width'], $image->info['height'])) {
+    if (!imagecopyresampled($res, $image->getResource(), 0, 0, 0, 0, $width, $height, $image->get('width'), $image->get('height'))) {
       return FALSE;
     }
 
-    imagedestroy($image->resource);
+    imagedestroy($image->getResource());
     // Update image object.
-    $image->resource = $res;
-    $image->info['width'] = $width;
-    $image->info['height'] = $height;
+    $image->setResource($res);
+    $image->set('width', $width);
+    $image->set('height', $height);
     return TRUE;
   }
 
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::rotate().
    */
-  public function rotate($image, $degrees, $background = NULL) {
+  public function rotate(ImageFile $image, $degrees, $background = NULL) {
     // PHP installations using non-bundled GD do not have imagerotate.
     if (!function_exists('imagerotate')) {
-      watchdog('image', 'The image %file could not be rotated because the imagerotate() function is not available in this PHP installation.', array('%file' => $image->source));
+      watchdog('image', 'The image %file could not be rotated because the imagerotate() function is not available in this PHP installation.', array('%file' => $image->getSource()));
       return FALSE;
     }
 
-    $width = $image->info['width'];
-    $height = $image->info['height'];
+    $width = $image->get('width');
+    $height = $image->get('height');
 
     // Convert the hexadecimal background value to a color index value.
     if (isset($background)) {
@@ -84,88 +85,89 @@ public function rotate($image, $degrees, $background = NULL) {
       for ($i = 16; $i >= 0; $i -= 8) {
         $rgb[] = (($background >> $i) & 0xFF);
       }
-      $background = imagecolorallocatealpha($image->resource, $rgb[0], $rgb[1], $rgb[2], 0);
+      $background = imagecolorallocatealpha($image->getResource(), $rgb[0], $rgb[1], $rgb[2], 0);
     }
     // Set the background color as transparent if $background is NULL.
     else {
       // Get the current transparent color.
-      $background = imagecolortransparent($image->resource);
+      $background = imagecolortransparent($image->getResource());
 
       // If no transparent colors, use white.
       if ($background == 0) {
-        $background = imagecolorallocatealpha($image->resource, 255, 255, 255, 0);
+        $background = imagecolorallocatealpha($image->getResource(), 255, 255, 255, 0);
       }
     }
 
     // Images are assigned a new color palette when rotating, removing any
     // transparency flags. For GIF images, keep a record of the transparent color.
-    if ($image->info['extension'] == 'gif') {
-      $transparent_index = imagecolortransparent($image->resource);
+    if ($image->get('extension') == 'gif') {
+      $transparent_index = imagecolortransparent($image->getResource());
       if ($transparent_index != 0) {
-        $transparent_gif_color = imagecolorsforindex($image->resource, $transparent_index);
+        $transparent_gif_color = imagecolorsforindex($image->getResource(), $transparent_index);
       }
     }
 
-    $image->resource = imagerotate($image->resource, 360 - $degrees, $background);
+    $image->setResource(imagerotate($image->getResource(), 360 - $degrees, $background));
 
     // GIFs need to reassign the transparent color after performing the rotate.
     if (isset($transparent_gif_color)) {
-      $background = imagecolorexactalpha($image->resource, $transparent_gif_color['red'], $transparent_gif_color['green'], $transparent_gif_color['blue'], $transparent_gif_color['alpha']);
-      imagecolortransparent($image->resource, $background);
+      $background = imagecolorexactalpha($image->getResource(), $transparent_gif_color['red'], $transparent_gif_color['green'], $transparent_gif_color['blue'], $transparent_gif_color['alpha']);
+      imagecolortransparent($image->getResource(), $background);
     }
 
-    $image->info['width'] = imagesx($image->resource);
-    $image->info['height'] = imagesy($image->resource);
+    $image->set('width', imagesx($image->getResource()));
+    $image->set('height', imagesy($image->getResource()));
     return TRUE;
   }
 
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::crop().
    */
-  public function crop($image, $x, $y, $width, $height) {
+  public function crop(ImageFile $image, $x, $y, $width, $height) {
     $res = $this->createTmp($image, $width, $height);
 
-    if (!imagecopyresampled($res, $image->resource, 0, 0, $x, $y, $width, $height, $width, $height)) {
+    if (!imagecopyresampled($res, $image->getResource(), 0, 0, $x, $y, $width, $height, $width, $height)) {
       return FALSE;
     }
 
     // Destroy the original image and return the modified image.
-    imagedestroy($image->resource);
-    $image->resource = $res;
-    $image->info['width'] = $width;
-    $image->info['height'] = $height;
+    imagedestroy($image->getResource());
+    $image->setResource($res);
+    $image->set('width', $width);
+    $image->set('height', $height);
     return TRUE;
   }
 
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::desaturate().
    */
-  public function desaturate($image) {
+  public function desaturate(ImageFile $image) {
     // PHP installations using non-bundled GD do not have imagefilter.
     if (!function_exists('imagefilter')) {
-      watchdog('image', 'The image %file could not be desaturated because the imagefilter() function is not available in this PHP installation.', array('%file' => $image->source));
+      watchdog('image', 'The image %file could not be desaturated because the imagefilter() function is not available in this PHP installation.', array('%file' => $image->getSource()));
       return FALSE;
     }
 
-    return imagefilter($image->resource, IMG_FILTER_GRAYSCALE);
+    return imagefilter($image->getResource(), IMG_FILTER_GRAYSCALE);
   }
 
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::load().
    */
-  public function load($image) {
-    $extension = str_replace('jpg', 'jpeg', $image->info['extension']);
+  public function load(ImageFile $image) {
+    $extension = str_replace('jpg', 'jpeg', $image->get('extension'));
     $function = 'imagecreatefrom' . $extension;
-    if (function_exists($function) && $image->resource = $function($image->source)) {
-      if (!imageistruecolor($image->resource)) {
+    if (function_exists($function) && $resource = $function($image->getSource())) {
+      $image->setResource($resource);
+      if (!imageistruecolor($resource)) {
         // Convert indexed images to true color, so that filters work
         // correctly and don't result in unnecessary dither.
-        $new_image = $this->createTmp($image, $image->info['width'], $image->info['height']);
-        imagecopy($new_image, $image->resource, 0, 0, 0, 0, $image->info['width'], $image->info['height']);
-        imagedestroy($image->resource);
-        $image->resource = $new_image;
+        $new_image = $this->createTmp($image, $image->get('width'), $image->get('height'));
+        imagecopy($new_image, $resource, 0, 0, 0, 0, $image->get('width'), $image->get('height'));
+        imagedestroy($resource);
+        $image->setResource($new_image);
       }
-      return (bool) $image->resource;
+      return (bool) $image->getResource();
     }
 
     return FALSE;
@@ -174,7 +176,7 @@ public function load($image) {
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::save().
    */
-  public function save($image, $destination) {
+  public function save(ImageFile $image, $destination) {
     $scheme = file_uri_scheme($destination);
     // Work around lack of stream wrapper support in imagejpeg() and imagepng().
     if ($scheme && file_stream_wrapper_valid_scheme($scheme)) {
@@ -188,21 +190,21 @@ public function save($image, $destination) {
       $destination = drupal_realpath($destination);
     }
 
-    $extension = str_replace('jpg', 'jpeg', $image->info['extension']);
+    $extension = str_replace('jpg', 'jpeg', $image->get('extension'));
     $function = 'image' . $extension;
     if (!function_exists($function)) {
       return FALSE;
     }
     if ($extension == 'jpeg') {
-      $success = $function($image->resource, $destination, config('system.image.gd')->get('jpeg_quality'));
+      $success = $function($image->getResource(), $destination, config('system.image.gd')->get('jpeg_quality'));
     }
     else {
       // Always save PNG images with full transparency.
       if ($extension == 'png') {
-        imagealphablending($image->resource, FALSE);
-        imagesavealpha($image->resource, TRUE);
+        imagealphablending($image->getResource(), FALSE);
+        imagesavealpha($image->getResource(), TRUE);
       }
-      $success = $function($image->resource, $destination);
+      $success = $function($image->getResource(), $destination);
     }
     // Move temporary local file to remote destination.
     if (isset($permanent_destination) && $success) {
@@ -214,9 +216,9 @@ public function save($image, $destination) {
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::getInfo().
    */
-  public function getInfo($image) {
+  public function getInfo(ImageFile $image) {
     $details = FALSE;
-    $data = getimagesize($image->source);
+    $data = getimagesize($image->getSource());
 
     if (isset($data) && is_array($data)) {
       $extensions = array('1' => 'gif', '2' => 'jpg', '3' => 'png');
@@ -248,13 +250,13 @@ public function getInfo($image) {
   public function createTmp($image, $width, $height) {
     $res = imagecreatetruecolor($width, $height);
 
-    if ($image->info['extension'] == 'gif') {
+    if ($image->get('extension') == 'gif') {
       // Grab transparent color index from image resource.
-      $transparent = imagecolortransparent($image->resource);
+      $transparent = imagecolortransparent($image->getResource());
 
       if ($transparent >= 0) {
         // The original must have a transparent color, allocate to the new image.
-        $transparent_color = imagecolorsforindex($image->resource, $transparent);
+        $transparent_color = imagecolorsforindex($image->getResource(), $transparent);
         $transparent = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
 
         // Flood with our new transparent color.
@@ -262,7 +264,7 @@ public function createTmp($image, $width, $height) {
         imagecolortransparent($res, $transparent);
       }
     }
-    elseif ($image->info['extension'] == 'png') {
+    elseif ($image->get('extension') == 'png') {
       imagealphablending($res, FALSE);
       $transparency = imagecolorallocatealpha($res, 0, 0, 0, 127);
       imagefill($res, 0, 0, $transparency);
diff --git a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php
index 0b22f3c..e7b9dad 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php
@@ -7,13 +7,43 @@
 
 namespace Drupal\system\Plugin;
 
+use Drupal\Component\Plugin\PluginInspectionInterface;
+use Drupal\Core\Image\ImageFile;
+
+/**
+ * @defgroup image Image toolkits
+ * @{
+ * Functions for image file manipulations.
+ *
+ * Drupal's image toolkits provide an abstraction layer for common image file
+ * manipulations like scaling, cropping, and rotating. The abstraction frees
+ * module authors from the need to support multiple image libraries, and it
+ * allows site administrators to choose the library that's best for them.
+ *
+ * PHP includes the GD library by default so a GD toolkit is installed with
+ * Drupal. Other toolkits like ImageMagick are available from contrib modules.
+ * GD works well for small images, but using it with larger files may cause PHP
+ * to run out of memory. In contrast the ImageMagick library does not suffer
+ * from this problem, but it requires the ISP to have installed additional
+ * software.
+ *
+ * Image toolkits are discovered using the Plugin system using
+ * \Drupal\system\Plugin\ImageToolkitManager. The toolkit must then be enabled
+ * using the admin/config/media/image-toolkit form.
+ *
+ * Only one toolkit may be selected at a time. If a module author wishes to call
+ * a specific toolkit they can check that it is installed by calling
+ * \Drupal\system\Plugin\ImageToolkitManager::getAvailableToolkits(), and then
+ * calling its functions directly.
+ */
+
 /**
  * Defines an interface for image toolkits.
  *
  * An image toolkit provides common image file manipulations like scaling,
  * cropping, and rotating.
  */
-interface ImageToolkitInterface {
+interface ImageToolkitInterface extends PluginInspectionInterface {
 
   /**
    * Retrieves toolkit's settings form.
@@ -32,7 +62,7 @@ function settingsFormSubmit($form, &$form_state);
   /**
    * Scales an image to the specified size.
    *
-   * @param object $image
+   * @param \Drupal\Core\Image\ImageFile $image
    *   An image object. The $image->resource, $image->info['width'], and
    *   $image->info['height'] values will be modified by this call.
    * @param int $width
@@ -42,15 +72,13 @@ function settingsFormSubmit($form, &$form_state);
    *
    * @return bool
    *   TRUE or FALSE, based on success.
-   *
-   * @see image_resize()
    */
-  function resize($image, $width, $height);
+  function resize(ImageFile $image, $width, $height);
 
   /**
    * Rotates an image the given number of degrees.
    *
-   * @param object $image
+   * @param \Drupal\Core\Image\ImageFile $image
    *   An image object. The $image->resource, $image->info['width'], and
    *   $image->info['height'] values will be modified by this call.
    * @param int $degrees
@@ -64,15 +92,13 @@ function resize($image, $width, $height);
    *
    * @return bool
    *   TRUE or FALSE, based on success.
-   *
-   * @see image_rotate()
    */
-  function rotate($image, $degrees, $background = NULL);
+  function rotate(ImageFile $image, $degrees, $background = NULL);
 
   /**
    * Crops an image.
    *
-   * @param object $image
+   * @param \Drupal\Core\Image\ImageFile $image
    *   An image object. The $image->resource, $image->info['width'], and
    *   $image->info['height'] values will be modified by this call.
    * @param int $x
@@ -89,56 +115,50 @@ function rotate($image, $degrees, $background = NULL);
    *
    * @see image_crop()
    */
-  function crop($image, $x, $y, $width, $height);
+  function crop(ImageFile $image, $x, $y, $width, $height);
 
   /**
    * Converts an image resource to grayscale.
    *
    * Note that transparent GIFs loose transparency when desaturated.
    *
-   * @param object $image
+   * @param \Drupal\Core\Image\ImageFile $image
    *   An image object. The $image->resource value will be modified by this
    *   call.
    *
    * @return bool
    *   TRUE or FALSE, based on success.
-   *
-   * @see image_desaturate()
    */
-  function desaturate($image);
+  function desaturate(ImageFile $image);
 
   /**
    * Creates an image resource from a file.
    *
-   * @param object $image
+   * @param \Drupal\Core\Image\ImageFile $image
    *   An image object. The $image->resource value will populated by this call.
    *
    * @return bool
    *   TRUE or FALSE, based on success.
-   *
-   * @see image_load()
    */
-  function load($image);
+  function load(ImageFile $image);
 
   /**
    * Writes an image resource to a destination file.
    *
-   * @param object $image
+   * @param \Drupal\Core\Image\ImageFile $image
    *   An image object.
    * @param string $destination
    *   A string file URI or path where the image should be saved.
    *
    * @return bool
    *   TRUE or FALSE, based on success.
-   *
-   * @see image_save()
    */
-  function save($image, $destination);
+  function save(ImageFile $image, $destination);
 
   /**
    * Gets details about an image.
    *
-   * @param object $image
+   * @param \Drupal\Core\Image\ImageFile $image
    *   An image object.
    *
    * @return array
@@ -149,9 +169,9 @@ function save($image, $destination);
    *   - "extension": Commonly used file extension for the image.
    *   - "mime_type": MIME type ('image/jpeg', 'image/gif', 'image/png').
    *
-   * @see image_get_info()
+   * @see \Drupal\Core\Image\ImageFile::getInfo()
    */
-  function getInfo($image);
+  function getInfo(ImageFile $image);
 
   /**
    * Verifies Image Toolkit is set up correctly.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
index 624dc79..fb03c41 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Image;
 
+use Drupal\Core\Image\ImageFile;
 use Drupal\simpletest\DrupalUnitTestBase;
 use Drupal\system\Plugin\ImageToolkitManager;
 
@@ -79,15 +80,15 @@ function colorsAreEqual($color_a, $color_b) {
   /**
    * Function for finding a pixel's RGBa values.
    */
-  function getPixelColor($image, $x, $y) {
-    $color_index = imagecolorat($image->resource, $x, $y);
+  function getPixelColor(ImageFile $image, $x, $y) {
+    $color_index = imagecolorat($image->getResource(), $x, $y);
 
-    $transparent_index = imagecolortransparent($image->resource);
+    $transparent_index = imagecolortransparent($image->getResource());
     if ($color_index == $transparent_index) {
       return array(0, 0, 0, 127);
     }
 
-    return array_values(imagecolorsforindex($image->resource, $color_index));
+    return array_values(imagecolorsforindex($image->getResource(), $color_index));
   }
 
   /**
@@ -110,49 +111,49 @@ function testManipulations() {
     // Setup a list of tests to perform on each type.
     $operations = array(
       'resize' => array(
-        'function' => 'resize',
+        'method' => 'resize',
         'arguments' => array(20, 10),
         'width' => 20,
         'height' => 10,
         'corners' => $default_corners,
       ),
       'scale_x' => array(
-        'function' => 'scale',
+        'method' => 'scale',
         'arguments' => array(20, NULL),
         'width' => 20,
         'height' => 10,
         'corners' => $default_corners,
       ),
       'scale_y' => array(
-        'function' => 'scale',
+        'method' => 'scale',
         'arguments' => array(NULL, 10),
         'width' => 20,
         'height' => 10,
         'corners' => $default_corners,
       ),
       'upscale_x' => array(
-        'function' => 'scale',
+        'method' => 'scale',
         'arguments' => array(80, NULL, TRUE),
         'width' => 80,
         'height' => 40,
         'corners' => $default_corners,
       ),
       'upscale_y' => array(
-        'function' => 'scale',
+        'method' => 'scale',
         'arguments' => array(NULL, 40, TRUE),
         'width' => 80,
         'height' => 40,
         'corners' => $default_corners,
       ),
       'crop' => array(
-        'function' => 'crop',
+        'method' => 'crop',
         'arguments' => array(12, 4, 16, 12),
         'width' => 16,
         'height' => 12,
         'corners' => array_fill(0, 4, $this->white),
       ),
       'scale_and_crop' => array(
-        'function' => 'scale_and_crop',
+        'method' => 'scaleAndCrop',
         'arguments' => array(10, 8),
         'width' => 10,
         'height' => 8,
@@ -164,28 +165,28 @@ function testManipulations() {
     if (function_exists('imagerotate')) {
       $operations += array(
         'rotate_5' => array(
-          'function' => 'rotate',
+          'method' => 'rotate',
           'arguments' => array(5, 0xFF00FF), // Fuchsia background.
           'width' => 42,
           'height' => 24,
           'corners' => array_fill(0, 4, $this->fuchsia),
         ),
         'rotate_90' => array(
-          'function' => 'rotate',
+          'method' => 'rotate',
           'arguments' => array(90, 0xFF00FF), // Fuchsia background.
           'width' => 20,
           'height' => 40,
           'corners' => array($this->transparent, $this->red, $this->green, $this->blue),
         ),
         'rotate_transparent_5' => array(
-          'function' => 'rotate',
+          'method' => 'rotate',
           'arguments' => array(5),
           'width' => 42,
           'height' => 24,
           'corners' => array_fill(0, 4, $this->transparent),
         ),
         'rotate_transparent_90' => array(
-          'function' => 'rotate',
+          'method' => 'rotate',
           'arguments' => array(90),
           'width' => 20,
           'height' => 40,
@@ -198,7 +199,7 @@ function testManipulations() {
     if (function_exists('imagefilter')) {
       $operations += array(
         'desaturate' => array(
-          'function' => 'desaturate',
+          'method' => 'desaturate',
           'arguments' => array(),
           'height' => 20,
           'width' => 40,
@@ -219,17 +220,17 @@ function testManipulations() {
     foreach ($files as $file) {
       foreach ($operations as $op => $values) {
         // Load up a fresh image.
-        $image = image_load(drupal_get_path('module', 'simpletest') . '/files/' . $file, $manager->createInstance('gd'));
+        $image = new ImageFile(drupal_get_path('module', 'simpletest') . '/files/' . $file, $manager->createInstance('gd'));
         if (!$image) {
           $this->fail(t('Could not load image %file.', array('%file' => $file)));
           continue 2;
         }
 
         // All images should be converted to truecolor when loaded.
-        $image_truecolor = imageistruecolor($image->resource);
+        $image_truecolor = imageistruecolor($image->getResource());
         $this->assertTrue($image_truecolor, format_string('Image %file after load is a truecolor image.', array('%file' => $file)));
 
-        if ($image->info['extension'] == 'gif') {
+        if ($image->get('extension') == 'gif') {
           if ($op == 'desaturate') {
             // Transparent GIFs and the imagefilter function don't work together.
             $values['corners'][3][3] = 0;
@@ -237,11 +238,7 @@ function testManipulations() {
         }
 
         // Perform our operation.
-        $function = 'image_' . $values['function'];
-        $arguments = array();
-        $arguments[] = &$image;
-        $arguments = array_merge($arguments, $values['arguments']);
-        call_user_func_array($function, $arguments);
+        call_user_func_array(array($image, $values['method']), $values['arguments']);
 
         // To keep from flooding the test with assert values, make a general
         // value for whether each group of values fail.
@@ -250,24 +247,24 @@ function testManipulations() {
         $correct_colors = TRUE;
 
         // Check the real dimensions of the image first.
-        if (imagesy($image->resource) != $values['height'] || imagesx($image->resource) != $values['width']) {
+        if (imagesy($image->getResource()) != $values['height'] || imagesx($image->getResource()) != $values['width']) {
           $correct_dimensions_real = FALSE;
         }
 
         // Check that the image object has an accurate record of the dimensions.
-        if ($image->info['width'] != $values['width'] || $image->info['height'] != $values['height']) {
+        if ($image->get('width') != $values['width'] || $image->get('height') != $values['height']) {
           $correct_dimensions_object = FALSE;
         }
 
         $directory = $this->public_files_directory .'/imagetest';
         file_prepare_directory($directory, FILE_CREATE_DIRECTORY);
-        image_save($image, $directory . '/' . $op . '.' . $image->info['extension']);
+        $image->save($directory . '/' . $op . '.' . $image->get('extension'));
 
         $this->assertTrue($correct_dimensions_real, format_string('Image %file after %action action has proper dimensions.', array('%file' => $file, '%action' => $op)));
         $this->assertTrue($correct_dimensions_object, format_string('Image %file object after %action action is reporting the proper height and width values.', array('%file' => $file, '%action' => $op)));
 
         // JPEG colors will always be messed up due to compression.
-        if ($image->info['extension'] != 'jpg') {
+        if ($image->get('extension') != 'jpg') {
           // Now check each of the corners to ensure color correctness.
           foreach ($values['corners'] as $key => $corner) {
             // Get the location of the corner.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php
index 14a84e9..8eae39f 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php
@@ -7,10 +7,11 @@
 
 namespace Drupal\system\Tests\Image;
 
+use Drupal\Core\Image\ImageFile;
 use Drupal\system\Plugin\ImageToolkitManager;
 
 /**
- * Test that the functions in image.inc correctly pass data to the toolkit.
+ * Tests that the methods in ImageFile correctly pass data to the toolkit.
  */
 class ToolkitTest extends ToolkitTestBase {
   public static function getInfo() {
@@ -37,9 +38,10 @@ function testGetAvailableToolkits() {
    * Test the image_load() function.
    */
   function testLoad() {
-    $image = image_load($this->file, $this->toolkit);
+    $image = new ImageFile($this->file);
+    $image->setToolkit($this->toolkit)->getResource();
     $this->assertTrue(is_object($image), 'Returned an object.');
-    $this->assertEqual($this->toolkit, $image->toolkit, 'Image had toolkit set.');
+    $this->assertEqual($this->toolkit->getPluginId(), $image->getToolkitId(), 'Image had toolkit set.');
     $this->assertToolkitOperationsCalled(array('load', 'get_info'));
   }
 
@@ -47,7 +49,7 @@ function testLoad() {
    * Test the image_save() function.
    */
   function testSave() {
-    $this->assertFalse(image_save($this->image), 'Function returned the expected value.');
+    $this->assertFalse($this->image->save(), 'Function returned the expected value.');
     $this->assertToolkitOperationsCalled(array('save'));
   }
 
@@ -55,7 +57,7 @@ function testSave() {
    * Test the image_resize() function.
    */
   function testResize() {
-    $this->assertTrue(image_resize($this->image, 1, 2), 'Function returned the expected value.');
+    $this->assertTrue($this->image->resize(1, 2), 'Function returned the expected value.');
     $this->assertToolkitOperationsCalled(array('resize'));
 
     // Check the parameters.
@@ -69,7 +71,7 @@ function testResize() {
    */
   function testScale() {
 // TODO: need to test upscaling
-    $this->assertTrue(image_scale($this->image, 10, 10), 'Function returned the expected value.');
+    $this->assertTrue($this->image->scale(10, 10), 'Function returned the expected value.');
     $this->assertToolkitOperationsCalled(array('resize'));
 
     // Check the parameters.
@@ -82,7 +84,7 @@ function testScale() {
    * Test the image_scale_and_crop() function.
    */
   function testScaleAndCrop() {
-    $this->assertTrue(image_scale_and_crop($this->image, 5, 10), 'Function returned the expected value.');
+    $this->assertTrue($this->image->scaleAndCrop(5, 10), 'Function returned the expected value.');
     $this->assertToolkitOperationsCalled(array('resize', 'crop'));
 
     // Check the parameters.
@@ -98,7 +100,7 @@ function testScaleAndCrop() {
    * Test the image_rotate() function.
    */
   function testRotate() {
-    $this->assertTrue(image_rotate($this->image, 90, 1), 'Function returned the expected value.');
+    $this->assertTrue($this->image->rotate(90, 1), 'Function returned the expected value.');
     $this->assertToolkitOperationsCalled(array('rotate'));
 
     // Check the parameters.
@@ -111,7 +113,7 @@ function testRotate() {
    * Test the image_crop() function.
    */
   function testCrop() {
-    $this->assertTrue(image_crop($this->image, 1, 2, 3, 4), 'Function returned the expected value.');
+    $this->assertTrue($this->image->crop(1, 2, 3, 4), 'Function returned the expected value.');
     $this->assertToolkitOperationsCalled(array('crop'));
 
     // Check the parameters.
@@ -126,7 +128,7 @@ function testCrop() {
    * Test the image_desaturate() function.
    */
   function testDesaturate() {
-    $this->assertTrue(image_desaturate($this->image), 'Function returned the expected value.');
+    $this->assertTrue($this->image->desaturate(), 'Function returned the expected value.');
     $this->assertToolkitOperationsCalled(array('desaturate'));
 
     // Check the parameters.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php
index 80b1588..b066601 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\system\Tests\Image;
 
+use Drupal\Core\Image\ImageFile;
 use Drupal\simpletest\WebTestBase;
 use Drupal\system\Plugin\ImageToolkitManager;
 use stdClass;
@@ -23,8 +24,19 @@
    */
   public static $modules = array('image_test');
 
+  /**
+   * @var \Drupal\system\Plugin\ImageToolkitInterface
+   */
   protected $toolkit;
+
+  /**
+   * @var string
+   */
   protected $file;
+
+  /**
+   * @var \Drupal\Core\Image\ImageFile
+   */
   protected $image;
 
   function setUp() {
@@ -40,10 +52,9 @@ function setUp() {
 
     // Setup a dummy image to work with, this replicate image_load() so we
     // can avoid calling it.
-    $this->image = new stdClass();
-    $this->image->source = $this->file;
-    $this->image->info = image_get_info($this->file);
-    $this->image->toolkit = $this->toolkit;
+    $this->image = new ImageFile($this->file);
+    $this->image->getInfo();
+    $this->image->setToolkit($this->toolkit);
 
     // Clear out any hook calls.
     $this->imageTestReset();
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..cc5979a 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']];
-        // Set the missing ieid key as this is unknown because it is a UUID.
-        $style['effects'][$uuid]['ieid'] = $uuid;
+        $style['effects'][$uuid] = $style['effects'][$effect['id']];
+        // Set the missing uuid key as this is unknown because it is a UUID.
+        $style['effects'][$uuid]['uuid'] = $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),
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 1e1bae3..c85dbaf 100644
--- a/core/modules/system/system.api.php
+++ b/core/modules/system/system.api.php
@@ -2150,8 +2150,8 @@ function hook_file_download($uri) {
       return -1;
     }
     else {
-      $info = image_get_info($uri);
-      return array('Content-Type' => $info['mime_type']);
+      $image = new \Drupal\Core\Image\ImageFile($uri);
+      return array('Content-Type' => $image->get('mime_type'));
     }
   }
 }
diff --git a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php
index 12ebae7..8203b18 100644
--- a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php
+++ b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Plugin\PluginBase;
 use Drupal\Component\Annotation\Plugin;
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageFile;
 use Drupal\system\Plugin\ImageToolkitInterface;
 
 /**
@@ -38,7 +39,7 @@ public function settingsFormSubmit($form, &$form_state) {}
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::getInfo().
    */
-  public function getInfo($image) {
+  public function getInfo(ImageFile $image) {
     $this->logCall('get_info', array($image));
     return array();
   }
@@ -46,7 +47,7 @@ public function getInfo($image) {
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::load().
    */
-  public function load($image) {
+  public function load(ImageFile $image) {
     $this->logCall('load', array($image));
     return $image;
   }
@@ -54,7 +55,7 @@ public function load($image) {
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::save().
    */
-  public function save($image, $destination) {
+  public function save(ImageFile $image, $destination) {
     $this->logCall('save', array($image, $destination));
     // Return false so that image_save() doesn't try to chmod the destination
     // file that we didn't bother to create.
@@ -64,7 +65,7 @@ public function save($image, $destination) {
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::crop().
    */
-  public function crop($image, $x, $y, $width, $height) {
+  public function crop(ImageFile $image, $x, $y, $width, $height) {
     $this->logCall('crop', array($image, $x, $y, $width, $height));
     return TRUE;
   }
@@ -72,7 +73,7 @@ public function crop($image, $x, $y, $width, $height) {
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::resize().
    */
-  public function resize($image, $width, $height) {
+  public function resize(ImageFile $image, $width, $height) {
     $this->logCall('resize', array($image, $width, $height));
     return TRUE;
   }
@@ -80,7 +81,7 @@ public function resize($image, $width, $height) {
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::rotate().
    */
-  public function rotate($image, $degrees, $background = NULL) {
+  public function rotate(ImageFile $image, $degrees, $background = NULL) {
     $this->logCall('rotate', array($image, $degrees, $background));
     return TRUE;
   }
@@ -88,7 +89,7 @@ public function rotate($image, $degrees, $background = NULL) {
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::desaturate().
    */
-  public function desaturate($image) {
+  public function desaturate(ImageFile $image) {
     $this->logCall('desaturate', array($image));
     return TRUE;
   }
