diff --git a/core/lib/Drupal/Component/Plugin/PluginType.php b/core/lib/Drupal/Component/Plugin/PluginType.php
index d52351f..745587c 100644
--- a/core/lib/Drupal/Component/Plugin/PluginType.php
+++ b/core/lib/Drupal/Component/Plugin/PluginType.php
@@ -18,7 +18,9 @@ abstract class PluginType implements PluginTypeInterface {
    */
   public function getPluginDefinition($plugin_id) {
     $definition = $this->discovery->getPluginDefinition($plugin_id);
-    $this->processPluginDefinition($definition);
+    if (isset($definition)) {
+      $this->processPluginDefinition($definition, $plugin_id);
+    }
     return $definition;
   }
 
@@ -27,8 +29,8 @@ abstract class PluginType implements PluginTypeInterface {
    */
   public function getPluginDefinitions() {
     $definitions = $this->discovery->getPluginDefinitions();
-    foreach ($definitions as $k => $definition) {
-      $this->processPluginDefinition($definitions[$k]);
+    foreach ($definitions as $plugin_id => &$definition) {
+      $this->processPluginDefinition($definition, $plugin_id);
     }
 
     return $definitions;
@@ -55,7 +57,7 @@ abstract class PluginType implements PluginTypeInterface {
    * additional processing logic they can do that be replacing or extending the
    * method.
    */
-  protected function processPluginDefinition(&$definition) {
+  protected function processPluginDefinition(&$definition, $plugin_id) {
     $definition += $this->defaults;
   }
 }
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..af5f369
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Plugin\Discovery\HookDiscovery.
+ */
+
+namespace Drupal\Core\Plugin\Discovery;
+use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
+
+/**
+ * Provides a hook-based plugin discovery class.
+ */
+class HookDiscovery implements DiscoveryInterface {
+
+  protected $hook;
+  protected $cacheKey;
+  protected $definitions;
+
+  function __construct($hook, $cache_key = NULL) {
+    $this->hook = $hook;
+    $this->cacheKey = $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->cacheKey) && $cache = cache()->get($this->cacheKey)) {
+        $this->definitions = $cache->data;
+      }
+      else {
+        $this->definitions = array();
+
+        foreach (module_implements($this->hook) as $module) {
+          $function = $module . '_' . $this->hook;
+          foreach ($function() as $plugin_id => $definition) {
+            $definition['module'] = $module;
+            $this->definitions[$plugin_id] = $definition;
+          }
+        }
+
+        drupal_alter($this->hook, $this->definitions);
+
+        if (isset($this->cacheKey)) {
+          cache()->set($this->cacheKey, $this->definitions);
+        }
+      }
+    }
+
+    return $this->definitions;
+  }
+
+}
diff --git a/core/modules/image/image.admin.inc b/core/modules/image/image.admin.inc
index f31d98f..f5f12f8 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 9d94ac2..148fc14 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -7,6 +7,7 @@
 
 use Symfony\Component\HttpFoundation\Response;
 use Symfony\Component\HttpFoundation\StreamedResponse;
+use Drupal\image\Effect;
 
 /**
  * Image style constant for user presets in the database.
@@ -482,6 +483,16 @@ function image_field_update_instance($instance, $prior_instance) {
 }
 
 /**
+ * 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
@@ -913,7 +924,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.
@@ -921,41 +932,15 @@ 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;
+  // @todo Rather than hard-coding the plugin type class, register this with
+  //   Drupal's dependency injection container once it supports compilable
+  //   registration of module classes.
+  $plugin_type = new Effect();
+  return $plugin_type->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
@@ -977,8 +962,11 @@ 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;
+  // @todo Rather than hard-coding the plugin type class, register this with
+  //   Drupal's dependency injection container once it supports compilable
+  //   registration of module classes.
+  $plugin_type = new Effect();
+  return $plugin_type->getPluginDefinition($effect);
 }
 
 /**
@@ -1180,12 +1168,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..8b30730
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Effect.php
@@ -0,0 +1,30 @@
+<?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() {
+    // Effects include translated strings.
+    $cache_key = 'image_effects:' . drupal_container()->get(LANGUAGE_TYPE_INTERFACE)->langcode;
+
+    $this->discovery = new HookDiscovery('image_effect_info', $cache_key);
+    $this->defaults = array(
+      'data' => array(),
+    );
+  }
+
+  /**
+   *
+   */
+  protected function processPluginDefinition(&$definition, $plugin_id) {
+    parent::processPluginDefinition($definition, $plugin_id);
+    $definition['name'] = $plugin_id;
+  }
+}
