19

Let's say I have an image (e.g. 1024 x 768 px) which is displayed in a UIImageView (e.g. 300 x 300 px). Now, I'd like to convert a point from the image, e.g. the position of a person's nose (x: 500, y:600), to the corresponding point on the UIImageView with its contentMode taken into account.

If the contentMode is fixed at UIViewContentModeScaleToFill, conversion will be easy. But if it's UIViewContentModeScaleAspectFit, things getting more complex.

Is there an elegant way to achieve that? I don't really want to calculate that for every single contentMode (more than 10, I think).

Thanks!

nubbel
  • 1,552
  • 17
  • 24

4 Answers4

33

Today, I've had some spare time to put together my solution to this problem and publish it on GitHub: UIImageView-GeometryConversion

It's a category on UIImageView that provides methods for converting points and rects from the image to view coordinates and respects all of the 13 different content modes.

Hope you like it!

nubbel
  • 1,552
  • 17
  • 24
  • Very nice - It would be even nicer if you had also provided the other way around (from imageView coordinate to image coordinate). – sarfata Jun 17 '12 at 05:58
  • 1
    Dont bother, I have done it for some of the modes, I am finishing it right now and will send you a pull request on github later today. – sarfata Jun 19 '12 at 21:57
  • 4
    I have posted a pull request: https://github.com/nubbel/UIImageView-GeometryConversion/pull/1 – sarfata Jun 20 '12 at 03:40
  • Thank you, I merged your changes! – nubbel Jun 20 '12 at 09:33
  • Facing Rotation Problem using camera image the library convert in different then presented image. – Hassy May 23 '17 at 08:26
  • 1
    I've just noticed a bug in the Swift version. Lines 139 and 140 should be `let imageTopLeft = convertPoint(fromViewPoint: viewTopLeft)` and `let imageBottomRight = convertPoint(fromViewPoint: viewBottomRight)` respectively. – tomblah Oct 28 '19 at 04:32
10

That's my quick'n'dirty solution:

- (CGPoint)convertPoint:(CGPoint)sourcePoint fromContentSize:(CGSize)sourceSize {
    CGPoint targetPoint = sourcePoint;
    CGSize  targetSize  = self.bounds.size;

    CGFloat ratioX = targetSize.width / sourceSize.width;
    CGFloat ratioY = targetSize.height / sourceSize.height;

    if (self.contentMode == UIViewContentModeScaleToFill) {
        targetPoint.x *= ratioX;
        targetPoint.y *= ratioY;
    }
    else if(self.contentMode == UIViewContentModeScaleAspectFit) {
        CGFloat scale = MIN(ratioX, ratioY);

        targetPoint.x *= scale;
        targetPoint.y *= scale;

        targetPoint.x += (self.frame.size.width - sourceSize.width * scale) / 2.0f;
        targetPoint.y += (self.frame.size.height - sourceSize.height * scale) / 2.0f;
    }
    else if(self.contentMode == UIViewContentModeScaleAspectFill) {
        CGFloat scale = MAX(ratioX, ratioY);

        targetPoint.x *= scale;
        targetPoint.y *= scale;

        targetPoint.x += (self.frame.size.width - sourceSize.width * scale) / 2.0f;
        targetPoint.y += (self.frame.size.height - sourceSize.height * scale) / 2.0f;
    }

    return targetPoint;
}

- (CGRect)convertRect:(CGRect)sourceRect fromContentSize:(CGSize)sourceSize {
    CGRect targetRect = sourceRect;
    CGSize targetSize  = self.bounds.size;

    CGFloat ratioX = targetSize.width / sourceSize.width;
    CGFloat ratioY = targetSize.height / sourceSize.height;

    if (self.contentMode == UIViewContentModeScaleToFill) {
        targetRect.origin.x *= ratioX;
        targetRect.origin.y *= ratioY;

        targetRect.size.width *= ratioX;
        targetRect.size.height *= ratioY;
    }
    else if(self.contentMode == UIViewContentModeScaleAspectFit) {
        CGFloat scale = MIN(ratioX, ratioY);

        targetRect.origin.x *= scale;
        targetRect.origin.y *= scale;

        targetRect.origin.x += (self.frame.size.width - sourceSize.width * scale) / 2.0f;
        targetRect.origin.y += (self.frame.size.height - sourceSize.height * scale) / 2.0f;

        targetRect.size.width *= scale;
        targetRect.size.height *= scale;
    }
    else if(self.contentMode == UIViewContentModeScaleAspectFill) {
        CGFloat scale = MAX(ratioX, ratioY);

        targetRect.origin.x *= scale;
        targetRect.origin.y *= scale;

        targetRect.origin.x += (self.frame.size.width - sourceSize.width * scale) / 2.0f;
        targetRect.origin.y += (self.frame.size.height - sourceSize.height * scale) / 2.0f;

        targetRect.size.width *= scale;
        targetRect.size.height *= scale;
    }

    return targetRect;
}

When it's refactored and optimized, I'll publish it on github and post the link down here. Maybe even that snippet could be helpful for someone.

Michal
  • 4,846
  • 3
  • 33
  • 25
nubbel
  • 1,552
  • 17
  • 24
1

Nope, there is no built-in, public, or elegant way to do it.

You need to reverse engineer the functions for the content modes you need yourself.

PeyloW
  • 36,742
  • 12
  • 80
  • 99
  • Thanks, I'll do it for UIViewContentModeScaleToFill, UIViewContentModeAspectFit and UIViewContentModeAspectFill. That should be enough. – nubbel Aug 28 '11 at 18:06
  • 2
    @nubbel - When done please make it public on github or in some other way, so that the next person do not have to jump through the same hoops. – PeyloW Aug 28 '11 at 20:13
-1

Why cant you just rescale the coordinates:

X(UIImageView) = (X(Image)/1024)*300

and

Y(UIImageView) = (Y(Image)/768)*300

TommyG
  • 4,145
  • 10
  • 42
  • 66
  • That is good for UIViewContentModeScaleToFill, but for other contentModes? – nubbel Aug 28 '11 at 18:04
  • doesn't work for UIContentModeScaleToFit since margin will appear and the image will be centered in the uiimageview – Vassily May 09 '12 at 09:50