diff --git a/core/modules/image/image.services.yml b/core/modules/image/image.services.yml
index 2f17bb5..ae82fbb 100644
--- a/core/modules/image/image.services.yml
+++ b/core/modules/image/image.services.yml
@@ -1,4 +1,7 @@
 services:
+  image.derivative.processor:
+    class: Drupal\image\ImageDerivativeProcessor
+    arguments: ['@image.factory', '@stream_wrapper_manager', '@private_key']
   path_processor.image_styles:
     class: Drupal\image\PathProcessor\PathProcessorImageStyles
     arguments: ['@stream_wrapper_manager']
diff --git a/core/modules/image/src/Entity/ImageStyle.php b/core/modules/image/src/Entity/ImageStyle.php
index 7992aea..ba5f733 100644
--- a/core/modules/image/src/Entity/ImageStyle.php
+++ b/core/modules/image/src/Entity/ImageStyle.php
@@ -170,172 +170,56 @@ protected static function replaceImageStyle(ImageStyleInterface $style) {
    * {@inheritdoc}
    */
   public function buildUri($uri) {
-    $source_scheme = $scheme = $this->fileUriScheme($uri);
-    $default_scheme = $this->fileDefaultScheme();
-
-    if ($source_scheme) {
-      $path = $this->fileUriTarget($uri);
-      // The scheme of derivative image files only needs to be computed for
-      // source files not stored in the default scheme.
-      if ($source_scheme != $default_scheme) {
-        $class = $this->getStreamWrapperManager()->getClass($source_scheme);
-        $is_writable = $class::getType() & StreamWrapperInterface::WRITE;
-
-        // Compute the derivative URI scheme. Derivatives created from writable
-        // source stream wrappers will inherit the scheme. Derivatives created
-        // from read-only stream wrappers will fall-back to the default scheme.
-        $scheme = $is_writable ? $source_scheme : $default_scheme;
-      }
-    }
-    else {
-      $path = $uri;
-      $source_scheme = $scheme = $default_scheme;
-    }
-    return "$scheme://styles/{$this->id()}/$source_scheme/{$this->addExtension($path)}";
+    $processor = \Drupal::service('image.derivative.processor');
+    return $processor->buildUri($this, $uri);
   }
 
   /**
    * {@inheritdoc}
    */
   public function buildUrl($path, $clean_urls = NULL) {
-    $uri = $this->buildUri($path);
-    // The token query is added even if the
-    // 'image.settings:allow_insecure_derivatives' configuration is TRUE, so
-    // that the emitted links remain valid if it is changed back to the default
-    // FALSE. However, sites which need to prevent the token query from being
-    // emitted at all can additionally set the
-    // 'image.settings:suppress_itok_output' configuration to TRUE to achieve
-    // that (if both are set, the security token will neither be emitted in the
-    // image derivative URL nor checked for in
-    // \Drupal\image\ImageStyleInterface::deliver()).
-    $token_query = [];
-    if (!\Drupal::config('image.settings')->get('suppress_itok_output')) {
-      // The passed $path variable can be either a relative path or a full URI.
-      $original_uri = file_uri_scheme($path) ? file_stream_wrapper_uri_normalize($path) : file_build_uri($path);
-      $token_query = [IMAGE_DERIVATIVE_TOKEN => $this->getPathToken($original_uri)];
-    }
-
-    if ($clean_urls === NULL) {
-      // Assume clean URLs unless the request tells us otherwise.
-      $clean_urls = TRUE;
-      try {
-        $request = \Drupal::request();
-        $clean_urls = RequestHelper::isCleanUrl($request);
-      }
-      catch (ServiceNotFoundException $e) {
-      }
-    }
-
-    // If not using clean URLs, the image derivative callback is only available
-    // with the script path. If the file does not exist, use Url::fromUri() to
-    // ensure that it is included. Once the file exists it's fine to fall back
-    // to the actual file path, this avoids bootstrapping PHP once the files are
-    // built.
-    if ($clean_urls === FALSE && file_uri_scheme($uri) == 'public' && !file_exists($uri)) {
-      $directory_path = $this->getStreamWrapperManager()->getViaUri($uri)->getDirectoryPath();
-      return Url::fromUri('base:' . $directory_path . '/' . file_uri_target($uri), ['absolute' => TRUE, 'query' => $token_query])->toString();
-    }
-
-    $file_url = file_create_url($uri);
-    // Append the query string with the token, if necessary.
-    if ($token_query) {
-      $file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($token_query);
-    }
-
-    return $file_url;
+    $processor = \Drupal::service('image.derivative.processor');
+    return $processor->buildUrl($this, $path, $clean_urls = NULL);
   }
 
   /**
    * {@inheritdoc}
    */
   public function flush($path = NULL) {
-    // A specific image path has been provided. Flush only that derivative.
-    if (isset($path)) {
-      $derivative_uri = $this->buildUri($path);
-      if (file_exists($derivative_uri)) {
-        file_unmanaged_delete($derivative_uri);
-      }
-      return $this;
-    }
-
-    // Delete the style directory in each registered wrapper.
-    $wrappers = $this->getStreamWrapperManager()->getWrappers(StreamWrapperInterface::WRITE_VISIBLE);
-    foreach ($wrappers as $wrapper => $wrapper_data) {
-      if (file_exists($directory = $wrapper . '://styles/' . $this->id())) {
-        file_unmanaged_delete_recursive($directory);
-      }
-    }
-
-    // Let other modules update as necessary on flush.
-    $module_handler = \Drupal::moduleHandler();
-    $module_handler->invokeAll('image_style_flush', [$this]);
-
-    // Clear caches so that formatters may be added for this style.
-    drupal_theme_rebuild();
-
-    Cache::invalidateTags($this->getCacheTagsToInvalidate());
-
-    return $this;
+    $processor = \Drupal::service('image.derivative.processor');
+    return $processor->flush($this, $path);
   }
 
   /**
    * {@inheritdoc}
    */
   public function createDerivative($original_uri, $derivative_uri) {
-    // If the source file doesn't exist, return FALSE without creating folders.
-    $image = $this->getImageFactory()->get($original_uri);
-    if (!$image->isValid()) {
-      return FALSE;
-    }
-
-    // Get the folder for the final location of this style.
-    $directory = drupal_dirname($derivative_uri);
-
-    // Build the destination folder tree if it doesn't already exist.
-    if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
-      \Drupal::logger('image')->error('Failed to create style directory: %directory', ['%directory' => $directory]);
-      return FALSE;
-    }
-
-    foreach ($this->getEffects() as $effect) {
-      $effect->applyEffect($image);
-    }
-
-    if (!$image->save($derivative_uri)) {
-      if (file_exists($derivative_uri)) {
-        \Drupal::logger('image')->error('Cached image file %destination already exists. There may be an issue with your rewrite configuration.', ['%destination' => $derivative_uri]);
-      }
-      return FALSE;
-    }
-
-    return TRUE;
+    $processor = \Drupal::service('image.derivative.processor');
+    return $processor->createDerivative($this, $original_uri, $derivative_uri);
   }
 
   /**
    * {@inheritdoc}
    */
   public function transformDimensions(array &$dimensions, $uri) {
-    foreach ($this->getEffects() as $effect) {
-      $effect->transformDimensions($dimensions, $uri);
-    }
+    $processor = \Drupal::service('image.derivative.processor');
+    return $processor->transformDimensions($this, $dimensions, $uri);
   }
 
   /**
    * {@inheritdoc}
    */
   public function getDerivativeExtension($extension) {
-    foreach ($this->getEffects() as $effect) {
-      $extension = $effect->getDerivativeExtension($extension);
-    }
-    return $extension;
+    $processor = \Drupal::service('image.derivative.processor');
+    return $processor->getDerivativeExtension($this, $extension);
   }
 
   /**
    * {@inheritdoc}
    */
   public function getPathToken($uri) {
-    // Return the first 8 characters.
-    return substr(Crypt::hmacBase64($this->id() . ':' . $this->addExtension($uri), $this->getPrivateKey() . $this->getHashSalt()), 0, 8);
+    $processor = \Drupal::service('image.derivative.processor');
+    return $processor->getPathToken($this, $uri);
   }
 
   /**
@@ -442,6 +326,7 @@ protected function getImageFactory() {
    *
    * @return string
    *   The Drupal private key.
+   * @deprecated
    */
   protected function getPrivateKey() {
     return \Drupal::service('private_key')->get();
@@ -454,6 +339,7 @@ protected function getPrivateKey() {
    *   A salt based on information in settings.php, not in the database.
    *
    * @throws \RuntimeException
+   * @deprecated
    */
   protected function getHashSalt() {
     return Settings::getHashSalt();
@@ -472,6 +358,7 @@ protected function getHashSalt() {
    * @return string
    *   The given path if this image style doesn't change its extension, or the
    *   path with the added extension if it does.
+   * @deprecated
    */
   protected function addExtension($path) {
     $original_extension = pathinfo($path, PATHINFO_EXTENSION);
@@ -497,6 +384,7 @@ protected function addExtension($path) {
    * @return string
    *   A string containing the name of the scheme, or FALSE if none. For
    *   example, the URI "public://example.txt" would return "public".
+   * @deprecated
    */
   protected function fileUriScheme($uri) {
     return file_uri_scheme($uri);
@@ -518,6 +406,7 @@ protected function fileUriScheme($uri) {
    *   A string containing the target (path), or FALSE if none.
    *   For example, the URI "public://sample/test.txt" would return
    *   "sample/test.txt".
+   * @deprecated
    */
   protected function fileUriTarget($uri) {
     return file_uri_target($uri);
@@ -532,6 +421,7 @@ protected function fileUriTarget($uri) {
    *
    * @return string
    *   'public', 'private' or any other file scheme defined as the default.
+   * @deprecated
    */
   protected function fileDefaultScheme() {
     return file_default_scheme();
@@ -544,6 +434,7 @@ protected function fileDefaultScheme() {
    *   The stream wrapper manager service
    *
    * @todo Properly inject this service in Drupal 9.0.x.
+   * @deprecated
    */
   protected function getStreamWrapperManager() {
     return \Drupal::service('stream_wrapper_manager');
diff --git a/core/modules/image/src/ImageDerivativeProcessor.php b/core/modules/image/src/ImageDerivativeProcessor.php
new file mode 100644
index 0000000..f5634c4
--- /dev/null
+++ b/core/modules/image/src/ImageDerivativeProcessor.php
@@ -0,0 +1,322 @@
+<?php
+
+namespace Drupal\image;
+
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\Entity\EntityWithPluginCollectionInterface;
+use Drupal\Core\Routing\RequestHelper;
+use Drupal\Core\Site\Settings;
+use Drupal\Core\Url;
+use Drupal\image\ImageEffectPluginCollection;
+use Drupal\image\ImageEffectInterface;
+use Drupal\image\ImageStyleInterface;
+use Drupal\Component\Utility\Crypt;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+use Symfony\Component\DependencyInjection\Exception\ServiceNotFoundException;
+use Drupal\Core\Entity\Entity\EntityViewDisplay;
+
+/**
+ * The service processing image derivatives.
+ */
+class ImageDerivativeProcessor  {
+  /**
+   * {@inheritdoc}
+   */
+  public function buildUri(ImageStyleInterface $image_style, $uri) {
+    $source_scheme = $scheme = $this->fileUriScheme($uri);
+    $default_scheme = $this->fileDefaultScheme();
+
+    if ($source_scheme) {
+      $path = $this->fileUriTarget($uri);
+      // The scheme of derivative image files only needs to be computed for
+      // source files not stored in the default scheme.
+      if ($source_scheme != $default_scheme) {
+        $class = $this->getStreamWrapperManager()->getClass($source_scheme);
+        $is_writable = $class::getType() & StreamWrapperInterface::WRITE;
+
+        // Compute the derivative URI scheme. Derivatives created from writable
+        // source stream wrappers will inherit the scheme. Derivatives created
+        // from read-only stream wrappers will fall-back to the default scheme.
+        $scheme = $is_writable ? $source_scheme : $default_scheme;
+      }
+    }
+    else {
+      $path = $uri;
+      $source_scheme = $scheme = $default_scheme;
+    }
+    return "$scheme://styles/{$image_style->id()}/$source_scheme/{$this->addExtension($image_style, $path)}";
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildUrl(ImageStyleInterface $image_style, $path, $clean_urls = NULL) {
+    $uri = $this->buildUri($image_style, $path);
+    // The token query is added even if the
+    // 'image.settings:allow_insecure_derivatives' configuration is TRUE, so
+    // that the emitted links remain valid if it is changed back to the default
+    // FALSE. However, sites which need to prevent the token query from being
+    // emitted at all can additionally set the
+    // 'image.settings:suppress_itok_output' configuration to TRUE to achieve
+    // that (if both are set, the security token will neither be emitted in the
+    // image derivative URL nor checked for in
+    // \Drupal\image\ImageStyleInterface::deliver()).
+    $token_query = [];
+    if (!\Drupal::config('image.settings')->get('suppress_itok_output')) {
+      // The passed $path variable can be either a relative path or a full URI.
+      $original_uri = file_uri_scheme($path) ? file_stream_wrapper_uri_normalize($path) : file_build_uri($path);
+      $token_query = [IMAGE_DERIVATIVE_TOKEN => $this->getPathToken($image_style, $original_uri)];
+    }
+
+    if ($clean_urls === NULL) {
+      // Assume clean URLs unless the request tells us otherwise.
+      $clean_urls = TRUE;
+      try {
+        $request = \Drupal::request();
+        $clean_urls = RequestHelper::isCleanUrl($request);
+      }
+      catch (ServiceNotFoundException $e) {
+      }
+    }
+
+    // If not using clean URLs, the image derivative callback is only available
+    // with the script path. If the file does not exist, use Url::fromUri() to
+    // ensure that it is included. Once the file exists it's fine to fall back
+    // to the actual file path, this avoids bootstrapping PHP once the files are
+    // built.
+    if ($clean_urls === FALSE && file_uri_scheme($uri) == 'public' && !file_exists($uri)) {
+      $directory_path = $this->getStreamWrapperManager()->getViaUri($uri)->getDirectoryPath();
+      return Url::fromUri('base:' . $directory_path . '/' . file_uri_target($uri), ['absolute' => TRUE, 'query' => $token_query])->toString();
+    }
+
+    $file_url = file_create_url($uri);
+    // Append the query string with the token, if necessary.
+    if ($token_query) {
+      $file_url .= (strpos($file_url, '?') !== FALSE ? '&' : '?') . UrlHelper::buildQuery($token_query);
+    }
+
+    return $file_url;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function flush(ImageStyleInterface $image_style, $path = NULL) {
+    // A specific image path has been provided. Flush only that derivative.
+    if (isset($path)) {
+      $derivative_uri = $this->buildUri($image_style, $path);
+      if (file_exists($derivative_uri)) {
+        file_unmanaged_delete($derivative_uri);
+      }
+      return $image_style;
+    }
+
+    // Delete the style directory in each registered wrapper.
+    $wrappers = $this->getStreamWrapperManager()->getWrappers(StreamWrapperInterface::WRITE_VISIBLE);
+    foreach ($wrappers as $wrapper => $wrapper_data) {
+      if (file_exists($directory = $wrapper . '://styles/' . $image_style->id())) {
+        file_unmanaged_delete_recursive($directory);
+      }
+    }
+
+    // Let other modules update as necessary on flush.
+    $module_handler = \Drupal::moduleHandler();
+    $module_handler->invokeAll('image_style_flush', [$image_style]);
+
+    // Clear caches so that formatters may be added for this style.
+    drupal_theme_rebuild();
+
+    Cache::invalidateTags($image_style->getCacheTagsToInvalidate());
+
+    return $image_style;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function createDerivative(ImageStyleInterface $image_style, $original_uri, $derivative_uri) {
+    // If the source file doesn't exist, return FALSE without creating folders.
+    $image = $this->getImageFactory()->get($original_uri);
+    if (!$image->isValid()) {
+      return FALSE;
+    }
+
+    // Get the folder for the final location of this style.
+    $directory = drupal_dirname($derivative_uri);
+
+    // Build the destination folder tree if it doesn't already exist.
+    if (!file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS)) {
+      \Drupal::logger('image')->error('Failed to create style directory: %directory', ['%directory' => $directory]);
+      return FALSE;
+    }
+
+    foreach ($image_style->getEffects() as $effect) {
+      $effect->applyEffect($image);
+    }
+
+    if (!$image->save($derivative_uri)) {
+      if (file_exists($derivative_uri)) {
+        \Drupal::logger('image')->error('Cached image file %destination already exists. There may be an issue with your rewrite configuration.', ['%destination' => $derivative_uri]);
+      }
+      return FALSE;
+    }
+
+    return TRUE;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transformDimensions(ImageStyleInterface $image_style, array &$dimensions, $uri) {
+    foreach ($image_style->getEffects() as $effect) {
+      $effect->transformDimensions($dimensions, $uri);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDerivativeExtension(ImageStyleInterface $image_style, $extension) {
+    foreach ($image_style->getEffects() as $effect) {
+      $extension = $effect->getDerivativeExtension($extension);
+    }
+    return $extension;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getPathToken(ImageStyleInterface $image_style, $uri) {
+    // Return the first 8 characters.
+    return substr(Crypt::hmacBase64($image_style->id() . ':' . $this->addExtension($image_style, $uri), $this->getPrivateKey() . $this->getHashSalt()), 0, 8);
+  }
+
+  /**
+   * Returns the image factory.
+   *
+   * @return \Drupal\Core\Image\ImageFactory
+   *   The image factory.
+   */
+  protected function getImageFactory() {
+    return \Drupal::service('image.factory');
+  }
+
+  /**
+   * Gets the Drupal private key.
+   *
+   * @return string
+   *   The Drupal private key.
+   * @deprecated
+   */
+  protected function getPrivateKey() {
+    return \Drupal::service('private_key')->get();
+  }
+
+  /**
+   * Gets a salt useful for hardening against SQL injection.
+   *
+   * @return string
+   *   A salt based on information in settings.php, not in the database.
+   *
+   * @throws \RuntimeException
+   * @deprecated
+   */
+  protected function getHashSalt() {
+    return Settings::getHashSalt();
+  }
+
+  /**
+   * Adds an extension to a path.
+   *
+   * If this image style changes the extension of the derivative, this method
+   * adds the new extension to the given path. This way we avoid filename
+   * clashes while still allowing us to find the source image.
+   *
+   * @param string $path
+   *   The path to add the extension to.
+   *
+   * @return string
+   *   The given path if this image style doesn't change its extension, or the
+   *   path with the added extension if it does.
+   * @deprecated
+   */
+  protected function addExtension(ImageStyleInterface $image_style, $path) {
+    $original_extension = pathinfo($path, PATHINFO_EXTENSION);
+    $extension = $this->getDerivativeExtension($image_style, $original_extension);
+    if ($original_extension !== $extension) {
+      $path .= '.' . $extension;
+    }
+    return $path;
+  }
+
+  /**
+   * Provides a wrapper for file_uri_scheme() to allow unit testing.
+   *
+   * Returns the scheme of a URI (e.g. a stream).
+   *
+   * @param string $uri
+   *   A stream, referenced as "scheme://target"  or "data:target".
+   *
+   * @see file_uri_target()
+   *
+   * @todo: Remove when https://www.drupal.org/node/2050759 is in.
+   *
+   * @return string
+   *   A string containing the name of the scheme, or FALSE if none. For
+   *   example, the URI "public://example.txt" would return "public".
+   */
+  protected function fileUriScheme($uri) {
+    return file_uri_scheme($uri);
+  }
+
+  /**
+   * Provides a wrapper for file_uri_target() to allow unit testing.
+   *
+   * Returns the part of a URI after the schema.
+   *
+   * @param string $uri
+   *   A stream, referenced as "scheme://target" or "data:target".
+   *
+   * @see file_uri_scheme()
+   *
+   * @todo: Convert file_uri_target() into a proper injectable service.
+   *
+   * @return string|bool
+   *   A string containing the target (path), or FALSE if none.
+   *   For example, the URI "public://sample/test.txt" would return
+   *   "sample/test.txt".
+   */
+  protected function fileUriTarget($uri) {
+    return file_uri_target($uri);
+  }
+
+  /**
+   * Provides a wrapper for file_default_scheme() to allow unit testing.
+   *
+   * Gets the default file stream implementation.
+   *
+   * @todo: Convert file_default_scheme() into a proper injectable service.
+   *
+   * @return string
+   *   'public', 'private' or any other file scheme defined as the default.
+   */
+  protected function fileDefaultScheme() {
+    return file_default_scheme();
+  }
+
+  /**
+   * Gets the stream wrapper manager service.
+   *
+   * @return \Drupal\Core\StreamWrapper\StreamWrapperManagerInterface
+   *   The stream wrapper manager service
+   *
+   * @todo Properly inject this service in Drupal 9.0.x.
+   */
+  protected function getStreamWrapperManager() {
+    return \Drupal::service('stream_wrapper_manager');
+  }
+
+}
diff --git a/core/modules/image/src/ImageStyleInterface.php b/core/modules/image/src/ImageStyleInterface.php
index 8430fdc..6007180 100644
--- a/core/modules/image/src/ImageStyleInterface.php
+++ b/core/modules/image/src/ImageStyleInterface.php
@@ -54,6 +54,7 @@ public function setName($name);
    *
    * @return string
    *   The URI to the image derivative for this style.
+   * @deprecated
    */
   public function buildUri($uri);
 
@@ -71,6 +72,7 @@ public function buildUri($uri);
    *
    * @see \Drupal\image\Controller\ImageStyleDownloadController::deliver()
    * @see file_url_transform_relative()
+   * @deprecated
    */
   public function buildUrl($path, $clean_urls = NULL);
 
@@ -86,6 +88,7 @@ public function buildUrl($path, $clean_urls = NULL);
    * @return string
    *   An eight-character token which can be used to protect image style
    *   derivatives against denial-of-service attacks.
+   * @deprecated
    */
   public function getPathToken($uri);
 
@@ -97,6 +100,7 @@ public function getPathToken($uri);
    *   image derivative will be flushed.
    *
    * @return $this
+   * @deprecated
    */
   public function flush($path = NULL);
 
@@ -114,6 +118,7 @@ public function flush($path = NULL);
    * @return bool
    *   TRUE if an image derivative was generated, or FALSE if the image
    *   derivative could not be generated.
+   * @deprecated
    */
   public function createDerivative($original_uri, $derivative_uri);
 
@@ -138,6 +143,7 @@ public function createDerivative($original_uri, $derivative_uri);
    *   performance.
    *
    * @see ImageEffectInterface::transformDimensions
+   * @deprecated
    */
   public function transformDimensions(array &$dimensions, $uri);
 
@@ -150,6 +156,7 @@ public function transformDimensions(array &$dimensions, $uri);
    * @return string
    *   The extension the derivative image will have, given the extension of the
    *   original.
+   * @deprecated
    */
   public function getDerivativeExtension($extension);
 
