Increase the rotation of the image within the scroll to fit the overlay frame

Through this question and answer I now have a working tool for detecting when a randomly rotated image does not completely fall outside the crop rectangle.

The next step is to figure out how to properly adjust it using the scroll view scaling to make sure there are no empty spaces in the crop rectangle. To clarify, I want to enlarge (enlarge) the image; the crop rectangle must remain unmodified.

The layout hierarchy looks like this:

containing UIScrollView
    UIImageView (this gets arbitrarily rotated)
        crop rect overlay view

      

... where the UIImageView can also be scaled and panned inside the scrollView.

There are 4 gesture events to consider:

  • Hard disk gesture (done): performed by detecting misconfiguration and discarding the contents.
  • Rotating CGAffineTransform
  • Scroll view
  • Adjusting a rectangular crop overlay box

As far as I can tell, I have to use the same logic for 2, 3 and 4 to adjust the scaling of the scroll view so that the image fits properly.

How do I correctly calculate the scale factor required to make the rotated image fit perfectly into the cropping rectangle?

To better illustrate what I'm trying to accomplish, here's an example of the wrong size:

enter image description here

I need to calculate the scaling factor needed to make it look like this:

enter image description here

Here is the code I have used so far using Oluseyi's solution below. It works when the angle of rotation is negligible (less than 1 radian, for example), but something about that and it's really awkward.

CGRect visibleRect = [_scrollView convertRect:_scrollView.bounds toView:_imageView];
CGRect cropRect = _cropRectView.frame;

CGFloat rotationAngle = fabs(self.rotationAngle);

CGFloat a = visibleRect.size.height * sinf(rotationAngle);
CGFloat b = visibleRect.size.width * cosf(rotationAngle);
CGFloat c = visibleRect.size.height * cosf(rotationAngle);
CGFloat d = visibleRect.size.width * sinf(rotationAngle);

CGFloat zoomDiff = MAX(cropRect.size.width / (a + b), cropRect.size.height / (c + d));
CGFloat newZoomScale = (zoomDiff > 1) ? zoomDiff : 1.0 / zoomDiff;

[UIView animateWithDuration:0.2
                      delay:0.05
                    options:NO
                 animations:^{
                         [self centerToCropRect:[self convertRect:cropRect toView:self.zoomingView]];
                         _scrollView.zoomScale = _scrollView.zoomScale * newZoomScale;
                 } completion:^(BOOL finished) {
                     if (![self rotatedView:_imageView containsViewCompletely:_cropRectView]) 
                     {
                         // Damn, it still broken - this happens a lot
                     }
                     else
                     {
                         // Woo! Fixed
                     }
                     _didDetectBadRotation = NO;
                 }];

      

Note. I am using AutoLayout which makes the frames and borders look worse.

+3


source to share


3 answers


Suppose the image rectangle (blue in the diagram) and the crop rectangle (red) have the same aspect ratio and center. When rotated, the image rectangle now has a bounding rectangle (green), which is what you want your frame to be scaled (effectively by shrinking the image).

To scale effectively, you need to know the dimensions of the new bounding box and use a scale factor that is appropriate for it. The dimensions of the bounding box are pretty obvious.

(a + b) x (c + d)

      

Note that each segment a, b, c, d is either the adjacent or opposite side of the right triangle formed by the bounding rectangle and the rotated flip.

a = image_rect_height * sin(rotation_angle)
b = image_rect_width * cos(rotation_angle)
c = image_rect_width * sin(rotation_angle)
d = image_rect_height * cos(rotation_angle)

      



Your scale factor is just

MAX(crop_rect_width / (a + b), crop_rect_height / (c + d))

      

Here's a reference diagram:

enter image description here

+6


source


Fill the frame of the overlay rectangle:

For a square crop, you need to know the new borders of the rotated image that will fill the crop view.

Let's look at the reference diagram: the reference diagram You need to find the height of the right triangle (image number 2). Both heights are equal.

CGFloat sinAlpha = sin(alpha);
CGFloat cosAlpha = cos(alpha);
CGFloat hypotenuse = /* calculate */;
CGFloat altitude = hypotenuse * sinAlpha * cosAlpha;

      



Then you need to calculate the new width for the rotated image and the desired scaling factor as follows:

 CGFloat newWidth = previousWidth + altitude * 2;
 CGFloat scale = newWidth / previousWidth;

      

I have implemented this method here .

+3


source


I will answer using example code, but mostly this problem becomes very simple if you think in a rotating view coordinate system.

UIView* container = [[UIView alloc] initWithFrame:CGRectMake(80, 200, 100, 100)];
container.backgroundColor = [UIColor blueColor];

UIView* content2 = [[UIView alloc] initWithFrame:CGRectMake(-50, -50, 150, 150)];
content2.backgroundColor = [[UIColor greenColor] colorWithAlphaComponent:0.5];
[container addSubview:content2];

[self.view setBackgroundColor:[UIColor blackColor]];
[self.view addSubview:container];

[container.layer setSublayerTransform:CATransform3DMakeRotation(M_PI / 8.0, 0, 0, 1)];

//And now the calculations
CGRect containerFrameInContentCoordinates = [content2 convertRect:container.bounds fromView:container];
CGRect unionBounds = CGRectUnion(content2.bounds, containerFrameInContentCoordinates);

CGFloat midX = CGRectGetMidX(content2.bounds);
CGFloat midY = CGRectGetMidY(content2.bounds);

CGFloat scaleX1 = (-1 * CGRectGetMinX(unionBounds) + midX) / midX;
CGFloat scaleX2 = (CGRectGetMaxX(unionBounds) - midX) / midX;
CGFloat scaleY1 = (-1 * CGRectGetMinY(unionBounds) + midY) / midY;
CGFloat scaleY2 = (CGRectGetMaxY(unionBounds) - midY) / midY;

CGFloat scaleX = MAX(scaleX1, scaleX2);
CGFloat scaleY = MAX(scaleY1, scaleY2);
CGFloat scale = MAX(scaleX, scaleY);

content2.transform = CGAffineTransformScale(content2.transform, scale, scale);

      

0


source







All Articles