diff --git a/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/Rotate.php b/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/Rotate.php index e567ea4..d43c803 100644 --- a/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/Rotate.php +++ b/core/modules/system/src/Plugin/ImageToolkit/Operation/gd/Rotate.php @@ -3,6 +3,7 @@ namespace Drupal\system\Plugin\ImageToolkit\Operation\gd; use Drupal\Component\Utility\Color; +use Drupal\Component\Utility\Rectangle; /** * Defines GD2 rotate operation. @@ -96,6 +97,13 @@ protected function execute(array $arguments) { // Stores the original GD resource. $original_res = $this->getToolkit()->getResource(); + // Get expected width and height resulting from the rotation. + $rect = new Rectangle($this->getToolkit()->getWidth(), $this->getToolkit()->getHeight()); + $rect = $rect->rotate($arguments['degrees']); + $expected_width = $rect->getBoundingWidth(); + $expected_height = $rect->getBoundingHeight(); + + // Rotate the image. if ($new_res = imagerotate($this->getToolkit()->getResource(), 360 - $arguments['degrees'], $arguments['background_idx'])) { $this->getToolkit()->setResource($new_res); imagedestroy($original_res); @@ -108,6 +116,67 @@ protected function execute(array $arguments) { imagecolortransparent($this->getToolkit()->getResource(), $transparent_idx); } + // Resizes the image if width and height are not as expected. + // PHP 7.0.25+ and 7.1.11+ GD rotates differently then it did in PHP 5.5 + // and above, resulting in different dimensions. Here we are harmonizing + // final size across PHP versions by resizing the image to the dimensions + // we expect. + // @see https://bugs.php.net/bug.php?id=65148 + if ($this->getToolkit()->getWidth() != $expected_width || $this->getToolkit()->getHeight() != $expected_height) { + // If either dimension of the current image is bigger than expected, + // crop the image. + if ($this->getToolkit()->getWidth() > $expected_width || $this->getToolkit()->getHeight() > $expected_height) { + $crop_width = min($expected_width, $this->getToolkit()->getWidth()); + $crop_height = min($expected_height, $this->getToolkit()->getHeight()); + if (!$this->getToolkit()->apply('crop', [ + 'x' => $this->getToolkit()->getWidth() / 2 - $crop_width / 2, + 'y' => $this->getToolkit()->getHeight() / 2 - $crop_height /2, + 'width' => $crop_width, + 'height' => $crop_height, + ])) { + return FALSE; + } + } + // If the image at this point is smaller than expected, place it above + // a canvas of the expected dimensions. + if ($this->getToolkit()->getWidth() < $expected_width || $this->getToolkit()->getHeight() < $expected_height) { + // Store the current GD resource. + $temp_res = $this->getToolkit()->getResource(); + + // Prepare the canvas. + $data = [ + 'width' => $expected_width, + 'height' => $expected_height, + 'extension' => image_type_to_extension($this->getToolkit()->getType(), FALSE), + 'transparent_color' => $this->getToolkit()->getTransparentColor(), + 'is_temp' => TRUE, + ]; + if (!$this->getToolkit()->apply('create_new', $data)) { + return FALSE; + } + + // Fill the canvas with the required background color. + imagefill($this->getToolkit()->getResource(), 0, 0, $arguments['background_idx']); + + // Overlay the current image on the canvas. + imagealphablending($temp_res, TRUE); + imagesavealpha($temp_res, TRUE); + imagealphablending($this->getToolkit()->getResource(), TRUE); + imagesavealpha($this->getToolkit()->getResource(), TRUE); + $x_pos = (int) ($expected_width / 2 - imagesx($temp_res) / 2); + $y_pos = (int) ($expected_height / 2 - imagesy($temp_res) / 2); + if (imagecopy($this->getToolkit()->getResource(), $temp_res, $x_pos, $y_pos, 0, 0, imagesx($temp_res), imagesy($temp_res))) { + imagedestroy($temp_res); + } + else { + // In case of failure, destroy the temporary resource and restore + // the original one. + imagedestroy($this->getToolkit()->getResource()); + $this->getToolkit()->setResource($temp_res); + return FALSE; + } + } + } return TRUE; }