Rectangle.php 5.3 KB

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