From 8cfc089e26558b294bf548090b44425e6f467e32 Mon Sep 17 00:00:00 2001
From: Alex Pott <alex.a.pott@googlemail.com>
Date: Sun, 11 Aug 2013 00:57:37 +0100
Subject: [PATCH] Issue #2033669 by tim.plunkett, larowlan: Image file objects
 should be classed.

---
 core/core.services.yml                        |   3 +
 core/includes/common.inc                      |   1 -
 core/includes/image.inc                       | 332 ----------------
 .../Component/{Image => Utility}/Image.php    |   4 +-
 core/lib/Drupal/Core/Image/Image.php          | 346 +++++++++++++++++
 core/lib/Drupal/Core/Image/ImageFactory.php   |  61 +++
 core/lib/Drupal/Core/Image/ImageInterface.php | 245 ++++++++++++
 core/modules/file/file.module                 |  24 +-
 .../lib/Drupal/file/Tests/ValidatorTest.php   |   6 +-
 core/modules/image/image.admin.inc            |  27 +-
 core/modules/image/image.field.inc            |  20 +-
 core/modules/image/image.install              |   1 -
 core/modules/image/image.module               |   8 +-
 .../ImageStyleDownloadController.php          |  24 +-
 .../lib/Drupal/image/ImageEffectInterface.php |   7 +-
 .../image/Plugin/Core/Entity/ImageStyle.php   |   5 +-
 .../Plugin/ImageEffect/CropImageEffect.php    |  11 +-
 .../ImageEffect/DesaturateImageEffect.php     |   7 +-
 .../Plugin/ImageEffect/ResizeImageEffect.php  |   7 +-
 .../Plugin/ImageEffect/RotateImageEffect.php  |   7 +-
 .../ImageEffect/ScaleAndCropImageEffect.php   |   7 +-
 .../Plugin/ImageEffect/ScaleImageEffect.php   |   9 +-
 .../lib/Drupal/image/Tests/FileMoveTest.php   |   6 +-
 .../image/Tests/ImageDimensionsTest.php       |  43 ++-
 .../image/Tests/ImageFieldValidateTest.php    |   7 +-
 .../lib/Drupal/image/Tests/ImageItemTest.php  |  18 +-
 .../image/Tests/ImageStylesPathAndUrlTest.php |   6 +-
 .../ImageEffect/NullTestImageEffect.php       |   3 +-
 .../system/Plugin/ImageToolkit/GDToolkit.php  | 130 +++----
 .../system/Plugin/ImageToolkitInterface.php   |  72 ++--
 .../system/Tests/Image/ToolkitGdTest.php      |  35 +-
 .../Drupal/system/Tests/Image/ToolkitTest.php |  22 +-
 .../system/Tests/Image/ToolkitTestBase.php    |  40 +-
 core/modules/system/system.api.php            |   4 +-
 .../Plugin/ImageToolkit/TestToolkit.php       |  52 ++-
 .../{ImageTest.php => ImageUtilityTest.php}   |  16 +-
 .../Drupal/Tests/Core/Image/ImageTest.php     | 358 ++++++++++++++++++
 37 files changed, 1376 insertions(+), 598 deletions(-)
 delete mode 100644 core/includes/image.inc
 rename core/lib/Drupal/Component/{Image => Utility}/Image.php (96%)
 create mode 100644 core/lib/Drupal/Core/Image/Image.php
 create mode 100644 core/lib/Drupal/Core/Image/ImageFactory.php
 create mode 100644 core/lib/Drupal/Core/Image/ImageInterface.php
 rename core/tests/Drupal/Tests/Component/Image/{ImageTest.php => ImageUtilityTest.php} (92%)
 create mode 100644 core/tests/Drupal/Tests/Core/Image/ImageTest.php

diff --git a/core/core.services.yml b/core/core.services.yml
index 158adb74ac34..142d0cd311a9 100644
--- a/core/core.services.yml
+++ b/core/core.services.yml
@@ -496,6 +496,9 @@ services:
     class: Drupal\system\Plugin\ImageToolkitInterface
     factory_method: getDefaultToolkit
     factory_service: image.toolkit.manager
+  image.factory:
+    class: Drupal\Core\Image\ImageFactory
+    arguments: ['@image.toolkit']
   breadcrumb:
     class: Drupal\Core\Breadcrumb\BreadcrumbManager
   token:
diff --git a/core/includes/common.inc b/core/includes/common.inc
index 48db270314be..842136e28269 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -3086,7 +3086,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 9e2d06f4e8f7..000000000000
--- 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/Component/Image/Image.php b/core/lib/Drupal/Component/Utility/Image.php
similarity index 96%
rename from core/lib/Drupal/Component/Image/Image.php
rename to core/lib/Drupal/Component/Utility/Image.php
index 635b37460760..dd06806b5f1f 100644
--- a/core/lib/Drupal/Component/Image/Image.php
+++ b/core/lib/Drupal/Component/Utility/Image.php
@@ -2,10 +2,10 @@
 
 /**
  * @file
- * Contains \Drupal\Component\Image\Image.
+ * Contains \Drupal\Component\Utility\Image.
  */
 
-namespace Drupal\Component\Image;
+namespace Drupal\Component\Utility;
 
 /**
  * Provides helpers to operate on images.
diff --git a/core/lib/Drupal/Core/Image/Image.php b/core/lib/Drupal/Core/Image/Image.php
new file mode 100644
index 000000000000..a515fe43839f
--- /dev/null
+++ b/core/lib/Drupal/Core/Image/Image.php
@@ -0,0 +1,346 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Image\Image.
+ */
+
+namespace Drupal\Core\Image;
+
+use Drupal\system\Plugin\ImageToolkitInterface;
+use Drupal\Component\Utility\Image as ImageUtility;
+
+/**
+ * Defines an image object to represent an image file.
+ *
+ * @see \Drupal\system\Plugin\ImageToolkitInterface
+ * @see \Drupal\image\ImageEffectInterface
+ *
+ * @ingroup image
+ */
+class Image implements ImageInterface {
+
+  /**
+   * String specifying the path of the image file.
+   *
+   * @var string
+   */
+  protected $source;
+
+  /**
+   * An image toolkit object.
+   *
+   * @var \Drupal\system\Plugin\ImageToolkitInterface
+   */
+  protected $toolkit;
+
+  /**
+   * An image file handle.
+   *
+   * @var resource
+   */
+  protected $resource;
+
+  /**
+   * Height, in pixels.
+   *
+   * @var int
+   */
+  protected $height = 0;
+
+  /**
+   * Width, in pixels.
+   *
+   * @var int
+   */
+  protected $width = 0;
+
+  /**
+   * Commonly used file extension for the image.
+   *
+   * @var string
+   */
+  protected $extension = '';
+
+  /**
+   * MIME type ('image/jpeg', 'image/gif', 'image/png').
+   *
+   * @var string
+   */
+  protected $mimeType = '';
+
+  /**
+   * File size in bytes.
+   *
+   * @var int
+   */
+  protected $fileSize = 0;
+
+  /**
+   * If this image file has been processed.
+   *
+   * @var bool
+   */
+  protected $processed = FALSE;
+
+  /**
+   * Constructs a new Image object.
+   *
+   * @param string $source
+   *   The path to an image file.
+   * @param \Drupal\system\Plugin\ImageToolkitInterface $toolkit
+   *   The image toolkit.
+   */
+  public function __construct($source, ImageToolkitInterface $toolkit) {
+    $this->source = $source;
+    $this->toolkit = $toolkit;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getExtension() {
+    $this->processInfo();
+    return $this->extension;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getHeight() {
+    $this->processInfo();
+    return $this->height;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setHeight($height) {
+    $this->height = $height;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getWidth() {
+    $this->processInfo();
+    return $this->width;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setWidth($width) {
+    $this->width = $width;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFileSize() {
+    $this->processInfo();
+    return $this->fileSize;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getMimeType() {
+    $this->processInfo();
+    return $this->mimeType;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setResource($resource) {
+    $this->resource = $resource;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function hasResource() {
+    return (bool) $this->resource;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getResource() {
+    if (!$this->hasResource()) {
+      $this->processInfo();
+      $this->toolkit->load($this);
+    }
+    return $this->resource;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setSource($source) {
+    $this->source = $source;
+    return $this;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSource() {
+    return $this->source;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getToolkitId() {
+    return $this->toolkit->getPluginId();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save($destination = NULL) {
+    if (empty($destination)) {
+      $destination = $this->getSource();
+    }
+    if ($return = $this->toolkit->save($this, $destination)) {
+      // Clear the cached file size and refresh the image information.
+      clearstatcache(TRUE, $destination);
+      $this->setSource($destination);
+      $this->processInfo();
+
+      // @todo Use File utility when https://drupal.org/node/2050759 is in.
+      if ($this->chmod($destination)) {
+        return $return;
+      }
+    }
+    return FALSE;
+  }
+
+  /**
+   * Prepares the image information.
+   *
+   * 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 bool
+   *   FALSE, if the file could not be found or is not an image. Otherwise, the
+   *   image information is populated.
+   */
+  protected function processInfo() {
+    if ($this->processed) {
+      return TRUE;
+    }
+
+    $destination = $this->getSource();
+    if (!is_file($destination) && !is_uploaded_file($destination)) {
+      return FALSE;
+    }
+
+    if ($details = $this->toolkit->getInfo($this)) {
+      $this->height = $details['height'];
+      $this->width = $details['width'];
+      $this->extension = $details['extension'];
+      $this->mimeType = $details['mime_type'];
+      $this->fileSize = filesize($destination);
+      $this->processed = TRUE;
+    }
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function scale($width = NULL, $height = NULL, $upscale = FALSE) {
+    $dimensions = array(
+      'width' => $this->getWidth(),
+      'height' => $this->getHeight(),
+    );
+
+    // Scale the dimensions - if they don't change then just return success.
+    if (!ImageUtility::scaleDimensions($dimensions, $width, $height, $upscale)) {
+      return TRUE;
+    }
+
+    return $this->resize($dimensions['width'], $dimensions['height']);
+
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function scaleAndCrop($width, $height) {
+    $scale = max($width / $this->getWidth(), $height / $this->getHeight());
+    $x = ($this->getWidth() * $scale - $width) / 2;
+    $y = ($this->getHeight() * $scale - $height) / 2;
+
+    if ($this->resize($this->getWidth() * $scale, $this->getHeight() * $scale)) {
+      return $this->crop($x, $y, $width, $height);
+    }
+    return FALSE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function crop($x, $y, $width, $height) {
+    $aspect = $this->getHeight() / $this->getWidth();
+    if (empty($height)) $height = $width * $aspect;
+    if (empty($width)) $width = $height / $aspect;
+
+    $width = (int) round($width);
+    $height = (int) round($height);
+
+    return $this->toolkit->crop($this, $x, $y, $width, $height);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function resize($width, $height) {
+    $width = (int) round($width);
+    $height = (int) round($height);
+
+    return $this->toolkit->resize($this, $width, $height);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function desaturate() {
+    return $this->toolkit->desaturate($this);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function rotate($degrees, $background = NULL) {
+    return $this->toolkit->rotate($this, $degrees, $background);
+  }
+
+  /**
+   * Provides a wrapper for drupal_chmod() to allow unit testing.
+   *
+   * @param string $uri
+   *   A string containing a URI file, or directory path.
+   * @param int $mode
+   *   Integer value for the permissions. Consult PHP chmod() documentation for
+   *   more information.
+   *
+   * @see drupal_chmod()
+   *
+   * @todo Remove when https://drupal.org/node/2050759 is in.
+   *
+   * @return bool
+   *   TRUE for success, FALSE in the event of an error.
+   */
+  protected function chmod($uri, $mode = NULL) {
+    return drupal_chmod($uri, $mode);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Image/ImageFactory.php b/core/lib/Drupal/Core/Image/ImageFactory.php
new file mode 100644
index 000000000000..0ffc9a25c9eb
--- /dev/null
+++ b/core/lib/Drupal/Core/Image/ImageFactory.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Image\ImageFactory.
+ */
+
+namespace Drupal\Core\Image;
+
+use Drupal\system\Plugin\ImageToolkitInterface;
+
+/**
+ * Provides a factory for image objects.
+ */
+class ImageFactory {
+
+  /**
+   * The image toolkit to use for this factory.
+   *
+   * @var \Drupal\system\Plugin\ImageToolkitInterface
+   */
+  protected $toolkit;
+
+  /**
+   * Constructs a new ImageFactory object.
+   *
+   * @param \Drupal\system\Plugin\ImageToolkitInterface $toolkit
+   *   The image toolkit to use for this image factory.
+   */
+  public function __construct(ImageToolkitInterface $toolkit) {
+    $this->toolkit = $toolkit;
+  }
+
+  /**
+   * Sets a custom image toolkit.
+   *
+   * @param \Drupal\system\Plugin\ImageToolkitInterface $toolkit
+   *   The image toolkit to use for this image factory.
+   *
+   * @return self
+   *   Returns this image.
+   */
+  public function setToolkit(ImageToolkitInterface $toolkit) {
+    $this->toolkit = $toolkit;
+    return $this;
+  }
+
+  /**
+   * Constructs a new Image object.
+   *
+   * @param string $source
+   *   The path to an image file.
+   *
+   * @return \Drupal\Core\Image\ImageInterface
+   *   The new Image object.
+   */
+  public function get($source) {
+    return new Image($source, $this->toolkit);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Image/ImageInterface.php b/core/lib/Drupal/Core/Image/ImageInterface.php
new file mode 100644
index 000000000000..b83c578e391d
--- /dev/null
+++ b/core/lib/Drupal/Core/Image/ImageInterface.php
@@ -0,0 +1,245 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Image\ImageInterface.
+ */
+
+namespace Drupal\Core\Image;
+
+/**
+ * Provides an interface for image objects.
+ */
+interface ImageInterface {
+
+  /**
+   * Returns the extension of the image file.
+   *
+   * @return string
+   *   The extension of the file, or an empty string if the file is invalid.
+   */
+  public function getExtension();
+
+  /**
+   * Returns the height of the image file.
+   *
+   * @return int
+   *   The height of the file, or 0 if the file is invalid.
+   */
+  public function getHeight();
+
+  /**
+   * Sets the height of the image file.
+   *
+   * @param int $height
+   *
+   * @return self
+   *   Returns this image file.
+   */
+  public function setHeight($height);
+
+  /**
+   * Returns the width of the image file.
+   *
+   * @return int
+   *   The width of the file, or 0 if the file is invalid.
+   */
+  public function getWidth();
+
+  /**
+   * Sets the width of the image file.
+   *
+   * @param int $width
+   *
+   * @return self
+   *   Returns this image file.
+   */
+  public function setWidth($width);
+
+  /**
+   * Returns the size of the image file.
+   *
+   * @return int
+   *   The size of the file in bytes, or 0 if the file is invalid.
+   */
+  public function getFileSize();
+
+  /**
+   * Returns the MIME type of the image file.
+   *
+   * @return string
+   *   The MIME type of the file, or an empty string if the file is invalid.
+   */
+  public function getMimeType();
+
+  /**
+   * Sets the image file resource.
+   *
+   * @param resource $resource
+   *   The image file handle.
+   *
+   * @return self
+   *   Returns this image file.
+   */
+  public function setResource($resource);
+
+  /**
+   * 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();
+
+  /**
+   * Retrieves the image file resource.
+   *
+   * @return resource
+   *   The image file handle.
+   */
+  public function getResource();
+
+  /**
+   * 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);
+
+  /**
+   * Retrieves the source path of the image file.
+   *
+   * @return string
+   *   The source path of the image file.
+   */
+  public function getSource();
+
+  /**
+   * Returns the ID of the image toolkit used for this image file.
+   *
+   * @return string
+   *   The ID of the image toolkit.
+   */
+  public function getToolkitId();
+
+  /**
+   * 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);
+
+  /**
+   * 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);
+
+  /**
+   * 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);
+
+  /**
+   * 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()
+   */
+  public function crop($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);
+
+  /**
+   * Converts an image to grayscale.
+   *
+   * @return bool
+   *   TRUE on success, FALSE on failure.
+   *
+   * @see \Drupal\system\Plugin\ImageToolkitInterface::desaturate()
+   */
+  public function desaturate();
+
+  /**
+   * 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);
+
+}
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index d37a1baa5c38..ff37a175bc57 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -9,7 +9,6 @@
 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;
@@ -406,7 +405,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 Image::getInfo() as an image.
  *
  * @param Drupal\file\File $file
  *   A file entity.
@@ -419,8 +418,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());
-  if (!$info || empty($info['extension'])) {
+  $image = Drupal::service('image.factory')->get($file->getFileUri());
+  if (!$image->getExtension()) {
     $errors[] = t('Only JPEG, PNG and GIF images are allowed.');
   }
 
@@ -454,16 +453,19 @@ 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_factory = Drupal::service('image.factory');
+  $image = $image_factory->get($file->getFileUri());
+  if ($image->getExtension()) {
     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) {
+      if ($image->getWidth() > $width || $image->getHeight() > $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 = $image_factory->get($file->getFileUri());
+        if ($image->getResource()) {
+          $image->scale($width, $height);
+          $image->save();
+          $file->filesize = $image->getFileSize();
           drupal_set_message(t('The image was resized to fit within the maximum allowed dimensions of %dimensions pixels.', array('%dimensions' => $maximum_dimensions)));
         }
         else {
@@ -475,7 +477,7 @@ function file_validate_image_resolution(File $file, $maximum_dimensions = 0, $mi
     if ($minimum_dimensions) {
       // Check that it is larger than the given dimensions.
       list($width, $height) = explode('x', $minimum_dimensions);
-      if ($info['width'] < $width || $info['height'] < $height) {
+      if ($image->getWidth() < $width || $image->getHeight() < $height) {
         $errors[] = t('The image is too small; the minimum dimensions are %dimensions pixels.', array('%dimensions' => $minimum_dimensions));
       }
     }
diff --git a/core/modules/file/lib/Drupal/file/Tests/ValidatorTest.php b/core/modules/file/lib/Drupal/file/Tests/ValidatorTest.php
index 8012c4dfa623..5214aa4483e1 100644
--- a/core/modules/file/lib/Drupal/file/Tests/ValidatorTest.php
+++ b/core/modules/file/lib/Drupal/file/Tests/ValidatorTest.php
@@ -87,9 +87,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 = $this->container->get('image.factory')->get($this->image->getFileUri());
+      $this->assertTrue($image->getWidth() <= 10, 'Image scaled to correct width.', 'File');
+      $this->assertTrue($image->getHeight() <= 5, 'Image scaled to correct height.', 'File');
 
       drupal_unlink('temporary://druplicon.png');
     }
diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc
index ffde15d0e8dd..5f8d46a83a8d 100644
--- a/core/modules/image/image.admin.inc
+++ b/core/modules/image/image.admin.inc
@@ -76,7 +76,12 @@ function theme_image_style_preview($variables) {
 
   // Set up original file information.
   $original_path = $sample_image;
-  $original_image = image_get_info($original_path);
+  $image_factory = Drupal::service('image.factory');
+  $original_image = $image_factory->get($original_path);
+  $original_image = array(
+    'width' => $original_image->getWidth(),
+    'height' => $original_image->getHeight(),
+  );
   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']);
@@ -85,15 +90,18 @@ function theme_image_style_preview($variables) {
     $original_height = min($original_image['height'], $sample_height);
     $original_width = round($original_height / $original_image['height'] * $original_image['width']);
   }
-  $original_attributes = array_intersect_key($original_image, array('width' => '', 'height' => ''));
-  $original_attributes['style'] = 'width: ' . $original_width . 'px; height: ' . $original_height . 'px;';
+  $original_image['style'] = 'width: ' . $original_width . 'px; height: ' . $original_height . 'px;';
 
   // Set up preview file information.
   $preview_file = $style->buildUri($original_path);
   if (!file_exists($preview_file)) {
     $style->createDerivative($original_path, $preview_file);
   }
-  $preview_image = image_get_info($preview_file);
+  $preview_image = $image_factory->get($preview_file);
+  $preview_image = array(
+    'width' => $preview_image->getWidth(),
+    'height' => $preview_image->getHeight(),
+  );
   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']);
@@ -102,8 +110,7 @@ function theme_image_style_preview($variables) {
     $preview_height = min($preview_image['height'], $sample_height);
     $preview_width = round($preview_height / $preview_image['height'] * $preview_image['width']);
   }
-  $preview_attributes = array_intersect_key($preview_image, array('width' => '', 'height' => ''));
-  $preview_attributes['style'] = 'width: ' . $preview_width . 'px; height: ' . $preview_height . 'px;';
+  $preview_image['style'] = 'width: ' . $preview_width . 'px; height: ' . $preview_height . 'px;';
 
   // In the previews, timestamps are added to prevent caching of images.
   $output = '<div class="image-style-preview preview clearfix">';
@@ -112,8 +119,8 @@ function theme_image_style_preview($variables) {
   $original_url = file_create_url($original_path);
   $output .= '<div class="preview-image-wrapper">';
   $output .= t('original') . ' (' . l(t('view actual size'), $original_url) . ')';
-  $output .= '<div class="preview-image original-image" style="' . $original_attributes['style'] . '">';
-  $output .= '<a href="' . $original_url . '">' . theme('image', array('uri' => $original_path, 'alt' => t('Sample original image'), 'title' => '', 'attributes' => $original_attributes)) . '</a>';
+  $output .= '<div class="preview-image original-image" style="' . $original_image['style'] . '">';
+  $output .= '<a href="' . $original_url . '">' . theme('image', array('uri' => $original_path, 'alt' => t('Sample original image'), 'title' => '', 'attributes' => $original_image)) . '</a>';
   $output .= '<div class="height" style="height: ' . $original_height . 'px"><span>' . $original_image['height'] . 'px</span></div>';
   $output .= '<div class="width" style="width: ' . $original_width . 'px"><span>' . $original_image['width'] . 'px</span></div>';
   $output .= '</div>'; // End preview-image.
@@ -123,8 +130,8 @@ function theme_image_style_preview($variables) {
   $preview_url = file_create_url($preview_file) . '?cache_bypass=' . REQUEST_TIME;
   $output .= '<div class="preview-image-wrapper">';
   $output .= check_plain($style->label()) . ' (' . l(t('view actual size'), file_create_url($preview_file) . '?' . time()) . ')';
-  $output .= '<div class="preview-image modified-image" style="' . $preview_attributes['style'] . '">';
-  $output .= '<a href="' . file_create_url($preview_file) . '?' . time() . '">' . theme('image', array('uri' => $preview_url, 'alt' => t('Sample modified image'), 'title' => '', 'attributes' => $preview_attributes)) . '</a>';
+  $output .= '<div class="preview-image modified-image" style="' . $preview_image['style'] . '">';
+  $output .= '<a href="' . file_create_url($preview_file) . '?' . time() . '">' . theme('image', array('uri' => $preview_url, 'alt' => t('Sample modified image'), 'title' => '', 'attributes' => $preview_image)) . '</a>';
   $output .= '<div class="height" style="height: ' . $preview_height . 'px"><span>' . $preview_image['height'] . 'px</span></div>';
   $output .= '<div class="width" style="width: ' . $preview_width . 'px"><span>' . $preview_image['width'] . 'px</span></div>';
   $output .= '</div>'; // End preview-image.
diff --git a/core/modules/image/image.field.inc b/core/modules/image/image.field.inc
index 6cff0a3897b1..4741a73330f4 100644
--- a/core/modules/image/image.field.inc
+++ b/core/modules/image/image.field.inc
@@ -226,15 +226,14 @@ function _image_field_resolution_validate($element, &$form_state) {
  * Implements hook_field_presave().
  */
 function image_field_presave(EntityInterface $entity, $field, $instance, $langcode, &$items) {
-
+  $image_factory = Drupal::service('image.factory');
   // 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)) {
-        $item['width'] = $info['width'];
-        $item['height'] = $info['height'];
+      $image = $image_factory->get(file_load($item['target_id'])->getFileUri());
+      if ($image->getExtension()) {
+        $item['width'] = $image->getWidth();
+        $item['height'] = $image->getHeight();
       }
     }
   }
@@ -301,11 +300,10 @@ 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)) {
-        $variables['width'] = $info['width'];
-        $variables['height'] = $info['height'];
+      $image = Drupal::service('image.factory')->get($file->getFileUri());
+      if ($image->getExtension()) {
+        $variables['width'] = $image->getWidth();
+        $variables['height'] = $image->getHeight();
       }
       else {
         $variables['width'] = $variables['height'] = NULL;
diff --git a/core/modules/image/image.install b/core/modules/image/image.install
index 1617e8c5d752..58cf110bb103 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().
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 053075d15b88..28b7bc241943 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -9,7 +9,6 @@
 use Drupal\field\Plugin\Core\Entity\Field;
 use Drupal\field\Plugin\Core\Entity\FieldInstance;
 use Drupal\file\Plugin\Core\Entity\File;
-use Drupal\image\ImageStyleInterface;
 use Drupal\image\Plugin\Core\Entity\ImageStyle;
 use Drupal\field\FieldInterface;
 use Drupal\field\FieldInstanceInterface;
@@ -268,15 +267,16 @@ 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 = Drupal::service('image.factory')->get($uri);
+    if ($image->getExtension()) {
       // Check the permissions of the original to grant access to this image.
       $headers = Drupal::moduleHandler()->invokeAll('file_download', array($original_uri));
       // Confirm there's at least one module granting access and none denying access.
       if (!empty($headers) && !in_array(-1, $headers)) {
         return array(
           // Send headers describing the image's size, and MIME-type...
-          'Content-Type' => $info['mime_type'],
-          'Content-Length' => $info['file_size'],
+          'Content-Type' => $image->getMimeType(),
+          'Content-Length' => $image->getFileSize(),
           // By not explicitly setting them here, this uses normal Drupal
           // Expires, Cache-Control and ETag headers to prevent proxy or
           // browser caching of private images.
diff --git a/core/modules/image/lib/Drupal/image/Controller/ImageStyleDownloadController.php b/core/modules/image/lib/Drupal/image/Controller/ImageStyleDownloadController.php
index 34146c265b30..801396221b9f 100644
--- a/core/modules/image/lib/Drupal/image/Controller/ImageStyleDownloadController.php
+++ b/core/modules/image/lib/Drupal/image/Controller/ImageStyleDownloadController.php
@@ -11,6 +11,7 @@
 use Drupal\Core\Config\ConfigFactory;
 use Drupal\Core\Controller\ControllerInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Image\ImageFactory;
 use Drupal\Core\Lock\LockBackendInterface;
 use Drupal\Core\StringTranslation\Translator\TranslatorInterface;
 use Drupal\image\ImageStyleInterface;
@@ -48,6 +49,13 @@ class ImageStyleDownloadController extends FileDownloadController implements Con
    */
   protected $translator;
 
+  /**
+   * The image factory.
+   *
+   * @var \Drupal\Core\Image\ImageFactory
+   */
+  protected $imageFactory;
+
   /**
    * Constructs a ImageStyleDownloadController object.
    *
@@ -59,12 +67,15 @@ class ImageStyleDownloadController extends FileDownloadController implements Con
    *   The lock backend.
    * @param \Drupal\Core\StringTranslation\Translator\TranslatorInterface $translator
    *   The translator service.
+   * @param \Drupal\Core\Image\ImageFactory $image_factory
+   *   The image factory.
    */
-  public function __construct(ModuleHandlerInterface $module_handler, ConfigFactory $config_factory, LockBackendInterface $lock, TranslatorInterface $translator) {
+  public function __construct(ModuleHandlerInterface $module_handler, ConfigFactory $config_factory, LockBackendInterface $lock, TranslatorInterface $translator, ImageFactory $image_factory) {
     parent::__construct($module_handler);
     $this->configFactory = $config_factory;
     $this->lock = $lock;
     $this->translator = $translator;
+    $this->imageFactory = $image_factory;
   }
 
   /**
@@ -75,7 +86,8 @@ public static function create(ContainerInterface $container) {
       $container->get('module_handler'),
       $container->get('config.factory'),
       $container->get('lock'),
-      $container->get('string_translation')
+      $container->get('string_translation'),
+      $container->get('image.factory')
     );
   }
 
@@ -159,11 +171,11 @@ public function deliver(Request $request, $scheme, ImageStyleInterface $image_st
     }
 
     if ($success) {
-      $image = image_load($derivative_uri);
-      $uri = $image->source;
+      $image = $this->imageFactory->get($derivative_uri);
+      $uri = $image->getSource();
       $headers += array(
-        'Content-Type' => $image->info['mime_type'],
-        'Content-Length' => $image->info['file_size'],
+        'Content-Type' => $image->getMimeType(),
+        'Content-Length' => $image->getFileSize(),
       );
       return new BinaryFileResponse($uri, 200, $headers);
     }
diff --git a/core/modules/image/lib/Drupal/image/ImageEffectInterface.php b/core/modules/image/lib/Drupal/image/ImageEffectInterface.php
index 19575e91f2ba..66b7f4e66770 100644
--- a/core/modules/image/lib/Drupal/image/ImageEffectInterface.php
+++ b/core/modules/image/lib/Drupal/image/ImageEffectInterface.php
@@ -9,6 +9,7 @@
 
 use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Component\Plugin\ConfigurablePluginInterface;
+use Drupal\Core\Image\ImageInterface;
 
 /**
  * Defines the interface for image effects.
@@ -18,13 +19,13 @@ interface ImageEffectInterface extends PluginInspectionInterface, ConfigurablePl
   /**
    * Applies an image effect to the image object.
    *
-   * @param \stdClass $image
-   *   An image object returned by image_load().
+   * @param \Drupal\Core\Image\ImageInterface $image
+   *   An image file object.
    *
    * @return bool
    *   TRUE on success. FALSE if unable to perform the image effect on the image.
    */
-  public function applyEffect($image);
+  public function applyEffect(ImageInterface $image);
 
   /**
    * Determines the dimensions of the styled image.
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 73976f9df796..25e0b81fea4b 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
@@ -281,7 +281,8 @@ public function createDerivative($original_uri, $derivative_uri) {
       return FALSE;
     }
 
-    if (!$image = image_load($original_uri)) {
+    $image = \Drupal::service('image.factory')->get($original_uri);
+    if (!$image->getResource()) {
       return FALSE;
     }
 
@@ -289,7 +290,7 @@ public function createDerivative($original_uri, $derivative_uri) {
       $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);
       }
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php
index 11f228ed3025..e46d2fa74d81 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php
@@ -8,6 +8,7 @@
 namespace Drupal\image\Plugin\ImageEffect;
 
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageInterface;
 use Drupal\image\Annotation\ImageEffect;
 
 /**
@@ -24,17 +25,17 @@ class CropImageEffect extends ResizeImageEffect {
   /**
    * {@inheritdoc}
    */
-  public function applyEffect($image) {
+  public function applyEffect(ImageInterface $image) {
     // Set sane default values.
     $this->configuration += array(
       'anchor' => 'center-center',
     );
 
     list($x, $y) = explode('-', $this->configuration['anchor']);
-    $x = image_filter_keyword($x, $image->info['width'], $this->configuration['width']);
-    $y = image_filter_keyword($y, $image->info['height'], $this->configuration['height']);
-    if (!image_crop($image, $x, $y, $this->configuration['width'], $this->configuration['height'])) {
-      watchdog('image', 'Image crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+    $x = image_filter_keyword($x, $image->getWidth(), $this->configuration['width']);
+    $y = image_filter_keyword($y, $image->getHeight(), $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->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()), WATCHDOG_ERROR);
       return FALSE;
     }
     return TRUE;
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php
index 816ed3e2b0e0..912bec9d688c 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/DesaturateImageEffect.php
@@ -8,6 +8,7 @@
 namespace Drupal\image\Plugin\ImageEffect;
 
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageInterface;
 use Drupal\image\Annotation\ImageEffect;
 use Drupal\image\ImageEffectBase;
 
@@ -31,9 +32,9 @@ public function transformDimensions(array &$dimensions) {
   /**
    * {@inheritdoc}
    */
-  public function applyEffect($image) {
-    if (!image_desaturate($image)) {
-      watchdog('image', 'Image desaturate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+  public function applyEffect(ImageInterface $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->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()), 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
index 484e6a0422ee..766e5a7b87b7 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ResizeImageEffect.php
@@ -8,6 +8,7 @@
 namespace Drupal\image\Plugin\ImageEffect;
 
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageInterface;
 use Drupal\image\Annotation\ImageEffect;
 use Drupal\image\ConfigurableImageEffectInterface;
 use Drupal\image\ImageEffectBase;
@@ -26,9 +27,9 @@ class ResizeImageEffect extends ImageEffectBase implements ConfigurableImageEffe
   /**
    * {@inheritdoc}
    */
-  public function applyEffect($image) {
-    if (!image_resize($image, $this->configuration['width'], $this->configuration['height'])) {
-      watchdog('image', 'Image resize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+  public function applyEffect(ImageInterface $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->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()), WATCHDOG_ERROR);
       return FALSE;
     }
     return TRUE;
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php
index dee537e6b7d2..fe563aefb6be 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php
@@ -8,6 +8,7 @@
 namespace Drupal\image\Plugin\ImageEffect;
 
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageInterface;
 use Drupal\image\Annotation\ImageEffect;
 use Drupal\image\ConfigurableImageEffectInterface;
 use Drupal\image\ImageEffectBase;
@@ -26,7 +27,7 @@ class RotateImageEffect extends ImageEffectBase implements ConfigurableImageEffe
   /**
    * {@inheritdoc}
    */
-  public function applyEffect($image) {
+  public function applyEffect(ImageInterface $image) {
     // Set sane default values.
     $this->configuration += array(
       'degrees' => 0,
@@ -53,8 +54,8 @@ public function applyEffect($image) {
       $this->configuration['degrees'] = rand(-1 * $degrees, $degrees);
     }
 
-    if (!image_rotate($image, $this->configuration['degrees'], $this->configuration['bgcolor'])) {
-      watchdog('image', 'Image rotate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+    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->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()), WATCHDOG_ERROR);
       return FALSE;
     }
     return TRUE;
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php
index dfa59550ef51..39bd2ec0bf66 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleAndCropImageEffect.php
@@ -8,6 +8,7 @@
 namespace Drupal\image\Plugin\ImageEffect;
 
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageInterface;
 use Drupal\image\Annotation\ImageEffect;
 
 /**
@@ -24,9 +25,9 @@ class ScaleAndCropImageEffect extends ResizeImageEffect {
   /**
    * {@inheritdoc}
    */
-  public function applyEffect($image) {
-    if (!image_scale_and_crop($image, $this->configuration['width'], $this->configuration['height'])) {
-      watchdog('image', 'Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+  public function applyEffect(ImageInterface $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->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()), 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
index 47ada5a7a0eb..c7b2f53ce346 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/ScaleImageEffect.php
@@ -7,8 +7,9 @@
 
 namespace Drupal\image\Plugin\ImageEffect;
 
-use Drupal\Component\Image\Image;
 use Drupal\Core\Annotation\Translation;
+use Drupal\Component\Utility\Image;
+use Drupal\Core\Image\ImageInterface;
 use Drupal\image\Annotation\ImageEffect;
 
 /**
@@ -25,7 +26,7 @@ class ScaleImageEffect extends ResizeImageEffect {
   /**
    * {@inheritdoc}
    */
-  public function applyEffect($image) {
+  public function applyEffect(ImageInterface $image) {
     // Set sane default values.
     $this->configuration += array(
       'width' => NULL,
@@ -33,8 +34,8 @@ public function applyEffect($image) {
       'upscale' => FALSE,
     );
 
-    if (!image_scale($image, $this->configuration['width'], $this->configuration['height'], $this->configuration['upscale'])) {
-      watchdog('image', 'Image scale failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+    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->getMimeType(), '%dimensions' => $image->getWidth() . 'x' . $image->getHeight()), WATCHDOG_ERROR);
       return FALSE;
     }
     return TRUE;
diff --git a/core/modules/image/lib/Drupal/image/Tests/FileMoveTest.php b/core/modules/image/lib/Drupal/image/Tests/FileMoveTest.php
index 2fc0b68c1f3a..13518e0ae0ba 100644
--- a/core/modules/image/lib/Drupal/image/Tests/FileMoveTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/FileMoveTest.php
@@ -7,12 +7,12 @@
 
 namespace Drupal\image\Tests;
 
-use Drupal\system\Tests\Image\ToolkitTestBase;
+use Drupal\simpletest\WebTestBase;
 
 /**
  * Tests the file move function for images and image styles.
  */
-class FileMoveTest extends ToolkitTestBase {
+class FileMoveTest extends WebTestBase {
 
   /**
    * Modules to enable.
@@ -38,7 +38,7 @@ function testNormal() {
 
     // Create derivative image.
     $styles = entity_load_multiple('image_style');
-    $style = image_style_load(key($styles));
+    $style = reset($styles);
     $original_uri = $file->getFileUri();
     $derivative_uri = $style->buildUri($original_uri);
     $style->createDerivative($original_uri, $derivative_uri);
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php
index 0ff67388e362..c136fa6ce6ca 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageDimensionsTest.php
@@ -35,6 +35,7 @@ public static function getInfo() {
    * Test styled image dimensions cumulatively.
    */
   function testImageDimensions() {
+    $image_factory = $this->container->get('image.factory');
     // Create a working copy of the file.
     $files = $this->drupalGetTestFiles('image');
     $file = reset($files);
@@ -53,9 +54,9 @@ 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 = $image_factory->get($original_uri);
+    $this->assertEqual($image_file->getWidth(), $variables['width']);
+    $this->assertEqual($image_file->getHeight(), $variables['height']);
 
     // Scale an image that is wider than it is high.
     $effect = array(
@@ -75,9 +76,9 @@ function testImageDimensions() {
     $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 = $image_factory->get($generated_uri);
+    $this->assertEqual($image_file->getWidth(), 120);
+    $this->assertEqual($image_file->getHeight(), 60);
 
     // Rotate 90 degrees anticlockwise.
     $effect = array(
@@ -96,9 +97,9 @@ function testImageDimensions() {
     $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 = $image_factory->get($generated_uri);
+    $this->assertEqual($image_file->getWidth(), 60);
+    $this->assertEqual($image_file->getHeight(), 120);
 
     // Scale an image that is higher than it is wide (rotated by previous effect).
     $effect = array(
@@ -118,9 +119,9 @@ function testImageDimensions() {
     $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 = $image_factory->get($generated_uri);
+    $this->assertEqual($image_file->getWidth(), 45);
+    $this->assertEqual($image_file->getHeight(), 90);
 
     // Test upscale disabled.
     $effect = array(
@@ -140,9 +141,9 @@ function testImageDimensions() {
     $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 = $image_factory->get($generated_uri);
+    $this->assertEqual($image_file->getWidth(), 45);
+    $this->assertEqual($image_file->getHeight(), 90);
 
     // Add a desaturate effect.
     $effect = array(
@@ -158,9 +159,9 @@ function testImageDimensions() {
     $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 = $image_factory->get($generated_uri);
+    $this->assertEqual($image_file->getWidth(), 45);
+    $this->assertEqual($image_file->getHeight(), 90);
 
     // Add a random rotate effect.
     $effect = array(
@@ -199,9 +200,9 @@ function testImageDimensions() {
     $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 = $image_factory->get($generated_uri);
+    $this->assertEqual($image_file->getWidth(), 30);
+    $this->assertEqual($image_file->getHeight(), 30);
 
     // Rotate to a non-multiple of 90 degrees.
     $effect = array(
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageFieldValidateTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageFieldValidateTest.php
index 1cd0ce953795..e3d48bf9ea02 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageFieldValidateTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageFieldValidateTest.php
@@ -36,12 +36,13 @@ function testResolution() {
     // big, so cycle through test image files until we have what we need.
     $image_that_is_too_big = FALSE;
     $image_that_is_too_small = FALSE;
+    $image_factory = $this->container->get('image.factory');
     foreach ($this->drupalGetTestFiles('image') as $image) {
-      $info = image_get_info($image->uri);
-      if ($info['width'] > $max_resolution) {
+      $image_file = $image_factory->get($image->uri);
+      if ($image_file->getWidth() > $max_resolution) {
         $image_that_is_too_big = $image;
       }
-      if ($info['width'] < $min_resolution) {
+      if ($image_file->getWidth() < $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 5e7472f9617b..fdd368604d0d 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageItemTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageItemTest.php
@@ -30,6 +30,11 @@ class ImageItemTest extends FieldUnitTestBase {
    */
   protected $image;
 
+  /**
+   * @var \Drupal\Core\Image\ImageFactory
+   */
+  protected $imageFactory;
+
   public static function getInfo() {
     return array(
       'name' => 'Image field item API',
@@ -58,6 +63,7 @@ public function setUp() {
       'uri' => 'public://example.jpg',
     ));
     $this->image->save();
+    $this->imageFactory = $this->container->get('image.factory');
   }
 
   /**
@@ -78,9 +84,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 = $this->imageFactory->get('public://example.jpg');
+    $this->assertEqual($entity->image_test->width, $image->getWidth());
+    $this->assertEqual($entity->image_test->height, $image->getHeight());
     $this->assertEqual($entity->image_test->entity->id(), $this->image->id());
     $this->assertEqual($entity->image_test->entity->uuid(), $this->image->uuid());
 
@@ -98,9 +104,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 = $this->imageFactory->get('public://example-2.jpg');
+    $this->assertEqual($entity->image_test->width, $image->getWidth());
+    $this->assertEqual($entity->image_test->height, $image->getHeight());
     $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/ImageStylesPathAndUrlTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
index cae45d7eb552..4648701e8bf2 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageStylesPathAndUrlTest.php
@@ -153,9 +153,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 = $this->container->get('image.factory')->get($generated_uri);
+    $this->assertEqual($this->drupalGetHeader('Content-Type'), $image->getMimeType(), 'Expected Content-Type was reported.');
+    $this->assertEqual($this->drupalGetHeader('Content-Length'), $image->getFileSize(), '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/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
index 23b1931450ba..52731fee4eb6 100644
--- 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
@@ -8,6 +8,7 @@
 namespace Drupal\image_module_test\Plugin\ImageEffect;
 
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Image\ImageInterface;
 use Drupal\image\Annotation\ImageEffect;
 use Drupal\image\ImageEffectBase;
 
@@ -24,7 +25,7 @@ class NullTestImageEffect extends ImageEffectBase {
   /**
    * {@inheritdoc}
    */
-  public function applyEffect($image) {
+  public function applyEffect(ImageInterface $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 06cb4a61e16f..86612c72ab35 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\ImageInterface;
 use Drupal\system\Plugin\ImageToolkitInterface;
 
 /**
@@ -23,7 +24,7 @@
 class GDToolkit extends PluginBase implements ImageToolkitInterface {
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::settingsForm().
+   * {@inheritdoc}
    */
   public function settingsForm() {
     $form['image_jpeg_quality'] = array(
@@ -39,7 +40,7 @@ public function settingsForm() {
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::settingsFormSubmit().
+   * {@inheritdoc}
    */
   public function settingsFormSubmit($form, &$form_state) {
     config('system.image.gd')
@@ -48,133 +49,134 @@ public function settingsFormSubmit($form, &$form_state) {
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::resize().
+   * {@inheritdoc}
    */
-  public function resize($image, $width, $height) {
+  public function resize(ImageInterface $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->getWidth(), $image->getHeight())) {
       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)
+      ->setWidth($width)
+      ->setHeight($height);
     return TRUE;
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::rotate().
+   * {@inheritdoc}
    */
-  public function rotate($image, $degrees, $background = NULL) {
+  public function rotate(ImageInterface $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'];
-
     // Convert the hexadecimal background value to a color index value.
     if (isset($background)) {
       $rgb = array();
       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->getExtension() == '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
+      ->setWidth(imagesx($image->getResource()))
+      ->setHeight(imagesy($image->getResource()));
     return TRUE;
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::crop().
+   * {@inheritdoc}
    */
-  public function crop($image, $x, $y, $width, $height) {
+  public function crop(ImageInterface $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)
+      ->setWidth($width)
+      ->setHeight($height);
     return TRUE;
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::desaturate().
+   * {@inheritdoc}
    */
-  public function desaturate($image) {
+  public function desaturate(ImageInterface $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().
+   * {@inheritdoc}
    */
-  public function load($image) {
-    $extension = str_replace('jpg', 'jpeg', $image->info['extension']);
+  public function load(ImageInterface $image) {
+    $extension = str_replace('jpg', 'jpeg', $image->getExtension());
     $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->getWidth(), $image->getHeight());
+        imagecopy($new_image, $resource, 0, 0, 0, 0, $image->getWidth(), $image->getHeight());
+        imagedestroy($resource);
+        $image->setResource($new_image);
       }
-      return (bool) $image->resource;
+      return (bool) $image->getResource();
     }
 
     return FALSE;
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::save().
+   * {@inheritdoc}
    */
-  public function save($image, $destination) {
+  public function save(ImageInterface $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->getExtension());
     $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) {
@@ -212,11 +214,11 @@ public function save($image, $destination) {
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::getInfo().
+   * {@inheritdoc}
    */
-  public function getInfo($image) {
+  public function getInfo(ImageInterface $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');
@@ -235,7 +237,7 @@ public function getInfo($image) {
   /**
    * Creates a truecolor image preserving transparency from a provided image.
    *
-   * @param object $image
+   * @param \Drupal\Core\Image\ImageInterface $image
    *   An image object.
    * @param int $width
    *   The new width of the new image, in pixels.
@@ -245,16 +247,16 @@ public function getInfo($image) {
    * @return resource
    *   A GD image handle.
    */
-  public function createTmp($image, $width, $height) {
+  public function createTmp(ImageInterface $image, $width, $height) {
     $res = imagecreatetruecolor($width, $height);
 
-    if ($image->info['extension'] == 'gif') {
+    if ($image->getExtension() == '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->getExtension() == 'png') {
       imagealphablending($res, FALSE);
       $transparency = imagecolorallocatealpha($res, 0, 0, 0, 127);
       imagefill($res, 0, 0, $transparency);
@@ -277,7 +279,7 @@ public function createTmp($image, $width, $height) {
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::isAvailable().
+   * {@inheritdoc}
    */
   public static function isAvailable() {
     if ($check = get_extension_funcs('gd')) {
diff --git a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php
index 0b22f3c6c9fe..774f0509b95d 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\ImageInterface;
+
+/**
+ * @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\ImageInterface $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(ImageInterface $image, $width, $height);
 
   /**
    * Rotates an image the given number of degrees.
    *
-   * @param object $image
+   * @param \Drupal\Core\Image\ImageInterface $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(ImageInterface $image, $degrees, $background = NULL);
 
   /**
    * Crops an image.
    *
-   * @param object $image
+   * @param \Drupal\Core\Image\ImageInterface $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(ImageInterface $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\ImageInterface $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(ImageInterface $image);
 
   /**
    * Creates an image resource from a file.
    *
-   * @param object $image
+   * @param \Drupal\Core\Image\ImageInterface $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(ImageInterface $image);
 
   /**
    * Writes an image resource to a destination file.
    *
-   * @param object $image
+   * @param \Drupal\Core\Image\ImageInterface $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(ImageInterface $image, $destination);
 
   /**
    * Gets details about an image.
    *
-   * @param object $image
+   * @param \Drupal\Core\Image\ImageInterface $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\ImageInterface::processInfo()
    */
-  function getInfo($image);
+  function getInfo(ImageInterface $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 624dc79179d8..1797b7819ddf 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
@@ -7,8 +7,8 @@
 
 namespace Drupal\system\Tests\Image;
 
+use Drupal\Core\Image\ImageInterface;
 use Drupal\simpletest\DrupalUnitTestBase;
-use Drupal\system\Plugin\ImageToolkitManager;
 
 /**
  * Test the core GD image manipulation functions.
@@ -79,15 +79,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(ImageInterface $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));
   }
 
   /**
@@ -152,7 +152,7 @@ function testManipulations() {
         'corners' => array_fill(0, 4, $this->white),
       ),
       'scale_and_crop' => array(
-        'function' => 'scale_and_crop',
+        'function' => 'scaleAndCrop',
         'arguments' => array(10, 8),
         'width' => 10,
         'height' => 8,
@@ -215,21 +215,22 @@ function testManipulations() {
       );
     }
 
-    $manager = new ImageToolkitManager($this->container->get('container.namespaces'), $this->container->get('cache.cache'), $this->container->get('language_manager'));
+    $toolkit = $this->container->get('image.toolkit.manager')->createInstance('gd');
+    $image_factory = $this->container->get('image.factory')->setToolkit($toolkit);
     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 = $image_factory->get(drupal_get_path('module', 'simpletest') . '/files/' . $file);
         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->getExtension() == '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['function']), $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->getWidth() != $values['width'] || $image->getHeight() != $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->getExtension());
 
         $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->getExtension() != '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 14a84e90cc17..5f6c4dcd2a68 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php
@@ -10,7 +10,7 @@
 use Drupal\system\Plugin\ImageToolkitManager;
 
 /**
- * Test that the functions in image.inc correctly pass data to the toolkit.
+ * Tests that the methods in Image correctly pass data to the toolkit.
  */
 class ToolkitTest extends ToolkitTestBase {
   public static function getInfo() {
@@ -34,12 +34,12 @@ function testGetAvailableToolkits() {
   }
 
   /**
-   * Test the image_load() function.
+   * Tests Image's methods.
    */
   function testLoad() {
-    $image = image_load($this->file, $this->toolkit);
+    $image = $this->getImage();
     $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 +47,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 +55,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 +69,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 +82,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 +98,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 +111,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 +126,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 80b158882135..a7d097b945d8 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php
@@ -9,7 +9,6 @@
 
 use Drupal\simpletest\WebTestBase;
 use Drupal\system\Plugin\ImageToolkitManager;
-use stdClass;
 
 /**
  * Base class for image manipulation testing.
@@ -23,8 +22,25 @@ abstract class ToolkitTestBase extends WebTestBase {
    */
   public static $modules = array('image_test');
 
+  /**
+   * The image toolkit.
+   *
+   * @var \Drupal\system\Plugin\ImageToolkitInterface
+   */
   protected $toolkit;
+
+  /**
+   * The URI for the file.
+   *
+   * @var string
+   */
   protected $file;
+
+  /**
+   * The image object for the test file.
+   *
+   * @var \Drupal\Core\Image\ImageInterface
+   */
   protected $image;
 
   function setUp() {
@@ -38,17 +54,27 @@ function setUp() {
     $file = current($this->drupalGetTestFiles('image'));
     $this->file = $file->uri;
 
-    // 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;
+    // Setup a dummy image to work with.
+    $this->image = $this->getImage();
 
     // Clear out any hook calls.
     $this->imageTestReset();
   }
 
+  /**
+   * Sets up an image with the custom toolkit.
+   *
+   * @return \Drupal\Core\Image\ImageInterface
+   *   The image object.
+   */
+  protected function getImage() {
+    $image = $this->container->get('image.factory')
+      ->setToolkit($this->toolkit)
+      ->get($this->file);
+    $image->getResource();
+    return $image;
+  }
+
   /**
    * Assert that all of the specified image toolkit operations were called
    * exactly once once, other values result in failure.
diff --git a/core/modules/system/system.api.php b/core/modules/system/system.api.php
index 8ef7556d3f73..134ec5eca916 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 = Drupal::service('image.factory')->get($uri);
+      return array('Content-Type' => $image->getMimeType());
     }
   }
 }
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 12ebae711046..7f1492f08860 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\ImageInterface;
 use Drupal\system\Plugin\ImageToolkitInterface;
 
 /**
@@ -23,7 +24,7 @@
 class TestToolkit extends PluginBase implements ImageToolkitInterface {
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::settingsForm().
+   * {@inheritdoc}
    */
   public function settingsForm() {
     $this->logCall('settings', array());
@@ -31,30 +32,45 @@ public function settingsForm() {
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::settingsFormSubmit().
+   * {@inheritdoc}
    */
   public function settingsFormSubmit($form, &$form_state) {}
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::getInfo().
+   * {@inheritdoc}
    */
-  public function getInfo($image) {
+  public function getInfo(ImageInterface $image) {
     $this->logCall('get_info', array($image));
-    return array();
+
+    $details = FALSE;
+    $data = getimagesize($image->getSource());
+
+    if (isset($data) && is_array($data)) {
+      $extensions = array('1' => 'gif', '2' => 'jpg', '3' => 'png');
+      $extension = isset($extensions[$data[2]]) ?  $extensions[$data[2]] : '';
+      $details = array(
+        'width'     => $data[0],
+        'height'    => $data[1],
+        'extension' => $extension,
+        'mime_type' => $data['mime'],
+      );
+    }
+
+    return $details;
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::load().
+   * {@inheritdoc}
    */
-  public function load($image) {
+  public function load(ImageInterface $image) {
     $this->logCall('load', array($image));
     return $image;
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::save().
+   * {@inheritdoc}
    */
-  public function save($image, $destination) {
+  public function save(ImageInterface $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.
@@ -62,33 +78,33 @@ public function save($image, $destination) {
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::crop().
+   * {@inheritdoc}
    */
-  public function crop($image, $x, $y, $width, $height) {
+  public function crop(ImageInterface $image, $x, $y, $width, $height) {
     $this->logCall('crop', array($image, $x, $y, $width, $height));
     return TRUE;
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::resize().
+   * {@inheritdoc}
    */
-  public function resize($image, $width, $height) {
+  public function resize(ImageInterface $image, $width, $height) {
     $this->logCall('resize', array($image, $width, $height));
     return TRUE;
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::rotate().
+   * {@inheritdoc}
    */
-  public function rotate($image, $degrees, $background = NULL) {
+  public function rotate(ImageInterface $image, $degrees, $background = NULL) {
     $this->logCall('rotate', array($image, $degrees, $background));
     return TRUE;
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::desaturate().
+   * {@inheritdoc}
    */
-  public function desaturate($image) {
+  public function desaturate(ImageInterface $image) {
     $this->logCall('desaturate', array($image));
     return TRUE;
   }
@@ -112,7 +128,7 @@ protected function logCall($op, $args) {
   }
 
   /**
-   * Implements Drupal\system\Plugin\ImageToolkitInterface::isAvailable().
+   * {@inheritdoc}
    */
   public static function isAvailable() {
     return TRUE;
diff --git a/core/tests/Drupal/Tests/Component/Image/ImageTest.php b/core/tests/Drupal/Tests/Component/Image/ImageUtilityTest.php
similarity index 92%
rename from core/tests/Drupal/Tests/Component/Image/ImageTest.php
rename to core/tests/Drupal/Tests/Component/Image/ImageUtilityTest.php
index 05d1be9e13fb..e31f8bbf7065 100644
--- a/core/tests/Drupal/Tests/Component/Image/ImageTest.php
+++ b/core/tests/Drupal/Tests/Component/Image/ImageUtilityTest.php
@@ -2,24 +2,25 @@
 
 /**
  * @file
- * Contains \Drupal\Tests\Component\Image\ImageTest.
+ * Contains \Drupal\Tests\Component\Image\ImageUtilityTest.
  */
 
 namespace Drupal\Tests\Component\Image;
 
-use Drupal\Component\Image\Image;
+use Drupal\Component\Utility\Image;
 use Drupal\Tests\UnitTestCase;
 
 /**
- * Tests the Image component.
+ * Tests the Image utility component.
  *
- * @see \Drupal\Component\Image\Image
+ * @see \Drupal\Component\Utility\Image
  */
-class ImageTest extends UnitTestCase {
+class ImageUtilityTest extends UnitTestCase {
+
   public static function getInfo() {
     return array(
       'name' => 'Tests for the Image component',
-      'description' => 'Tests all control flow branches in Drupal\Component\Image\Image.',
+      'description' => 'Tests all control flow branches in \Drupal\Component\Utility\Image.',
       'group' => 'Image',
     );
   }
@@ -58,7 +59,7 @@ function testScaleDimensions($input, $output) {
    */
   public function providerTestScaleDimensions() {
     // Define input / output datasets to test different branch conditions.
-    $test = array();
+    $tests = array();
 
     // Test branch conditions:
     // - No height.
@@ -168,4 +169,5 @@ public function providerTestScaleDimensions() {
 
     return $tests;
   }
+
 }
diff --git a/core/tests/Drupal/Tests/Core/Image/ImageTest.php b/core/tests/Drupal/Tests/Core/Image/ImageTest.php
new file mode 100644
index 000000000000..c96f4c919c3d
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Image/ImageTest.php
@@ -0,0 +1,358 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Image\ImageTest.
+ */
+
+namespace Drupal\Tests\Core\Image;
+
+use Drupal\Core\Image\Image;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the image class.
+ */
+class ImageTest extends UnitTestCase {
+
+  /**
+   * Image object.
+   *
+   * @var \Drupal\Core\Image\Image
+   */
+  protected $image;
+
+  /**
+   * Image toolkit.
+   *
+   * @var \Drupal\system\Plugin\ImageToolkitInterface
+   */
+  protected $toolkit;
+
+  public static function getInfo() {
+    return array(
+      'name' => 'Image class functionality',
+      'description' => 'Tests the Image class.',
+      'group' => 'Image',
+    );
+  }
+
+  protected function setUp() {
+    // Use the Druplicon image.
+    $source = __DIR__ . '/../../../../../misc/druplicon.png';
+    $this->toolkit = $this->getMockBuilder('Drupal\system\Plugin\ImageToolkit\GDToolkit')
+      ->disableOriginalConstructor()
+      ->getMock();
+
+    $this->toolkit->expects($this->any())
+      ->method('getPluginId')
+      ->will($this->returnValue('gd'));
+
+    $this->toolkit->expects($this->any())
+      ->method('getInfo')
+      ->will($this->returnValue(array(
+        'width'     => 88,
+        'height'    => 100,
+        'extension' => 'png',
+        'mime_type' => 'image/png',
+      )));
+
+    $this->image = new Image($source, $this->toolkit);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::getExtension().
+   */
+  public function testGetExtension() {
+    $this->assertEquals($this->image->getExtension(), 'png');
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::getHeight().
+   */
+  public function testGetHeight() {
+    $this->assertEquals($this->image->getHeight(), 100);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::setHeight().
+   */
+  public function testSetHeight() {
+    $this->image->getHeight();
+    $this->image->setHeight(400);
+    $this->assertEquals($this->image->getHeight(), 400);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::getWidth().
+   */
+  public function testGetWidth() {
+    $this->assertEquals($this->image->getWidth(), 88);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::setWidth().
+   */
+  public function testSetWidth() {
+    $this->image->getHeight();
+    $this->image->setWidth(337);
+    $this->assertEquals($this->image->getWidth(), 337);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::getFileSize
+   */
+  public function testGetFileSize() {
+    $this->assertEquals($this->image->getFileSize(), 3905);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::getMimeType().
+   */
+  public function testGetMimeType() {
+    $this->assertEquals($this->image->getMimeType(), 'image/png');
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::setResource().
+   */
+  public function testSetResource() {
+    $resource = fopen($this->image->getSource(), 'r');
+    $this->image->setResource($resource);
+    $this->assertEquals($this->image->getResource(), $resource);
+
+    // Force \Drupal\Core\Image\Image::hasResource() to return FALSE.
+    $this->image->setResource(FALSE);
+    $this->assertNotNull($this->image->getResource());
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::hasResource().
+   */
+  public function testHasResource() {
+    $this->assertFalse($this->image->hasResource());
+    $resource = fopen($this->image->getSource(), 'r');
+    $this->image->setResource($resource);
+    $this->assertTrue($this->image->hasResource());
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::setSource().
+   */
+  public function testSetSource() {
+    $source = __DIR__ . '/../../../../../misc/grippie.png';
+    $this->image->setSource($source);
+    $this->assertEquals($this->image->getSource(), $source);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::getToolkitId().
+   */
+  public function testGetToolkitId() {
+    $this->assertEquals($this->image->getToolkitId(), 'gd');
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::save().
+   */
+  public function testSave() {
+    // This will fail if save() method isn't called on the toolkit.
+    $this->toolkit->expects($this->once())
+      ->method('save')
+      ->will($this->returnValue(TRUE));
+
+    $image = $this->getMock('Drupal\Core\Image\Image', array('chmod'), array($this->image->getSource(), $this->toolkit));
+    $image->expects($this->any())
+      ->method('chmod')
+      ->will($this->returnValue(TRUE));
+
+    $image->save();
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::save().
+   */
+  public function testSaveFails() {
+    // This will fail if save() method isn't called on the toolkit.
+    $this->toolkit->expects($this->once())
+      ->method('save')
+      ->will($this->returnValue(FALSE));
+
+    $this->assertFalse($this->image->save());
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::save().
+   */
+  public function testChmodFails() {
+    // This will fail if save() method isn't called on the toolkit.
+    $this->toolkit->expects($this->once())
+      ->method('save')
+      ->will($this->returnValue(TRUE));
+
+    $image = $this->getMock('Drupal\Core\Image\Image', array('chmod'), array($this->image->getSource(), $this->toolkit));
+    $image->expects($this->any())
+      ->method('chmod')
+      ->will($this->returnValue(FALSE));
+
+    $this->assertFalse($image->save());
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::save().
+   */
+  public function testProcessInfoFails() {
+    $this->image->setSource('magic-foobars.png');
+    $this->assertFalse((bool) $this->image->getWidth());
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::scale().
+   */
+  public function testScaleWidth() {
+    $this->toolkit->expects($this->once())
+      ->method('resize')
+      ->will($this->returnArgument(2));
+    $height = $this->image->scale(44);
+    $this->assertEquals($height, 50);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::scale().
+   */
+  public function testScaleHeight() {
+    $this->toolkit->expects($this->once())
+      ->method('resize')
+      ->will($this->returnArgument(1));
+
+    $width = $this->image->scale(NULL, 50);
+    $this->assertEquals($width, 44);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::scale().
+   */
+  public function testScaleSame() {
+    // Dimensions are the same, resize should not be called.
+    $this->toolkit->expects($this->never())
+      ->method('resize')
+      ->will($this->returnArgument(1));
+
+    $width = $this->image->scale(88, 100);
+    $this->assertEquals($width, 88);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::scaleAndCrop().
+   */
+  public function testScaleAndCropWidth() {
+    $this->toolkit->expects($this->once())
+      ->method('resize')
+      ->will($this->returnValue(TRUE));
+
+    $this->toolkit->expects($this->once())
+      ->method('crop')
+      ->will($this->returnArgument(1));
+
+    $x = $this->image->scaleAndCrop(34, 50);
+    $this->assertEquals($x, 5);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::scaleAndCrop().
+   */
+  public function testScaleAndCropHeight() {
+    $this->toolkit->expects($this->once())
+      ->method('resize')
+      ->will($this->returnValue(TRUE));
+
+    $this->toolkit->expects($this->once())
+      ->method('crop')
+      ->will($this->returnArgument(2));
+
+    $y = $this->image->scaleAndCrop(44, 40);
+    $this->assertEquals($y, 5);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::scaleAndCrop().
+   */
+  public function testScaleAndCropFails() {
+    $this->toolkit->expects($this->once())
+      ->method('resize')
+      ->will($this->returnValue(FALSE));
+
+    $this->toolkit->expects($this->never())
+      ->method('crop');
+    $this->image->scaleAndCrop(44, 40);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::crop().
+   */
+  public function testCropWidth() {
+    $this->toolkit->expects($this->once())
+      ->method('crop')
+      ->will($this->returnArgument(4));
+    // Cropping with width only should preserve the aspect ratio.
+    $height = $this->image->crop(0, 0, 44, NULL);
+    $this->assertEquals($height, 50);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::crop().
+   */
+  public function testCropHeight() {
+    $this->toolkit->expects($this->once())
+      ->method('crop')
+      ->will($this->returnArgument(3));
+    // Cropping with height only should preserve the aspect ratio.
+    $width = $this->image->crop(0, 0, NULL, 50);
+    $this->assertEquals($width, 44);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::crop().
+   */
+  public function testCrop() {
+    $this->toolkit->expects($this->once())
+      ->method('crop')
+      ->will($this->returnArgument(3));
+    $width = $this->image->crop(0, 0, 44, 50);
+    $this->assertEquals($width, 44);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::resize().
+   */
+  public function testResize() {
+    $this->toolkit->expects($this->exactly(2))
+      ->method('resize')
+      ->will($this->returnArgument(1));
+    // Resize with integer for width and height.
+    $this->image->resize(30, 40);
+    // Pass a float for width.
+    $width = $this->image->resize(30.4, 40);
+    // Ensure that the float was rounded to an integer first.
+    $this->assertEquals($width, 30);
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::desaturate().
+   */
+  public function testDesaturate() {
+    $this->toolkit->expects($this->once())
+      ->method('desaturate');
+    $this->image->desaturate();
+  }
+
+  /**
+   * Tests \Drupal\Core\Image\Image::rotate().
+   */
+  public function testRotate() {
+    $this->toolkit->expects($this->once())
+      ->method('rotate');
+    $this->image->rotate(90);
+  }
+
+}
-- 
GitLab