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..eaba89f
--- /dev/null
+++ b/core/lib/Drupal/Core/Plugin/Discovery/HookDiscovery.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ *
+ *
+ */
+
+namespace Drupal\Core\Plugin\Discovery;
+use Drupal\Component\Plugin\Discovery\DiscoveryInterface;
+
+class HookDiscovery implements DiscoveryInterface {
+
+  protected $hook;
+  protected $cache_key;
+  protected $before_alter;
+  protected $plugins;
+
+  function __construct($hook, $cache_key = NULL, $before_alter = NULL) {
+    $this->hook = $hook;
+    $this->cache_key = $cache_key;
+    $this->before_alter = $before_alter;
+  }
+
+  /**
+   * Implements DicoveryInterface::getPluginDefinition().
+   */
+  public function getPluginDefinition($plugin_id) {
+    $plugins = $this->getPluginDefinitions();
+    return $plugins[$plugin_id];
+  }
+
+  /**
+   * Implements DicoveryInterface::getPluginDefinitions().
+   */
+  public function getPluginDefinitions() {
+    if (!isset($this->plugins)) {
+      if (isset($this->cache_key) && $cache = cache()->get($this->cache_key)) {
+        $this->plugins = $cache->data;
+      }
+      else {
+        $plugins = module_invoke_all($this->hook);
+        if (isset($this->before_alter)) {
+          $plugins = call_user_func($this->before_alter, $plugins);
+        }
+        drupal_alter($this->hook, $plugins);
+        if (isset($this->cache_key)) {
+          cache()->set($this->cache_key, $plugins);
+        }
+        $this->plugins = $plugins;
+      }
+    }
+    return $this->plugins;
+  }
+}
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index e6a9bc8..1cf807f 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -5,6 +5,10 @@
  * Exposes global functionality for creating image styles.
  */
 
+// @todo Remove after merging 8.x HEAD (which contains module PSR-0
+//   registration) into plugins-kernel.
+require_once DRUPAL_ROOT . '/core/modules/image/lib/Drupal/image/Plugin/Type/Effect.php';
+
 /**
  * Image style constant for user presets in the database.
  */
@@ -412,6 +416,16 @@ function image_field_update_field($field, $prior_field, $has_data) {
 }
 
 /**
+ * Implements hook_module_implements_alter().
+ */
+function image_module_implements_alter(&$implementations, $hook) {
+  if ($hook == 'image_effect_info') {
+    // image_image_effect_info() is defined in image.effects.inc.
+    $implementations['image'] = array('group' => 'effects');
+  }
+}
+
+/**
  * Clear cached versions of a specific file in all styles.
  *
  * @param $path
@@ -831,7 +845,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.
@@ -839,41 +853,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 plugin('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
@@ -895,8 +879,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 plugin('image', 'effect')->getPluginDefinition($effect);
 }
 
 /**
@@ -1096,12 +1079,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/Plugin/Type/Effect.php b/core/modules/image/lib/Drupal/image/Plugin/Type/Effect.php
new file mode 100644
index 0000000..6e7e06f
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/Plugin/Type/Effect.php
@@ -0,0 +1,31 @@
+<?php
+
+/**
+ * Definition of Drupal\image\Plugin\Type\Effect.
+ */
+namespace Drupal\image\Plugin\Type;
+
+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('image_effect_info', $cache_key, array($this, 'beforeDefinitionAlter'));
+  }
+
+  /**
+   * Acts on effect definitions returned by hook_image_effect_info() prior to hook_image_effect_info_alter().
+   */
+  public function beforeDefinitionAlter($effects) {
+    foreach ($effects as $name => &$effect) {
+      $effect['name'] = $name;
+      $effect['data'] = isset($effect['data']) ? $effect['data'] : array();
+    }
+    ksort($effects);
+    return $effects;
+  }
+}
