Commit 1a6baa4c authored by alexpott's avatar alexpott

Issue #1551686 by mondrake, fietserwin, adci_contributor, ankitgarg,...

Issue #1551686 by mondrake, fietserwin, adci_contributor, ankitgarg, claudiu.cristea: Image rotate dimensions callback can calculate new dimensions for every angle.
parent 561b42ce
<?php
/**
* @file
* Contains \Drupal\Component\Utility\Rectangle.
*/
namespace Drupal\Component\Utility;
/**
* Rectangle rotation algebra class.
*
* This class is used by the image system to abstract, from toolkit
* implementations, the calculation of the expected dimensions resulting from
* an image rotate operation.
*
* Different versions of PHP for the GD toolkit, and alternative toolkits, use
* different algorithms to perform the rotation of an image and result in
* different dimensions of the output image. This prevents predictability of
* the final image size for instance by the image rotate effect, or by image
* toolkit rotate operations.
*
* This class implements a calculation algorithm that returns, given input
* width, height and rotation angle, dimensions of the expected image after
* rotation that are consistent with those produced by the GD rotate image
* toolkit operation using PHP 5.5 and above.
*
* @see \Drupal\system\Plugin\ImageToolkit\Operation\gd\Rotate
*/
class Rectangle {
/**
* The width of the rectangle.
*
* @var int
*/
protected $width;
/**
* The height of the rectangle.
*
* @var int
*/
protected $height;
/**
* The width of the rotated rectangle.
*
* @var int
*/
protected $boundingWidth;
/**
* The height of the rotated rectangle.
*
* @var int
*/
protected $boundingHeight;
/**
* Constructs a new Rectangle object.
*
* @param int $width
* The width of the rectangle.
* @param int $height
* The height of the rectangle.
*/
public function __construct($width, $height) {
if ($width > 0 && $height > 0) {
$this->width = $width;
$this->height = $height;
$this->boundingWidth = $width;
$this->boundingHeight = $height;
}
else {
throw new \InvalidArgumentException("Invalid dimensions ({$width}x{$height}) specified for a Rectangle object");
}
}
/**
* Rotates the rectangle.
*
* @param float $angle
* Rotation angle.
*
* @return $this
*/
public function rotate($angle) {
// PHP 5.5 GD bug: https://bugs.php.net/bug.php?id=65148: To prevent buggy
// behavior on negative multiples of 30 degrees we convert any negative
// angle to a positive one between 0 and 360 degrees.
$angle -= floor($angle / 360) * 360;
// For some rotations that are multiple of 30 degrees, we need to correct
// an imprecision between GD that uses C floats internally, and PHP that
// uses C doubles. Also, for rotations that are not multiple of 90 degrees,
// we need to introduce a correction factor of 0.5 to match the GD
// algorithm used in PHP 5.5 (and above) to calculate the width and height
// of the rotated image.
if ((int) $angle == $angle && $angle % 90 == 0) {
$imprecision = 0;
$correction = 0;
}
else {
$imprecision = -0.00001;
$correction = 0.5;
}
// Do the trigonometry, applying imprecision fixes where needed.
$rad = deg2rad($angle);
$cos = cos($rad);
$sin = sin($rad);
$a = $this->width * $cos;
$b = $this->height * $sin + $correction;
$c = $this->width * $sin;
$d = $this->height * $cos + $correction;
if ((int) $angle == $angle && in_array($angle, [60, 150, 300])) {
$a = $this->fixImprecision($a, $imprecision);
$b = $this->fixImprecision($b, $imprecision);
$c = $this->fixImprecision($c, $imprecision);
$d = $this->fixImprecision($d, $imprecision);
}
// This is how GD on PHP5.5 calculates the new dimensions.
$this->boundingWidth = abs((int) $a) + abs((int) $b);
$this->boundingHeight = abs((int) $c) + abs((int) $d);
return $this;
}
/**
* Performs an imprecision check on the input value and fixes it if needed.
*
* GD that uses C floats internally, whereas we at PHP level use C doubles.
* In some cases, we need to compensate imprecision.
*
* @param float $input
* The input value.
* @param float $imprecision
* The imprecision factor.
*
* @return float
* A value, where imprecision is added to input if the delta part of the
* input is lower than the absolute imprecision.
*/
protected function fixImprecision($input, $imprecision) {
if ($this->delta($input) < abs($imprecision)) {
return $input + $imprecision;
}
return $input;
}
/**
* Returns the fractional part of a float number, unsigned.
*
* @param float $input
* The input value.
*
* @return float
* The fractional part of the input number, unsigned.
*/
protected function fraction($input) {
return abs((int) $input - $input);
}
/**
* Returns the difference of a fraction from the closest between 0 and 1.
*
* @param float $input
* The input value.
*
* @return float
* the difference of a fraction from the closest between 0 and 1.
*/
protected function delta($input) {
$fraction = $this->fraction($input);
return $fraction > 0.5 ? (1 - $fraction) : $fraction;
}
/**
* Gets the bounding width of the rectangle.
*
* @return int
* The bounding width of the rotated rectangle.
*/
public function getBoundingWidth() {
return $this->boundingWidth;
}
/**
* Gets the bounding height of the rectangle.
*
* @return int
* The bounding height of the rotated rectangle.
*/
public function getBoundingHeight() {
return $this->boundingHeight;
}
}
......@@ -8,6 +8,7 @@
namespace Drupal\image\Plugin\ImageEffect;
use Drupal\Component\Utility\Color;
use Drupal\Component\Utility\Rectangle;
use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Image\ImageInterface;
use Drupal\image\ConfigurableImageEffectBase;
......@@ -43,14 +44,13 @@ public function applyEffect(ImageInterface $image) {
* {@inheritdoc}
*/
public function transformDimensions(array &$dimensions, $uri) {
// If the rotate is not random and the angle is a multiple of 90 degrees,
// If the rotate is not random and current dimensions are set,
// then the new dimensions can be determined.
if (!$this->configuration['random'] && ((int) ($this->configuration['degrees']) == $this->configuration['degrees']) && ($this->configuration['degrees'] % 90 == 0)) {
if ($this->configuration['degrees'] % 180 != 0) {
$temp = $dimensions['width'];
$dimensions['width'] = $dimensions['height'];
$dimensions['height'] = $temp;
}
if (!$this->configuration['random'] && $dimensions['width'] && $dimensions['height']) {
$rect = new Rectangle($dimensions['width'], $dimensions['height']);
$rect = $rect->rotate($this->configuration['degrees']);
$dimensions['width'] = $rect->getBoundingWidth();
$dimensions['height'] = $rect->getBoundingHeight();
}
else {
$dimensions['width'] = $dimensions['height'] = NULL;
......
......@@ -213,11 +213,14 @@ function testImageDimensions() {
$effect_id = $style->addImageEffect($effect);
$style->save();
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" alt="" class="image-style-test" />');
$this->assertEqual($this->getImageTag($variables), '<img src="' . $url . '" width="41" height="41" alt="" class="image-style-test" />');
$this->assertFalse(file_exists($generated_uri), 'Generated file does not exist.');
$this->drupalGet($url);
$this->assertResponse(200, 'Image was generated at the URL.');
$this->assertTrue(file_exists($generated_uri), 'Generated file does exist after we accessed it.');
$image_file = $image_factory->get($generated_uri);
$this->assertEqual($image_file->getWidth(), 41);
$this->assertEqual($image_file->getHeight(), 41);
$effect_plugin = $style->getEffect($effect_id);
$style->deleteImageEffect($effect_plugin);
......
......@@ -203,8 +203,8 @@ function testManipulations() {
'rotate_5' => array(
'function' => 'rotate',
'arguments' => array('degrees' => 5, 'background' => '#FF00FF'), // Fuchsia background.
'width' => 42,
'height' => 24,
'width' => 41,
'height' => 23,
'corners' => array_fill(0, 4, $this->fuchsia),
),
'rotate_90' => array(
......@@ -217,8 +217,8 @@ function testManipulations() {
'rotate_transparent_5' => array(
'function' => 'rotate',
'arguments' => array('degrees' => 5),
'width' => 42,
'height' => 24,
'width' => 41,
'height' => 23,
'corners' => array_fill(0, 4, $this->rotateTransparent),
),
'rotate_transparent_90' => array(
......@@ -289,20 +289,6 @@ function testManipulations() {
$correct_dimensions_real = TRUE;
$correct_dimensions_object = TRUE;
// PHP 5.5 GD bug: https://bugs.php.net/bug.php?id=65148. PHP 5.5 GD
// rotates differently then it did in PHP 5.4 resulting in different
// dimensions then what math teaches us. For the test images, the
// dimensions will be 1 pixel smaller in both dimensions (though other
// tests have shown a difference of 0 to 3 pixels in both dimensions.
// @todo: if and when the PHP bug gets solved, add an upper limit
// version check.
// @todo: in [#1551686] the dimension calculations for rotation are
// reworked. That issue should also check if these tests can be made
// more robust.
if (version_compare(PHP_VERSION, '5.5', '>=') && $values['function'] === 'rotate' && $values['arguments']['degrees'] % 90 != 0) {
$values['height']--;
$values['width']--;
}
if (imagesy($toolkit->getResource()) != $values['height'] || imagesx($toolkit->getResource()) != $values['width']) {
$correct_dimensions_real = FALSE;
}
......
This diff is collapsed.
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment