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..f6ac005
--- /dev/null
+++ b/src/Plugin/ImageToolkit/Operation/gd/Background.php
@@ -0,0 +1,89 @@
+<?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.
+    $data = [
+      'watermark_image' => $arguments['background_image'],
+      'x_offset' => 0,
+      'y_offset' => 0,
+    ];
+    if (!$this->getToolkit()->apply('watermark', $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 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..eda9266 100644
--- a/src/Plugin/ImageToolkit/Operation/gd/GDOperationTrait.php
+++ b/src/Plugin/ImageToolkit/Operation/gd/GDOperationTrait.php
@@ -81,4 +81,44 @@ trait GDOperationTrait {
     return $points;
   }
 
+  /**
+   * @todo
+   */
+  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/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 %}
