3 namespace Drupal\Component\Utility;
6 * Rectangle rotation algebra class.
8 * This class is used by the image system to abstract, from toolkit
9 * implementations, the calculation of the expected dimensions resulting from
10 * an image rotate operation.
12 * Different versions of PHP for the GD toolkit, and alternative toolkits, use
13 * different algorithms to perform the rotation of an image and result in
14 * different dimensions of the output image. This prevents predictability of
15 * the final image size for instance by the image rotate effect, or by image
16 * toolkit rotate operations.
18 * This class implements a calculation algorithm that returns, given input
19 * width, height and rotation angle, dimensions of the expected image after
20 * rotation that are consistent with those produced by the GD rotate image
21 * toolkit operation using PHP 5.5 and above.
23 * @see \Drupal\system\Plugin\ImageToolkit\Operation\gd\Rotate
28 * The width of the rectangle.
35 * The height of the rectangle.
42 * The width of the rotated rectangle.
46 protected $boundingWidth;
49 * The height of the rotated rectangle.
53 protected $boundingHeight;
56 * Constructs a new Rectangle object.
59 * The width of the rectangle.
61 * The height of the rectangle.
63 public function __construct($width, $height) {
64 if ($width > 0 && $height > 0) {
65 $this->width = $width;
66 $this->height = $height;
67 $this->boundingWidth = $width;
68 $this->boundingHeight = $height;
71 throw new \InvalidArgumentException("Invalid dimensions ({$width}x{$height}) specified for a Rectangle object");
76 * Rotates the rectangle.
83 public function rotate($angle) {
84 // PHP 5.5 GD bug: https://bugs.php.net/bug.php?id=65148: To prevent buggy
85 // behavior on negative multiples of 30 degrees we convert any negative
86 // angle to a positive one between 0 and 360 degrees.
87 $angle -= floor($angle / 360) * 360;
89 // For some rotations that are multiple of 30 degrees, we need to correct
90 // an imprecision between GD that uses C floats internally, and PHP that
91 // uses C doubles. Also, for rotations that are not multiple of 90 degrees,
92 // we need to introduce a correction factor of 0.5 to match the GD
93 // algorithm used in PHP 5.5 (and above) to calculate the width and height
94 // of the rotated image.
95 if ((int) $angle == $angle && $angle % 90 == 0) {
100 $imprecision = -0.00001;
104 // Do the trigonometry, applying imprecision fixes where needed.
105 $rad = deg2rad($angle);
108 $a = $this->width * $cos;
109 $b = $this->height * $sin + $correction;
110 $c = $this->width * $sin;
111 $d = $this->height * $cos + $correction;
112 if ((int) $angle == $angle && in_array($angle, [60, 150, 300])) {
113 $a = $this->fixImprecision($a, $imprecision);
114 $b = $this->fixImprecision($b, $imprecision);
115 $c = $this->fixImprecision($c, $imprecision);
116 $d = $this->fixImprecision($d, $imprecision);
119 // This is how GD on PHP5.5 calculates the new dimensions.
120 $this->boundingWidth = abs((int) $a) + abs((int) $b);
121 $this->boundingHeight = abs((int) $c) + abs((int) $d);
127 * Performs an imprecision check on the input value and fixes it if needed.
129 * GD that uses C floats internally, whereas we at PHP level use C doubles.
130 * In some cases, we need to compensate imprecision.
132 * @param float $input
134 * @param float $imprecision
135 * The imprecision factor.
138 * A value, where imprecision is added to input if the delta part of the
139 * input is lower than the absolute imprecision.
141 protected function fixImprecision($input, $imprecision) {
142 if ($this->delta($input) < abs($imprecision)) {
143 return $input + $imprecision;
149 * Returns the fractional part of a float number, unsigned.
151 * @param float $input
155 * The fractional part of the input number, unsigned.
157 protected function fraction($input) {
158 return abs((int) $input - $input);
162 * Returns the difference of a fraction from the closest between 0 and 1.
164 * @param float $input
168 * the difference of a fraction from the closest between 0 and 1.
170 protected function delta($input) {
171 $fraction = $this->fraction($input);
172 return $fraction > 0.5 ? (1 - $fraction) : $fraction;
176 * Gets the bounding width of the rectangle.
179 * The bounding width of the rotated rectangle.
181 public function getBoundingWidth() {
182 return $this->boundingWidth;
186 * Gets the bounding height of the rectangle.
189 * The bounding height of the rotated rectangle.
191 public function getBoundingHeight() {
192 return $this->boundingHeight;