Chris@0
|
1 <?php
|
Chris@0
|
2
|
Chris@0
|
3 namespace Drupal\Component\Utility;
|
Chris@0
|
4
|
Chris@0
|
5 /**
|
Chris@0
|
6 * Rectangle rotation algebra class.
|
Chris@0
|
7 *
|
Chris@0
|
8 * This class is used by the image system to abstract, from toolkit
|
Chris@0
|
9 * implementations, the calculation of the expected dimensions resulting from
|
Chris@0
|
10 * an image rotate operation.
|
Chris@0
|
11 *
|
Chris@0
|
12 * Different versions of PHP for the GD toolkit, and alternative toolkits, use
|
Chris@0
|
13 * different algorithms to perform the rotation of an image and result in
|
Chris@0
|
14 * different dimensions of the output image. This prevents predictability of
|
Chris@0
|
15 * the final image size for instance by the image rotate effect, or by image
|
Chris@0
|
16 * toolkit rotate operations.
|
Chris@0
|
17 *
|
Chris@0
|
18 * This class implements a calculation algorithm that returns, given input
|
Chris@0
|
19 * width, height and rotation angle, dimensions of the expected image after
|
Chris@0
|
20 * rotation that are consistent with those produced by the GD rotate image
|
Chris@0
|
21 * toolkit operation using PHP 5.5 and above.
|
Chris@0
|
22 *
|
Chris@0
|
23 * @see \Drupal\system\Plugin\ImageToolkit\Operation\gd\Rotate
|
Chris@0
|
24 */
|
Chris@0
|
25 class Rectangle {
|
Chris@0
|
26
|
Chris@0
|
27 /**
|
Chris@0
|
28 * The width of the rectangle.
|
Chris@0
|
29 *
|
Chris@0
|
30 * @var int
|
Chris@0
|
31 */
|
Chris@0
|
32 protected $width;
|
Chris@0
|
33
|
Chris@0
|
34 /**
|
Chris@0
|
35 * The height of the rectangle.
|
Chris@0
|
36 *
|
Chris@0
|
37 * @var int
|
Chris@0
|
38 */
|
Chris@0
|
39 protected $height;
|
Chris@0
|
40
|
Chris@0
|
41 /**
|
Chris@0
|
42 * The width of the rotated rectangle.
|
Chris@0
|
43 *
|
Chris@0
|
44 * @var int
|
Chris@0
|
45 */
|
Chris@0
|
46 protected $boundingWidth;
|
Chris@0
|
47
|
Chris@0
|
48 /**
|
Chris@0
|
49 * The height of the rotated rectangle.
|
Chris@0
|
50 *
|
Chris@0
|
51 * @var int
|
Chris@0
|
52 */
|
Chris@0
|
53 protected $boundingHeight;
|
Chris@0
|
54
|
Chris@0
|
55 /**
|
Chris@0
|
56 * Constructs a new Rectangle object.
|
Chris@0
|
57 *
|
Chris@0
|
58 * @param int $width
|
Chris@0
|
59 * The width of the rectangle.
|
Chris@0
|
60 * @param int $height
|
Chris@0
|
61 * The height of the rectangle.
|
Chris@0
|
62 */
|
Chris@0
|
63 public function __construct($width, $height) {
|
Chris@0
|
64 if ($width > 0 && $height > 0) {
|
Chris@0
|
65 $this->width = $width;
|
Chris@0
|
66 $this->height = $height;
|
Chris@0
|
67 $this->boundingWidth = $width;
|
Chris@0
|
68 $this->boundingHeight = $height;
|
Chris@0
|
69 }
|
Chris@0
|
70 else {
|
Chris@0
|
71 throw new \InvalidArgumentException("Invalid dimensions ({$width}x{$height}) specified for a Rectangle object");
|
Chris@0
|
72 }
|
Chris@0
|
73 }
|
Chris@0
|
74
|
Chris@0
|
75 /**
|
Chris@0
|
76 * Rotates the rectangle.
|
Chris@0
|
77 *
|
Chris@0
|
78 * @param float $angle
|
Chris@0
|
79 * Rotation angle.
|
Chris@0
|
80 *
|
Chris@0
|
81 * @return $this
|
Chris@0
|
82 */
|
Chris@0
|
83 public function rotate($angle) {
|
Chris@0
|
84 // PHP 5.5 GD bug: https://bugs.php.net/bug.php?id=65148: To prevent buggy
|
Chris@0
|
85 // behavior on negative multiples of 30 degrees we convert any negative
|
Chris@0
|
86 // angle to a positive one between 0 and 360 degrees.
|
Chris@0
|
87 $angle -= floor($angle / 360) * 360;
|
Chris@0
|
88
|
Chris@0
|
89 // For some rotations that are multiple of 30 degrees, we need to correct
|
Chris@0
|
90 // an imprecision between GD that uses C floats internally, and PHP that
|
Chris@0
|
91 // uses C doubles. Also, for rotations that are not multiple of 90 degrees,
|
Chris@0
|
92 // we need to introduce a correction factor of 0.5 to match the GD
|
Chris@0
|
93 // algorithm used in PHP 5.5 (and above) to calculate the width and height
|
Chris@0
|
94 // of the rotated image.
|
Chris@0
|
95 if ((int) $angle == $angle && $angle % 90 == 0) {
|
Chris@0
|
96 $imprecision = 0;
|
Chris@0
|
97 $correction = 0;
|
Chris@0
|
98 }
|
Chris@0
|
99 else {
|
Chris@0
|
100 $imprecision = -0.00001;
|
Chris@0
|
101 $correction = 0.5;
|
Chris@0
|
102 }
|
Chris@0
|
103
|
Chris@0
|
104 // Do the trigonometry, applying imprecision fixes where needed.
|
Chris@0
|
105 $rad = deg2rad($angle);
|
Chris@0
|
106 $cos = cos($rad);
|
Chris@0
|
107 $sin = sin($rad);
|
Chris@0
|
108 $a = $this->width * $cos;
|
Chris@0
|
109 $b = $this->height * $sin + $correction;
|
Chris@0
|
110 $c = $this->width * $sin;
|
Chris@0
|
111 $d = $this->height * $cos + $correction;
|
Chris@0
|
112 if ((int) $angle == $angle && in_array($angle, [60, 150, 300])) {
|
Chris@0
|
113 $a = $this->fixImprecision($a, $imprecision);
|
Chris@0
|
114 $b = $this->fixImprecision($b, $imprecision);
|
Chris@0
|
115 $c = $this->fixImprecision($c, $imprecision);
|
Chris@0
|
116 $d = $this->fixImprecision($d, $imprecision);
|
Chris@0
|
117 }
|
Chris@0
|
118
|
Chris@0
|
119 // This is how GD on PHP5.5 calculates the new dimensions.
|
Chris@0
|
120 $this->boundingWidth = abs((int) $a) + abs((int) $b);
|
Chris@0
|
121 $this->boundingHeight = abs((int) $c) + abs((int) $d);
|
Chris@0
|
122
|
Chris@0
|
123 return $this;
|
Chris@0
|
124 }
|
Chris@0
|
125
|
Chris@0
|
126 /**
|
Chris@0
|
127 * Performs an imprecision check on the input value and fixes it if needed.
|
Chris@0
|
128 *
|
Chris@0
|
129 * GD that uses C floats internally, whereas we at PHP level use C doubles.
|
Chris@0
|
130 * In some cases, we need to compensate imprecision.
|
Chris@0
|
131 *
|
Chris@0
|
132 * @param float $input
|
Chris@0
|
133 * The input value.
|
Chris@0
|
134 * @param float $imprecision
|
Chris@0
|
135 * The imprecision factor.
|
Chris@0
|
136 *
|
Chris@0
|
137 * @return float
|
Chris@0
|
138 * A value, where imprecision is added to input if the delta part of the
|
Chris@0
|
139 * input is lower than the absolute imprecision.
|
Chris@0
|
140 */
|
Chris@0
|
141 protected function fixImprecision($input, $imprecision) {
|
Chris@0
|
142 if ($this->delta($input) < abs($imprecision)) {
|
Chris@0
|
143 return $input + $imprecision;
|
Chris@0
|
144 }
|
Chris@0
|
145 return $input;
|
Chris@0
|
146 }
|
Chris@0
|
147
|
Chris@0
|
148 /**
|
Chris@0
|
149 * Returns the fractional part of a float number, unsigned.
|
Chris@0
|
150 *
|
Chris@0
|
151 * @param float $input
|
Chris@0
|
152 * The input value.
|
Chris@0
|
153 *
|
Chris@0
|
154 * @return float
|
Chris@0
|
155 * The fractional part of the input number, unsigned.
|
Chris@0
|
156 */
|
Chris@0
|
157 protected function fraction($input) {
|
Chris@0
|
158 return abs((int) $input - $input);
|
Chris@0
|
159 }
|
Chris@0
|
160
|
Chris@0
|
161 /**
|
Chris@0
|
162 * Returns the difference of a fraction from the closest between 0 and 1.
|
Chris@0
|
163 *
|
Chris@0
|
164 * @param float $input
|
Chris@0
|
165 * The input value.
|
Chris@0
|
166 *
|
Chris@0
|
167 * @return float
|
Chris@0
|
168 * the difference of a fraction from the closest between 0 and 1.
|
Chris@0
|
169 */
|
Chris@0
|
170 protected function delta($input) {
|
Chris@0
|
171 $fraction = $this->fraction($input);
|
Chris@0
|
172 return $fraction > 0.5 ? (1 - $fraction) : $fraction;
|
Chris@0
|
173 }
|
Chris@0
|
174
|
Chris@0
|
175 /**
|
Chris@0
|
176 * Gets the bounding width of the rectangle.
|
Chris@0
|
177 *
|
Chris@0
|
178 * @return int
|
Chris@0
|
179 * The bounding width of the rotated rectangle.
|
Chris@0
|
180 */
|
Chris@0
|
181 public function getBoundingWidth() {
|
Chris@0
|
182 return $this->boundingWidth;
|
Chris@0
|
183 }
|
Chris@0
|
184
|
Chris@0
|
185 /**
|
Chris@0
|
186 * Gets the bounding height of the rectangle.
|
Chris@0
|
187 *
|
Chris@0
|
188 * @return int
|
Chris@0
|
189 * The bounding height of the rotated rectangle.
|
Chris@0
|
190 */
|
Chris@0
|
191 public function getBoundingHeight() {
|
Chris@0
|
192 return $this->boundingHeight;
|
Chris@0
|
193 }
|
Chris@0
|
194
|
Chris@0
|
195 }
|