From 6ab088670ff2f40164dfef10b54ddf7e9c751732 Mon Sep 17 00:00:00 2001
From: Claudiu Cristea <clau.cristea@gmail.com>
Date: Sat, 10 Aug 2013 13:19:53 +0300
Subject: [PATCH] Issue #204497 by claudiu.cristea, Murz, tylor, edmund.kwok,
 RobLoach, sun, dman | FiReaNG3L: Fixed Configurable background color when
 cropping to larger dimensions.

---
 core/includes/image.inc                            | 30 ++++---
 .../image/ImageEffectBackgroundColorBase.php       | 74 +++++++++++++++++
 .../image/Plugin/ImageEffect/CropImageEffect.php   | 44 ++++++++++-
 .../image/Plugin/ImageEffect/RotateImageEffect.php | 50 +++---------
 .../Drupal/image/Tests/ImageAdminStylesTest.php    |  7 +-
 .../lib/Drupal/image/Tests/ImageEffectsTest.php    | 92 +++++++++++++++++++++-
 .../system/Plugin/ImageToolkit/GDToolkit.php       | 30 ++++---
 .../Drupal/system/Plugin/ImageToolkitInterface.php | 27 +++++--
 .../Drupal/system/Tests/Image/ToolkitGdTest.php    |  4 +-
 .../image_test/Plugin/ImageToolkit/TestToolkit.php | 12 +--
 10 files changed, 291 insertions(+), 79 deletions(-)
 create mode 100644 core/modules/image/lib/Drupal/image/ImageEffectBackgroundColorBase.php

diff --git a/core/includes/image.inc b/core/includes/image.inc
index 9e2d06f..b90a236 100644
--- a/core/includes/image.inc
+++ b/core/includes/image.inc
@@ -189,12 +189,14 @@ function image_resize($image, $width, $height) {
  *   An image object returned by image_load().
  * @param int $degrees
  *   The number of (clockwise) degrees to rotate the image.
- * @param string $background
- *   (optional) 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.
+ * @param string $background_color
+ *   (optional) An hexadecimal string specifying the background color to use for
+ *   uncovered area of the image after the rotation. Examples: "#RGB",
+ *   "#RRGGBB". For images that support transparency, this will default to
+ *   transparent. Otherwise it will be white.
+ * @param string $background_alpha
+ *   (optional) The value of alpha transparency for $background_color. Defaults
+ *   to 0.
  *
  * @return bool
  *   TRUE on success, FALSE on failure.
@@ -202,8 +204,8 @@ function image_resize($image, $width, $height) {
  * @see image_load()
  * @see \Drupal\system\Plugin\ImageToolkitInterface::rotate()
  */
-function image_rotate($image, $degrees, $background = NULL) {
-  return $image->toolkit->rotate($image, $degrees, $background);
+function image_rotate($image, $degrees, $background_color = NULL, $background_alpha = 0) {
+  return $image->toolkit->rotate($image, $degrees, $background_color, $background_alpha);
 }
 
 /**
@@ -219,6 +221,14 @@ function image_rotate($image, $degrees, $background = NULL) {
  *   The target width, in pixels.
  * @param int $height
  *   The target height, in pixels.
+ * @param string $background_color
+ *   (optional) An hexadecimal string specifying the background color to use for
+ *   the additional area created when cropping to larger dimensions than the
+ *   source. Examples: "#RGB", "#RRGGBB". For images that support transparency,
+ *   this will default to transparent. Otherwise will be white.
+ * @param string $background_alpha
+ *   (optional) The value of alpha transparency for $background_color. Defaults
+ *   to 0.
  *
  * @return bool
  *   TRUE on success, FALSE on failure.
@@ -227,7 +237,7 @@ function image_rotate($image, $degrees, $background = NULL) {
  * @see image_scale_and_crop()
  * @see \Drupal\system\Plugin\ImageToolkitInterface::crop()
  */
-function image_crop($image, $x, $y, $width, $height) {
+function image_crop($image, $x, $y, $width, $height, $background_color = NULL, $background_alpha = 0) {
   $aspect = $image->info['height'] / $image->info['width'];
   if (empty($height)) $height = $width / $aspect;
   if (empty($width)) $width = $height * $aspect;
@@ -235,7 +245,7 @@ function image_crop($image, $x, $y, $width, $height) {
   $width = (int) round($width);
   $height = (int) round($height);
 
-  return $image->toolkit->crop($image, $x, $y, $width, $height);
+  return $image->toolkit->crop($image, $x, $y, $width, $height, $background_color, $background_alpha);
 }
 
 /**
diff --git a/core/modules/image/lib/Drupal/image/ImageEffectBackgroundColorBase.php b/core/modules/image/lib/Drupal/image/ImageEffectBackgroundColorBase.php
new file mode 100644
index 0000000..6129e8a
--- /dev/null
+++ b/core/modules/image/lib/Drupal/image/ImageEffectBackgroundColorBase.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image\ImageEffectBackgroundColorBase.
+ */
+
+namespace Drupal\image;
+
+use Drupal\image\ImageEffectBase;
+
+/**
+ * Provide a base class for effects that requires background color.
+ */
+abstract class ImageEffectBackgroundColorBase extends ImageEffectBase implements ConfigurableImageEffectInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applyEffect($image) {
+    // Set sane default values.
+    $this->configuration += array(
+      'background_has_color' => FALSE,
+      'background_color' => NULL,
+      'background_alpha' => 0,
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getForm() {
+    $form = array();
+    $this->configuration += array(
+      'background_has_color' => FALSE,
+      'background_color' => '',
+      'background_alpha' => 0,
+    );
+
+    $states = array(
+      '#states' => array(
+        'visible' => array(
+          ':input[name="data[background_has_color]"]' => array('checked' => TRUE),
+        ),
+      ),
+    );
+
+    $form['background_has_color'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Add color to additional created areas'),
+      '#description' => t('Applying the %effect effect may reveal additional areas. By default, those areas are transparent, if the image type allows, or black if the image has no transparency capability. Check to define a specific color to be filled into new areas.', array('%effect' => $this->label())),
+      '#default_value' => $this->configuration['background_has_color'],
+    );
+    $form['background_color'] = array(
+      '#type' => 'color',
+      '#title' => t('Color'),
+      '#default_value' => $this->configuration['background_color'],
+      '#size' => 12,
+      '#maxlength' => 7,
+      '#description' => t('Pickup or enter a hex string specifying the color. Examples: "#RGB", "#RRGGBB".'),
+    ) + $states;
+    $form['background_alpha'] = array(
+      '#type' => 'number',
+      '#title' => t('Alpha transparency'),
+      '#default_value' => $this->configuration['background_alpha'],
+      '#description' => t('From 0 (opaque) to 100% (transparent).'),
+      '#field_suffix' => '%',
+      '#min' => 0,
+      '#max' => 100,
+    ) + $states;
+    return $form;
+  }
+
+}
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php
index 11f228e..4d88834 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/CropImageEffect.php
@@ -9,6 +9,7 @@
 
 use Drupal\Core\Annotation\Translation;
 use Drupal\image\Annotation\ImageEffect;
+use Drupal\image\ImageEffectBackgroundColorBase;
 
 /**
  * Crops an image resource.
@@ -19,13 +20,14 @@
  *   description = @Translation("Resizing will make images an exact set of dimensions. This may cause images to be stretched or shrunk disproportionately.")
  * )
  */
-class CropImageEffect extends ResizeImageEffect {
+class CropImageEffect extends ImageEffectBackgroundColorBase {
 
   /**
    * {@inheritdoc}
    */
   public function applyEffect($image) {
     // Set sane default values.
+    parent::applyEffect($image);
     $this->configuration += array(
       'anchor' => 'center-center',
     );
@@ -33,7 +35,12 @@ public function applyEffect($image) {
     list($x, $y) = explode('-', $this->configuration['anchor']);
     $x = image_filter_keyword($x, $image->info['width'], $this->configuration['width']);
     $y = image_filter_keyword($y, $image->info['height'], $this->configuration['height']);
-    if (!image_crop($image, $x, $y, $this->configuration['width'], $this->configuration['height'])) {
+
+    $has_background_color = (bool) $this->configuration['background_has_color'];
+    $background_color = $has_background_color ? $this->configuration['background_color'] : NULL;
+    $background_alpha = $has_background_color ? $this->configuration['background_alpha'] : 0;
+
+    if (!image_crop($image, $x, $y, $this->configuration['width'], $this->configuration['height'], $background_color, $background_alpha)) {
       watchdog('image', 'Image crop failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
       return FALSE;
     }
@@ -43,6 +50,15 @@ public function applyEffect($image) {
   /**
    * {@inheritdoc}
    */
+  public function transformDimensions(array &$dimensions) {
+    // The new image will have the exact dimensions defined for the effect.
+    $dimensions['width'] = $this->configuration['width'];
+    $dimensions['height'] = $this->configuration['height'];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function getSummary() {
     return array(
       '#theme' => 'image_crop_summary',
@@ -54,12 +70,32 @@ public function getSummary() {
    * {@inheritdoc}
    */
   public function getForm() {
+    $form = parent::getForm();
     $this->configuration += array(
       'width' => '',
       'height' => '',
       'anchor' => 'center-center',
     );
-    $form = parent::getForm();
+    $form['width'] = array(
+      '#type' => 'number',
+      '#title' => t('Width'),
+      '#default_value' => $this->configuration['width'],
+      '#field_suffix' => ' ' . t('pixels'),
+      '#required' => TRUE,
+      '#min' => 1,
+      // Place this element above the parent elements.
+      '#weight' => -2,
+    );
+    $form['height'] = array(
+      '#type' => 'number',
+      '#title' => t('Height'),
+      '#default_value' => $this->configuration['height'],
+      '#field_suffix' => ' ' . t('pixels'),
+      '#required' => TRUE,
+      '#min' => 1,
+      // Place this element above the parent elements.
+      '#weight' => -1,
+    );
     $form['anchor'] = array(
       '#type' => 'radios',
       '#title' => t('Anchor'),
@@ -77,6 +113,8 @@ public function getForm() {
       '#theme' => 'image_anchor',
       '#default_value' => $this->configuration['anchor'],
       '#description' => t('The part of the image that will be retained during the crop.'),
+      // Place this element below the parent elements.
+      '#weight' => 1,
     );
     return $form;
   }
diff --git a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php
index dee537e..2778493 100644
--- a/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php
+++ b/core/modules/image/lib/Drupal/image/Plugin/ImageEffect/RotateImageEffect.php
@@ -10,7 +10,7 @@
 use Drupal\Core\Annotation\Translation;
 use Drupal\image\Annotation\ImageEffect;
 use Drupal\image\ConfigurableImageEffectInterface;
-use Drupal\image\ImageEffectBase;
+use Drupal\image\ImageEffectBackgroundColorBase;
 
 /**
  * Rotates an image resource.
@@ -21,39 +21,29 @@
  *   description = @Translation("Rotating an image may cause the dimensions of an image to increase to fit the diagonal.")
  * )
  */
-class RotateImageEffect extends ImageEffectBase implements ConfigurableImageEffectInterface {
+class RotateImageEffect extends ImageEffectBackgroundColorBase {
 
   /**
    * {@inheritdoc}
    */
   public function applyEffect($image) {
     // Set sane default values.
+    parent::applyEffect($image);
     $this->configuration += array(
       'degrees' => 0,
-      'bgcolor' => NULL,
       'random' => FALSE,
     );
 
-    // Convert short #FFF syntax to full #FFFFFF syntax.
-    if (strlen($this->configuration['bgcolor']) == 4) {
-      $c = $this->configuration['bgcolor'];
-      $this->configuration['bgcolor'] = $c[0] . $c[1] . $c[1] . $c[2] . $c[2] . $c[3] . $c[3];
-    }
-
-    // Convert #FFFFFF syntax to hexadecimal colors.
-    if ($this->configuration['bgcolor'] != '') {
-      $this->configuration['bgcolor'] = hexdec(str_replace('#', '0x', $this->configuration['bgcolor']));
-    }
-    else {
-      $this->configuration['bgcolor'] = NULL;
-    }
+    $has_background_color = (bool) $this->configuration['background_has_color'];
+    $background_color = $has_background_color ? $this->configuration['background_color'] : NULL;
+    $background_alpha = $has_background_color ? $this->configuration['background_alpha'] : 0;
 
     if (!empty($this->configuration['random'])) {
       $degrees = abs((float) $this->configuration['degrees']);
       $this->configuration['degrees'] = rand(-1 * $degrees, $degrees);
     }
 
-    if (!image_rotate($image, $this->configuration['degrees'], $this->configuration['bgcolor'])) {
+    if (!image_rotate($image, $this->configuration['degrees'], $background_color, $background_alpha)) {
       watchdog('image', 'Image rotate failed using the %toolkit toolkit on %path (%mimetype, %dimensions)', array('%toolkit' => $image->toolkit->getPluginId(), '%path' => $image->source, '%mimetype' => $image->info['mime_type'], '%dimensions' => $image->info['width'] . 'x' . $image->info['height']), WATCHDOG_ERROR);
       return FALSE;
     }
@@ -92,6 +82,8 @@ public function getSummary() {
    * {@inheritdoc}
    */
   public function getForm() {
+    $form = parent::getForm();
+
     $form['degrees'] = array(
       '#type' => 'number',
       '#default_value' => (isset($this->configuration['degrees'])) ? $this->configuration['degrees'] : 0,
@@ -99,34 +91,18 @@ public function getForm() {
       '#description' => t('The number of degrees the image should be rotated. Positive numbers are clockwise, negative are counter-clockwise.'),
       '#field_suffix' => '&deg;',
       '#required' => TRUE,
-    );
-    $form['bgcolor'] = array(
-      '#type' => 'textfield',
-      '#default_value' => (isset($this->configuration['bgcolor'])) ? $this->configuration['bgcolor'] : '#FFFFFF',
-      '#title' => t('Background color'),
-      '#description' => t('The background color to use for exposed areas of the image. Use web-style hex colors (#FFFFFF for white, #000000 for black). Leave blank for transparency on image types that support it.'),
-      '#size' => 7,
-      '#maxlength' => 7,
-      '#element_validate' => array(array($this, 'validateColorEffect')),
+      // Place this element above the parent elements.
+      '#weight' => -1,
     );
     $form['random'] = array(
       '#type' => 'checkbox',
       '#default_value' => (isset($this->configuration['random'])) ? $this->configuration['random'] : 0,
       '#title' => t('Randomize'),
       '#description' => t('Randomize the rotation angle for each image. The angle specified above is used as a maximum.'),
+      // Place this element below the parent elements.
+      '#weight' => 1,
     );
     return $form;
   }
 
-  /**
-   * Validates to ensure a hexadecimal color value.
-   */
-  public function validateColorEffect(array $element, array &$form_state) {
-    if ($element['#value'] != '') {
-      if (!preg_match('/^#[0-9A-F]{3}([0-9A-F]{3})?$/', $element['#value'])) {
-        form_error($element, t('!name must be a hexadecimal color value.', array('!name' => $element['#title'])));
-      }
-    }
-  }
-
 }
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php
index 6bcf733..41d62f7 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageAdminStylesTest.php
@@ -89,6 +89,9 @@ function testStyle() {
         'data[width]' => 130,
         'data[height]' => 131,
         'data[anchor]' => 'center-center',
+        'data[background_has_color]' => TRUE,
+        'data[background_color]' => '#ffff00',
+        'data[background_alpha]' => 0,
       ),
       'image_desaturate' => array(
         // No options for desaturate.
@@ -96,7 +99,9 @@ function testStyle() {
       'image_rotate' => array(
         'data[degrees]' => 5,
         'data[random]' => 1,
-        'data[bgcolor]' => '#FFFF00',
+        'data[background_has_color]' => TRUE,
+        'data[background_color]' => '#ffff00',
+        'data[background_alpha]' => 0,
       ),
     );
 
diff --git a/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php b/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php
index 74f41ec..4cb4b97 100644
--- a/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php
+++ b/core/modules/image/lib/Drupal/image/Tests/ImageEffectsTest.php
@@ -132,14 +132,16 @@ function testRotateEffect() {
     // @todo: need to test with 'random' => TRUE
     $this->assertImageEffect('image_rotate', array(
       'degrees' => 90,
-      'bgcolor' => '#fff',
+      'background_has_color' => TRUE,
+      'background_color' => '#FFFFFF',
+      'background_alpha' => 0,
     ));
     $this->assertToolkitOperationsCalled(array('rotate'));
 
     // Check the parameters.
     $calls = $this->imageTestGetAllCalls();
     $this->assertEqual($calls['rotate'][0][1], 90, 'Degrees were passed correctly');
-    $this->assertEqual($calls['rotate'][0][2], 0xffffff, 'Background color was passed correctly');
+    $this->assertEqual($calls['rotate'][0][2], '#FFFFFF', 'Background color was passed correctly');
   }
 
   /**
@@ -177,4 +179,90 @@ protected function assertImageEffect($effect_name, array $data) {
     return $this->assertTrue($effect->applyEffect($this->image), 'Function returned the expected value.');
   }
 
+  /**
+   * Tests that image cropping uses the correct background color.
+   */
+  public function testCropBackgroundColor() {
+
+    // Source image width and height.
+    $width = 50;
+    $height = 25;
+
+    // Create a new non-squared image filled with pure blue color.
+    $resource = imagecreatetruecolor($width, $height);
+    imagefill($resource, 0, 0, imagecolorallocate($resource, 0, 0, 255));
+
+    // Create an image destination.
+    $extension_png = image_type_to_extension(IMAGETYPE_PNG);
+    $file = 'public://' . $this->randomName() . $extension_png;
+
+    // Save test image to disk.
+    $image = (object) array(
+      'source' => $file,
+      'resource' => $resource,
+      'info' => array(
+        'width' => $width,
+        'height' => $height,
+        'extension' => ltrim($extension_png, '.'),
+        'mime_type' => image_type_to_mime_type(IMAGETYPE_PNG),
+      ),
+      'toolkit' => \Drupal::service('image.toolkit'),
+    );
+    image_save($image, $file);
+
+    // Define 2 image styles.
+    $styles = array(
+      'color' => array(
+        'label' => 'Color',
+        'color' => array(
+          'background_has_color' => TRUE,
+          'background_color' => '#ff0000',
+          'background_alpha' => 0,
+        ),
+        'expected' => array('red' => 255, 'green' => 0, 'blue' => 0, 'alpha' => 0),
+      ),
+      'no_color' => array(
+        'label' => 'No color',
+        'color' => array(
+          'background_has_color' => FALSE,
+          'background_color' => NULL,
+          'background_alpha' => 0,
+        ),
+        'expected' => array('red' => 0, 'green' => 0, 'blue' => 0, 'alpha' => 127),
+      ),
+    );
+
+    foreach ($styles as $name => $info) {
+
+      // Create the image style.
+      $style = entity_create('image_style', array('name' => $name, 'label' => $info['label']));
+      $style->save();
+
+      // Build and attach the effect.
+      $effect = array(
+        'weight' => 0,
+        'id' => 'image_crop',
+        'data' => array(
+          'width' => 50,
+          'height' => 50,
+          'anchor' => 'center-center',
+        ),
+      );
+      $effect['data'] += $info['color'];
+      $style->saveImageEffect($effect);
+
+      // Create derivative with a simple GET.
+      $this->drupalGet($style->buildUrl($image->source));
+
+      // Load image as resource to inspect the pixel from (5, 5) which is in the
+      // new added area.
+      $uri = $style->buildUri($image->source);
+      $derivative = image_load($uri);
+      $rgb = imagecolorat($derivative->resource, 5, 5);
+      $colors = imagecolorsforindex($derivative->resource, $rgb);
+      $color = array('%color' => $info['color']['background_has_color'] ? 'red (#ff0000)' : 'transparent');
+      $this->assertIdentical($colors, $info['expected'], format_string('Pixel from (5, 5) is %color', $color));
+    }
+  }
+
 }
diff --git a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
index 06cb4a6..4acb59c 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkit/GDToolkit.php
@@ -10,6 +10,7 @@
 use Drupal\Component\Plugin\PluginBase;
 use Drupal\Component\Annotation\Plugin;
 use Drupal\Core\Annotation\Translation;
+use Drupal\Core\Utility\Color;
 use Drupal\system\Plugin\ImageToolkitInterface;
 
 /**
@@ -68,7 +69,7 @@ public function resize($image, $width, $height) {
   /**
    * Implements \Drupal\system\Plugin\ImageToolkitInterface::rotate().
    */
-  public function rotate($image, $degrees, $background = NULL) {
+  public function rotate($image, $degrees, $background_color = NULL, $background_alpha = 0) {
     // 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));
@@ -79,14 +80,12 @@ public function rotate($image, $degrees, $background = NULL) {
     $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);
+    if (!empty($background_color)) {
+      if ($background = Color::hexToRgb($background_color)) {
+        $background = imagecolorallocatealpha($image->resource, $background['red'], $background['green'], $background['blue'], $background_alpha);
       }
-      $background = imagecolorallocatealpha($image->resource, $rgb[0], $rgb[1], $rgb[2], 0);
     }
-    // Set the background color as transparent if $background is NULL.
+    // Set the background color as transparent if $background_color is NULL.
     else {
       // Get the current transparent color.
       $background = imagecolortransparent($image->resource);
@@ -120,12 +119,23 @@ public function rotate($image, $degrees, $background = NULL) {
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::crop().
+   * {@inheritdoc}
    */
-  public function crop($image, $x, $y, $width, $height) {
+  public function crop($image, $x, $y, $width, $height, $background_color = NULL, $background_alpha = 0) {
     $res = $this->createTmp($image, $width, $height);
 
-    if (!imagecopyresampled($res, $image->resource, 0, 0, $x, $y, $width, $height, $width, $height)) {
+    // Fill the background color if it has been defined.
+    if (!empty($background_color)) {
+      if ($background = Color::hexToRgb($background_color)) {
+        $background = imagecolorallocatealpha($res, $background['red'], $background['green'], $background['blue'], $background_alpha);
+        imagefill($res, 0, 0, $background);
+      }
+    }
+
+    // Copy the source image to our new destination image. We use
+    // $image->info['width'] instead of $width because we are copying using the
+    // source image's width and height, not the destination width and height.
+    if (!imagecopyresampled($res, $image->resource, -$x, -$y, 0, 0, $image->info['width'], $image->info['height'], $image->info['width'], $image->info['height'])) {
       return FALSE;
     }
 
diff --git a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php
index 0b22f3c..bc83360 100644
--- a/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php
+++ b/core/modules/system/lib/Drupal/system/Plugin/ImageToolkitInterface.php
@@ -55,19 +55,21 @@ function resize($image, $width, $height);
    *   $image->info['height'] values will be modified by this call.
    * @param int $degrees
    *   The number of (clockwise) degrees to rotate the image.
-   * @param string $background
-   *   (optional) 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.
+   * @param string $background_color
+   *   (optional) An hexadecimal string specifying the background color to use
+   *   for uncovered area of the image after the rotation. Examples: "#RGB",
+   *   "#RRGGBB". For images that support transparency, this will default to
+   *   transparent. Otherwise it will be white.
+   * @param string $background_alpha
+   *   (optional) The value of alpha transparency for $background_color.
+   *   Defaults to 0.
    *
    * @return bool
    *   TRUE or FALSE, based on success.
    *
    * @see image_rotate()
    */
-  function rotate($image, $degrees, $background = NULL);
+  function rotate($image, $degrees, $background_color = NULL, $background_alpha = 0);
 
   /**
    * Crops an image.
@@ -83,13 +85,22 @@ function rotate($image, $degrees, $background = NULL);
    *   The width of the cropped area, in pixels.
    * @param int $height
    *   The height of the cropped area, in pixels.
+   * @param string $background_color
+   *   (optional) An hexadecimal string specifying the background color to use
+   *   for the additional area created when cropping to larger dimensions than
+   *   the source. Examples: "#RGB", "#RRGGBB". For images that support
+   *   transparency, this will default to transparent. Otherwise it will be
+   *   white.
+   * @param string $background_alpha
+   *   (optional) The value of alpha transparency for $background_color.
+   *   Defaults to 0.
    *
    * @return bool
    *   TRUE or FALSE, based on success.
    *
    * @see image_crop()
    */
-  function crop($image, $x, $y, $width, $height);
+  function crop($image, $x, $y, $width, $height, $background_color = NULL, $background_alpha = 0);
 
   /**
    * Converts an image resource to grayscale.
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 624dc79..7041bbd 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Image/ToolkitGdTest.php
@@ -165,14 +165,14 @@ function testManipulations() {
       $operations += array(
         'rotate_5' => array(
           'function' => 'rotate',
-          'arguments' => array(5, 0xFF00FF), // Fuchsia background.
+          'arguments' => array(5, '#FF00FF'), // Fuchsia background.
           'width' => 42,
           'height' => 24,
           'corners' => array_fill(0, 4, $this->fuchsia),
         ),
         'rotate_90' => array(
           'function' => 'rotate',
-          'arguments' => array(90, 0xFF00FF), // Fuchsia background.
+          'arguments' => array(90, '#FF00FF'), // Fuchsia background.
           'width' => 20,
           'height' => 40,
           'corners' => array($this->transparent, $this->red, $this->green, $this->blue),
diff --git a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php
index 12ebae7..256fded 100644
--- a/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php
+++ b/core/modules/system/tests/modules/image_test/lib/Drupal/image_test/Plugin/ImageToolkit/TestToolkit.php
@@ -62,10 +62,10 @@ public function save($image, $destination) {
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::crop().
+   * {@inheritdoc}
    */
-  public function crop($image, $x, $y, $width, $height) {
-    $this->logCall('crop', array($image, $x, $y, $width, $height));
+  public function crop($image, $x, $y, $width, $height, $background_color = NULL, $background_alpha = 0) {
+    $this->logCall('crop', array($image, $x, $y, $width, $height, $background_color, $background_alpha));
     return TRUE;
   }
 
@@ -78,10 +78,10 @@ public function resize($image, $width, $height) {
   }
 
   /**
-   * Implements \Drupal\system\Plugin\ImageToolkitInterface::rotate().
+   * {@inheritdoc}
    */
-  public function rotate($image, $degrees, $background = NULL) {
-    $this->logCall('rotate', array($image, $degrees, $background));
+  public function rotate($image, $degrees, $background_color = NULL, $background_alpha = 0) {
+    $this->logCall('rotate', array($image, $degrees, $background_color, $background_alpha));
     return TRUE;
   }
 
-- 
1.8.3.1

