You are here

class Rectangle in Drupal 10

Same name and namespace in other branches
  1. 8 core/lib/Drupal/Component/Utility/Rectangle.php \Drupal\Component\Utility\Rectangle
  2. 9 core/lib/Drupal/Component/Utility/Rectangle.php \Drupal\Component\Utility\Rectangle

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.

Hierarchy

Expanded class hierarchy of Rectangle

See also

\Drupal\system\Plugin\ImageToolkit\Operation\gd\Rotate

2 files declare their use of Rectangle
RectangleTest.php in core/tests/Drupal/Tests/Component/Utility/RectangleTest.php
RotateImageEffect.php in core/modules/image/src/Plugin/ImageEffect/RotateImageEffect.php

File

core/lib/Drupal/Component/Utility/Rectangle.php, line 25

Namespace

Drupal\Component\Utility
View source
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 = -1.0E-5;
      $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;
  }

}

Members