Search code examples
objective-calgorithmgeometrycgaffinetransformcgaffinetransformscale

Rotate image in bounds


I try to rotate an image view in its superview so that this image view while rotating always touches superview's borders not crossing them, with appropriate resizing. How can I implement this? The image view should be able to rotate around 360˚.

Here I use calculations based on triangle formulas, considering initial image view diagonal angle.

Maybe I should take into account new bounding frame of the image view after it gets rotated (its x and y coordinates get negative and its frame size after transform gets bigger too).

No success so far, my image view gets sized down too quickly and too much. So my goal as I understand to get proper scale factor for CGAffineTransformScale. Maybe there are other ways to do the same.

// set initial values

    _planImageView.layer.affineTransform = CGAffineTransformScale(CGAffineTransformIdentity, 1, 1);

    _degrees = 0;

    _initialWidth = _planImageView.frame.size.width;
    _initialHeight = _planImageView.frame.size.height;
    _initialAngle = MathUtils::radiansToDegrees(atan((_initialWidth / 2) / (_initialHeight / 2)));

// rotation routine

- (void)rotatePlanWithDegrees:(double)degrees
{
    double deltaDegrees = degrees - _degrees;
    _initialAngle -= deltaDegrees;
    double newAngle = _initialAngle;
    double newWidth = (_initialWidth / 2) * tan(MathUtils::degreesToRadians(newAngle)) * 2;
    double newHeight = newWidth * (_initialHeight / _initialWidth);

    NSLog(@"DEG %f DELTA %f A %f W %f H %f", degrees, deltaDegrees, newAngle, newWidth, newHeight);

    double currentScale = newWidth / _initialWidth;

    _planImageView.layer.affineTransform = CGAffineTransformScale(CGAffineTransformIdentity, currentScale, currentScale);
    _planImageView.layer.affineTransform = CGAffineTransformRotate(_planImageView.layer.affineTransform, (CGFloat) MathUtils::degreesToRadians(degrees));

    _degrees = degrees;

    self->_planImageView.center = _center;

//    NSLog(@"%@", NSStringFromCGRect(_planImageView.frame));
}

example EDIT

I overwrote routine thanks to the answer and now it works!

- (void)rotatePlanWithDegrees:(double)degrees
{
    double newWidth =
            _initialWidth  * abs(cos(MathUtils::degreesToRadians(degrees))) +
            _initialHeight * abs(sin(MathUtils::degreesToRadians(degrees)));
    double newHeight =
            _initialWidth  * abs(sin(MathUtils::degreesToRadians(degrees))) +
            _initialHeight * abs(cos(MathUtils::degreesToRadians(degrees)));

    CGFloat scale = (CGFloat) MIN(
            self.planImageScrollView.frame.size.width / newWidth,
            self.planImageScrollView.frame.size.height / newHeight);

    CGAffineTransform rotationTransform = CGAffineTransformMakeRotation((CGFloat) MathUtils::degreesToRadians(degrees));
    CGAffineTransform scaleTransform  = CGAffineTransformMakeScale(scale, scale);
    _planImageView.layer.affineTransform = CGAffineTransformConcat(rotationTransform, scaleTransform);

    self->_planImageView.center = _center;
}

Solution

  • When you rotate a rectangle W x H, the bounding box takes the dimensions W' = W |cos Θ| + H |sin Θ|, H' = W |sin Θ| + H |cos Θ|.

    If you need to fit that in a W" x H" rectangle, the scaling factor is the smallest of W"/W' and H"/H'.