diff --git a/core/includes/image.inc b/core/includes/image.inc
index f6ae7f1..34d0d32 100644
--- a/core/includes/image.inc
+++ b/core/includes/image.inc
@@ -5,6 +5,8 @@
  * API for manipulating images.
  */
 
+use Drupal\system\Plugin\ImageToolkitManager;
+
 /**
  * @defgroup image Image toolkits
  * @{
@@ -39,15 +41,17 @@
  * @return
  *   An array with the toolkit names as keys and the descriptions as values.
  */
-function image_get_available_toolkits() {
+function image_get_available_toolkits($key = NULL) {
   // hook_image_toolkits returns an array of toolkit names.
-  $toolkits = module_invoke_all('image_toolkits');
+
+  $manager = new ImageToolkitManager();
+  $toolkits = $manager->getDefinitions();
 
   $output = array();
-  foreach ($toolkits as $name => $info) {
+  foreach ($toolkits as $id => $definition) {
     // Only allow modules that aren't marked as unavailable.
-    if ($info['available']) {
-      $output[$name] = $info['title'];
+    if (call_user_func($definition['class'] . '::checkAvailable')) {
+      $output[$id] = $definition;
     }
   }
 
@@ -58,46 +62,32 @@ function image_get_available_toolkits() {
  * Retrieve the name of the currently used toolkit.
  *
  * @return
- *   String containing the name of the selected toolkit, or FALSE on error.
+ *   Object of the default toolkit, or FALSE on error.
  */
-function image_get_toolkit() {
-  static $toolkit;
+function image_get_toolkit($toolkit_id = NULL) {
+  static $toolkit = array();
 
-  if (!isset($toolkit)) {
+  if (empty($toolkit_id)) {
+    $toolkit_id = config('system.image')->get('toolkit');
+  }
+
+  if (!isset($toolkit[$toolkit_id])) {
     $toolkits = image_get_available_toolkits();
-    $toolkit = variable_get('image_toolkit', 'gd');
-    if (!isset($toolkits[$toolkit]) || !function_exists('image_' . $toolkit . '_load')) {
+
+    if (!isset($toolkits[$toolkit_id]) || !class_exists($toolkits[$toolkit_id]['class'])) {
       // The selected toolkit isn't available so return the first one found. If
       // none are available this will return FALSE.
       reset($toolkits);
-      $toolkit = key($toolkits);
+      $toolkit_id = key($toolkits);
     }
-  }
-
-  return $toolkit;
-}
 
-/**
- * Invokes the given method using the currently selected toolkit.
- *
- * @param $method
- *   A string containing the method to invoke.
- * @param $image
- *   An image object returned by image_load().
- * @param $params
- *   An optional array of parameters to pass to the toolkit method.
- *
- * @return
- *   Mixed values (typically Boolean indicating successful operation).
- */
-function image_toolkit_invoke($method, stdClass $image, array $params = array()) {
-  $function = 'image_' . $image->toolkit . '_' . $method;
-  if (function_exists($function)) {
-    array_unshift($params, $image);
-    return call_user_func_array($function, $params);
+    if ($toolkit_id) {
+      $manager = new ImageToolkitManager();
+      $toolkit[$toolkit_id] = $manager->createInstance($toolkit_id);
+    }
   }
-  watchdog('image', 'The selected image handling toolkit %toolkit can not correctly process %function.', array('%toolkit' => $image->toolkit, '%function' => $function), WATCHDOG_ERROR);
-  return FALSE;
+
+  return $toolkit[$toolkit_id];
 }
 
 /**
@@ -110,7 +100,7 @@ function image_toolkit_invoke($method, stdClass $image, array $params = array())
  * @param $filepath
  *   String specifying the path of the image file.
  * @param $toolkit
- *   An optional image toolkit name to override the default.
+ *   An optional image toolkit object to override the default.
  *
  * @return
  *   FALSE, if the file could not be found or is not an image. Otherwise, a
@@ -134,7 +124,7 @@ function image_get_info($filepath, $toolkit = FALSE) {
     $image = new stdClass();
     $image->source = $filepath;
     $image->toolkit = $toolkit;
-    $details = image_toolkit_invoke('get_info', $image);
+    $details = $toolkit->getInfo($image);
     if (isset($details) && is_array($details)) {
       $details['file_size'] = filesize($filepath);
     }
@@ -280,7 +270,7 @@ function image_resize(stdClass $image, $width, $height) {
   $width = (int) round($width);
   $height = (int) round($height);
 
-  return image_toolkit_invoke('resize', $image, array($width, $height));
+  return $image->toolkit->resize($image, $width, $height);
 }
 
 /**
@@ -304,7 +294,7 @@ function image_resize(stdClass $image, $width, $height) {
  * @see image_gd_rotate()
  */
 function image_rotate(stdClass $image, $degrees, $background = NULL) {
-  return image_toolkit_invoke('rotate', $image, array($degrees, $background));
+  return $image->toolkit->rotate($image, $degrees, $background);
 }
 
 /**
@@ -336,7 +326,7 @@ function image_crop(stdClass $image, $x, $y, $width, $height) {
   $width = (int) round($width);
   $height = (int) round($height);
 
-  return image_toolkit_invoke('crop', $image, array($x, $y, $width, $height));
+  return $image->toolkit->crop($image, $x, $y, $width, $height);
 }
 
 /**
@@ -352,7 +342,7 @@ function image_crop(stdClass $image, $x, $y, $width, $height) {
  * @see image_gd_desaturate()
  */
 function image_desaturate(stdClass $image) {
-  return image_toolkit_invoke('desaturate', $image);
+  return $image->toolkit->desaturate($image);
 }
 
 
@@ -364,7 +354,7 @@ function image_desaturate(stdClass $image) {
  * @param $file
  *   Path to an image file.
  * @param $toolkit
- *   An optional, image toolkit name to override the default.
+ *   An optional, image toolkit object to override the default.
  *
  * @return
  *   An image object or FALSE if there was a problem loading the file. The
@@ -391,7 +381,7 @@ function image_load($file, $toolkit = FALSE) {
     $image->info = image_get_info($file, $toolkit);
     if (isset($image->info) && is_array($image->info)) {
       $image->toolkit = $toolkit;
-      if (image_toolkit_invoke('load', $image)) {
+      if ($toolkit->load($image)) {
         return $image;
       }
     }
@@ -419,7 +409,7 @@ function image_save(stdClass $image, $destination = NULL) {
   if (empty($destination)) {
     $destination = $image->source;
   }
-  if ($return = image_toolkit_invoke('save', $image, array($destination))) {
+  if ($return = $image->toolkit->save($image, $destination)) {
     // Clear the cached file size and refresh the image information.
     clearstatcache();
     $image->info = image_get_info($destination, $image->toolkit);
diff --git a/core/modules/image/image.effects.inc b/core/modules/image/image.effects.inc
index 35a6a74..4f7379b 100644
--- a/core/modules/image/image.effects.inc
+++ b/core/modules/image/image.effects.inc
@@ -79,7 +79,7 @@ function image_image_effect_info() {
  */
 function image_resize_effect(&$image, $data) {
   if (!image_resize($image, $data['width'], $data['height'])) {
-    watchdog('image', 'Image resize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit, '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+    watchdog('image', 'Image resize failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => get_class($image->toolkit), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
     return FALSE;
   }
   return TRUE;
@@ -130,7 +130,7 @@ function image_scale_effect(&$image, $data) {
   );
 
   if (!image_scale($image, $data['width'], $data['height'], $data['upscale'])) {
-    watchdog('image', 'Image scale failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit, '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+    watchdog('image', 'Image scale failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => get_class($image->toolkit), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
     return FALSE;
   }
   return TRUE;
@@ -184,7 +184,7 @@ function image_crop_effect(&$image, $data) {
   $x = image_filter_keyword($x, $image->info['width'], $data['width']);
   $y = image_filter_keyword($y, $image->info['height'], $data['height']);
   if (!image_crop($image, $x, $y, $data['width'], $data['height'])) {
-    watchdog('image', 'Image crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit, '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+    watchdog('image', 'Image crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => get_class($image->toolkit), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
     return FALSE;
   }
   return TRUE;
@@ -206,7 +206,7 @@ function image_crop_effect(&$image, $data) {
  */
 function image_scale_and_crop_effect(&$image, $data) {
   if (!image_scale_and_crop($image, $data['width'], $data['height'])) {
-    watchdog('image', 'Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit, '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+    watchdog('image', 'Image scale and crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => get_class($image->toolkit), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
     return FALSE;
   }
   return TRUE;
@@ -225,7 +225,7 @@ function image_scale_and_crop_effect(&$image, $data) {
  */
 function image_desaturate_effect(&$image, $data) {
   if (!image_desaturate($image)) {
-    watchdog('image', 'Image desaturate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit, '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+    watchdog('image', 'Image desaturate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => get_class($image->toolkit), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
     return FALSE;
   }
   return TRUE;
@@ -278,7 +278,7 @@ function image_rotate_effect(&$image, $data) {
   }
 
   if (!image_rotate($image, $data['degrees'], $data['bgcolor'])) {
-    watchdog('image', 'Image rotate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit, '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
+    watchdog('image', 'Image rotate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => get_class($image->toolkit), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
     return FALSE;
   }
   return TRUE;
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php
index fb3269c..b7a5b67 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php
@@ -20,7 +20,7 @@ class ImageEffectsTest extends ToolkitTestBase {
    *
    * @var array
    */
-  public static $modules = array('image_test');
+  public static $modules = array('image_test', 'image');
 
   public static function getInfo() {
     return array(
diff --git a/core/modules/system/config/system.image.gd.yml b/core/modules/system/config/system.image.gd.yml
new file mode 100644
index 0000000..fbc379f
--- /dev/null
+++ b/core/modules/system/config/system.image.gd.yml
@@ -0,0 +1 @@
+jpeg_quality: '75'
diff --git a/core/modules/system/config/system.image.yml b/core/modules/system/config/system.image.yml
new file mode 100644
index 0000000..9a1688f
--- /dev/null
+++ b/core/modules/system/config/system.image.yml
@@ -0,0 +1 @@
+toolkit: gd
diff --git a/core/modules/system/image.gd.inc b/core/modules/system/image.gd.inc
deleted file mode 100644
index f6f12ae..0000000
--- a/core/modules/system/image.gd.inc
+++ /dev/null
@@ -1,367 +0,0 @@
-<?php
-
-/**
- * @file
- * GD2 toolkit for image manipulation within Drupal.
- */
-
-/**
- * @addtogroup image
- * @{
- */
-
-/**
- * Retrieve settings for the GD2 toolkit.
- */
-function image_gd_settings() {
-  if (image_gd_check_settings()) {
-    $form['status'] = array(
-      '#markup' => t('The GD toolkit is installed and working properly.')
-    );
-
-    $form['image_jpeg_quality'] = array(
-      '#type' => 'number',
-      '#title' => t('JPEG quality'),
-      '#description' => t('Define the image quality for JPEG manipulations. Ranges from 0 to 100. Higher values mean better image quality but bigger files.'),
-      '#min' => 0,
-      '#max' => 100,
-      '#default_value' => variable_get('image_jpeg_quality', 75),
-      '#field_suffix' => t('%'),
-    );
-
-    return $form;
-  }
-  else {
-    form_set_error('image_toolkit', t('The GD image toolkit requires that the GD module for PHP be installed and configured properly. For more information see <a href="@url">PHP\'s image documentation</a>.', array('@url' => 'http://php.net/image')));
-    return FALSE;
-  }
-}
-
-/**
- * Verify GD2 settings (that the right version is actually installed).
- *
- * @return
- *   A boolean indicating if the GD toolkit is available on this machine.
- */
-function image_gd_check_settings() {
-  if ($check = get_extension_funcs('gd')) {
-    if (in_array('imagegd2', $check)) {
-      // GD2 support is available.
-      return TRUE;
-    }
-  }
-  return FALSE;
-}
-
-/**
- * Scale an image to the specified size using GD.
- *
- * @param $image
- *   An image object. The $image->resource, $image->info['width'], and
- *   $image->info['height'] values will be modified by this call.
- * @param $width
- *   The new width of the resized image, in pixels.
- * @param $height
- *   The new height of the resized image, in pixels.
- * @return
- *   TRUE or FALSE, based on success.
- *
- * @see image_resize()
- */
-function image_gd_resize(stdClass $image, $width, $height) {
-  $res = image_gd_create_tmp($image, $width, $height);
-
-  if (!imagecopyresampled($res, $image->resource, 0, 0, 0, 0, $width, $height, $image->info['width'], $image->info['height'])) {
-    return FALSE;
-  }
-
-  imagedestroy($image->resource);
-  // Update image object.
-  $image->resource = $res;
-  $image->info['width'] = $width;
-  $image->info['height'] = $height;
-  return TRUE;
-}
-
-/**
- * Rotate an image the given number of degrees.
- *
- * @param $image
- *   An image object. The $image->resource, $image->info['width'], and
- *   $image->info['height'] values will be modified by this call.
- * @param $degrees
- *   The number of (clockwise) degrees to rotate the image.
- * @param $background
- *   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
- *   TRUE or FALSE, based on success.
- *
- * @see image_rotate()
- */
-function image_gd_rotate(stdClass $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));
-    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);
-  }
-  // Set the background color as transparent if $background is NULL.
-  else {
-    // Get the current transparent color.
-    $background = imagecolortransparent($image->resource);
-
-    // If no transparent colors, use white.
-    if ($background == 0) {
-      $background = imagecolorallocatealpha($image->resource, 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 ($transparent_index != 0) {
-      $transparent_gif_color = imagecolorsforindex($image->resource, $transparent_index);
-    }
-  }
-
-  $image->resource = imagerotate($image->resource, 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);
-  }
-
-  $image->info['width'] = imagesx($image->resource);
-  $image->info['height'] = imagesy($image->resource);
-  return TRUE;
-}
-
-/**
- * Crop an image using the GD toolkit.
- *
- * @param $image
- *   An image object. The $image->resource, $image->info['width'], and
- *   $image->info['height'] values will be modified by this call.
- * @param $x
- *   The starting x offset at which to start the crop, in pixels.
- * @param $y
- *   The starting y offset at which to start the crop, in pixels.
- * @param $width
- *   The width of the cropped area, in pixels.
- * @param $height
- *   The height of the cropped area, in pixels.
- * @return
- *   TRUE or FALSE, based on success.
- *
- * @see image_crop()
- */
-function image_gd_crop(stdClass $image, $x, $y, $width, $height) {
-  $res = image_gd_create_tmp($image, $width, $height);
-
-  if (!imagecopyresampled($res, $image->resource, 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;
-  return TRUE;
-}
-
-/**
- * Convert an image resource to grayscale.
- *
- * Note that transparent GIFs loose transparency when desaturated.
- *
- * @param $image
- *   An image object. The $image->resource value will be modified by this call.
- * @return
- *   TRUE or FALSE, based on success.
- *
- * @see image_desaturate()
- */
-function image_gd_desaturate(stdClass $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));
-    return FALSE;
-  }
-
-  return imagefilter($image->resource, IMG_FILTER_GRAYSCALE);
-}
-
-/**
- * GD helper function to create an image resource from a file.
- *
- * @param $image
- *   An image object. The $image->resource value will populated by this call.
- * @return
- *   TRUE or FALSE, based on success.
- *
- * @see image_load()
- */
-function image_gd_load(stdClass $image) {
-  $extension = str_replace('jpg', 'jpeg', $image->info['extension']);
-  $function = 'imagecreatefrom' . $extension;
-  if (function_exists($function) && $image->resource = $function($image->source)) {
-    if (!imageistruecolor($image->resource)) {
-      // Convert indexed images to true color, so that filters work
-      // correctly and don't result in unnecessary dither.
-      $new_image = image_gd_create_tmp($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;
-    }
-    return (bool) $image->resource;
-  }
-
-  return FALSE;
-}
-
-/**
- * GD helper to write an image resource to a destination file.
- *
- * @param $image
- *   An image object.
- * @param $destination
- *   A string file URI or path where the image should be saved.
- * @return
- *   TRUE or FALSE, based on success.
- *
- * @see image_save()
- */
-function image_gd_save(stdClass $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)) {
-    // If destination is not local, save image to temporary local file.
-    $local_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
-    if (!isset($local_wrappers[$scheme])) {
-      $permanent_destination = $destination;
-      $destination = drupal_tempnam('temporary://', 'gd_');
-    }
-    // Convert stream wrapper URI to normal path.
-    $destination = drupal_realpath($destination);
-  }
-
-  $extension = str_replace('jpg', 'jpeg', $image->info['extension']);
-  $function = 'image' . $extension;
-  if (!function_exists($function)) {
-    return FALSE;
-  }
-  if ($extension == 'jpeg') {
-    $success = $function($image->resource, $destination, variable_get('image_jpeg_quality', 75));
-  }
-  else {
-    // Always save PNG images with full transparency.
-    if ($extension == 'png') {
-      imagealphablending($image->resource, FALSE);
-      imagesavealpha($image->resource, TRUE);
-    }
-    $success = $function($image->resource, $destination);
-  }
-  // Move temporary local file to remote destination.
-  if (isset($permanent_destination) && $success) {
-    return (bool) file_unmanaged_move($destination, $permanent_destination, FILE_EXISTS_REPLACE);
-  }
-  return $success;
-}
-
-/**
- * Create a truecolor image preserving transparency from a provided image.
- *
- * @param $image
- *   An image object.
- * @param $width
- *   The new width of the new image, in pixels.
- * @param $height
- *   The new height of the new image, in pixels.
- * @return
- *   A GD image handle.
- */
-function image_gd_create_tmp(stdClass $image, $width, $height) {
-  $res = imagecreatetruecolor($width, $height);
-
-  if ($image->info['extension'] == 'gif') {
-    // Grab transparent color index from image resource.
-    $transparent = imagecolortransparent($image->resource);
-
-    if ($transparent >= 0) {
-      // The original must have a transparent color, allocate to the new image.
-      $transparent_color = imagecolorsforindex($image->resource, $transparent);
-      $transparent = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
-
-      // Flood with our new transparent color.
-      imagefill($res, 0, 0, $transparent);
-      imagecolortransparent($res, $transparent);
-    }
-  }
-  elseif ($image->info['extension'] == 'png') {
-    imagealphablending($res, FALSE);
-    $transparency = imagecolorallocatealpha($res, 0, 0, 0, 127);
-    imagefill($res, 0, 0, $transparency);
-    imagealphablending($res, TRUE);
-    imagesavealpha($res, TRUE);
-  }
-  else {
-    imagefill($res, 0, 0, imagecolorallocate($res, 255, 255, 255));
-  }
-
-  return $res;
-}
-
-/**
- * Get details about an image.
- *
- * @param $image
- *   An image object.
- * @return
- *   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').
- *
- * @see image_get_info()
- */
-function image_gd_get_info(stdClass $image) {
-  $details = FALSE;
-  $data = getimagesize($image->source);
-
-  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;
-}
-
-/**
- * @} End of "addtogroup image".
- */
diff --git a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php
new file mode 100644
index 0000000..2981f7a
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php
@@ -0,0 +1,156 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Plugin\ImageToolkitInterface.
+ */
+
+namespace Drupal\system\Plugin;
+use stdClass;
+
+/**
+ * Defines an interface for image toolkits.
+ *
+ * An image toolkit provides common image file manipulations like scaling,
+ * cropping, and rotating.
+ */
+interface ImageToolkitInterface {
+
+  /**
+   * Retrieve toolkit's settings form.
+   *
+   * @see system_image_toolkit_settings()
+   */
+  function settingsForm();
+
+  /**
+   * Form submission handler for toolkit's settings form.
+   *
+   * @see system_image_toolkit_settings_submit()
+   */
+  function settingsFormSubmit($form, &$form_state);
+
+  /**
+   * Scale an image to the specified size.
+   *
+   * @param $image
+   *   An image object. The $image->resource, $image->info['width'], and
+   *   $image->info['height'] values will be modified by this call.
+   * @param $width
+   *   The new width of the resized image, in pixels.
+   * @param $height
+   *   The new height of the resized image, in pixels.
+   * @return
+   *   TRUE or FALSE, based on success.
+   *
+   * @see image_resize()
+   */
+  function resize(stdClass $image, $width, $height);
+
+  /**
+   * Rotate an image the given number of degrees.
+   *
+   * @param $image
+   *   An image object. The $image->resource, $image->info['width'], and
+   *   $image->info['height'] values will be modified by this call.
+   * @param $degrees
+   *   The number of (clockwise) degrees to rotate the image.
+   * @param $background
+   *   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
+   *   TRUE or FALSE, based on success.
+   *
+   * @see image_rotate()
+   */
+  function rotate(stdClass $image, $degrees, $background = NULL);
+
+  /**
+   * Crop an image.
+   *
+   * @param $image
+   *   An image object. The $image->resource, $image->info['width'], and
+   *   $image->info['height'] values will be modified by this call.
+   * @param $x
+   *   The starting x offset at which to start the crop, in pixels.
+   * @param $y
+   *   The starting y offset at which to start the crop, in pixels.
+   * @param $width
+   *   The width of the cropped area, in pixels.
+   * @param $height
+   *   The height of the cropped area, in pixels.
+   * @return
+   *   TRUE or FALSE, based on success.
+   *
+   * @see image_crop()
+   */
+  function crop(stdClass $image, $x, $y, $width, $height);
+
+  /**
+   * Convert an image resource to grayscale.
+   *
+   * Note that transparent GIFs loose transparency when desaturated.
+   *
+   * @param $image
+   *   An image object. The $image->resource value will be modified by this call.
+   * @return
+   *   TRUE or FALSE, based on success.
+   *
+   * @see image_desaturate()
+   */
+  function desaturate(stdClass $image);
+
+  /**
+   * Create an image resource from a file.
+   *
+   * @param $image
+   *   An image object. The $image->resource value will populated by this call.
+   * @return
+   *   TRUE or FALSE, based on success.
+   *
+   * @see image_load()
+   */
+  function load(stdClass $image);
+
+  /**
+   * Write an image resource to a destination file.
+   *
+   * @param $image
+   *   An image object.
+   * @param $destination
+   *   A string file URI or path where the image should be saved.
+   * @return
+   *   TRUE or FALSE, based on success.
+   *
+   * @see image_save()
+   */
+  function save(stdClass $image, $destination);
+
+  /**
+   * Get details about an image.
+   *
+   * @param $image
+   *   An image object.
+   * @return
+   *   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').
+   *
+   * @see image_get_info()
+   */
+  function getInfo(stdClass $image);
+
+  /**
+   * Verify Image Toolkit is set up correctly.
+   *
+   * @return
+   *   A boolean indicating if the GD toolkit is available on this machine.
+   */
+  static function checkAvailable();
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitManager.php b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitManager.php
new file mode 100644
index 0000000..d801de0
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitManager.php
@@ -0,0 +1,24 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Plugin\ImageToolkitManager.
+ */
+
+namespace Drupal\system\Plugin;
+
+use Drupal\Component\Plugin\Factory\DefaultFactory;
+use Drupal\Component\Plugin\PluginManagerBase;
+use Drupal\Core\Plugin\Discovery\AnnotatedClassDiscovery;
+
+/**
+ * Manages aggregator fetcher plugins.
+ */
+class ImageToolkitManager extends PluginManagerBase {
+
+  public function __construct() {
+    //$this->discovery = new HookDiscovery('image_toolkits');
+    $this->discovery = new AnnotatedClassDiscovery('system', 'imagetoolkit');
+    $this->factory = new DefaultFactory($this->discovery);
+  }
+}
diff --git a/core/modules/system/lib/Drupal/system/Plugin/system/imagetoolkit/GDToolkit.php b/core/modules/system/lib/Drupal/system/Plugin/system/imagetoolkit/GDToolkit.php
new file mode 100644
index 0000000..0a1b2af
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/system/imagetoolkit/GDToolkit.php
@@ -0,0 +1,301 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\system\Plugin\system\imagetoolkit\GDToolkit;.
+ */
+
+namespace Drupal\system\Plugin\system\imagetoolkit;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\system\Plugin\ImageToolkitInterface;
+use stdClass;
+
+/**
+ * Defines the GD2 toolkit for image manipulation within Drupal.
+ *
+ * @Plugin(
+ *   id = "gd",
+ *   title = @Translation("GD2 image manipulation toolkit"),
+ *   description = @Translation("GD2 image manipulation toolkit.")
+ * )
+ */
+class GDToolkit implements ImageToolkitInterface {
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::settingsForm().
+   */
+  function settingsForm() {
+    if ($this->checkAvailable()) {
+      $form['status'] = array(
+        '#markup' => t('The GD toolkit is installed and working properly.')
+      );
+
+      $form['image_jpeg_quality'] = array(
+        '#type' => 'number',
+        '#title' => t('JPEG quality'),
+        '#description' => t('Define the image quality for JPEG manipulations. Ranges from 0 to 100. Higher values mean better image quality but bigger files.'),
+        '#min' => 0,
+        '#max' => 100,
+        '#default_value' => config('system.image.gd')->get('jpeg_quality'),
+        '#field_suffix' => t('%'),
+      );
+      return $form;
+    }
+    else {
+      form_set_error('image_toolkit', t('The GD image toolkit requires that the GD module for PHP be installed and configured properly. For more information see <a href="@url">PHP\'s image documentation</a>.', array('@url' => 'http://php.net/image')));
+      return FALSE;
+    }
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::settingsFormSubmit().
+   */
+  function settingsFormSubmit($form, &$form_state) {
+    config('system.image.gd')
+      ->set('jpeg_quality', $form_state['values']['gd']['image_jpeg_quality'])
+      ->save();
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::resize().
+   */
+  function resize(stdClass $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'])) {
+      return FALSE;
+    }
+
+    imagedestroy($image->resource);
+    // Update image object.
+    $image->resource = $res;
+    $image->info['width'] = $width;
+    $image->info['height'] = $height;
+    return TRUE;
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::rotate().
+   */
+  function rotate(stdClass $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));
+      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);
+    }
+    // Set the background color as transparent if $background is NULL.
+    else {
+      // Get the current transparent color.
+      $background = imagecolortransparent($image->resource);
+
+      // If no transparent colors, use white.
+      if ($background == 0) {
+        $background = imagecolorallocatealpha($image->resource, 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 ($transparent_index != 0) {
+        $transparent_gif_color = imagecolorsforindex($image->resource, $transparent_index);
+      }
+    }
+
+    $image->resource = imagerotate($image->resource, 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);
+    }
+
+    $image->info['width'] = imagesx($image->resource);
+    $image->info['height'] = imagesy($image->resource);
+    return TRUE;
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::crop().
+   */
+  function crop(stdClass $image, $x, $y, $width, $height) {
+    $res = $this->createTmp($image, $width, $height);
+
+    if (!imagecopyresampled($res, $image->resource, 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;
+    return TRUE;
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::desaturate().
+   */
+  function desaturate(stdClass $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));
+      return FALSE;
+    }
+
+    return imagefilter($image->resource, IMG_FILTER_GRAYSCALE);
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::load().
+   */
+  function load(stdClass $image) {
+    $extension = str_replace('jpg', 'jpeg', $image->info['extension']);
+    $function = 'imagecreatefrom' . $extension;
+    if (function_exists($function) && $image->resource = $function($image->source)) {
+      if (!imageistruecolor($image->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;
+      }
+      return (bool) $image->resource;
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::save().
+   */
+  function save(stdClass $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)) {
+      // If destination is not local, save image to temporary local file.
+      $local_wrappers = file_get_stream_wrappers(STREAM_WRAPPERS_LOCAL);
+      if (!isset($local_wrappers[$scheme])) {
+        $permanent_destination = $destination;
+        $destination = drupal_tempnam('temporary://', 'gd_');
+      }
+      // Convert stream wrapper URI to normal path.
+      $destination = drupal_realpath($destination);
+    }
+
+    $extension = str_replace('jpg', 'jpeg', $image->info['extension']);
+    $function = 'image' . $extension;
+    if (!function_exists($function)) {
+      return FALSE;
+    }
+    if ($extension == 'jpeg') {
+      $success = $function($image->resource, $destination, config('system.image.gd')->get('jpeg_quality'));
+    }
+    else {
+      // Always save PNG images with full transparency.
+      if ($extension == 'png') {
+        imagealphablending($image->resource, FALSE);
+        imagesavealpha($image->resource, TRUE);
+      }
+      $success = $function($image->resource, $destination);
+    }
+    // Move temporary local file to remote destination.
+    if (isset($permanent_destination) && $success) {
+      return (bool) file_unmanaged_move($destination, $permanent_destination, FILE_EXISTS_REPLACE);
+    }
+    return $success;
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::getInfo().
+   */
+  function getInfo(stdClass $image) {
+    $details = FALSE;
+    $data = getimagesize($image->source);
+
+    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;
+  }
+
+  /**
+   * Create a truecolor image preserving transparency from a provided image.
+   *
+   * @param $image
+   *   An image object.
+   * @param $width
+   *   The new width of the new image, in pixels.
+   * @param $height
+   *   The new height of the new image, in pixels.
+   * @return
+   *   A GD image handle.
+   */
+  function createTmp(stdClass $image, $width, $height) {
+    $res = imagecreatetruecolor($width, $height);
+
+    if ($image->info['extension'] == 'gif') {
+      // Grab transparent color index from image resource.
+      $transparent = imagecolortransparent($image->resource);
+
+      if ($transparent >= 0) {
+        // The original must have a transparent color, allocate to the new image.
+        $transparent_color = imagecolorsforindex($image->resource, $transparent);
+        $transparent = imagecolorallocate($res, $transparent_color['red'], $transparent_color['green'], $transparent_color['blue']);
+
+        // Flood with our new transparent color.
+        imagefill($res, 0, 0, $transparent);
+        imagecolortransparent($res, $transparent);
+      }
+    }
+    elseif ($image->info['extension'] == 'png') {
+      imagealphablending($res, FALSE);
+      $transparency = imagecolorallocatealpha($res, 0, 0, 0, 127);
+      imagefill($res, 0, 0, $transparency);
+      imagealphablending($res, TRUE);
+      imagesavealpha($res, TRUE);
+    }
+    else {
+      imagefill($res, 0, 0, imagecolorallocate($res, 255, 255, 255));
+    }
+
+    return $res;
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::checkAvailable().
+   */
+  public static function checkAvailable() {
+    if ($check = get_extension_funcs('gd')) {
+      if (in_array('imagegd2', $check)) {
+        // GD2 support is available.
+        return TRUE;
+      }
+    }
+    return FALSE;
+  }
+}
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 611126d..f03a1e5 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\system\Tests\Image;
 
 use Drupal\simpletest\WebTestBase;
+use Drupal\system\Plugin\ImageToolkitManager;
 
 /**
  * Test the core GD image manipulation functions.
@@ -35,8 +36,9 @@ class ToolkitGdTest extends WebTestBase {
   }
 
   protected function checkRequirements() {
-    image_get_available_toolkits();
-    if (!function_exists('image_gd_check_settings') || !image_gd_check_settings()) {
+    $manager = new ImageToolkitManager();
+    $definition = $manager->getDefinition('gd');
+    if (!call_user_func($definition['class'] . '::checkAvailable')) {
       return array(
         'Image manipulations for the GD toolkit cannot run because the GD toolkit is not available.',
       );
@@ -204,7 +206,7 @@ class ToolkitGdTest extends WebTestBase {
     foreach ($files as $file) {
       foreach ($operations as $op => $values) {
         // Load up a fresh image.
-        $image = image_load(drupal_get_path('module', 'simpletest') . '/files/' . $file, 'gd');
+        $image = image_load(drupal_get_path('module', 'simpletest') . '/files/' . $file, image_get_toolkit('gd'));
         if (!$image) {
           $this->fail(t('Could not load image %file.', array('%file' => $file)));
           continue 2;
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 091fc5a..c70719a 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTest.php
@@ -36,7 +36,7 @@ class ToolkitTest extends ToolkitTestBase {
   function testLoad() {
     $image = image_load($this->file, $this->toolkit);
     $this->assertTrue(is_object($image), t('Returned an object.'));
-    $this->assertEqual($this->toolkit, $image->toolkit, t('Image had toolkit set.'));
+    $this->assertEqual($this->toolkit, $image->toolkit);
     $this->assertToolkitOperationsCalled(array('load', 'get_info'));
   }
 
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 ac47080..d06938a 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitTestBase.php
@@ -30,7 +30,7 @@ abstract class ToolkitTestBase extends WebTestBase {
     parent::setUp();
 
     // Use the image_test.module's test toolkit.
-    $this->toolkit = 'test';
+    $this->toolkit = image_get_toolkit('test');
 
     // Pick a file for testing.
     $file = current($this->drupalGetTestFiles('image'));
diff --git a/core/modules/system/system.admin.inc b/core/modules/system/system.admin.inc
index 2e424ad..84df903 100644
--- a/core/modules/system/system.admin.inc
+++ b/core/modules/system/system.admin.inc
@@ -5,6 +5,7 @@
  * Admin page callbacks for the system module.
  */
 
+use Drupal\system\Plugin\ImageToolkitManager;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@@ -1858,39 +1859,59 @@ function system_file_system_settings() {
  * Form builder; Configure site image toolkit usage.
  *
  * @ingroup forms
- * @see system_settings_form()
+ * @see system_image_toolkit_settings_submit()
  */
-function system_image_toolkit_settings() {
+function system_image_toolkit_settings($form, &$form_state) {
+  $config = config('system.image');
+
   $toolkits_available = image_get_available_toolkits();
-  $current_toolkit = image_get_toolkit();
+  $current_toolkit = config('system.image')->get('toolkit');
 
-  if (count($toolkits_available) == 0) {
-    variable_del('image_toolkit');
-    $form['image_toolkit_help'] = array(
-      '#markup' => t("No image toolkits were detected. Drupal includes support for <a href='!gd-link'>PHP's built-in image processing functions</a> but they were not detected on this system. You should consult your system administrator to have them enabled, or try using a third party toolkit.", array('gd-link' => url('http://php.net/gd'))),
-    );
-    return $form;
+  $options = array();
+  foreach($toolkits_available as $id => $definition) {
+    $options[$id] = $definition['title'];
   }
 
-  if (count($toolkits_available) > 1) {
-    $form['image_toolkit'] = array(
-      '#type' => 'radios',
-      '#title' => t('Select an image processing toolkit'),
-      '#default_value' => variable_get('image_toolkit', $current_toolkit),
-      '#options' => $toolkits_available
+  $form['image_toolkit'] = array(
+    '#type' => 'radios',
+    '#title' => t('Select an image processing toolkit'),
+    '#default_value' => $current_toolkit,
+    '#options' => $options,
+  );
+
+  // Get the toolkit settings forms.
+  $manager = new ImageToolkitManager();
+  foreach ($toolkits_available as $id => $definition) {
+    $toolkit = $manager->createInstance($id);
+    $form['image_toolkit_settings'][$id] = array(
+      '#type' => 'fieldset',
+      '#title' => t('@toolkit settings', array('@toolkit' => $definition['title'])),
+      '#collapsible' => TRUE,
+      '#collapsed' => ($id == $current_toolkit) ? FALSE : TRUE,
+      '#tree' => TRUE,
     );
-  }
-  else {
-    variable_set('image_toolkit', key($toolkits_available));
+    $form['image_toolkit_settings'][$id] += $toolkit->settingsForm();
   }
 
-  // Get the toolkit's settings form.
-  $function = 'image_' . $current_toolkit . '_settings';
-  if (function_exists($function)) {
-    $form['image_toolkit_settings'] = $function();
-  }
+  return system_config_form($form, $form_state);
+}
 
-  return system_settings_form($form);
+/**
+ * Form submission handler for system_image_toolkit_settings().
+ */
+function system_image_toolkit_settings_submit($form, &$form_state) {
+  config('system.image')
+    ->set('toolkit', $form_state['values']['image_toolkit'])
+    ->save();
+
+  // Call the form submit handler for each of the toolkits.
+  // Get the toolkit settings forms.
+  $manager = new ImageToolkitManager();
+  $toolkits_available = image_get_available_toolkits();
+  foreach ($toolkits_available as $id => $definition) {
+    $toolkit = $manager->createInstance($id);
+    $toolkit->settingsFormSubmit($form, $form_state);
+  }
 }
 
 /**
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index 0e42214..194703e 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -2047,6 +2047,20 @@ function system_update_8016() {
 }
 
 /**
+ * Moves image toolkit settings from variable to config.
+ *
+ * @ingroup config_upgrade
+ */
+function system_update_8017() {
+  update_variables_to_config('system.image', array(
+    'image_toolkit' => 'toolkit',
+  ));
+  update_variables_to_config('system.image.gd', array(
+    'image_jpeg_quality' => 'jpeg_quality',
+  ));
+}
+
+/**
  * @} End of "defgroup updates-7.x-to-8.x".
  * The next series of updates should start at 9000.
  */
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 983c317..f7c4793 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -3728,19 +3728,6 @@ function theme_system_compact_link() {
 }
 
 /**
- * Implements hook_image_toolkits().
- */
-function system_image_toolkits() {
-  include_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'system') . '/' . 'image.gd.inc';
-  return array(
-    'gd' => array(
-      'title' => t('GD2 image manipulation toolkit'),
-      'available' => function_exists('image_gd_check_settings') && image_gd_check_settings(),
-    ),
-  );
-}
-
-/**
  * Attempts to get a file using drupal_http_request and to store it locally.
  *
  * @param $url
diff --git a/core/modules/system/tests/modules/image_test/image_test.module b/core/modules/system/tests/modules/image_test/image_test.module
index de640f0..c6291f2 100644
--- a/core/modules/system/tests/modules/image_test/image_test.module
+++ b/core/modules/system/tests/modules/image_test/image_test.module
@@ -6,22 +6,6 @@
  */
 
 /**
- * Implements hook_image_toolkits().
- */
-function image_test_image_toolkits() {
-  return array(
-    'test' => array(
-      'title' => t('A dummy toolkit that works'),
-      'available' => TRUE,
-    ),
-    'broken' => array(
-      'title' => t('A dummy toolkit that is "broken"'),
-      'available' => FALSE,
-    ),
-  );
-}
-
-/**
  * Reset/initialize the history of calls to the toolkit functions.
  *
  * @see image_test_get_all_calls()
diff --git a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/system/imagetoolkit/BrokenToolkit.php b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/system/imagetoolkit/BrokenToolkit.php
new file mode 100644
index 0000000..439191d
--- /dev/null
+++ b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/system/imagetoolkit/BrokenToolkit.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\image_test\Plugin\system\imagetoolkit\BrokenToolkit.
+ */
+
+namespace Drupal\image_test\Plugin\system\imagetoolkit;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * Defines a Test toolkit for image manipulation within Drupal.
+ *
+ * @Plugin(
+ *   id = "broken",
+ *   title = @Translation("A dummy toolkit that is broken"),
+ *   description = @Translation("A dummy toolkit that is broken.")
+ * )
+ */
+class BrokenToolkit extends TestToolkit {
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::checkAvailable().
+   */
+  static function checkAvailable() {
+    return FALSE;
+  }
+}
diff --git a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/system/imagetoolkit/TestToolkit.php b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/system/imagetoolkit/TestToolkit.php
new file mode 100644
index 0000000..df91486
--- /dev/null
+++ b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/system/imagetoolkit/TestToolkit.php
@@ -0,0 +1,121 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\image_test\Plugin\system\imagetoolkit\TestToolkit.
+ */
+
+namespace Drupal\image_test\Plugin\system\imagetoolkit;
+
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\system\Plugin\ImageToolkitInterface;
+use stdClass;
+
+/**
+ * Defines a Test toolkit for image manipulation within Drupal.
+ *
+ * @Plugin(
+ *   id = "test",
+ *   title = @Translation("A dummy toolkit that works"),
+ *   description = @Translation("A dummy toolkit that works.")
+ * )
+ */
+class TestToolkit implements ImageToolkitInterface {
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::settingsForm().
+   */
+  function settingsForm() {
+    $this->_logCall('settings', array());
+    return array();
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::settingsFormSubmit().
+   */
+  function settingsFormSubmit($form, &$form_state) {}
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::getInfo().
+   */
+  function getInfo(stdClass $image) {
+    $this->_logCall('get_info', array($image));
+    return array();
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::load().
+   */
+  function load(stdClass $image) {
+    $this->_logCall('load', array($image));
+    return $image;
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::save().
+   */
+  function save(stdClass $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.
+    return FALSE;
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::crop().
+   */
+  function crop(stdClass $image, $x, $y, $width, $height) {
+    $this->_logCall('crop', array($image, $x, $y, $width, $height));
+    return TRUE;
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::resize().
+   */
+  function resize(stdClass $image, $width, $height) {
+    $this->_logCall('resize', array($image, $width, $height));
+    return TRUE;
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::rotate().
+   */
+  function rotate(stdClass $image, $degrees, $background = NULL) {
+    $this->_logCall('rotate', array($image, $degrees, $background));
+    return TRUE;
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::desaturate().
+   */
+  function desaturate(stdClass $image) {
+    $this->_logCall('desaturate', array($image));
+    return TRUE;
+  }
+
+  /**
+   * Store the values passed to a toolkit call.
+   *
+   * @param $op
+   *   One of the image toolkit operations: 'get_info', 'load', 'save',
+   *   'settings', 'resize', 'rotate', 'crop', 'desaturate'.
+   * @param $args
+   *   Values passed to hook.
+   *
+   * @see image_test_get_all_calls()
+   * @see image_test_reset()
+   */
+  function _logCall($op, $args) {
+    $results = variable_get('image_test_results', array());
+    $results[$op][] = $args;
+    variable_set('image_test_results', $results);
+  }
+
+  /**
+   * Implements Drupal\system\Plugin\ImageToolkitInterface::checkAvailable().
+   */
+  static function checkAvailable() {
+    return TRUE;
+  }
+}
