diff --git a/src/Plugin/ImageToolkit/Operation/gd/Background.php b/src/Plugin/ImageToolkit/Operation/gd/Background.php index f6ac005..978b3a6 100644 --- a/src/Plugin/ImageToolkit/Operation/gd/Background.php +++ b/src/Plugin/ImageToolkit/Operation/gd/Background.php @@ -51,12 +51,18 @@ class Background extends GDImageToolkitOperationBase { } // Overlay background at 0,0. - $data = [ - 'watermark_image' => $arguments['background_image'], - 'x_offset' => 0, - 'y_offset' => 0, - ]; - if (!$this->getToolkit()->apply('watermark', $data)) { + $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()); diff --git a/src/Plugin/ImageToolkit/Operation/gd/GDOperationTrait.php b/src/Plugin/ImageToolkit/Operation/gd/GDOperationTrait.php index eda9266..b5c2381 100644 --- a/src/Plugin/ImageToolkit/Operation/gd/GDOperationTrait.php +++ b/src/Plugin/ImageToolkit/Operation/gd/GDOperationTrait.php @@ -82,7 +82,36 @@ trait GDOperationTrait { } /** - * @todo + * 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) { 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 @@ +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('', $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)); + } + } + +}