Chris@0: 0 && $height > 0) { Chris@0: $this->width = $width; Chris@0: $this->height = $height; Chris@0: $this->boundingWidth = $width; Chris@0: $this->boundingHeight = $height; Chris@0: } Chris@0: else { Chris@0: throw new \InvalidArgumentException("Invalid dimensions ({$width}x{$height}) specified for a Rectangle object"); Chris@0: } Chris@0: } Chris@0: Chris@0: /** Chris@0: * Rotates the rectangle. Chris@0: * Chris@0: * @param float $angle Chris@0: * Rotation angle. Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function rotate($angle) { Chris@0: // PHP 5.5 GD bug: https://bugs.php.net/bug.php?id=65148: To prevent buggy Chris@0: // behavior on negative multiples of 30 degrees we convert any negative Chris@0: // angle to a positive one between 0 and 360 degrees. Chris@0: $angle -= floor($angle / 360) * 360; Chris@0: Chris@0: // For some rotations that are multiple of 30 degrees, we need to correct Chris@0: // an imprecision between GD that uses C floats internally, and PHP that Chris@0: // uses C doubles. Also, for rotations that are not multiple of 90 degrees, Chris@0: // we need to introduce a correction factor of 0.5 to match the GD Chris@0: // algorithm used in PHP 5.5 (and above) to calculate the width and height Chris@0: // of the rotated image. Chris@0: if ((int) $angle == $angle && $angle % 90 == 0) { Chris@0: $imprecision = 0; Chris@0: $correction = 0; Chris@0: } Chris@0: else { Chris@0: $imprecision = -0.00001; Chris@0: $correction = 0.5; Chris@0: } Chris@0: Chris@0: // Do the trigonometry, applying imprecision fixes where needed. Chris@0: $rad = deg2rad($angle); Chris@0: $cos = cos($rad); Chris@0: $sin = sin($rad); Chris@0: $a = $this->width * $cos; Chris@0: $b = $this->height * $sin + $correction; Chris@0: $c = $this->width * $sin; Chris@0: $d = $this->height * $cos + $correction; Chris@0: if ((int) $angle == $angle && in_array($angle, [60, 150, 300])) { Chris@0: $a = $this->fixImprecision($a, $imprecision); Chris@0: $b = $this->fixImprecision($b, $imprecision); Chris@0: $c = $this->fixImprecision($c, $imprecision); Chris@0: $d = $this->fixImprecision($d, $imprecision); Chris@0: } Chris@0: Chris@0: // This is how GD on PHP5.5 calculates the new dimensions. Chris@0: $this->boundingWidth = abs((int) $a) + abs((int) $b); Chris@0: $this->boundingHeight = abs((int) $c) + abs((int) $d); Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Performs an imprecision check on the input value and fixes it if needed. Chris@0: * Chris@0: * GD that uses C floats internally, whereas we at PHP level use C doubles. Chris@0: * In some cases, we need to compensate imprecision. Chris@0: * Chris@0: * @param float $input Chris@0: * The input value. Chris@0: * @param float $imprecision Chris@0: * The imprecision factor. Chris@0: * Chris@0: * @return float Chris@0: * A value, where imprecision is added to input if the delta part of the Chris@0: * input is lower than the absolute imprecision. Chris@0: */ Chris@0: protected function fixImprecision($input, $imprecision) { Chris@0: if ($this->delta($input) < abs($imprecision)) { Chris@0: return $input + $imprecision; Chris@0: } Chris@0: return $input; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the fractional part of a float number, unsigned. Chris@0: * Chris@0: * @param float $input Chris@0: * The input value. Chris@0: * Chris@0: * @return float Chris@0: * The fractional part of the input number, unsigned. Chris@0: */ Chris@0: protected function fraction($input) { Chris@0: return abs((int) $input - $input); Chris@0: } Chris@0: Chris@0: /** Chris@0: * Returns the difference of a fraction from the closest between 0 and 1. Chris@0: * Chris@0: * @param float $input Chris@0: * The input value. Chris@0: * Chris@0: * @return float Chris@0: * the difference of a fraction from the closest between 0 and 1. Chris@0: */ Chris@0: protected function delta($input) { Chris@0: $fraction = $this->fraction($input); Chris@0: return $fraction > 0.5 ? (1 - $fraction) : $fraction; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the bounding width of the rectangle. Chris@0: * Chris@0: * @return int Chris@0: * The bounding width of the rotated rectangle. Chris@0: */ Chris@0: public function getBoundingWidth() { Chris@0: return $this->boundingWidth; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Gets the bounding height of the rectangle. Chris@0: * Chris@0: * @return int Chris@0: * The bounding height of the rotated rectangle. Chris@0: */ Chris@0: public function getBoundingHeight() { Chris@0: return $this->boundingHeight; Chris@0: } Chris@0: Chris@0: }