diff --git a/core/lib/Drupal/Component/Plugin/Discovery/StaticDiscovery.php b/core/lib/Drupal/Component/Plugin/Discovery/StaticDiscovery.php
index 97bd093..e48ab07 100644
--- a/core/lib/Drupal/Component/Plugin/Discovery/StaticDiscovery.php
+++ b/core/lib/Drupal/Component/Plugin/Discovery/StaticDiscovery.php
@@ -43,7 +43,7 @@ class StaticDiscovery implements DiscoveryInterface {
    * Set a plugin definition.
    */
   public function setPluginDefinition($plugin_id, array $definition) {
-    $this->plugin_definitions[$plugin_id] = $this->pluginType->processPluginDefinition($definition);
+    $this->plugin_definitions[$plugin_id] = $this->pluginType->processPluginDefinition($plugin_id, $definition);
   }
 
   /**
diff --git a/core/lib/Drupal/Component/Plugin/PluginType.php b/core/lib/Drupal/Component/Plugin/PluginType.php
index a4809fc..bcf1b49 100644
--- a/core/lib/Drupal/Component/Plugin/PluginType.php
+++ b/core/lib/Drupal/Component/Plugin/PluginType.php
@@ -34,7 +34,7 @@ abstract class PluginType implements PluginTypeInterface {
    * additional processing logic they can do that be replacing or extending the
    * method.
    */
-  public function processPluginDefinition($definition) {
+  public function processPluginDefinition($plugin_id, $definition) {
     return $definition + $this->defaults;
   }
 
diff --git a/core/lib/Drupal/Component/Plugin/PluginTypeInterface.php b/core/lib/Drupal/Component/Plugin/PluginTypeInterface.php
index 971f030..250f721 100644
--- a/core/lib/Drupal/Component/Plugin/PluginTypeInterface.php
+++ b/core/lib/Drupal/Component/Plugin/PluginTypeInterface.php
@@ -36,13 +36,15 @@ interface PluginTypeInterface {
    * This method should be called by the discovery class, before returning
    * results in getPluginDefinition() and getPluginDefinitions().
    *
+   * @param $plugin_id
+   *   The plugin id.
    * @param array $definition
    *   The plugin implementation definition.
    *
    * @return array
    *   The processed plugin implementation definition.
    */
-  public function processPluginDefinition($definition);
+  public function processPluginDefinition($plugin_id, $definition);
 
   /**
    * Instantiates a fully configured plugin instance.
diff --git a/core/lib/Drupal/Core/Plugin/Discovery/ConfigDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/ConfigDiscovery.php
index 2f690e8..493b129 100644
--- a/core/lib/Drupal/Core/Plugin/Discovery/ConfigDiscovery.php
+++ b/core/lib/Drupal/Core/Plugin/Discovery/ConfigDiscovery.php
@@ -34,7 +34,7 @@ class ConfigDiscovery implements DiscoveryInterface {
       $derviative_mapper = new $config['derivative']($this->definition_root, $plugin_id, $config);
       $config = $derviative_mapper->getDerviative($plugin_id);
     }
-    return $this->pluginType->processPluginDefinition($config);
+    return $this->pluginType->processPluginDefinition($plugin_id, $config);
   }
 
   /**
@@ -48,7 +48,7 @@ class ConfigDiscovery implements DiscoveryInterface {
         $derviative_mapper = new $plugins[$plugin_id]['derivative']($this->definition_root, $plugin_id, $plugins[$plugin_id]);
         $derivatives = $derviative_mapper->getDerivatives();
         foreach ($derivatives as $derivative_id => $derivative) {
-          $plugins[$derivative_id] = $this->pluginType->processPluginDefinition($derivative);
+          $plugins[$derivative_id] = $this->pluginType->processPluginDefinition($plugin_id, $derivative);
         }
         unset($plugins[$plugin_id]);
       }
diff --git a/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php
new file mode 100644
index 0000000..e140b86
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php
@@ -0,0 +1,79 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Plugin\Discovery\HookDiscovery.
+ */
+
+namespace Drupal\Core\Plugin\Discovery;
+use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
+use Drupal\Component\Plugin\PluginTypeInterface;
+
+/**
+ * Provides a hook-based plugin discovery class.
+ */
+class HookDiscovery implements DiscoveryInterface {
+
+  /**
+   * @var PluginTypeInterface
+   */
+  protected $pluginType;
+
+  protected $hook;
+  protected $cache_key;
+  protected $definitions;
+
+  function __construct(PluginTypeInterface $plugin_type, $hook, $cache_key = NULL) {
+    $this->pluginType = $plugin_type;
+    $this->hook = $hook;
+    $this->cache_key = $cache_key;
+  }
+
+  /**
+   * Implements DicoveryInterface::getPluginDefinition().
+   */
+  public function getPluginDefinition($plugin_id) {
+    $plugins = $this->getPluginDefinitions();
+    return $plugins[$plugin_id];
+  }
+
+  /**
+   * Implements DicoveryInterface::getPluginDefinitions().
+   */
+  public function getPluginDefinitions() {
+    if (!isset($this->definitions)) {
+      if (isset($this->cache_key) && $cache = cache()->get($this->cache_key)) {
+        $this->definitions = $cache->data;
+      }
+      else {
+        $this->definitions = array();
+
+        foreach (module_implements($this->hook) as $module) {
+          $definitions = (array) module_invoke($module, $this->hook);
+          foreach ($definitions as $plugin_id => $definition) {
+            $definition['module'] = $module;
+            $this->definitions[$plugin_id] = $this->pluginType->processPluginDefinition($plugin_id, $definition);
+          }
+        }
+
+        drupal_alter($this->hook, $this->definitions);
+
+        if (isset($this->cache_key)) {
+          cache()->set($this->cache_key, $this->definitions);
+        }
+      }
+    }
+
+    return $this->definitions;
+  }
+
+  /**
+   * Clears the persistent and "static" cache of plugin implementations.
+   */
+  public function clear() {
+    if (isset($this->cache_key)) {
+      cache()->delete($this->cache_key);
+    }
+    unset($this->definitions);
+  }
+}
diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc
index c6b1356..73eeb84 100644
--- a/core/modules/image/image.admin.inc
+++ b/core/modules/image/image.admin.inc
@@ -94,8 +94,10 @@ function image_style_form($form, &$form_state, $style) {
   }
 
   // Build the new image effect addition form and add it to the effect list.
+  $image_effects = image_effect_definitions();
+  ksort($image_effects);
   $new_effect_options = array();
-  foreach (image_effect_definitions() as $effect => $definition) {
+  foreach ($image_effects as $effect => $definition) {
     $new_effect_options[$effect] = check_plain($definition['label']);
   }
   $form['effects']['new'] = array(
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index a0816e4..ceac1b1 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -412,6 +412,16 @@ function image_field_update_field($field, $prior_field, $has_data) {
 }
 
 /**
+ * Implements hook_hook_info().
+ */
+function image_hook_info() {
+  $hooks['image_effect_info'] = array(
+    'group' => 'effects',
+  );
+  return $hooks;
+}
+
+/**
  * Clear cached versions of a specific file in all styles.
  *
  * @param $path
@@ -837,7 +847,7 @@ function image_style_path($style_name, $uri) {
 }
 
 /**
- * Pull in image effects exposed by modules implementing hook_image_effect_info().
+ * Returns the definitions of all image effects.
  *
  * @return
  *   An array of image effects to be used when transforming images.
@@ -845,41 +855,11 @@ function image_style_path($style_name, $uri) {
  * @see image_effect_definition_load()
  */
 function image_effect_definitions() {
-  global $language_interface;
-
-  // hook_image_effect_info() includes translated strings, so each language is
-  // cached separately.
-  $langcode = $language_interface->langcode;
-
-  $effects = &drupal_static(__FUNCTION__);
-
-  if (!isset($effects)) {
-    if ($cache = cache()->get("image_effects:$langcode") && !empty($cache->data)) {
-      $effects = $cache->data;
-    }
-    else {
-      $effects = array();
-      include_once DRUPAL_ROOT . '/core/modules/image/image.effects.inc';
-      foreach (module_implements('image_effect_info') as $module) {
-        foreach (module_invoke($module, 'image_effect_info') as $name => $effect) {
-          // Ensure the current toolkit supports the effect.
-          $effect['module'] = $module;
-          $effect['name'] = $name;
-          $effect['data'] = isset($effect['data']) ? $effect['data'] : array();
-          $effects[$name] = $effect;
-        }
-      }
-      uasort($effects, '_image_effect_definitions_sort');
-      drupal_alter('image_effect_info', $effects);
-      cache()->set("image_effects:$langcode", $effects);
-    }
-  }
-
-  return $effects;
+  return drupal_object('Drupal\image\Effect')->getPluginDefinitions();
 }
 
 /**
- * Load the definition for an image effect.
+ * Returns the definition of an image effect.
  *
  * The effect definition is a set of core properties for an image effect, not
  * containing any user-settings. The definition defines various functions to
@@ -901,8 +881,7 @@ function image_effect_definitions() {
  *     one-line summary of the effect. Does not include the "theme_" prefix.
  */
 function image_effect_definition_load($effect) {
-  $definitions = image_effect_definitions();
-  return isset($definitions[$effect]) ? $definitions[$effect] : FALSE;
+  return drupal_object('Drupal\image\Effect')->getPluginDefinition($effect);
 }
 
 /**
@@ -1104,12 +1083,3 @@ function image_filter_keyword($value, $current_pixels, $new_pixels) {
   }
   return $value;
 }
-
-/**
- * Internal function for sorting image effect definitions through uasort().
- *
- * @see image_effect_definitions()
- */
-function _image_effect_definitions_sort($a, $b) {
-  return strcasecmp($a['name'], $b['name']);
-}
diff --git a/core/modules/image/lib/Drupal/image/Effect.php b/core/modules/image/lib/Drupal/image/Effect.php
new file mode 100644
index 0000000..767f12a
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Effect.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * Definition of Drupal\image\Effect.
+ */
+namespace Drupal\image;
+
+use Drupal\Component\Plugin\PluginType;
+use Drupal\Core\Plugin\Discovery\HookDiscovery;
+
+class Effect extends PluginType {
+
+  public function __construct() {
+    global $language_interface;
+
+    // Effects include translated strings.
+    $cache_key = 'image_effects:' . $language_interface->langcode;
+    $this->discovery = new HookDiscovery($this, 'image_effect_info', $cache_key);
+    $this->defaults = array(
+      'data' => array(),
+    );
+  }
+
+  /**
+   * Implements PluginTypeInterface::processPluginDefinition().
+   */
+  public function processPluginDefinition($plugin_id, $definition) {
+    $definition['name'] = $plugin_id;
+    return $definition + $this->defaults;
+  }
+}
