diff --git a/README.md b/README.md
index cd3e787..efad6cc 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@ The effects that this module provides include:
 Effect name      | Description                                                                                  | GD toolkit | [ImageMagick](https://drupal.org/project/imagemagick) toolkit |
 -----------------|----------------------------------------------------------------------------------------------|:----------:|:-------------------:|
 Auto orientation | Uses EXIF Orientation tags to determine the image orientation.                               | X          | X                   |
+Background       | Places the source image anywhere over a selected background image.                           | X          | X                   |
 Brightness       | Supports changing brightness settings of an image. Also supports negative values (darkening).| X          | X                   |
 Color shift      | Colorizes image.                                                                             | X          | X                   |
 Contrast         | Supports changing contrast settings of an image. Also supports negative values.              | X          | X                   |
diff --git a/config/schema/image_effects.schema.yml b/config/schema/image_effects.schema.yml
index 6fc82e9..e161cca 100644
--- a/config/schema/image_effects.schema.yml
+++ b/config/schema/image_effects.schema.yml
@@ -99,6 +99,32 @@ image.effect.image_effects_auto_orient:
       type: boolean
       label: 'Scan EXIF data when calculating styled image dimensions'
 
+image.effect.image_effects_background:
+  type: mapping
+  label: 'Background image effect'
+  mapping:
+    background_image:
+      type: string
+      label: 'Background image path'
+    background_image_width:
+      type: string
+      label: 'Background image width in px'
+    background_image_height:
+      type: string
+      label: 'Background image height in px'
+    placement:
+      type: string
+      label: 'Position of the source image on the background image'
+    x_offset:
+      type: integer
+      label: 'X offset of the source image vs placement'
+    y_offset:
+      type: integer
+      label: 'Y offset of the source image vs placement'
+    opacity:
+      type: integer
+      label: 'Opacity of the source image'
+
 image.effect.image_effects_brightness:
   type: mapping
   label: 'Adjust image brightness'
diff --git a/image_effects.module b/image_effects.module
index ebabde1..21da1eb 100644
--- a/image_effects.module
+++ b/image_effects.module
@@ -21,14 +21,18 @@ function image_effects_theme() {
         'border_color' => '#000000',
       ],
     ],
-    // Color shift image effect - summary.
-    'image_effects_color_shift_summary' => [
+    // Background image effect - summary
+    'image_effects_background_summary' => [
       'variables' => ['data' => NULL, 'effect' => []],
     ],
     // Brightness image effect - summary
     'image_effects_brightness_summary' => [
       'variables' => ['data' => NULL, 'effect' => []],
     ],
+    // Color shift image effect - summary.
+    'image_effects_color_shift_summary' => [
+      'variables' => ['data' => NULL, 'effect' => []],
+    ],
     // Contrast image effect - summary
     'image_effects_contrast_summary' => [
       'variables' => ['data' => NULL, 'effect' => []],
diff --git a/src/Plugin/ImageEffect/BackgroundImageEffect.php b/src/Plugin/ImageEffect/BackgroundImageEffect.php
new file mode 100644
index 0000000..a8b9ae5
--- /dev/null
+++ b/src/Plugin/ImageEffect/BackgroundImageEffect.php
@@ -0,0 +1,212 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image_effects\Plugin\ImageEffect\BackgroundImageEffect.
+ */
+
+namespace Drupal\image_effects\Plugin\ImageEffect;
+
+use Drupal\Core\Image\ImageInterface;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\image\ConfigurableImageEffectBase;
+use Drupal\image_effects\Plugin\ImageEffectsPluginBaseInterface;
+use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Image\ImageFactory;
+
+/**
+ * Class BackgroundImageEffect
+ *
+ * @ImageEffect(
+ *   id = "image_effects_background",
+ *   label = @Translation("Background"),
+ *   description = @Translation("Places the source image anywhere over a selected background image.")
+ * )
+ */
+class BackgroundImageEffect extends ConfigurableImageEffectBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * The image factory service.
+   *
+   * @var \Drupal\Core\Image\ImageFactory
+   */
+  protected $imageFactory;
+
+  /**
+   * The image selector plugin.
+   *
+   * @var \Drupal\image_effects\Plugin\ImageEffectsPluginBaseInterface
+   */
+  protected $imageSelector;
+
+  /**
+   * Constructs an BackgroundImageEffect object
+   *
+   * @param array $configuration
+   *   A configuration array containing information about the plugin instance.
+   * @param string $plugin_id
+   *   The plugin_id for the plugin instance.
+   * @param array $plugin_definition
+   *   The plugin implementation definition.
+   * @param \Psr\Log\LoggerInterface $logger
+   *   A logger instance.
+   * @param \Drupal\Core\Image\ImageFactory $image_factory
+   *   The image factory service.
+   * @param \Drupal\image_effects\Plugin\ImageEffectsPluginBaseInterface $image_selector
+   *   The image selector plugin.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, LoggerInterface $logger, ImageFactory $image_factory, ImageEffectsPluginBaseInterface $image_selector) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition, $logger);
+    $this->imageFactory = $image_factory;
+    $this->imageSelector = $image_selector;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('logger.factory')->get('image'),
+      $container->get('image.factory'),
+      $container->get('plugin.manager.image_effects.image_selector')->getPlugin()
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function defaultConfiguration() {
+    return [
+      'placement' => 'center-center',
+      'x_offset' => 0,
+      'y_offset' => 0,
+      'opacity' => 100,
+      'background_image' => '',
+    ] + parent::defaultConfiguration();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getSummary() {
+    $summary = array(
+      '#theme' => 'image_effects_background_summary',
+      '#data' => $this->configuration,
+    );
+    $summary += parent::getSummary();
+
+    return $summary;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, FormStateInterface $form_state) {
+    $options = [
+      '#title' => $this->t('Background image'),
+      '#description' => $this->t('Image to use for background.'),
+      '#default_value' => $this->configuration['background_image'],
+    ];
+    $form['background_image'] = $this->imageSelector->selectionElement($options);
+    $form['placement'] = [
+      '#type' => 'radios',
+      '#title' => $this->t('Placement'),
+      '#options' => [
+        'left-top' => $this->t('Top left'),
+        'center-top' => $this->t('Top center'),
+        'right-top' => $this->t('Top right'),
+        'left-center' => $this->t('Center left'),
+        'center-center' => $this->t('Center'),
+        'right-center' => $this->t('Center right'),
+        'left-bottom' => $this->t('Bottom left'),
+        'center-bottom' => $this->t('Bottom center'),
+        'right-bottom' => $this->t('Bottom right'),
+      ],
+      '#theme' => 'image_anchor',
+      '#default_value' => $this->configuration['placement'],
+      '#description' => $this->t('Position of the source image on the background image.'),
+    ];
+    $form['x_offset'] = [
+      '#type'  => 'number',
+      '#title' => $this->t('Horizontal offset'),
+      '#field_suffix'  => 'px',
+      '#description'   => $this->t('Additional horizontal offset from placement.'),
+      '#default_value' => $this->configuration['x_offset'],
+      '#maxlength' => 4,
+      '#size' => 4,
+    ];
+    $form['y_offset'] = [
+      '#type'  => 'number',
+      '#title' => $this->t('Vertical offset'),
+      '#field_suffix'  => 'px',
+      '#description'   => $this->t('Additional vertical offset from placement.'),
+      '#default_value' => $this->configuration['y_offset'],
+      '#maxlength' => 4,
+      '#size' => 4,
+    ];
+    $form['opacity'] = [
+      '#type' => 'number',
+      '#title' => $this->t('Opacity'),
+      '#field_suffix' => '%',
+      '#description' => $this->t('Opacity of the source image when overlaid on the background image, in percentage. 0% means fully transparent, 100% fully opaque.'),
+      '#default_value' => $this->configuration['opacity'],
+      '#min' => 0,
+      '#max' => 100,
+      '#maxlength' => 3,
+      '#size' => 3
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, FormStateInterface $form_state) {
+    parent::submitConfigurationForm($form, $form_state);
+    $this->configuration['placement'] = $form_state->getValue('placement');
+    $this->configuration['x_offset'] = $form_state->getValue('x_offset');
+    $this->configuration['y_offset'] = $form_state->getValue('y_offset');
+    $this->configuration['opacity'] = $form_state->getValue('opacity');
+    $this->configuration['background_image'] = $form_state->getValue('background_image');
+
+    // Stores background image width and height in configuration to avoid the
+    // need to fetch the image from storage in ::transformDimensions.
+    $background_image = $this->imageFactory->get($this->configuration['background_image']);
+    $this->configuration['background_image_width'] = $background_image->getWidth();
+    $this->configuration['background_image_height'] = $background_image->getHeight();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function applyEffect(ImageInterface $image) {
+    $background_image = $this->imageFactory->get($this->configuration['background_image']);
+    if (!$background_image->isValid()) {
+      return FALSE;
+    }
+    list($x, $y) = explode('-', $this->configuration['placement']);
+    $x_pos = image_filter_keyword($x, $background_image->getWidth(), $image->getWidth());
+    $y_pos = image_filter_keyword($y, $background_image->getHeight(), $image->getHeight());
+    return $image->apply('background', [
+      'x_offset' => $x_pos + $this->configuration['x_offset'],
+      'y_offset' => $y_pos + $this->configuration['y_offset'],
+      'opacity' => $this->configuration['opacity'],
+      'background_image' => $background_image,
+    ]);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transformDimensions(array &$dimensions, $uri) {
+    $dimensions['width'] = $this->configuration['background_image_width'];
+    $dimensions['height'] = $this->configuration['background_image_height'];
+    return;
+  }
+
+}
diff --git a/src/Plugin/ImageToolkit/Operation/BackgroundTrait.php b/src/Plugin/ImageToolkit/Operation/BackgroundTrait.php
new file mode 100644
index 0000000..559f5f7
--- /dev/null
+++ b/src/Plugin/ImageToolkit/Operation/BackgroundTrait.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image_effects\Plugin\ImageToolkit\Operation\BackgroundTrait.
+ */
+
+namespace Drupal\image_effects\Plugin\ImageToolkit\Operation;
+
+use Drupal\Core\Image\ImageInterface;
+
+/**
+ * Base trait for image_effects Background operations.
+ */
+trait BackgroundTrait {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function arguments() {
+    return [
+      'x_offset' => [
+        'description' => 'X offset for source image.',
+      ],
+      'y_offset' => [
+        'description' => 'Y offset for source image.'
+      ],
+      'opacity' => [
+        'description' => 'Opacity for source image.',
+        'required' => FALSE,
+        'default' => 100,
+      ],
+      'background_image' => [
+        'description' => 'Image to use for background.'
+      ]
+    ];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function validateArguments(array $arguments) {
+    // Ensure source image opacity is in the range 0-100.
+    if ($arguments['opacity'] > 100 || $arguments['opacity'] < 0) {
+      throw new \InvalidArgumentException("Invalid opacity ('{$arguments['opacity']}') specified for the image 'background' operation");
+    }
+    // Ensure background_image is an expected ImageInterface object.
+    if (!$arguments['background_image'] instanceof ImageInterface) {
+      throw new \InvalidArgumentException("Background image passed to the 'background' operation is invalid");
+    }
+    // Ensure background_image is a valid image.
+    if (!$arguments['background_image']->isValid()) {
+      $source = $arguments['background_image']->getSource();
+      throw new \InvalidArgumentException("Invalid image at {$source}");
+    }
+    return $arguments;
+  }
+
+}
diff --git a/src/Plugin/ImageToolkit/Operation/gd/Background.php b/src/Plugin/ImageToolkit/Operation/gd/Background.php
new file mode 100644
index 0000000..978b3a6
--- /dev/null
+++ b/src/Plugin/ImageToolkit/Operation/gd/Background.php
@@ -0,0 +1,95 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\image_effects\Plugin\ImageToolkit\Operation\gd\Background.
+ */
+
+namespace Drupal\image_effects\Plugin\ImageToolkit\Operation\gd;
+
+use Drupal\system\Plugin\ImageToolkit\Operation\gd\GDImageToolkitOperationBase;
+use Drupal\image_effects\Plugin\ImageToolkit\Operation\BackgroundTrait;
+use Drupal\image_effects\Plugin\ImageToolkit\Operation\gd\GDOperationTrait;
+
+/**
+ * Defines GD Background operation.
+ *
+ * @ImageToolkitOperation(
+ *   id = "image_effects_gd_background",
+ *   toolkit = "gd",
+ *   operation = "background",
+ *   label = @Translation("Background"),
+ *   description = @Translation("Places the source image over a background image.")
+ * )
+ */
+class Background extends GDImageToolkitOperationBase {
+
+  use BackgroundTrait;
+  use GDOperationTrait;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function execute(array $arguments) {
+    // Preserves original resource, to be destroyed upon success.
+    $original_resource = $this->getToolkit()->getResource();
+
+    // Prepare a new image.
+    $data = [
+      'width' => $arguments['background_image']->getWidth(),
+      'height' => $arguments['background_image']->getHeight(),
+      'extension' => image_type_to_extension($this->getToolkit()->getType(), FALSE),
+      'transparent_color' => $this->getToolkit()->getTransparentColor(),
+      'is_temp' => TRUE,
+    ];
+    if (!$this->getToolkit()->apply('create_new', $data)) {
+      // In case of failure, destroy the temporary resource and restore
+      // the original one.
+      imagedestroy($this->getToolkit()->getResource());
+      $this->getToolkit()->setResource($original_resource);
+      return FALSE;
+    }
+
+    // Overlay background at 0,0.
+    $success = $this->imageCopyMergeAlpha(
+      $this->getToolkit()->getResource(),
+      $arguments['background_image']->getToolkit()->getResource(),
+      0,
+      0,
+      0,
+      0,
+      $arguments['background_image']->getWidth(),
+      $arguments['background_image']->getHeight(),
+      100
+    );
+    if (!$success) {
+      // In case of failure, destroy the temporary resource and restore
+      // the original one.
+      imagedestroy($this->getToolkit()->getResource());
+      $this->getToolkit()->setResource($original_resource);
+      return FALSE;
+    }
+
+    // Overlay original source at offset.
+    $success = $this->imageCopyMergeAlpha(
+      $this->getToolkit()->getResource(),
+      $original_resource,
+      $arguments['x_offset'],
+      $arguments['y_offset'],
+      0,
+      0,
+      imagesx($original_resource),
+      imagesy($original_resource),
+      $arguments['opacity']
+    );
+    if ($success) {
+      imagedestroy($original_resource);
+    }
+    else {
+      imagedestroy($this->getToolkit()->getResource());
+      $this->getToolkit()->setResource($original_resource);
+    }
+    return $success;
+  }
+
+}
diff --git a/src/Plugin/ImageToolkit/Operation/gd/GDOperationTrait.php b/src/Plugin/ImageToolkit/Operation/gd/GDOperationTrait.php
index 94070e0..b5c2381 100644
--- a/src/Plugin/ImageToolkit/Operation/gd/GDOperationTrait.php
+++ b/src/Plugin/ImageToolkit/Operation/gd/GDOperationTrait.php
@@ -81,4 +81,73 @@ trait GDOperationTrait {
     return $points;
   }
 
+  /**
+   * Copy and merge part of an image, preserving alpha.
+   *
+   * The standard imagecopymerge() function in PHP GD fails to preserve the
+   * alpha information of two merged images. This method implements the
+   * workaround described in
+   * http://php.net/manual/en/function.imagecopymerge.php#92787
+   *
+   * @param resource $dst_im
+   *   Destination image link resource.
+   * @param resource $src_im
+   *   Source image link resource.
+   * @param int $dst_x
+   *   X-coordinate of destination point.
+   * @param int $dst_y
+   *   Y-coordinate of destination point.
+   * @param int $src_x
+   *   X-coordinate of source point.
+   * @param int $src_y
+   *   Y-coordinate of source point.
+   * @param int $src_w
+   *   Source width.
+   * @param int $src_h
+   *   Source height.
+   * @param int $pct
+   *   Opacity of the source image in percentage.
+   *
+   * @return bool
+   *   Returns TRUE on success or FALSE on failure.
+   *
+   * @see http://php.net/manual/en/function.imagecopymerge.php#92787
+   */
+  protected function imageCopyMergeAlpha($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h, $pct) {
+    if ($pct === 100) {
+      // Use imagecopy() if opacity is 100%.
+      return imagecopy($dst_im, $src_im, $dst_x, $dst_y, $src_x, $src_y, $src_w, $src_h);
+    }
+    else {
+      // If opacity is below 100%, use the approach described in
+      // http://php.net/manual/it/function.imagecopymerge.php#92787
+      // to preserve watermark alpha.
+
+      // Create a cut resource.
+      // @todo when #2583041 is committed, add a check for memory
+      // availability before creating the resource.
+      $cut = imagecreatetruecolor($src_w, $src_h);
+      if (!is_resource($cut)) {
+        return FALSE;
+      }
+
+      // Copy relevant section from destination image to the cut resource.
+      if (!imagecopy($cut, $dst_im, 0, 0, $dst_x, $dst_y, $src_w, $src_h)) {
+        imagedestroy($cut);
+        return FALSE;
+      }
+
+      // Copy relevant section from merged image to the cut resource.
+      if (!imagecopy($cut, $src_im, 0, 0, $src_x, $src_y, $src_w, $src_h)) {
+        imagedestroy($cut);
+        return FALSE;
+      }
+
+      // Insert cut resource to destination image.
+      $success = imagecopymerge($dst_im, $cut, $dst_x, $dst_y, 0, 0, $src_w, $src_h, $pct);
+      imagedestroy($cut);
+      return $success;
+    }
+  }
+
 }
diff --git a/src/Plugin/ImageToolkit/Operation/gd/Watermark.php b/src/Plugin/ImageToolkit/Operation/gd/Watermark.php
index 2bed581..ef69234 100644
--- a/src/Plugin/ImageToolkit/Operation/gd/Watermark.php
+++ b/src/Plugin/ImageToolkit/Operation/gd/Watermark.php
@@ -8,6 +8,7 @@
 namespace Drupal\image_effects\Plugin\ImageToolkit\Operation\gd;
 
 use Drupal\system\Plugin\ImageToolkit\Operation\gd\GDImageToolkitOperationBase;
+use Drupal\image_effects\Plugin\ImageToolkit\Operation\gd\GDOperationTrait;
 use Drupal\image_effects\Plugin\ImageToolkit\Operation\WatermarkTrait;
 
 /**
@@ -23,88 +24,24 @@ use Drupal\image_effects\Plugin\ImageToolkit\Operation\WatermarkTrait;
  */
 class Watermark extends GDImageToolkitOperationBase {
 
+  use GDOperationTrait;
   use WatermarkTrait;
 
   /**
    * {@inheritdoc}
    */
   protected function execute(array $arguments) {
-    if ($arguments['opacity'] === 100) {
-      // Use imagecopy() if watermark opacity is 100%.
-      return imagecopy(
-        $this->getToolkit()->getResource(),
-        $arguments['watermark_image']->getToolkit()->getResource(),
-        $arguments['x_offset'],
-        $arguments['y_offset'],
-        0,
-        0,
-        $arguments['watermark_image']->getToolkit()->getWidth(),
-        $arguments['watermark_image']->getToolkit()->getHeight()
-      );
-    }
-    else {
-      // If opacity is below 100%, use the approach described in
-      // http://php.net/manual/it/function.imagecopymerge.php#92787
-      // to preserve watermark alpha.
-
-      // Create a cut resource.
-      // @todo when #2583041 is committed, add a check for memory
-      // availability before crating the resource.
-      $cut = imagecreatetruecolor(
-        $arguments['watermark_image']->getToolkit()->getWidth(),
-        $arguments['watermark_image']->getToolkit()->getHeight()
-      );
-      if (!is_resource($cut)) {
-        return FALSE;
-      }
-
-      // Copy relevant section from destination image to the cut resource.
-      $success = imagecopy(
-        $cut,
-        $this->getToolkit()->getResource(),
-        0,
-        0,
-        $arguments['x_offset'],
-        $arguments['y_offset'],
-        $arguments['watermark_image']->getToolkit()->getWidth(),
-        $arguments['watermark_image']->getToolkit()->getHeight()
-      );
-      if (!$success) {
-        imagedestroy($cut);
-        return FALSE;
-      }
-
-      // Copy relevant section from watermark image to the cut resource.
-      $success = imagecopy(
-        $cut,
-        $arguments['watermark_image']->getToolkit()->getResource(),
-        0,
-        0,
-        0,
-        0,
-        $arguments['watermark_image']->getToolkit()->getWidth(),
-        $arguments['watermark_image']->getToolkit()->getHeight()
-      );
-      if (!$success) {
-        imagedestroy($cut);
-        return FALSE;
-      }
-
-      // Insert cut resource to destination image.
-      $success = imagecopymerge(
-        $this->getToolkit()->getResource(),
-        $cut,
-        $arguments['x_offset'],
-        $arguments['y_offset'],
-        0,
-        0,
-        $arguments['watermark_image']->getToolkit()->getWidth(),
-        $arguments['watermark_image']->getToolkit()->getHeight(),
-        $arguments['opacity']
-      );
-      imagedestroy($cut);
-      return $success;
-    }
+    return $this->imageCopyMergeAlpha(
+      $this->getToolkit()->getResource(),
+      $arguments['watermark_image']->getToolkit()->getResource(),
+      $arguments['x_offset'],
+      $arguments['y_offset'],
+      0,
+      0,
+      $arguments['watermark_image']->getToolkit()->getWidth(),
+      $arguments['watermark_image']->getToolkit()->getHeight(),
+      $arguments['opacity']
+    );
   }
 
 }
diff --git a/src/Tests/ImageEffectsBackgroundTest.php b/src/Tests/ImageEffectsBackgroundTest.php
new file mode 100644
index 0000000..a828345
--- /dev/null
+++ b/src/Tests/ImageEffectsBackgroundTest.php
@@ -0,0 +1,112 @@
+<?php
+
+/**
+ * @file
+ * Background effect test case script.
+ */
+
+namespace Drupal\image_effects\Tests;
+
+use Drupal\image\Entity\ImageStyle;
+
+/**
+ * Background effect test.
+ *
+ * @group Image Effects
+ */
+class ImageEffectsBackgroundTest extends ImageEffectsTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->toolkits = ['gd', 'imagemagick'];
+  }
+
+  /**
+   * Background effect test.
+   */
+  public function testBackgroundEffect() {
+    // Test operations on toolkits.
+    $this->executeTestOnToolkits([$this, 'doTestBackgroundOperations']);
+  }
+
+  /**
+   * Background operations test.
+   */
+  public function doTestBackgroundOperations() {
+    $image_factory = $this->container->get('image.factory');
+
+    $test_file = drupal_get_path('module', 'simpletest') . '/files/image-test.png';
+    $original_uri = file_unmanaged_copy($test_file, 'public://', FILE_EXISTS_RENAME);
+    $generated_uri = 'public://styles/image_effects_test/public/' . \Drupal::service('file_system')->basename($original_uri);
+
+    $background_file = drupal_get_path('module', 'simpletest') . '/files/image-1.png';
+    $background_uri = file_unmanaged_copy($background_file, 'public://', FILE_EXISTS_RENAME);
+
+    $effect = [
+      'id' => 'image_effects_background',
+      'data' => [
+        'placement' => 'left-top',
+        'x_offset' => 0,
+        'y_offset' => 0,
+        'opacity' => 100,
+        'background_image' => $background_uri,
+      ],
+    ];
+    $uuid = $this->addEffectToTestStyle($effect);
+
+    // Load Image Style.
+    $image_style = ImageStyle::load('image_effects_test');
+
+    // Check that ::transformDimensions returns expected dimensions.
+    $image = $image_factory->get($original_uri);
+    $this->assertEqual(40, $image->getWidth());
+    $this->assertEqual(20, $image->getHeight());
+    $url = file_url_transform_relative($image_style->buildUrl($original_uri));
+    $variables = array(
+      '#theme' => 'image_style',
+      '#style_name' => 'image_effects_test',
+      '#uri' => $original_uri,
+      '#width' => $image->getWidth(),
+      '#height' => $image->getHeight(),
+    );
+    $this->assertEqual('<img src="' . $url . '" width="360" height="240" alt="" class="image-style-image-effects-test" />', $this->getImageTag($variables));
+
+    // Check that ::applyEffect generates image with expected canvas.
+    $image_style->createDerivative($original_uri, $image_style->buildUri($original_uri));
+    $image = $image_factory->get($generated_uri, 'gd');
+    $this->assertEqual(360, $image->getWidth());
+    $this->assertEqual(240, $image->getHeight());
+    $this->assertTrue($this->colorsAreEqual($this->red, $this->getPixelColor($image, 0, 0)));
+    $this->assertTrue($this->colorsAreEqual($this->green, $this->getPixelColor($image, 39, 0)));
+    $this->assertTrue($this->colorsAreEqual([185, 185, 185, 0], $this->getPixelColor($image, 0, 19)));
+    $this->assertTrue($this->colorsAreEqual($this->blue, $this->getPixelColor($image, 39, 19)));
+
+    // Remove effect.
+    $this->removeEffectFromTestStyle($uuid);
+
+    // For the GD toolkit, test we are not left with orphan resource after
+    // applying the operation.
+    if ($image_factory->getToolkitId() === 'gd') {
+      $image = $image_factory->get($original_uri);
+      // Store the original GD resource.
+      $old_res = $image->getToolkit()->getResource();
+      // Apply the operation.
+      $image->apply('background', [
+        'x_offset' => 0,
+        'y_offset' => 0,
+        'opacity' => 100,
+        'background_image' => $image_factory->get($background_uri),
+      ]);
+      // The operation replaced the resource, check that the old one has
+      // been destroyed.
+      $new_res = $image->getToolkit()->getResource();
+      $this->assertTrue(is_resource($new_res));
+      $this->assertNotEqual($new_res, $old_res);
+      $this->assertFalse(is_resource($old_res));
+    }
+  }
+
+}
diff --git a/templates/image-effects-background-summary.html.twig b/templates/image-effects-background-summary.html.twig
new file mode 100644
index 0000000..fdcb98c
--- /dev/null
+++ b/templates/image-effects-background-summary.html.twig
@@ -0,0 +1,30 @@
+{#
+/**
+ * @file
+ * Default theme implementation for a summary of an image background effect.
+ *
+ * Available variables:
+ * - data: The current configuration for this background effect, including:
+ *   - placement: The position of the source image on the background image.
+ *   - x_offset: The X offset vs placement.
+ *   - y_offset: The Y offset vs placement.
+ *   - opacity: The opacity of the source image when overlaid on the background
+ *     image.
+ *   - background_image: The background image file URI/path.
+ *   - background_image_width: The background image width.
+ *   - background_image_height: The background image height.
+ * - effect: The effect information, including:
+ *   - id: The effect identifier.
+ *   - label: The effect name.
+ *   - description: The effect description.
+ *
+ * @ingroup themeable
+ */
+#}
+{% spaceless %}
+  - {{ 'Image'|t }}: <strong>{{ data.background_image|e }}</strong> ({{ data.background_image_width|e }}x{{ data.background_image_height|e }}),
+    {{ 'Placement'|t }}: {{ data.placement|e }},
+    {% if data.x_offset %}{{ 'X offset'|t }}: {{ data.x_offset|e }}px,{% endif %}
+    {% if data.y_offset %}{{ 'Y offset'|t }}: {{ data.y_offset|e }}px,{% endif %}
+    {{ 'Opacity'|t }}: {{ data.opacity|e }}%
+{% endspaceless %}
